@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.
- package/CHANGELOG.md +7 -0
- package/README.md +19 -0
- package/out/client/sigv4/codewhisperer.d.ts +3 -0
- package/out/client/sigv4/codewhisperer.js +14 -0
- package/out/client/sigv4/codewhisperer.js.map +1 -0
- package/out/client/sigv4/service.json +346 -0
- package/out/client/token/bearer-token-service.json +1627 -0
- package/out/client/token/codewhisperer.d.ts +14 -0
- package/out/client/token/codewhisperer.js +23 -0
- package/out/client/token/codewhisperer.js.map +1 -0
- package/out/index.d.ts +2 -0
- package/out/index.js +19 -0
- package/out/index.js.map +1 -0
- package/out/language-server/auto-trigger/autoTrigger.d.ts +38 -0
- package/out/language-server/auto-trigger/autoTrigger.js +119 -0
- package/out/language-server/auto-trigger/autoTrigger.js.map +1 -0
- package/out/language-server/auto-trigger/autoTrigger.test.d.ts +1 -0
- package/out/language-server/auto-trigger/autoTrigger.test.js +82 -0
- package/out/language-server/auto-trigger/autoTrigger.test.js.map +1 -0
- package/out/language-server/auto-trigger/coefficients.json +405 -0
- package/out/language-server/codeWhispererSecurityScanServer.d.ts +4 -0
- package/out/language-server/codeWhispererSecurityScanServer.js +76 -0
- package/out/language-server/codeWhispererSecurityScanServer.js.map +1 -0
- package/out/language-server/codeWhispererServer.d.ts +7 -0
- package/out/language-server/codeWhispererServer.js +425 -0
- package/out/language-server/codeWhispererServer.js.map +1 -0
- package/out/language-server/codeWhispererServer.test.d.ts +1 -0
- package/out/language-server/codeWhispererServer.test.js +1218 -0
- package/out/language-server/codeWhispererServer.test.js.map +1 -0
- package/out/language-server/codeWhispererService.d.ts +84 -0
- package/out/language-server/codeWhispererService.js +168 -0
- package/out/language-server/codeWhispererService.js.map +1 -0
- package/out/language-server/dependencyGraph/commonUtil.d.ts +2 -0
- package/out/language-server/dependencyGraph/commonUtil.js +20 -0
- package/out/language-server/dependencyGraph/commonUtil.js.map +1 -0
- package/out/language-server/dependencyGraph/constants.d.ts +2 -0
- package/out/language-server/dependencyGraph/constants.js +6 -0
- package/out/language-server/dependencyGraph/constants.js.map +1 -0
- package/out/language-server/dependencyGraph/csharpDependencyGraph.d.ts +27 -0
- package/out/language-server/dependencyGraph/csharpDependencyGraph.js +145 -0
- package/out/language-server/dependencyGraph/csharpDependencyGraph.js.map +1 -0
- package/out/language-server/dependencyGraph/csharpDependencyGraph.test.d.ts +1 -0
- package/out/language-server/dependencyGraph/csharpDependencyGraph.test.js +341 -0
- package/out/language-server/dependencyGraph/csharpDependencyGraph.test.js.map +1 -0
- package/out/language-server/dependencyGraph/dependencyGraph.d.ts +125 -0
- package/out/language-server/dependencyGraph/dependencyGraph.js +152 -0
- package/out/language-server/dependencyGraph/dependencyGraph.js.map +1 -0
- package/out/language-server/dependencyGraph/dependencyGraphFactory.d.ts +15 -0
- package/out/language-server/dependencyGraph/dependencyGraphFactory.js +22 -0
- package/out/language-server/dependencyGraph/dependencyGraphFactory.js.map +1 -0
- package/out/language-server/languageDetection.d.ts +3 -0
- package/out/language-server/languageDetection.js +56 -0
- package/out/language-server/languageDetection.js.map +1 -0
- package/out/language-server/mergeRightUtils.d.ts +6 -0
- package/out/language-server/mergeRightUtils.js +37 -0
- package/out/language-server/mergeRightUtils.js.map +1 -0
- package/out/language-server/mergeRightUtils.test.d.ts +1 -0
- package/out/language-server/mergeRightUtils.test.js +53 -0
- package/out/language-server/mergeRightUtils.test.js.map +1 -0
- package/out/language-server/proxy-server.d.ts +2 -0
- package/out/language-server/proxy-server.js +35 -0
- package/out/language-server/proxy-server.js.map +1 -0
- package/out/language-server/securityScan/securityScanHandler.d.ts +17 -0
- package/out/language-server/securityScan/securityScanHandler.js +139 -0
- package/out/language-server/securityScan/securityScanHandler.js.map +1 -0
- package/out/language-server/securityScan/securityScanHandler.test.d.ts +1 -0
- package/out/language-server/securityScan/securityScanHandler.test.js +170 -0
- package/out/language-server/securityScan/securityScanHandler.test.js.map +1 -0
- package/out/language-server/securityScan/types.d.ts +49 -0
- package/out/language-server/securityScan/types.js +3 -0
- package/out/language-server/securityScan/types.js.map +1 -0
- package/out/language-server/session/sessionManager.d.ts +91 -0
- package/out/language-server/session/sessionManager.js +234 -0
- package/out/language-server/session/sessionManager.js.map +1 -0
- package/out/language-server/session/sessionManager.test.d.ts +1 -0
- package/out/language-server/session/sessionManager.test.js +584 -0
- package/out/language-server/session/sessionManager.test.js.map +1 -0
- package/out/language-server/telemetry/codePercentage.d.ts +17 -0
- package/out/language-server/telemetry/codePercentage.js +82 -0
- package/out/language-server/telemetry/codePercentage.js.map +1 -0
- package/out/language-server/telemetry/codePercentage.test.d.ts +1 -0
- package/out/language-server/telemetry/codePercentage.test.js +85 -0
- package/out/language-server/telemetry/codePercentage.test.js.map +1 -0
- package/out/language-server/telemetry/types.d.ts +68 -0
- package/out/language-server/telemetry/types.js +3 -0
- package/out/language-server/telemetry/types.js.map +1 -0
- package/out/language-server/telemetry/userTriggerDecision.test.d.ts +1 -0
- package/out/language-server/telemetry/userTriggerDecision.test.js +946 -0
- package/out/language-server/telemetry/userTriggerDecision.test.js.map +1 -0
- package/out/language-server/telemetry.test.d.ts +1 -0
- package/out/language-server/telemetry.test.js +96 -0
- package/out/language-server/telemetry.test.js.map +1 -0
- package/out/language-server/testUtils.d.ts +69 -0
- package/out/language-server/testUtils.js +90 -0
- package/out/language-server/testUtils.js.map +1 -0
- package/out/language-server/utils.d.ts +6 -0
- package/out/language-server/utils.js +31 -0
- package/out/language-server/utils.js.map +1 -0
- package/package.json +53 -0
- 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
|