Skip to content

Commit bd66a73

Browse files
authored
Merge pull request #469 from forcedotcom/sh/pft-telemetry
adds support for sending PDP Events via o11y @W-21179952
2 parents e7e06b1 + d8c3a99 commit bd66a73

11 files changed

Lines changed: 146 additions & 6 deletions

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Apache License Version 2.0
22

3-
Copyright (c) 2025 Salesforce, Inc.
3+
Copyright (c) 2026 Salesforce, Inc.
44
All rights reserved.
55

66
Apache License

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@salesforce/o11y-reporter": "1.7.3",
4747
"applicationinsights": "^2.9.8",
4848
"got": "^11",
49+
"o11y_schema": "^260.41.0",
4950
"proxy-agent": "^6.5.0"
5051
},
5152
"devDependencies": {

src/exported.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ import { TelemetryReporter } from './telemetryReporter';
1818
export * from './telemetryReporter';
1919
export { isEnabled } from './enabledCheck';
2020
export default TelemetryReporter;
21-
export type { Attributes, O11ySchema, O11yBatchingConfig } from './types';
21+
export type { Attributes, O11ySchema, O11yBatchingConfig, PdpEvent } from './types';

src/o11yReporter.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
* limitations under the License.
1515
*/
1616
import { O11yService, type BatchingOptions } from '@salesforce/o11y-reporter';
17-
import { Attributes, O11ySchema, Properties, TelemetryOptions } from './types';
17+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
18+
// @ts-ignore o11y_schema/sf_pdp.d.ts is not a valid module
19+
import { pdpEventSchema } from 'o11y_schema/sf_pdp';
20+
import { Attributes, O11ySchema, PdpEvent, Properties, TelemetryOptions } from './types';
1821
import { BaseReporter } from './baseReporter';
1922
import { buildPropertiesAndMeasurements } from './utils';
2023

@@ -124,6 +127,21 @@ export class O11yReporter extends BaseReporter {
124127
}
125128
}
126129

130+
/**
131+
* Sends a PDP event via O11y service.
132+
*
133+
* @param event - PDP event to send.
134+
*/
135+
public async sendPdpEvent(event: PdpEvent): Promise<void> {
136+
await this.initialized;
137+
138+
this.service.logEventWithSchema(event, pdpEventSchema);
139+
140+
if (!this._batchingEnabled) {
141+
await this.service.forceFlush();
142+
}
143+
}
144+
127145
public async flush(): Promise<void> {
128146
// Wait for initialization to complete before using the service
129147
await this.initialized;

src/telemetryReporter.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { ProxyAgent } from 'proxy-agent';
2323
import { AppInsights, TelemetryClient } from './appInsights';
2424
import { isEnabled } from './enabledCheck';
2525
import { O11yReporter } from './o11yReporter';
26-
import { Attributes, O11ySchema, Properties, TelemetryOptions } from './types';
26+
import { Attributes, O11ySchema, PdpEvent, Properties, TelemetryOptions } from './types';
2727

2828
/**
2929
* This is the main telemetry reporter that should be used by consumers.
@@ -176,6 +176,19 @@ export class TelemetryReporter extends AsyncCreatable<TelemetryOptions> {
176176
}
177177
}
178178

179+
/**
180+
* Sends a PDP event via O11y.
181+
*
182+
* @param event - PDP event data to send.
183+
*/
184+
public sendPdpEvent(event: PdpEvent): void {
185+
if (this.isSfdxTelemetryEnabled() && this.enableO11y && this.o11yReporter) {
186+
void this.o11yReporter.sendPdpEvent(event).catch((error) => {
187+
this.logger.debug('Failed to send PDP event to O11y:', error);
188+
});
189+
}
190+
}
191+
179192
/**
180193
* Sends message to both AppInsights and O11y (if enabled).
181194
*

src/types.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,50 @@ export type Attributes = {
3535
*/
3636
export type O11ySchema = Record<string, unknown>;
3737

38+
/**
39+
* PDP Product Feature Taxonomy (PFT) event sent via O11y.
40+
*/
41+
export type PdpEvent = {
42+
/**
43+
* Unique identifier for the event. Follows this naming convention:
44+
* <object>.<action>
45+
*
46+
* object = Specific object within the Product Feature that give us context around the action in lowerCamelCase format.
47+
* Note: The object name can include context around the Product Feature (eg. slackforceMessage).
48+
* Examples: calculatedInsightsRecord,checkoutPaymentmethod, slackforceMessage, promptBuilderTemplate
49+
*
50+
* action = Action the user takes in past tense. This should only be ONE word, in lower case
51+
* Examples: processed, selected, sent, saved
52+
*/
53+
eventName: `${string}.${string}`;
54+
/**
55+
* Product Feature ID from GUS.
56+
*
57+
* Examples:
58+
* Salesforce CLI = aJCEE0000000mHP4AY
59+
* Salesforce Extensions for VS Code = aJCEE0000000mLm4AI
60+
*/
61+
productFeatureId: `aJC${string}`;
62+
/**
63+
* Populate this if there is a unique component with your Event for which a distinct count would be a relevant metric
64+
* E.g., CLI plugin command name (<pluginName.commandName>) or ext command name.
65+
*/
66+
componentId?: string;
67+
/**
68+
* Populate this if there is a unique quantity with your Event for which a sum would be a relevant metric for your
69+
* Product Feature. E.g., rowsProcessed → total Rows processed for Data Streams.
70+
*/
71+
eventVolume?: number;
72+
/**
73+
* Use this field to specify the name of your flexible attribute (eg. experimentId, buttonColor).
74+
*/
75+
contextName?: string;
76+
/**
77+
* Use this field to specify the value of your flexible attribute (eg. exp_123, green).
78+
*/
79+
contextValue?: string;
80+
};
81+
3882
/**
3983
* Batching configuration for O11y telemetry
4084
*

test/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"extends": "@salesforce/dev-config/tsconfig-test-strict",
3-
"include": ["unit/**/*.ts", "../node_modules/@types/**/*.d.ts"],
3+
"include": ["unit/**/*.ts", "../node_modules/@types/**/*.d.ts", "../src/**/*.ts", "../**/*.d.ts"],
44
"compilerOptions": {
55
"noEmit": true,
66
"skipLibCheck": true,

test/unit/telemetryReporter.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
*/
1616
import * as os from 'node:os';
1717
import { ConfigAggregator, Logger } from '@salesforce/core';
18+
import { O11yService } from '@salesforce/o11y-reporter';
1819
import got from 'got';
1920
import { expect } from 'chai';
2021
import * as sinon from 'sinon';
2122
import { AppInsights } from '../../src/appInsights';
23+
import { O11yReporter } from '../../src/o11yReporter';
2224
import { TelemetryReporter } from '../../src/telemetryReporter';
25+
import type { PdpEvent } from '../../src/types';
2326
import * as enabledStubs from '../../src/enabledCheck';
2427

2528
describe('TelemetryReporter', () => {
@@ -48,6 +51,43 @@ describe('TelemetryReporter', () => {
4851
expect(sendStub.calledOnce).to.be.true;
4952
});
5053

54+
it('should send PDPEvent', async () => {
55+
sandbox.stub(ConfigAggregator.prototype, 'getPropertyValue').returns('false');
56+
57+
const mockO11yService = {
58+
initialize: sandbox.stub().resolves(),
59+
logEvent: sandbox.stub(),
60+
logEventWithSchema: sandbox.stub(),
61+
forceFlush: sandbox.stub().resolves(),
62+
enableAutoBatching: sandbox.stub().returns(() => {}),
63+
};
64+
sandbox.stub(O11yService, 'getInstance').returns(mockO11yService as unknown as O11yService);
65+
66+
const sendPdpEventStub = sandbox.stub(O11yReporter.prototype, 'sendPdpEvent').resolves();
67+
68+
const reporter = await TelemetryReporter.create({
69+
project: 'salesforce-cli',
70+
key: 'not-used',
71+
userId: 'test-user-id-for-pft-testing',
72+
waitForConnection: true,
73+
enableO11y: true,
74+
enableAppInsights: false,
75+
o11yUploadEndpoint: 'https://794testsite.my.site.com/byolwr/webruntime/log/metrics',
76+
});
77+
78+
const pdpEvent: PdpEvent = {
79+
eventName: 'salesforceCli.executed',
80+
productFeatureId: 'aJCEE0000000mHP4AY',
81+
componentId: '@salesforce/plugin-auth.org:web:login',
82+
contextName: 'orgId::devhubId',
83+
contextValue: '00Ded000000VsTxEAK::00D460000019MkyEAE',
84+
};
85+
reporter.sendPdpEvent(pdpEvent);
86+
87+
expect(sendPdpEventStub.calledOnce).to.be.true;
88+
expect(sendPdpEventStub.firstCall.args[0]).to.deep.equal(pdpEvent);
89+
});
90+
5191
it('should send a telemetry exception', async () => {
5292
const options = { project, key };
5393
sandbox.stub(ConfigAggregator.prototype, 'getPropertyValue').returns('false');

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
"rootDir": "src",
99
"baseUrl": "."
1010
},
11-
"include": ["src/**/*.ts"]
11+
"include": ["src/**/*.ts", "types/**/*.d.ts"]
1212
}

types/o11y_schema_sf_pdp.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2026, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
declare module 'o11y_schema/sf_pdp' {
17+
const pdpEventSchema: Record<string, unknown>;
18+
export { pdpEventSchema };
19+
}

0 commit comments

Comments
 (0)