@aws/lsp-codewhisperer 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +19 -0
  3. package/out/client/sigv4/codewhisperer.d.ts +3 -0
  4. package/out/client/sigv4/codewhisperer.js +14 -0
  5. package/out/client/sigv4/codewhisperer.js.map +1 -0
  6. package/out/client/sigv4/service.json +346 -0
  7. package/out/client/token/bearer-token-service.json +1627 -0
  8. package/out/client/token/codewhisperer.d.ts +14 -0
  9. package/out/client/token/codewhisperer.js +23 -0
  10. package/out/client/token/codewhisperer.js.map +1 -0
  11. package/out/index.d.ts +2 -0
  12. package/out/index.js +19 -0
  13. package/out/index.js.map +1 -0
  14. package/out/language-server/auto-trigger/autoTrigger.d.ts +38 -0
  15. package/out/language-server/auto-trigger/autoTrigger.js +119 -0
  16. package/out/language-server/auto-trigger/autoTrigger.js.map +1 -0
  17. package/out/language-server/auto-trigger/autoTrigger.test.d.ts +1 -0
  18. package/out/language-server/auto-trigger/autoTrigger.test.js +82 -0
  19. package/out/language-server/auto-trigger/autoTrigger.test.js.map +1 -0
  20. package/out/language-server/auto-trigger/coefficients.json +405 -0
  21. package/out/language-server/codeWhispererSecurityScanServer.d.ts +4 -0
  22. package/out/language-server/codeWhispererSecurityScanServer.js +76 -0
  23. package/out/language-server/codeWhispererSecurityScanServer.js.map +1 -0
  24. package/out/language-server/codeWhispererServer.d.ts +7 -0
  25. package/out/language-server/codeWhispererServer.js +425 -0
  26. package/out/language-server/codeWhispererServer.js.map +1 -0
  27. package/out/language-server/codeWhispererServer.test.d.ts +1 -0
  28. package/out/language-server/codeWhispererServer.test.js +1218 -0
  29. package/out/language-server/codeWhispererServer.test.js.map +1 -0
  30. package/out/language-server/codeWhispererService.d.ts +84 -0
  31. package/out/language-server/codeWhispererService.js +168 -0
  32. package/out/language-server/codeWhispererService.js.map +1 -0
  33. package/out/language-server/dependencyGraph/commonUtil.d.ts +2 -0
  34. package/out/language-server/dependencyGraph/commonUtil.js +20 -0
  35. package/out/language-server/dependencyGraph/commonUtil.js.map +1 -0
  36. package/out/language-server/dependencyGraph/constants.d.ts +2 -0
  37. package/out/language-server/dependencyGraph/constants.js +6 -0
  38. package/out/language-server/dependencyGraph/constants.js.map +1 -0
  39. package/out/language-server/dependencyGraph/csharpDependencyGraph.d.ts +27 -0
  40. package/out/language-server/dependencyGraph/csharpDependencyGraph.js +145 -0
  41. package/out/language-server/dependencyGraph/csharpDependencyGraph.js.map +1 -0
  42. package/out/language-server/dependencyGraph/csharpDependencyGraph.test.d.ts +1 -0
  43. package/out/language-server/dependencyGraph/csharpDependencyGraph.test.js +341 -0
  44. package/out/language-server/dependencyGraph/csharpDependencyGraph.test.js.map +1 -0
  45. package/out/language-server/dependencyGraph/dependencyGraph.d.ts +125 -0
  46. package/out/language-server/dependencyGraph/dependencyGraph.js +152 -0
  47. package/out/language-server/dependencyGraph/dependencyGraph.js.map +1 -0
  48. package/out/language-server/dependencyGraph/dependencyGraphFactory.d.ts +15 -0
  49. package/out/language-server/dependencyGraph/dependencyGraphFactory.js +22 -0
  50. package/out/language-server/dependencyGraph/dependencyGraphFactory.js.map +1 -0
  51. package/out/language-server/languageDetection.d.ts +3 -0
  52. package/out/language-server/languageDetection.js +56 -0
  53. package/out/language-server/languageDetection.js.map +1 -0
  54. package/out/language-server/mergeRightUtils.d.ts +6 -0
  55. package/out/language-server/mergeRightUtils.js +37 -0
  56. package/out/language-server/mergeRightUtils.js.map +1 -0
  57. package/out/language-server/mergeRightUtils.test.d.ts +1 -0
  58. package/out/language-server/mergeRightUtils.test.js +53 -0
  59. package/out/language-server/mergeRightUtils.test.js.map +1 -0
  60. package/out/language-server/proxy-server.d.ts +2 -0
  61. package/out/language-server/proxy-server.js +35 -0
  62. package/out/language-server/proxy-server.js.map +1 -0
  63. package/out/language-server/securityScan/securityScanHandler.d.ts +17 -0
  64. package/out/language-server/securityScan/securityScanHandler.js +139 -0
  65. package/out/language-server/securityScan/securityScanHandler.js.map +1 -0
  66. package/out/language-server/securityScan/securityScanHandler.test.d.ts +1 -0
  67. package/out/language-server/securityScan/securityScanHandler.test.js +170 -0
  68. package/out/language-server/securityScan/securityScanHandler.test.js.map +1 -0
  69. package/out/language-server/securityScan/types.d.ts +49 -0
  70. package/out/language-server/securityScan/types.js +3 -0
  71. package/out/language-server/securityScan/types.js.map +1 -0
  72. package/out/language-server/session/sessionManager.d.ts +91 -0
  73. package/out/language-server/session/sessionManager.js +234 -0
  74. package/out/language-server/session/sessionManager.js.map +1 -0
  75. package/out/language-server/session/sessionManager.test.d.ts +1 -0
  76. package/out/language-server/session/sessionManager.test.js +584 -0
  77. package/out/language-server/session/sessionManager.test.js.map +1 -0
  78. package/out/language-server/telemetry/codePercentage.d.ts +17 -0
  79. package/out/language-server/telemetry/codePercentage.js +82 -0
  80. package/out/language-server/telemetry/codePercentage.js.map +1 -0
  81. package/out/language-server/telemetry/codePercentage.test.d.ts +1 -0
  82. package/out/language-server/telemetry/codePercentage.test.js +85 -0
  83. package/out/language-server/telemetry/codePercentage.test.js.map +1 -0
  84. package/out/language-server/telemetry/types.d.ts +68 -0
  85. package/out/language-server/telemetry/types.js +3 -0
  86. package/out/language-server/telemetry/types.js.map +1 -0
  87. package/out/language-server/telemetry/userTriggerDecision.test.d.ts +1 -0
  88. package/out/language-server/telemetry/userTriggerDecision.test.js +946 -0
  89. package/out/language-server/telemetry/userTriggerDecision.test.js.map +1 -0
  90. package/out/language-server/telemetry.test.d.ts +1 -0
  91. package/out/language-server/telemetry.test.js +96 -0
  92. package/out/language-server/telemetry.test.js.map +1 -0
  93. package/out/language-server/testUtils.d.ts +69 -0
  94. package/out/language-server/testUtils.js +90 -0
  95. package/out/language-server/testUtils.js.map +1 -0
  96. package/out/language-server/utils.d.ts +6 -0
  97. package/out/language-server/utils.js +31 -0
  98. package/out/language-server/utils.js.map +1 -0
  99. package/package.json +53 -0
  100. package/script/generateServiceClient.ts +242 -0
@@ -0,0 +1,946 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const testing_1 = require("@aws/language-server-runtimes/out/testing");
4
+ const assert = require("assert");
5
+ const ts_sinon_1 = require("ts-sinon");
6
+ const vscode_languageserver_1 = require("vscode-languageserver");
7
+ const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
8
+ const codeWhispererServer_1 = require("../codeWhispererServer");
9
+ const sessionManager_1 = require("../session/sessionManager");
10
+ describe('Telemetry', () => {
11
+ const sandbox = ts_sinon_1.default.createSandbox();
12
+ let SESSION_IDS_LOG = [];
13
+ let sessionManager;
14
+ let sessionManagerSpy;
15
+ let generateSessionIdStub;
16
+ let clock;
17
+ beforeEach(() => {
18
+ const StubSessionIdGenerator = () => {
19
+ let id = 'some-random-session-uuid-' + SESSION_IDS_LOG.length;
20
+ SESSION_IDS_LOG.push(id);
21
+ return id;
22
+ };
23
+ generateSessionIdStub = ts_sinon_1.default
24
+ .stub(sessionManager_1.CodeWhispererSession.prototype, 'generateSessionId')
25
+ .callsFake(StubSessionIdGenerator);
26
+ sessionManager_1.SessionManager.reset();
27
+ sessionManager = sessionManager_1.SessionManager.getInstance();
28
+ sessionManagerSpy = sandbox.spy(sessionManager);
29
+ SESSION_IDS_LOG = [];
30
+ clock = ts_sinon_1.default.useFakeTimers({
31
+ now: 1483228800000,
32
+ });
33
+ });
34
+ afterEach(() => {
35
+ generateSessionIdStub.restore();
36
+ clock.restore();
37
+ sandbox.restore();
38
+ });
39
+ describe('User Trigger Decision telemetry', () => {
40
+ const HELLO_WORLD_IN_CSHARP = `class HelloWorld
41
+ {
42
+ static void Main()
43
+ {
44
+ Console.WriteLine("Hello World!");
45
+ }
46
+ }
47
+ `;
48
+ const AUTO_TRIGGER_POSITION = { line: 2, character: 21 };
49
+ const SOME_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///test.cs', 'csharp', 1, HELLO_WORLD_IN_CSHARP);
50
+ const SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID = vscode_languageserver_textdocument_1.TextDocument.create(
51
+ // Use unsupported extension, so that we can test that we get a match based on the LanguageId
52
+ 'file:///test.seesharp', 'CSharp', 1, HELLO_WORLD_IN_CSHARP);
53
+ const EXPECTED_REFERENCE = {
54
+ licenseName: 'test license',
55
+ repository: 'test repository',
56
+ url: 'test url',
57
+ recommendationContentSpan: { start: 0, end: 1 },
58
+ };
59
+ const DEFAULT_SUGGESTIONS = [
60
+ { itemId: 'cwspr-item-id-1', content: 'recommendation' },
61
+ { itemId: 'cwspr-item-id-2', content: 'recommendation' },
62
+ { itemId: 'cwspr-item-id-3', content: 'recommendation' },
63
+ ];
64
+ const EXPECTED_RESULT = {
65
+ sessionId: 'some-random-session-uuid-0',
66
+ items: [
67
+ {
68
+ itemId: DEFAULT_SUGGESTIONS[0].itemId,
69
+ insertText: DEFAULT_SUGGESTIONS[0].content,
70
+ range: undefined,
71
+ references: undefined,
72
+ },
73
+ {
74
+ itemId: DEFAULT_SUGGESTIONS[1].itemId,
75
+ insertText: DEFAULT_SUGGESTIONS[1].content,
76
+ range: undefined,
77
+ references: undefined,
78
+ },
79
+ {
80
+ itemId: DEFAULT_SUGGESTIONS[2].itemId,
81
+ insertText: DEFAULT_SUGGESTIONS[2].content,
82
+ range: undefined,
83
+ references: undefined,
84
+ },
85
+ ],
86
+ };
87
+ const EXPECTED_RESPONSE_CONTEXT = {
88
+ requestId: 'cwspr-request-id',
89
+ codewhispererSessionId: 'cwspr-session-id',
90
+ };
91
+ const DEFAULT_SESSION_RESULT_DATA = {
92
+ sessionId: 'some-random-session-uuid-0',
93
+ completionSessionResult: {
94
+ 'cwspr-item-id-1': {
95
+ seen: true,
96
+ accepted: false,
97
+ discarded: false,
98
+ },
99
+ 'cwspr-item-id-2': {
100
+ seen: true,
101
+ accepted: false,
102
+ discarded: false,
103
+ },
104
+ 'cwspr-item-id-3': {
105
+ seen: true,
106
+ accepted: false,
107
+ discarded: false,
108
+ },
109
+ },
110
+ };
111
+ const EMPTY_RESULT = { items: [], sessionId: '' };
112
+ let features;
113
+ let server;
114
+ // TODO move more of the service code out of the stub and into the testable realm
115
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
116
+ // for examples on how to mock just the SDK client
117
+ let service;
118
+ const setServiceResponse = (suggestions, responseContext, time = 2000) => {
119
+ service.generateSuggestions.callsFake(_request => {
120
+ clock.tick(time);
121
+ return Promise.resolve({
122
+ suggestions,
123
+ responseContext,
124
+ });
125
+ });
126
+ };
127
+ const autoTriggerInlineCompletionWithReferences = async () => await features.doInlineCompletionWithReferences({
128
+ textDocument: { uri: SOME_FILE.uri },
129
+ position: AUTO_TRIGGER_POSITION,
130
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
131
+ }, vscode_languageserver_1.CancellationToken.None);
132
+ const manualTriggerInlineCompletionWithReferences = async () => await features.doInlineCompletionWithReferences({
133
+ textDocument: { uri: SOME_FILE.uri },
134
+ position: { line: 0, character: 0 },
135
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
136
+ }, vscode_languageserver_1.CancellationToken.None);
137
+ beforeEach(async () => {
138
+ // Set up the server with a mock service, returning predefined recommendations
139
+ service = (0, ts_sinon_1.stubInterface)();
140
+ setServiceResponse(DEFAULT_SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
141
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
142
+ // Initialize the features, but don't start server yet
143
+ features = new testing_1.TestFeatures();
144
+ // Return no specific configuration for CodeWhisperer
145
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({}));
146
+ // Return credentialsStartUrl value
147
+ features.credentialsProvider.getConnectionMetadata.returns({
148
+ sso: {
149
+ startUrl: 'teststarturl',
150
+ },
151
+ });
152
+ // Start the server and open a document
153
+ await features.start(server);
154
+ features.openDocument(SOME_FILE).openDocument(SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID);
155
+ });
156
+ const aUserTriggerDecision = (override = {}) => {
157
+ return {
158
+ name: 'codewhisperer_userTriggerDecision',
159
+ data: {
160
+ codewhispererSessionId: 'cwspr-session-id',
161
+ codewhispererFirstRequestId: 'cwspr-request-id',
162
+ credentialStartUrl: 'teststarturl',
163
+ codewhispererSuggestionState: 'Reject',
164
+ codewhispererCompletionType: 'Line',
165
+ codewhispererLanguage: 'csharp',
166
+ codewhispererTriggerType: 'AutoTrigger',
167
+ codewhispererAutomatedTriggerType: 'SpecialCharacters',
168
+ codewhispererTriggerCharacter: '(',
169
+ codewhispererLineNumber: 2,
170
+ codewhispererCursorOffset: 21,
171
+ codewhispererSuggestionCount: 3,
172
+ codewhispererTotalShownTime: 0,
173
+ codewhispererTypeaheadLength: 0,
174
+ codewhispererTimeSinceLastDocumentChange: 0,
175
+ ...override,
176
+ },
177
+ };
178
+ };
179
+ describe('Case 1. Session is processed by server without sending results', () => {
180
+ it('should send Empty user desicion when Codewhisperer returned list of empty suggestions and close session', async () => {
181
+ const SUGGESTIONS = [
182
+ { itemId: 'cwspr-item-id-1', content: '' },
183
+ { itemId: 'cwspr-item-id-2', content: '' },
184
+ { itemId: 'cwspr-item-id-3', content: '' },
185
+ ];
186
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
187
+ await autoTriggerInlineCompletionWithReferences();
188
+ const currentSession = sessionManager.getCurrentSession();
189
+ assert(currentSession);
190
+ assert.equal(currentSession.state, 'CLOSED');
191
+ ts_sinon_1.default.assert.calledOnceWithExactly(sessionManagerSpy.closeSession, currentSession);
192
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
193
+ codewhispererSuggestionState: 'Empty',
194
+ });
195
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
196
+ });
197
+ it('should send Empty User Decision when Codewhisperer returned empty list of suggestions', async () => {
198
+ const SUGGESTIONS = [];
199
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
200
+ await autoTriggerInlineCompletionWithReferences();
201
+ const currentSession = sessionManager.getCurrentSession();
202
+ assert(currentSession);
203
+ assert.equal(currentSession.state, 'CLOSED');
204
+ ts_sinon_1.default.assert.calledOnceWithExactly(sessionManagerSpy.closeSession, currentSession);
205
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
206
+ codewhispererSuggestionState: 'Empty',
207
+ codewhispererCompletionType: undefined,
208
+ codewhispererSuggestionCount: 0,
209
+ });
210
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
211
+ });
212
+ it('should send Discard User Decision when all suggestions are filtered out by includeSuggestionsWithCodeReferences setting filter', async () => {
213
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: false }));
214
+ const SUGGESTIONS = [
215
+ {
216
+ itemId: 'cwspr-item-id-1',
217
+ content: 'recommendation with reference',
218
+ references: [EXPECTED_REFERENCE],
219
+ },
220
+ {
221
+ itemId: 'cwspr-item-id-2',
222
+ content: 'recommendation with reference',
223
+ references: [EXPECTED_REFERENCE],
224
+ },
225
+ {
226
+ itemId: 'cwspr-item-id-3',
227
+ content: 'recommendation with reference',
228
+ references: [EXPECTED_REFERENCE],
229
+ },
230
+ ];
231
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
232
+ await autoTriggerInlineCompletionWithReferences();
233
+ const currentSession = sessionManager.getCurrentSession();
234
+ assert(currentSession);
235
+ assert.equal(currentSession.state, 'CLOSED');
236
+ ts_sinon_1.default.assert.calledOnceWithExactly(sessionManagerSpy.closeSession, currentSession);
237
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
238
+ codewhispererSuggestionState: 'Discard',
239
+ });
240
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
241
+ });
242
+ it('should send Discard User Decision when all suggestions are discarded after right context merge', async () => {
243
+ const SUGGESTIONS = [
244
+ { itemId: 'cwspr-item-id-1', content: HELLO_WORLD_IN_CSHARP },
245
+ { itemId: 'cwspr-item-id-2', content: HELLO_WORLD_IN_CSHARP },
246
+ { itemId: 'cwspr-item-id-3', content: HELLO_WORLD_IN_CSHARP },
247
+ ];
248
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
249
+ await manualTriggerInlineCompletionWithReferences();
250
+ const currentSession = sessionManager.getCurrentSession();
251
+ assert(currentSession);
252
+ assert.equal(currentSession?.state, 'CLOSED');
253
+ ts_sinon_1.default.assert.calledOnceWithExactly(sessionManagerSpy.closeSession, currentSession);
254
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
255
+ codewhispererSuggestionState: 'Discard',
256
+ codewhispererCompletionType: 'Block',
257
+ codewhispererTriggerType: 'OnDemand',
258
+ codewhispererAutomatedTriggerType: undefined,
259
+ codewhispererTriggerCharacter: undefined,
260
+ codewhispererLineNumber: 0,
261
+ codewhispererCursorOffset: 0,
262
+ });
263
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
264
+ });
265
+ });
266
+ describe('Case 2. Session returns recommendation to client and is closed by LogInlineCompletionSessionResults notification', () => {
267
+ it('should emit User Decision event for active completion session when session results are received', async () => {
268
+ setServiceResponse(DEFAULT_SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
269
+ await autoTriggerInlineCompletionWithReferences();
270
+ const currentSession = sessionManager.getCurrentSession();
271
+ assert(currentSession);
272
+ assert.equal(currentSession?.state, 'ACTIVE');
273
+ ts_sinon_1.default.assert.notCalled(sessionManagerSpy.closeSession);
274
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
275
+ name: 'codewhisperer_userTriggerDecision',
276
+ });
277
+ await features.doLogInlineCompletionSessionResults(DEFAULT_SESSION_RESULT_DATA);
278
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, {
279
+ name: 'codewhisperer_userTriggerDecision',
280
+ });
281
+ });
282
+ it('should emit User Decision event with correct typeaheadLength value when session results are received', async () => {
283
+ setServiceResponse(DEFAULT_SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
284
+ await autoTriggerInlineCompletionWithReferences();
285
+ const currentSession = sessionManager.getCurrentSession();
286
+ assert(currentSession);
287
+ assert.equal(currentSession?.state, 'ACTIVE');
288
+ ts_sinon_1.default.assert.notCalled(sessionManagerSpy.closeSession);
289
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
290
+ name: 'codewhisperer_userTriggerDecision',
291
+ });
292
+ await features.doLogInlineCompletionSessionResults({
293
+ ...DEFAULT_SESSION_RESULT_DATA,
294
+ typeaheadLength: 20,
295
+ });
296
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, {
297
+ name: 'codewhisperer_userTriggerDecision',
298
+ data: {
299
+ codewhispererTypeaheadLength: 20,
300
+ },
301
+ });
302
+ });
303
+ it('should not emit User Decision event when session results are received after session was closed', async () => {
304
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
305
+ ...EXPECTED_RESPONSE_CONTEXT,
306
+ codewhispererSessionId: 'cwspr-session-id-1',
307
+ });
308
+ await autoTriggerInlineCompletionWithReferences();
309
+ const firstSession = sessionManager.getCurrentSession();
310
+ assert(firstSession);
311
+ assert.equal(firstSession.state, 'ACTIVE');
312
+ ts_sinon_1.default.assert.notCalled(sessionManagerSpy.closeSession);
313
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
314
+ name: 'codewhisperer_userTriggerDecision',
315
+ });
316
+ // Send second completion request to close first one
317
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
318
+ ...EXPECTED_RESPONSE_CONTEXT,
319
+ codewhispererSessionId: 'cwspr-session-id-2',
320
+ });
321
+ await autoTriggerInlineCompletionWithReferences();
322
+ assert.equal(firstSession.state, 'DISCARD');
323
+ assert.notEqual(firstSession, sessionManager.getCurrentSession());
324
+ ts_sinon_1.default.assert.calledWithExactly(sessionManagerSpy.closeSession, firstSession);
325
+ // Test that session reports it's status when second request is received
326
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, {
327
+ name: 'codewhisperer_userTriggerDecision',
328
+ data: {
329
+ codewhispererSessionId: 'cwspr-session-id-1',
330
+ codewhispererSuggestionState: 'Discard',
331
+ },
332
+ });
333
+ features.telemetry.emitMetric.resetHistory();
334
+ // Send session results for closed first session
335
+ await features.doLogInlineCompletionSessionResults({
336
+ ...DEFAULT_SESSION_RESULT_DATA,
337
+ sessionId: firstSession.id,
338
+ });
339
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
340
+ name: 'codewhisperer_userTriggerDecision',
341
+ });
342
+ });
343
+ it('should not emit User Decision event when session results received for session that does not exist', async () => {
344
+ // Send session results for closed first session
345
+ await features.doLogInlineCompletionSessionResults({
346
+ ...DEFAULT_SESSION_RESULT_DATA,
347
+ sessionId: 'cwspr-session-id-never-created',
348
+ });
349
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
350
+ name: 'codewhisperer_userTriggerDecision',
351
+ data: {
352
+ codewhispererSessionId: 'cwspr-session-id-never-created',
353
+ },
354
+ });
355
+ });
356
+ it('should emit Accept User Decision event for current active completion session when session results are received with accepted suggestion', async () => {
357
+ setServiceResponse(DEFAULT_SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
358
+ const SESSION_RESULT_DATA = {
359
+ sessionId: 'some-random-session-uuid-0',
360
+ completionSessionResult: {
361
+ 'cwspr-item-id-1': {
362
+ seen: true,
363
+ accepted: false,
364
+ discarded: false,
365
+ },
366
+ 'cwspr-item-id-2': {
367
+ seen: true,
368
+ accepted: true, // Second suggestion was accepted
369
+ discarded: false,
370
+ },
371
+ 'cwspr-item-id-3': {
372
+ seen: true,
373
+ accepted: false,
374
+ discarded: false,
375
+ },
376
+ },
377
+ };
378
+ await autoTriggerInlineCompletionWithReferences();
379
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
380
+ name: 'codewhisperer_userTriggerDecision',
381
+ });
382
+ // Send session results for closed first session
383
+ await features.doLogInlineCompletionSessionResults(SESSION_RESULT_DATA);
384
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
385
+ codewhispererSuggestionState: 'Accept',
386
+ });
387
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
388
+ });
389
+ it('should emit Reject User Decision event for current active completion session when session results are received without accepted suggestion', async () => {
390
+ setServiceResponse(DEFAULT_SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
391
+ const SESSION_RESULT_DATA = {
392
+ sessionId: 'some-random-session-uuid-0',
393
+ completionSessionResult: {
394
+ 'cwspr-item-id-1': {
395
+ seen: true, // Reject
396
+ accepted: false,
397
+ discarded: false,
398
+ },
399
+ 'cwspr-item-id-2': {
400
+ seen: false, // Unseen
401
+ accepted: false,
402
+ discarded: true,
403
+ },
404
+ 'cwspr-item-id-3': {
405
+ seen: false, // Discard
406
+ accepted: false,
407
+ discarded: true,
408
+ },
409
+ },
410
+ };
411
+ await autoTriggerInlineCompletionWithReferences();
412
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
413
+ name: 'codewhisperer_userTriggerDecision',
414
+ });
415
+ // Send session results for closed first session
416
+ await features.doLogInlineCompletionSessionResults(SESSION_RESULT_DATA);
417
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
418
+ codewhispererSuggestionState: 'Reject',
419
+ });
420
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
421
+ });
422
+ it('should send Discard User Decision when all suggestions have Discard state', async () => {
423
+ setServiceResponse(DEFAULT_SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
424
+ const SESSION_RESULT_DATA = {
425
+ sessionId: 'some-random-session-uuid-0',
426
+ completionSessionResult: {
427
+ 'cwspr-item-id-1': {
428
+ seen: false, // Discard
429
+ accepted: false,
430
+ discarded: true,
431
+ },
432
+ 'cwspr-item-id-2': {
433
+ seen: false, // Discard
434
+ accepted: false,
435
+ discarded: true,
436
+ },
437
+ 'cwspr-item-id-3': {
438
+ seen: false, // Discard
439
+ accepted: false,
440
+ discarded: true,
441
+ },
442
+ },
443
+ };
444
+ await autoTriggerInlineCompletionWithReferences();
445
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
446
+ name: 'codewhisperer_userTriggerDecision',
447
+ });
448
+ // Send session results for closed first session
449
+ await features.doLogInlineCompletionSessionResults(SESSION_RESULT_DATA);
450
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
451
+ codewhispererSuggestionState: 'Discard',
452
+ });
453
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
454
+ });
455
+ it('should set codewhispererTimeSinceLastDocumentChange as difference between 2 any document changes', async () => {
456
+ const typeSomething = async () => await features.doChangeTextDocument({
457
+ textDocument: { uri: SOME_FILE.uri, version: 1 },
458
+ contentChanges: [
459
+ {
460
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
461
+ text: 'f',
462
+ },
463
+ ],
464
+ });
465
+ await autoTriggerInlineCompletionWithReferences();
466
+ await typeSomething();
467
+ clock.tick(1234);
468
+ await typeSomething();
469
+ clock.tick(5678);
470
+ await typeSomething();
471
+ await features.doLogInlineCompletionSessionResults(DEFAULT_SESSION_RESULT_DATA);
472
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
473
+ codewhispererSuggestionState: 'Reject',
474
+ codewhispererTimeSinceLastDocumentChange: 5678,
475
+ });
476
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
477
+ });
478
+ });
479
+ describe('Case 3. Active session is closed by subsequent trigger', function () {
480
+ it('should close ACTIVE session and emit Discard user trigger decision event on Manual trigger', async () => {
481
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
482
+ ...EXPECTED_RESPONSE_CONTEXT,
483
+ codewhispererSessionId: 'cwspr-session-id-1',
484
+ });
485
+ await autoTriggerInlineCompletionWithReferences();
486
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
487
+ ...EXPECTED_RESPONSE_CONTEXT,
488
+ codewhispererSessionId: 'cwspr-session-id-2',
489
+ });
490
+ await manualTriggerInlineCompletionWithReferences();
491
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
492
+ codewhispererSessionId: 'cwspr-session-id-1',
493
+ codewhispererSuggestionState: 'Discard',
494
+ });
495
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
496
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
497
+ name: 'codewhisperer_userTriggerDecision',
498
+ data: {
499
+ codewhispererSessionId: 'cwspr-session-id-2',
500
+ },
501
+ });
502
+ });
503
+ it('should close ACTIVE session and emit Discard user trigger decision event on Auto trigger', async () => {
504
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
505
+ ...EXPECTED_RESPONSE_CONTEXT,
506
+ codewhispererSessionId: 'cwspr-session-id-1',
507
+ });
508
+ await autoTriggerInlineCompletionWithReferences();
509
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
510
+ ...EXPECTED_RESPONSE_CONTEXT,
511
+ codewhispererSessionId: 'cwspr-session-id-2',
512
+ });
513
+ await autoTriggerInlineCompletionWithReferences();
514
+ const expectedUserTriggerDecisionMetric = aUserTriggerDecision({
515
+ codewhispererSessionId: 'cwspr-session-id-1',
516
+ codewhispererSuggestionState: 'Discard',
517
+ });
518
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserTriggerDecisionMetric);
519
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
520
+ name: 'codewhisperer_userTriggerDecision',
521
+ data: {
522
+ codewhispererSessionId: 'cwspr-session-id-2',
523
+ },
524
+ });
525
+ });
526
+ it('should attach previous session trigger decision', async () => {
527
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
528
+ ...EXPECTED_RESPONSE_CONTEXT,
529
+ codewhispererSessionId: 'cwspr-session-id-1',
530
+ });
531
+ await autoTriggerInlineCompletionWithReferences();
532
+ const firstSession = sessionManager.getCurrentSession();
533
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
534
+ ...EXPECTED_RESPONSE_CONTEXT,
535
+ codewhispererSessionId: 'cwspr-session-id-2',
536
+ });
537
+ await autoTriggerInlineCompletionWithReferences();
538
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
539
+ ...EXPECTED_RESPONSE_CONTEXT,
540
+ codewhispererSessionId: 'cwspr-session-id-3',
541
+ });
542
+ await autoTriggerInlineCompletionWithReferences();
543
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
544
+ codewhispererSessionId: 'cwspr-session-id-1',
545
+ codewhispererSuggestionState: 'Discard',
546
+ }));
547
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
548
+ codewhispererSessionId: 'cwspr-session-id-2',
549
+ codewhispererSuggestionState: 'Discard',
550
+ codewhispererPreviousSuggestionState: firstSession?.getAggregatedUserTriggerDecision(), // 'Discard'
551
+ codewhispererTimeSinceLastUserDecision: 0,
552
+ }));
553
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
554
+ name: 'codewhisperer_userTriggerDecision',
555
+ data: {
556
+ codewhispererSessionId: 'cwspr-session-id-3',
557
+ },
558
+ });
559
+ });
560
+ it('should set correct values for past trigger result fields', async () => {
561
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
562
+ ...EXPECTED_RESPONSE_CONTEXT,
563
+ codewhispererSessionId: 'cwspr-session-id-1',
564
+ });
565
+ await autoTriggerInlineCompletionWithReferences();
566
+ const firstSession = sessionManager.getCurrentSession();
567
+ await features.doLogInlineCompletionSessionResults(DEFAULT_SESSION_RESULT_DATA);
568
+ clock.tick(1234);
569
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
570
+ ...EXPECTED_RESPONSE_CONTEXT,
571
+ codewhispererSessionId: 'cwspr-session-id-2',
572
+ });
573
+ await autoTriggerInlineCompletionWithReferences();
574
+ // Trigger 3rd session to close second one
575
+ await autoTriggerInlineCompletionWithReferences();
576
+ // For first session previous data does not exist
577
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
578
+ codewhispererSessionId: 'cwspr-session-id-1',
579
+ codewhispererSuggestionState: 'Reject',
580
+ codewhispererTimeSinceLastUserDecision: undefined,
581
+ codewhispererPreviousSuggestionState: undefined,
582
+ }));
583
+ // For second session previous data matches
584
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
585
+ codewhispererSessionId: 'cwspr-session-id-2',
586
+ codewhispererSuggestionState: 'Discard',
587
+ codewhispererPreviousSuggestionState: firstSession?.getAggregatedUserTriggerDecision(), // 'Reject'
588
+ codewhispererTimeSinceLastUserDecision: 1234,
589
+ }));
590
+ });
591
+ });
592
+ describe('Case 4. Inflight session is closed by subsequent completion request', function () {
593
+ it('should emit Discard user trigger decision event when REQUESTING session is closed before becoming ACTIVE', async () => {
594
+ // Chain requests in a callbacks
595
+ let concurrentCount = 0;
596
+ let requests = [];
597
+ service.generateSuggestions.callsFake(_request => {
598
+ clock.tick(1000);
599
+ let i = concurrentCount;
600
+ if (concurrentCount < 2) {
601
+ // Trigger second request before first one was resolved
602
+ concurrentCount++;
603
+ const req = autoTriggerInlineCompletionWithReferences();
604
+ requests.push(req);
605
+ clock.tick(10);
606
+ }
607
+ clock.tick(250);
608
+ return Promise.resolve({
609
+ suggestions: DEFAULT_SUGGESTIONS,
610
+ responseContext: {
611
+ ...EXPECTED_RESPONSE_CONTEXT,
612
+ codewhispererSessionId: `cwspr-session-id-${i}`,
613
+ },
614
+ });
615
+ });
616
+ const result = [await autoTriggerInlineCompletionWithReferences(), ...(await Promise.all(requests))];
617
+ assert.deepEqual(result, [
618
+ EMPTY_RESULT,
619
+ EMPTY_RESULT,
620
+ { ...EXPECTED_RESULT, sessionId: 'some-random-session-uuid-2' },
621
+ ]);
622
+ // 3 sessions were created, each one closes previous one in REQUESTING state
623
+ assert.equal(SESSION_IDS_LOG.length, 3);
624
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
625
+ codewhispererSessionId: 'cwspr-session-id-0',
626
+ codewhispererSuggestionState: 'Discard',
627
+ codewhispererTimeToFirstRecommendation: 2520,
628
+ }));
629
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
630
+ codewhispererSessionId: 'cwspr-session-id-1',
631
+ codewhispererSuggestionState: 'Discard',
632
+ codewhispererTimeToFirstRecommendation: 2510,
633
+ codewhispererPreviousSuggestionState: 'Discard',
634
+ }));
635
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
636
+ codewhispererSessionId: 'cwspr-session-id-2',
637
+ codewhispererSuggestionState: 'Empty',
638
+ }));
639
+ const activeSession = sessionManager.getActiveSession();
640
+ assert.equal(activeSession?.id, SESSION_IDS_LOG[2]);
641
+ await features.doLogInlineCompletionSessionResults({
642
+ ...DEFAULT_SESSION_RESULT_DATA,
643
+ sessionId: SESSION_IDS_LOG[2],
644
+ });
645
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, aUserTriggerDecision({
646
+ codewhispererSessionId: 'cwspr-session-id-2',
647
+ codewhispererSuggestionState: 'Reject',
648
+ codewhispererTimeSinceLastUserDecision: 260,
649
+ codewhispererPreviousSuggestionState: 'Discard',
650
+ codewhispererTimeToFirstRecommendation: 1250,
651
+ }));
652
+ });
653
+ });
654
+ it('should report user trigger decision only once for a session', async () => {
655
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
656
+ ...EXPECTED_RESPONSE_CONTEXT,
657
+ codewhispererSessionId: 'cwspr-session-id-1',
658
+ });
659
+ await autoTriggerInlineCompletionWithReferences();
660
+ const firstSession = sessionManager.getCurrentSession();
661
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
662
+ name: 'codewhisperer_userTriggerDecision',
663
+ });
664
+ // Record session results and close the session
665
+ await features.doLogInlineCompletionSessionResults(DEFAULT_SESSION_RESULT_DATA);
666
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, {
667
+ name: 'codewhisperer_userTriggerDecision',
668
+ data: {
669
+ codewhispererSessionId: 'cwspr-session-id-1',
670
+ },
671
+ });
672
+ assert.equal(firstSession?.state, 'CLOSED');
673
+ features.telemetry.emitMetric.resetHistory();
674
+ // Triggering new completion request creates new session
675
+ // and should not emit telemetry for previous session, which was closed earlier
676
+ setServiceResponse(DEFAULT_SUGGESTIONS, {
677
+ ...EXPECTED_RESPONSE_CONTEXT,
678
+ codewhispererSessionId: 'cwspr-session-id-2',
679
+ });
680
+ await autoTriggerInlineCompletionWithReferences();
681
+ // Or attempt to record data
682
+ await features.doLogInlineCompletionSessionResults(DEFAULT_SESSION_RESULT_DATA);
683
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
684
+ name: 'codewhisperer_userTriggerDecision',
685
+ data: {
686
+ codewhispererSessionId: 'cwspr-session-id-1',
687
+ },
688
+ });
689
+ });
690
+ });
691
+ describe('User Decision Telemetry', () => {
692
+ const HELLO_WORLD_IN_CSHARP = `class HelloWorld
693
+ {
694
+ static void Main()
695
+ {
696
+ Console.WriteLine("Hello World!");
697
+ }
698
+ }
699
+ `;
700
+ const AUTO_TRIGGER_POSITION = { line: 2, character: 21 };
701
+ const SOME_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///test.cs', 'csharp', 1, HELLO_WORLD_IN_CSHARP);
702
+ const SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID = vscode_languageserver_textdocument_1.TextDocument.create(
703
+ // Use unsupported extension, so that we can test that we get a match based on the LanguageId
704
+ 'file:///test.seesharp', 'CSharp', 1, HELLO_WORLD_IN_CSHARP);
705
+ const REFERENCE = [
706
+ {
707
+ licenseName: 'test license 1',
708
+ repository: 'test repository 1',
709
+ url: 'test url 1',
710
+ recommendationContentSpan: { start: 0, end: 1 },
711
+ },
712
+ {
713
+ licenseName: 'test license 2',
714
+ repository: 'test repository 2',
715
+ url: 'test url 2',
716
+ recommendationContentSpan: { start: 0, end: 1 },
717
+ },
718
+ {
719
+ licenseName: 'test license 1',
720
+ repository: 'test repository 3',
721
+ url: 'test url 3',
722
+ recommendationContentSpan: { start: 0, end: 1 },
723
+ },
724
+ ];
725
+ const DEFAULT_SUGGESTIONS = [
726
+ { itemId: 'cwspr-item-id-1', content: 'recommendation' },
727
+ { itemId: 'cwspr-item-id-2', content: 'recommendation' },
728
+ { itemId: 'cwspr-item-id-3', content: 'recommendation' },
729
+ ];
730
+ const EXPECTED_RESPONSE_CONTEXT = {
731
+ requestId: 'cwspr-request-id',
732
+ codewhispererSessionId: 'cwspr-session-id',
733
+ };
734
+ let features;
735
+ let server;
736
+ // TODO move more of the service code out of the stub and into the testable realm
737
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
738
+ // for examples on how to mock just the SDK client
739
+ let service;
740
+ const setServiceResponse = (suggestions, responseContext, time = 2000) => {
741
+ service.generateSuggestions.callsFake(_request => {
742
+ clock.tick(time);
743
+ return Promise.resolve({
744
+ suggestions,
745
+ responseContext,
746
+ });
747
+ });
748
+ };
749
+ const autoTriggerInlineCompletionWithReferences = async () => await features.doInlineCompletionWithReferences({
750
+ textDocument: { uri: SOME_FILE.uri },
751
+ position: AUTO_TRIGGER_POSITION,
752
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
753
+ }, vscode_languageserver_1.CancellationToken.None);
754
+ beforeEach(async () => {
755
+ // Set up the server with a mock service, returning predefined recommendations
756
+ service = (0, ts_sinon_1.stubInterface)();
757
+ setServiceResponse(DEFAULT_SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
758
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
759
+ // Initialize the features, but don't start server yet
760
+ features = new testing_1.TestFeatures();
761
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: true }));
762
+ // Return credentialsStartUrl value
763
+ features.credentialsProvider.getConnectionMetadata.returns({
764
+ sso: {
765
+ startUrl: 'teststarturl',
766
+ },
767
+ });
768
+ // Start the server and open a document
769
+ await features.start(server);
770
+ features.openDocument(SOME_FILE).openDocument(SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID);
771
+ });
772
+ const aUserDecision = (override = {}) => {
773
+ return {
774
+ name: 'codewhisperer_userDecision',
775
+ data: {
776
+ codewhispererRequestId: 'cwspr-request-id',
777
+ codewhispererSessionId: 'cwspr-session-id',
778
+ credentialStartUrl: 'teststarturl',
779
+ codewhispererCompletionType: 'Line',
780
+ codewhispererLanguage: 'csharp',
781
+ codewhispererTriggerType: 'AutoTrigger',
782
+ codewhispererSuggestionIndex: 0,
783
+ codewhispererSuggestionState: 'Discard',
784
+ codewhispererSuggestionReferences: ['MIT'],
785
+ codewhispererSuggestionReferenceCount: 1,
786
+ ...override,
787
+ },
788
+ };
789
+ };
790
+ it('should send correct empty reference when suggestion does not have any', async () => {
791
+ const SESSION_RESULT_DATA = {
792
+ sessionId: 'some-random-session-uuid-0',
793
+ completionSessionResult: {
794
+ 'cwspr-item-id-1': {
795
+ accepted: true, // 'Accept'
796
+ seen: true,
797
+ discarded: false,
798
+ },
799
+ },
800
+ };
801
+ const SUGGESTIONS = [
802
+ {
803
+ itemId: 'cwspr-item-id-1',
804
+ content: 'recommendation with reference',
805
+ references: undefined,
806
+ },
807
+ ];
808
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
809
+ await autoTriggerInlineCompletionWithReferences();
810
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
811
+ name: 'codewhisperer_userDecision',
812
+ });
813
+ await features.doLogInlineCompletionSessionResults(SESSION_RESULT_DATA);
814
+ const expectedUserDecisionMetric = aUserDecision({
815
+ codewhispererSuggestionIndex: 0,
816
+ codewhispererSuggestionState: 'Accept',
817
+ codewhispererSuggestionReferences: [],
818
+ codewhispererSuggestionReferenceCount: 0,
819
+ });
820
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserDecisionMetric);
821
+ });
822
+ it('should send correct reference when suggestion has any', async () => {
823
+ const SESSION_RESULT_DATA = {
824
+ sessionId: 'some-random-session-uuid-0',
825
+ completionSessionResult: {
826
+ 'cwspr-item-id-1-y': {
827
+ accepted: true, // 'Accept'
828
+ seen: true,
829
+ discarded: false,
830
+ },
831
+ },
832
+ };
833
+ const SUGGESTIONS = [
834
+ {
835
+ itemId: 'cwspr-item-id-1-y',
836
+ content: 'recommendation with reference',
837
+ references: [REFERENCE[0]],
838
+ },
839
+ ];
840
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
841
+ await autoTriggerInlineCompletionWithReferences();
842
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
843
+ name: 'codewhisperer_userDecision',
844
+ });
845
+ await features.doLogInlineCompletionSessionResults(SESSION_RESULT_DATA);
846
+ const expectedUserDecisionMetric = aUserDecision({
847
+ codewhispererSuggestionIndex: 0,
848
+ codewhispererSuggestionState: 'Accept',
849
+ codewhispererSuggestionReferences: ['test license 1'],
850
+ codewhispererSuggestionReferenceCount: 1,
851
+ });
852
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserDecisionMetric);
853
+ });
854
+ it('should send unique licenses when suggestion has any that overlaps', async () => {
855
+ const SESSION_RESULT_DATA = {
856
+ sessionId: 'some-random-session-uuid-0',
857
+ completionSessionResult: {
858
+ 'cwspr-item-id-1': {
859
+ accepted: true, // 'Accept'
860
+ seen: true,
861
+ discarded: false,
862
+ },
863
+ },
864
+ };
865
+ const SUGGESTIONS = [
866
+ {
867
+ itemId: 'cwspr-item-id-1',
868
+ content: 'recommendation with reference',
869
+ references: REFERENCE,
870
+ },
871
+ ];
872
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
873
+ await autoTriggerInlineCompletionWithReferences();
874
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
875
+ name: 'codewhisperer_userDecision',
876
+ });
877
+ await features.doLogInlineCompletionSessionResults(SESSION_RESULT_DATA);
878
+ const expectedUserDecisionMetric = aUserDecision({
879
+ codewhispererSuggestionIndex: 0,
880
+ codewhispererSuggestionState: 'Accept',
881
+ codewhispererSuggestionReferences: ['test license 1', 'test license 2'],
882
+ codewhispererSuggestionReferenceCount: 3,
883
+ });
884
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, expectedUserDecisionMetric);
885
+ });
886
+ it('should send multiple metrics on all suggestions returned', async () => {
887
+ const SESSION_RESULT_DATA = {
888
+ sessionId: 'some-random-session-uuid-0',
889
+ completionSessionResult: {
890
+ 'cwspr-item-id-1': {
891
+ accepted: true, // 'Accept'
892
+ seen: true,
893
+ discarded: false,
894
+ },
895
+ 'cwspr-item-id-2': {
896
+ accepted: false, // Discard
897
+ seen: false,
898
+ discarded: true,
899
+ },
900
+ 'cwspr-item-id-3': {
901
+ accepted: false, // 'Unseen'
902
+ seen: false,
903
+ discarded: false,
904
+ },
905
+ },
906
+ };
907
+ const SUGGESTIONS = [
908
+ {
909
+ itemId: 'cwspr-item-id-1',
910
+ content: 'recommendation with reference',
911
+ references: REFERENCE,
912
+ },
913
+ {
914
+ itemId: 'cwspr-item-id-2',
915
+ content: 'recommendation with reference',
916
+ references: REFERENCE,
917
+ },
918
+ {
919
+ itemId: 'cwspr-item-id-3',
920
+ content: 'recommendation with reference',
921
+ references: REFERENCE,
922
+ },
923
+ ];
924
+ setServiceResponse(SUGGESTIONS, EXPECTED_RESPONSE_CONTEXT);
925
+ await autoTriggerInlineCompletionWithReferences();
926
+ ts_sinon_1.default.assert.neverCalledWithMatch(features.telemetry.emitMetric, {
927
+ name: 'codewhisperer_userDecision',
928
+ });
929
+ await features.doLogInlineCompletionSessionResults(SESSION_RESULT_DATA);
930
+ const expectedStates = ['Accept', 'Discard', 'Unseen'];
931
+ const expectedUserDecisionMetrics = new Array();
932
+ SUGGESTIONS.forEach((_, i) => {
933
+ expectedUserDecisionMetrics.push(aUserDecision({
934
+ codewhispererSuggestionIndex: i,
935
+ codewhispererSuggestionState: expectedStates[i],
936
+ codewhispererSuggestionReferences: ['test license 1', 'test license 2'],
937
+ codewhispererSuggestionReferenceCount: 3,
938
+ }));
939
+ });
940
+ expectedUserDecisionMetrics.forEach(metric => {
941
+ ts_sinon_1.default.assert.calledWithMatch(features.telemetry.emitMetric, metric);
942
+ });
943
+ });
944
+ });
945
+ });
946
+ //# sourceMappingURL=userTriggerDecision.test.js.map