Skip to content

Commit f59669f

Browse files
authored
CodeWhisperer: Add telemetry optout status to SendTelemetryEventRequest (#3856)
Updated the API and put the field in SendTelemetryEvent API requests.
1 parent 5cf0615 commit f59669f

File tree

3 files changed

+139
-19
lines changed

3 files changed

+139
-19
lines changed

src/codewhisperer/client/codewhisperer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,14 @@ export class DefaultCodeWhispererClient {
193193
}
194194

195195
public async sendTelemetryEvent(request: SendTelemetryEventRequest) {
196+
const requestWithOptOut: SendTelemetryEventRequest = {
197+
...request,
198+
optOutPreference: globals.telemetry.telemetryEnabled ? 'OPTIN' : 'OPTOUT',
199+
}
196200
if (!AuthUtil.instance.isValidEnterpriseSsoInUse()) {
197201
return
198202
}
199-
const response = await (await this.createUserSdkClient()).sendTelemetryEvent(request).promise()
203+
const response = await (await this.createUserSdkClient()).sendTelemetryEvent(requestWithOptOut).promise()
200204
getLogger().debug(`codewhisperer: sendTelemetryEvent requestID: ${response.$response.requestId}`)
201205
}
202206
}

src/codewhisperer/client/user-service-2.json

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@
165165
"required": ["programmingLanguage", "acceptedCharacterCount", "totalCharacterCount", "timestamp"],
166166
"members": {
167167
"programmingLanguage": { "shape": "ProgrammingLanguage" },
168-
"acceptedCharacterCount": { "shape": "Integer" },
169-
"totalCharacterCount": { "shape": "Integer" },
168+
"acceptedCharacterCount": { "shape": "SensitiveInteger" },
169+
"totalCharacterCount": { "shape": "SensitiveInteger" },
170170
"timestamp": { "shape": "Timestamp" }
171171
}
172172
},
@@ -201,7 +201,8 @@
201201
},
202202
"CompletionType": {
203203
"type": "string",
204-
"enum": ["BLOCK", "LINE"]
204+
"enum": ["BLOCK", "LINE"],
205+
"sensitive": true
205206
},
206207
"Completions": {
207208
"type": "list",
@@ -239,10 +240,6 @@
239240
"kmsKeyArn": { "shape": "ResourceArn" }
240241
}
241242
},
242-
"Double": {
243-
"type": "double",
244-
"box": true
245-
},
246243
"FileContext": {
247244
"type": "structure",
248245
"required": ["leftFileContent", "rightFileContent", "filename", "programmingLanguage"],
@@ -256,7 +253,8 @@
256253
"FileContextFilenameString": {
257254
"type": "string",
258255
"max": 1024,
259-
"min": 1
256+
"min": 1,
257+
"sensitive": true
260258
},
261259
"FileContextLeftFileContentString": {
262260
"type": "string",
@@ -343,10 +341,6 @@
343341
"max": 10,
344342
"min": 0
345343
},
346-
"Integer": {
347-
"type": "integer",
348-
"box": true
349-
},
350344
"InternalServerException": {
351345
"type": "structure",
352346
"required": ["message"],
@@ -379,6 +373,11 @@
379373
"codeAnalysisFindings": { "shape": "String" }
380374
}
381375
},
376+
"OptOutPreference": {
377+
"type": "string",
378+
"enum": ["OPTIN", "OPTOUT"],
379+
"sensitive": true
380+
},
382381
"PaginationToken": {
383382
"type": "string",
384383
"max": 2048,
@@ -465,13 +464,22 @@
465464
"shape": "IdempotencyToken",
466465
"idempotencyToken": true
467466
},
468-
"telemetryEvent": { "shape": "TelemetryEvent" }
467+
"telemetryEvent": { "shape": "TelemetryEvent" },
468+
"optOutPreference": { "shape": "OptOutPreference" }
469469
}
470470
},
471471
"SendTelemetryEventResponse": {
472472
"type": "structure",
473473
"members": {}
474474
},
475+
"SensitiveDouble": {
476+
"type": "double",
477+
"sensitive": true
478+
},
479+
"SensitiveInteger": {
480+
"type": "integer",
481+
"sensitive": true
482+
},
475483
"Span": {
476484
"type": "structure",
477485
"members": {
@@ -523,7 +531,8 @@
523531
"String": { "type": "string" },
524532
"SuggestionState": {
525533
"type": "string",
526-
"enum": ["ACCEPT", "REJECT", "DISCARD", "EMPTY"]
534+
"enum": ["ACCEPT", "REJECT", "DISCARD", "EMPTY"],
535+
"sensitive": true
527536
},
528537
"SupplementalContext": {
529538
"type": "structure",
@@ -588,7 +597,7 @@
588597
"sessionId": { "shape": "UUID" },
589598
"requestId": { "shape": "UUID" },
590599
"programmingLanguage": { "shape": "ProgrammingLanguage" },
591-
"modificationPercentage": { "shape": "Double" },
600+
"modificationPercentage": { "shape": "SensitiveDouble" },
592601
"timestamp": { "shape": "Timestamp" }
593602
}
594603
},
@@ -609,10 +618,10 @@
609618
"programmingLanguage": { "shape": "ProgrammingLanguage" },
610619
"completionType": { "shape": "CompletionType" },
611620
"suggestionState": { "shape": "SuggestionState" },
612-
"recommendationLatencyMilliseconds": { "shape": "Double" },
621+
"recommendationLatencyMilliseconds": { "shape": "SensitiveDouble" },
613622
"timestamp": { "shape": "Timestamp" },
614-
"suggestionReferenceCount": { "shape": "Integer" },
615-
"generatedLine": { "shape": "Integer" }
623+
"suggestionReferenceCount": { "shape": "SensitiveInteger" },
624+
"generatedLine": { "shape": "SensitiveInteger" }
616625
}
617626
},
618627
"ValidationException": {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import sinon from 'sinon'
7+
import { anyString, spy } from '../../utilities/mockito'
8+
import { codeWhispererClient } from '../../../codewhisperer/client/codewhisperer'
9+
import CodeWhispererUserClient, {
10+
SendTelemetryEventResponse,
11+
TelemetryEvent,
12+
} from '../../../codewhisperer/client/codewhispereruserclient'
13+
import globals from '../../../shared/extensionGlobals'
14+
import { AWSError, Request, Service } from 'aws-sdk'
15+
import { DefaultAWSClientBuilder, ServiceOptions } from '../../../shared/awsClientBuilder'
16+
import { FakeAwsContext } from '../../utilities/fakeAwsContext'
17+
import userApiConfig = require('./../../../codewhisperer/client/user-service-2.json')
18+
import { AuthUtil } from '../../../codewhisperer/util/authUtil'
19+
20+
describe('codewhisperer', async function () {
21+
let clientSpy: CodeWhispererUserClient
22+
let telemetryEnabledDefault: boolean
23+
24+
beforeEach(async function () {
25+
sinon.restore()
26+
const builder = new DefaultAWSClientBuilder(new FakeAwsContext())
27+
clientSpy = spy(
28+
(await builder.createAwsService(Service, {
29+
apiConfig: userApiConfig,
30+
} as ServiceOptions)) as CodeWhispererUserClient
31+
)
32+
sinon.stub(codeWhispererClient, 'createUserSdkClient').returns(Promise.resolve(clientSpy))
33+
telemetryEnabledDefault = globals.telemetry.telemetryEnabled
34+
})
35+
36+
afterEach(function () {
37+
sinon.restore()
38+
globals.telemetry.telemetryEnabled = telemetryEnabledDefault
39+
})
40+
41+
it('sendTelemetryEvent for userTriggerDecision should respect telemetry optout status', async function () {
42+
await sendTelemetryEventOptoutCheckHelper({
43+
userTriggerDecisionEvent: {
44+
sessionId: anyString(),
45+
requestId: anyString(),
46+
programmingLanguage: { languageName: 'python' },
47+
completionType: 'BLOCK',
48+
suggestionState: 'ACCEPT',
49+
recommendationLatencyMilliseconds: 1,
50+
timestamp: new Date(),
51+
},
52+
})
53+
})
54+
55+
it('sendTelemetryEvent for codeScan should respect telemetry optout status', async function () {
56+
await sendTelemetryEventOptoutCheckHelper({
57+
codeScanEvent: {
58+
programmingLanguage: { languageName: 'python' },
59+
codeScanJobId: anyString(),
60+
timestamp: new Date(),
61+
},
62+
})
63+
})
64+
65+
it('sendTelemetryEvent for codePercentage should respect telemetry optout status', async function () {
66+
await sendTelemetryEventOptoutCheckHelper({
67+
codeCoverageEvent: {
68+
programmingLanguage: { languageName: 'python' },
69+
acceptedCharacterCount: 0,
70+
totalCharacterCount: 1,
71+
timestamp: new Date(),
72+
},
73+
})
74+
})
75+
76+
it('sendTelemetryEvent for userModification should respect telemetry optout status', async function () {
77+
await sendTelemetryEventOptoutCheckHelper({
78+
userModificationEvent: {
79+
sessionId: anyString(),
80+
requestId: anyString(),
81+
programmingLanguage: { languageName: 'python' },
82+
modificationPercentage: 2.0,
83+
timestamp: new Date(),
84+
},
85+
})
86+
})
87+
88+
async function sendTelemetryEventOptoutCheckHelper(payload: TelemetryEvent) {
89+
const stub = sinon.stub(clientSpy, 'sendTelemetryEvent').returns({
90+
promise: () =>
91+
Promise.resolve({
92+
$response: {
93+
requestId: anyString(),
94+
},
95+
}),
96+
} as Request<SendTelemetryEventResponse, AWSError>)
97+
98+
sinon.stub(AuthUtil.instance, 'isValidEnterpriseSsoInUse').returns(true)
99+
globals.telemetry.telemetryEnabled = true
100+
await codeWhispererClient.sendTelemetryEvent({ telemetryEvent: payload })
101+
sinon.assert.calledWith(stub, sinon.match({ optOutPreference: 'OPTIN' }))
102+
103+
globals.telemetry.telemetryEnabled = false
104+
await codeWhispererClient.sendTelemetryEvent({ telemetryEvent: payload })
105+
sinon.assert.calledWith(stub, sinon.match({ optOutPreference: 'OPTOUT' }))
106+
}
107+
})

0 commit comments

Comments
 (0)