Skip to content

Commit ee2c934

Browse files
authored
Merge pull request #5378 from hayemaxi/client-id
fix(telemetry): toolkit replacing amazon q clientId
2 parents b7572db + 471b9a1 commit ee2c934

File tree

3 files changed

+91
-73
lines changed

3 files changed

+91
-73
lines changed

packages/core/src/shared/telemetry/activation.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ import { DefaultTelemetryService } from './telemetryService'
1414
import { getLogger } from '../logger'
1515
import { getComputeRegion, isAmazonQ, isCloud9, productName } from '../extensionUtilities'
1616
import { openSettingsId, Settings } from '../settings'
17-
import { TelemetryConfig, setupTelemetryId } from './util'
17+
import { TelemetryConfig } from './util'
1818
import { isAutomation, isReleaseVersion } from '../vscode/env'
1919
import { AWSProduct } from './clienttelemetry'
2020
import { DefaultTelemetryClient } from './telemetryClient'
2121
import { telemetry } from './telemetry'
22-
import { Commands } from '../vscode/commands2'
2322

2423
export const noticeResponseViewSettings = localize('AWS.telemetry.notificationViewSettings', 'Settings')
2524
export const noticeResponseOk = localize('AWS.telemetry.notificationOk', 'OK')
@@ -71,19 +70,11 @@ export async function activate(
7170
})
7271
)
7372

74-
if (isAmazonQExt) {
75-
extensionContext.subscriptions.push(
76-
Commands.register('aws.amazonq.setupTelemetryId', async () => {
77-
await setupTelemetryId(extensionContext)
78-
})
79-
)
80-
}
81-
8273
// Prompt user about telemetry if they haven't been
8374
if (!isCloud9() && !hasUserSeenTelemetryNotice()) {
8475
showTelemetryNotice()
8576
}
86-
await setupTelemetryId(extensionContext)
77+
8778
await globals.telemetry.start()
8879
} catch (e) {
8980
// Only throw in a production build because:

packages/core/src/shared/telemetry/util.ts

Lines changed: 41 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ import { Result } from './telemetry.gen'
1717
import { MetricDatum } from './clienttelemetry'
1818
import { isValidationExemptMetric } from './exemptMetrics'
1919
import { isAmazonQ, isCloud9, isSageMaker } from '../../shared/extensionUtilities'
20-
import { isExtensionInstalled, VSCODE_EXTENSION_ID } from '../utilities'
2120
import { randomUUID } from '../crypto'
22-
import { activateExtension } from '../utilities/vsCodeUtils'
2321
import { ClassToInterfaceType } from '../utilities/tsUtils'
2422

2523
const legacySettingsTelemetryValueDisable = 'Disable'
2624
const legacySettingsTelemetryValueEnable = 'Enable'
2725

2826
const TelemetryFlag = addTypeName('boolean', convertLegacy)
29-
const telemetryClientIdEnvKey = '__TELEMETRY_CLIENT_ID'
27+
export const telemetryClientIdEnvKey = '__TELEMETRY_CLIENT_ID'
3028

3129
export class TelemetryConfig {
3230
private readonly _toolkitConfig
@@ -83,6 +81,19 @@ export function convertLegacy(value: unknown): boolean {
8381
}
8482
}
8583

84+
/**
85+
* Calculates the clientId for the current profile. This calculation is performed once
86+
* on first call and the result is stored for the remainder of the session.
87+
*
88+
* Web mode will always compute to whatever is stored in global state or vscode.machineId.
89+
* For normal use, the clientId is fetched from the first providing source:
90+
* 1. clientId stored in process.env
91+
* 2. clientId stored in current extension's global state.
92+
* 3. a random UUID
93+
*
94+
* The clientId in the current extension's global state AND the clientId stored in process.env
95+
* is updated to the result of above to allow other extensions to converge to the same clientId.
96+
*/
8697
export const getClientId = memoize(
8798
/**
8899
* @param nonce Dummy parameter to allow tests to defeat memoize().
@@ -100,11 +111,34 @@ export const getClientId = memoize(
100111
return '11111111-1111-1111-1111-111111111111'
101112
}
102113
try {
103-
let clientId = globalState.tryGet('telemetryClientId', String)
104-
if (!clientId) {
105-
clientId = randomUUID()
106-
globalState.tryUpdate('telemetryClientId', clientId)
114+
const globalClientId = process.env[telemetryClientIdEnvKey] // truly global across all extensions
115+
const localClientId = globalState.tryGet('telemetryClientId', String) // local to extension, despite accessing "global" state
116+
let clientId: string
117+
118+
if (isWeb()) {
119+
const machineId = vscode.env.machineId
120+
clientId = localClientId ?? machineId
121+
getLogger().debug(
122+
'getClientId: web mode determined clientId: %s, stored clientId was: %s, vscode.machineId was: %s',
123+
clientId,
124+
localClientId,
125+
machineId
126+
)
127+
} else {
128+
clientId = globalClientId ?? localClientId ?? randomUUID()
129+
getLogger().debug(
130+
'getClientId: determined clientId as: %s, process.env clientId was: %s, stored clientId was: %s',
131+
clientId,
132+
globalClientId,
133+
localClientId
134+
)
135+
if (!globalClientId) {
136+
getLogger().debug(`getClientId: setting clientId in process.env to: %s`, clientId)
137+
process.env[telemetryClientIdEnvKey] = clientId
138+
}
107139
}
140+
141+
globalState.tryUpdate('telemetryClientId', clientId)
108142
return clientId
109143
} catch (e) {
110144
getLogger().error('getClientId: failed to create client id: %O', e)
@@ -205,53 +239,6 @@ export function validateMetricEvent(event: MetricDatum, fatal: boolean) {
205239
}
206240
}
207241

208-
/**
209-
* Setup the telemetry client id at extension activation.
210-
* This function is designed to let AWS Toolkit and Amazon Q share
211-
* the same telemetry client id.
212-
*/
213-
export async function setupTelemetryId(extensionContext: vscode.ExtensionContext) {
214-
try {
215-
if (isWeb()) {
216-
await globals.globalState.update('telemetryClientId', vscode.env.machineId)
217-
} else {
218-
const currentClientId = globals.globalState.tryGet('telemetryClientId', String)
219-
const storedClientId = process.env[telemetryClientIdEnvKey]
220-
if (currentClientId && storedClientId) {
221-
if (extensionContext.extension.id === VSCODE_EXTENSION_ID.awstoolkit) {
222-
getLogger().debug(`telemetry: Store telemetry client id to env ${currentClientId}`)
223-
process.env[telemetryClientIdEnvKey] = currentClientId
224-
// notify amazon q to use this stored client id
225-
// if amazon q activates first. Do not block on activate amazon q
226-
if (isExtensionInstalled(VSCODE_EXTENSION_ID.amazonq)) {
227-
void activateExtension(VSCODE_EXTENSION_ID.amazonq).then(async () => {
228-
getLogger().debug(`telemetry: notifying Amazon Q to adopt client id ${currentClientId}`)
229-
await vscode.commands.executeCommand('aws.amazonq.setupTelemetryId')
230-
})
231-
}
232-
} else if (isAmazonQ()) {
233-
getLogger().debug(`telemetry: Set telemetry client id to ${storedClientId}`)
234-
await globals.globalState.update('telemetryClientId', storedClientId)
235-
} else {
236-
getLogger().error(`Unexpected extension id ${extensionContext.extension.id}`)
237-
}
238-
} else if (!currentClientId && storedClientId) {
239-
getLogger().debug(`telemetry: Write telemetry client id to global state ${storedClientId}`)
240-
await globals.globalState.update('telemetryClientId', storedClientId)
241-
} else if (currentClientId && !storedClientId) {
242-
getLogger().debug(`telemetry: Write telemetry client id to env ${currentClientId}`)
243-
process.env[telemetryClientIdEnvKey] = currentClientId
244-
} else {
245-
const clientId = getClientId(globals.globalState)
246-
getLogger().debug(`telemetry: Setup telemetry client id ${clientId}`)
247-
process.env[telemetryClientIdEnvKey] = clientId
248-
}
249-
}
250-
} catch (err) {
251-
getLogger().error(`Error while setting up telemetry id ${err}`)
252-
}
253-
}
254-
255242
/**
256243
* Potentially helpful values for the 'source' field in telemetry.
257244
*/

packages/core/src/test/shared/telemetry/util.test.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@
66
import assert from 'assert'
77
import { Memento, ConfigurationTarget } from 'vscode'
88
import { Settings } from '../../../shared/settings'
9-
import { convertLegacy, getClientId, getUserAgent, platformPair, TelemetryConfig } from '../../../shared/telemetry/util'
9+
import {
10+
convertLegacy,
11+
getClientId,
12+
getUserAgent,
13+
platformPair,
14+
telemetryClientIdEnvKey,
15+
TelemetryConfig,
16+
} from '../../../shared/telemetry/util'
1017
import { extensionVersion } from '../../../shared/vscode/env'
1118
import { FakeMemento } from '../../fakeExtensionContext'
1219
import { GlobalState } from '../../../shared/globalState'
20+
import { randomUUID } from 'crypto'
1321

1422
describe('TelemetryConfig', function () {
1523
const settingKey = 'aws.telemetry'
@@ -100,16 +108,48 @@ describe('TelemetryConfig', function () {
100108
})
101109

102110
describe('getClientId', function () {
103-
it('should generate a unique id', async function () {
104-
const c1 = getClientId(new GlobalState(new FakeMemento()), true, false, 'x1')
105-
const c2 = getClientId(new GlobalState(new FakeMemento()), true, false, 'x2')
111+
before(function () {
112+
delete process.env[telemetryClientIdEnvKey]
113+
})
114+
115+
afterEach(function () {
116+
delete process.env[telemetryClientIdEnvKey]
117+
})
118+
119+
function testGetClientId(globalState: GlobalState) {
120+
return getClientId(globalState, true, false, randomUUID())
121+
}
122+
123+
it('generates a unique id if no other id is available', function () {
124+
const c1 = testGetClientId(new GlobalState(new FakeMemento()))
125+
delete process.env[telemetryClientIdEnvKey]
126+
const c2 = testGetClientId(new GlobalState(new FakeMemento()))
106127
assert.notStrictEqual(c1, c2)
107128
})
108129

130+
it('uses id stored in global state if an id is not found in process.env', async function () {
131+
const expectedClientId = 'myId'
132+
133+
const memento = new GlobalState(new FakeMemento())
134+
await memento.update('telemetryClientId', expectedClientId)
135+
136+
assert.strictEqual(testGetClientId(memento), expectedClientId)
137+
})
138+
139+
it('uses the id stored in process.env if available', async function () {
140+
const expectedClientId = 'myId'
141+
142+
const e = new GlobalState(new FakeMemento())
143+
await e.update('telemetryClientId', randomUUID())
144+
process.env[telemetryClientIdEnvKey] = expectedClientId
145+
146+
assert.strictEqual(testGetClientId(new GlobalState(new FakeMemento())), expectedClientId)
147+
})
148+
109149
it('returns the same value across the calls sequentially', async function () {
110150
const memento = new GlobalState(new FakeMemento())
111-
const c1 = getClientId(memento, true, false, 'y1')
112-
const c2 = getClientId(memento, true, false, 'y2')
151+
const c1 = testGetClientId(memento)
152+
const c2 = testGetClientId(memento)
113153
assert.strictEqual(c1, c2)
114154
})
115155

@@ -123,7 +163,7 @@ describe('getClientId', function () {
123163
throw new Error()
124164
},
125165
}
126-
const clientId = getClientId(new GlobalState(memento), true, false, 'x3')
166+
const clientId = testGetClientId(new GlobalState(memento))
127167
// XXX: `notStrictEqual` since getClientId() is now synchronous. Because memento.update() is async.
128168
assert.notStrictEqual(clientId, '00000000-0000-0000-0000-000000000000')
129169
})
@@ -141,7 +181,7 @@ describe('getClientId', function () {
141181
return this.memento.get(key)
142182
}
143183
}
144-
const clientId = getClientId(new FakeGlobalState(memento), true, false, 'x4')
184+
const clientId = testGetClientId(new FakeGlobalState(memento))
145185
assert.strictEqual(clientId, '00000000-0000-0000-0000-000000000000')
146186
})
147187

0 commit comments

Comments
 (0)