Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apps/website/content/docs/telemetry/guides/node.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ The postinstall entry point reads package name and version from npm lifecycle en

It skips local top-level installs by default. Dependency installs under `node_modules` and global installs can be eligible unless disabled.

Postinstall payloads include `install_context` with one of `ci`, `dependency`, `global`, or `workspace`. CI environments are disabled before sending; the `ci` value exists as a defensive classification if a CI install is intentionally forced through for diagnostics.

When `DEBUG` includes `ngaf:telemetry`, `ngaf:*`, or `*`, the script prints the payload shape it attempted to send. It prints the normal install telemetry notice only when the ingest endpoint accepted the event.

## Failure behavior
Expand Down
14 changes: 7 additions & 7 deletions apps/website/content/docs/telemetry/reference/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ type NgafNodeEvent =
| 'ngaf:stream_errored';
```

| Event | Source | Properties from source |
| ------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `ngaf:postinstall` | package postinstall script | `pkg`, `version`, `node`, `node_version`, `os`, `arch`, `global_install`, `install_context`, package-manager fields when npm exposes them |
| `ngaf:runtime_instance_created` | Node adapter helper | `transport`, `provider`, `model`, `angularVersion`; `apiKey` is removed |
| `ngaf:stream_started` | Node adapter helper | `provider`, `model`, optional fields in the input object |
| `ngaf:stream_ended` | Node adapter helper | `provider`, `model`, `durationMs` when supplied |
| `ngaf:stream_errored` | Node adapter helper | stream properties plus `errorClass` |
| Event | Source | Properties from source |
| ------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `ngaf:postinstall` | package postinstall script | `pkg`, `version`, `node`, `node_version`, `os`, `arch`, `global_install`, package-manager fields when npm exposes them |
| `ngaf:runtime_instance_created` | Node adapter helper | `transport`, `provider`, `model`, `angularVersion`; `apiKey` is removed |
| `ngaf:stream_started` | Node adapter helper | `provider`, `model`, optional fields in the input object |
| `ngaf:stream_ended` | Node adapter helper | `provider`, `model`, `durationMs` when supplied |
| `ngaf:stream_errored` | Node adapter helper | stream properties plus `errorClass` |

`captureEvent()` also adds `sample_weight` to sent event properties.

Expand Down
34 changes: 16 additions & 18 deletions docs/gtm/taxonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,22 @@ Browser events never fire unless the consumer explicitly opts in. See `libs/tele

### `ngaf:postinstall` properties

| Property | Type | Notes |
| ------------------------------ | ------ | ---------------------------------------------------------------------------------- |
| `pkg` | string | Published `@ngaf/*` package name. |
| `version` | string | Published package version. |
| `node` | string | Current `process.version`; kept for existing dashboards. |
| `node_version` | string | Current `process.version`. |
| `os` | string | Current `process.platform`. |
| `arch` | string | Current `process.arch`. |
| `install_context` | enum | `ci`, `dependency`, `global`, or `workspace`; dashboards exclude `ci` defensively. |
| `package_manager` | string | Parsed from npm's package-manager user agent when available. |
| `package_manager_version` | string | Parsed from npm's package-manager user agent when available. |
| `package_manager_node_version` | string | Installer-reported Node version when available. |
| `package_manager_os` | string | Installer-reported OS token when available. |
| `package_manager_arch` | string | Installer-reported architecture token when available. |
| `package_manager_workspaces` | bool | Installer-reported workspace flag when available. |
| `global_install` | bool | Whether npm reports a global install. |
| `sample_weight` | number | Inverse sample rate for weighted counts. |
| Property | Type | Notes |
| ------------------------------ | ------ | ------------------------------------------------------------ |
| `pkg` | string | Published `@ngaf/*` package name. |
| `version` | string | Published package version. |
| `node` | string | Current `process.version`; kept for existing dashboards. |
| `node_version` | string | Current `process.version`. |
| `os` | string | Current `process.platform`. |
| `arch` | string | Current `process.arch`. |
| `package_manager` | string | Parsed from npm's package-manager user agent when available. |
| `package_manager_version` | string | Parsed from npm's package-manager user agent when available. |
| `package_manager_node_version` | string | Installer-reported Node version when available. |
| `package_manager_os` | string | Installer-reported OS token when available. |
| `package_manager_arch` | string | Installer-reported architecture token when available. |
| `package_manager_workspaces` | bool | Installer-reported workspace flag when available. |
| `global_install` | bool | Whether npm reports a global install. |
| `sample_weight` | number | Inverse sample rate for weighted counts. |

### Runtime telemetry properties

Expand Down Expand Up @@ -189,4 +188,3 @@ This file is human-edited. When events are added/renamed/removed, update the aff
| 2026-05-15 | Drop cockpit:install_command_copied, rename cockpit:six_signals_complete → cockpit:activation_complete (Spec 1C). |
| 2026-05-15 | Cockpit shell events: rename `recipe_start` → `recipe_opened`; add `mode_switched` and `code_copied` (Spec 1C implementation). |
| 2026-05-17 | Add `blog:cta_click` + `blog:copy_code_click` events; add `'blog'` to `AnalyticsSurface` (Spec 5). |
| 2026-05-18 | Add `install_context` to `ngaf:postinstall` and filter package telemetry dashboard insights away from `ci` context. |
15 changes: 5 additions & 10 deletions libs/telemetry/src/node/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,23 +123,18 @@ describe('node client', () => {
expect(body.properties).not.toHaveProperty('init_cwd');
});

test('createPostinstallProperties classifies CI installs for dashboard filtering', () => {
expect(
createPostinstallProperties(
{ pkg: '@ngaf/chat', version: '0.0.31' },
{ CI: 'true' }
)
).toEqual(expect.objectContaining({ install_context: 'ci' }));

test('createPostinstallProperties does not include derived install context', () => {
expect(
createPostinstallProperties(
{ pkg: '@ngaf/chat', version: '0.0.31' },
{
CI: 'true',
npm_config_global: 'true',
npm_config_user_agent:
'npm/10.9.2 node/v22.14.0 darwin arm64 workspaces/false',
'npm/10.9.2 node/v22.14.0 darwin arm64 workspaces/true',
}
)
).toEqual(expect.objectContaining({ install_context: 'dependency' }));
).not.toHaveProperty('install_context');
});

test('capturePostinstall awaits fetch before resolving', async () => {
Expand Down
26 changes: 0 additions & 26 deletions libs/telemetry/src/node/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,6 @@ function readBooleanToken(value: string | undefined): boolean | undefined {
return undefined;
}

function isCiEnv(env: NodeJS.ProcessEnv = process.env): boolean {
return [
env.CI,
env.GITHUB_ACTIONS,
env.CONTINUOUS_INTEGRATION,
env.BUILDKITE,
env.CIRCLECI,
].some((value) => readBooleanToken(value) === true);
}

function getPackageManager(
env: NodeJS.ProcessEnv = process.env
): Record<string, unknown> {
Expand Down Expand Up @@ -79,21 +69,6 @@ function getPackageManager(
return out;
}

function getInstallContext(
env: NodeJS.ProcessEnv = process.env
): 'ci' | 'dependency' | 'global' | 'workspace' {
if (isCiEnv(env)) return 'ci';
if (
readBooleanToken(env.npm_config_global) === true ||
env.npm_config_location === 'global'
) {
return 'global';
}
const packageManager = getPackageManager(env);
if (packageManager.package_manager_workspaces === true) return 'workspace';
return 'dependency';
}

// @internal
export function createPostinstallProperties(
input: PostinstallInput,
Expand All @@ -109,7 +84,6 @@ export function createPostinstallProperties(
global_install:
readBooleanToken(env.npm_config_global) === true ||
env.npm_config_location === 'global',
install_context: getInstallContext(env),
...getPackageManager(env),
};
}
Expand Down
2 changes: 1 addition & 1 deletion tools/posthog/dashboards/package-telemetry.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"slug": "package-telemetry",
"posthog_id": 1590768,
"name": "GTM · Package telemetry",
"description": "Published @ngaf/* install telemetry from ngaf:postinstall. Package install insights exclude CI install context.",
"description": "Published @ngaf/* install telemetry from ngaf:postinstall.",
"tags": ["gtm", "package-telemetry", "phase-1"],
"tiles": [
{
Expand Down
9 changes: 1 addition & 8 deletions tools/posthog/insights/package-installs-by-installer-os.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
"events": [
{
"event": "ngaf:postinstall",
"math": "total",
"properties": [
{
"key": "install_context",
"value": "ci",
"operator": "is_not"
}
]
"math": "total"
}
],
"breakdown": "package_manager_os",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
"events": [
{
"event": "ngaf:postinstall",
"math": "total",
"properties": [
{
"key": "install_context",
"value": "ci",
"operator": "is_not"
}
]
"math": "total"
}
],
"breakdown": "package_manager",
Expand Down
9 changes: 1 addition & 8 deletions tools/posthog/insights/package-installs-by-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
"events": [
{
"event": "ngaf:postinstall",
"math": "total",
"properties": [
{
"key": "install_context",
"value": "ci",
"operator": "is_not"
}
]
"math": "total"
}
],
"breakdown": "pkg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
"events": [
{
"event": "ngaf:postinstall",
"math": "total",
"properties": [
{
"key": "install_context",
"value": "ci",
"operator": "is_not"
}
]
"math": "total"
}
],
"breakdown": "package_manager_workspaces",
Expand Down
19 changes: 11 additions & 8 deletions tools/posthog/telemetry-contract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,13 @@ test('insight property filters are allowed by the event contract', async () => {
);
});

test('package telemetry dashboard filters install insights away from CI context', async () => {
test('postinstall telemetry contract does not expose derived install context', () => {
const contract = TELEMETRY_EVENT_CONTRACT['ngaf:postinstall'];
assert(!contract.allowedProperties.includes('install_context'));
assert(!contract.allowedBreakdowns.includes('install_context'));
});

test('package telemetry dashboard does not filter by derived install context', async () => {
const dashboard = await readJson<{ tiles: Array<{ insight: string }> }>(
join(HERE, 'dashboards', 'package-telemetry.json')
);
Expand All @@ -129,20 +135,17 @@ test('package telemetry dashboard filters install insights away from CI context'

for (const item of insight.events ?? []) {
if (item.event !== 'ngaf:postinstall') continue;
const excludesCiContext = (item.properties ?? []).some(
(property) =>
property.key === 'install_context' &&
property.value === 'ci' &&
property.operator === 'is_not'
const usesInstallContext = (item.properties ?? []).some(
(property) => property.key === 'install_context'
);
if (!excludesCiContext) violations.push(insight.slug);
if (usesInstallContext) violations.push(insight.slug);
}
}

assert.deepEqual(
violations,
[],
`Package telemetry insights must exclude install_context=ci:\n${violations.join(
`Package telemetry insights must not filter by install_context:\n${violations.join(
'\n'
)}`
);
Expand Down
2 changes: 0 additions & 2 deletions tools/posthog/telemetry-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type TelemetryEventContract = {
const installProperties = [
'arch',
'global_install',
'install_context',
'node',
'node_version',
'os',
Expand Down Expand Up @@ -150,7 +149,6 @@ export const TELEMETRY_EVENT_CONTRACT: Record<string, TelemetryEventContract> =
allowedProperties: installProperties,
allowedBreakdowns: [
'global_install',
'install_context',
'os',
'package_manager',
'package_manager_os',
Expand Down
Loading