Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/test/node/telemetry.spec.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { suite, test } from 'vitest';
8
import { ChatLocation } from '../../../platform/chat/common/commonTypes';
9
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
10
import { createPlatformServices } from '../../../platform/test/node/services';
11
import { allEvents, withTelemetryCapture } from '../../../platform/test/node/telemetry';
12
import { createTelemetryWithId, getCodeBlocks, sendConversationalMessageTelemetry, sendUserActionTelemetry } from '../../prompt/node/telemetry';
13
14
// TODO @lramos15 Re-enable once telemetry has been fully cleaned up
15
suite.skip('Conversation telemetry tests', { timeout: 10000 }, function () {
16
17
test('Test telemetryMessage', async function () {
18
// Set up inputs
19
const testingServiceCollection = createPlatformServices();
20
const document = undefined;
21
const messageText = 'hello world!';
22
const messageLen = 12;
23
const prompt = 'You are a programming assistant. Respond to the question hello world!';
24
const source = 'user';
25
const turnIndex = 0;
26
const intentClassifierScore = 1;
27
const intentClassifierLatency = 10;
28
29
// Call function
30
const [messages] = await withTelemetryCapture(testingServiceCollection, async accessor => {
31
const telemetryData = sendConversationalMessageTelemetry(
32
accessor.get(ITelemetryService),
33
document,
34
ChatLocation.Panel,
35
messageText,
36
{ source: source, turnIndex: turnIndex.toString() },
37
{
38
messageCharLen: messageLen,
39
promptCharLen: prompt.length,
40
intentClassifierScore: intentClassifierScore,
41
intentClassifierLatency: intentClassifierLatency,
42
},
43
createTelemetryWithId()
44
);
45
46
// Check that properties and measurements for standard telemetry are correct
47
assert.strictEqual(telemetryData.properties.source, source);
48
assert.strictEqual(telemetryData.properties.turnIndex, turnIndex.toString());
49
assert.strictEqual(telemetryData.measurements.messageCharLen, messageText.length);
50
assert.strictEqual(telemetryData.measurements.promptCharLen, prompt.length);
51
assert.strictEqual(telemetryData.measurements.intentClassifierScore, intentClassifierScore);
52
assert.strictEqual(telemetryData.measurements.intentClassifierLatency, intentClassifierLatency);
53
54
// Check that enhanced telemetry fields are not in standard telemetry data
55
assert(!('messageText' in telemetryData.properties));
56
});
57
58
// All of the below adapted from the ghostText telemetry integration tests
59
assert.ok(allEvents(messages));
60
const names = messages
61
.map(message => message.data.baseData.name.split('/')[1])
62
// In case we need a new Copilot token, we don't care about the messages that triggers
63
.filter(name => !['auth.new_login', 'auth.new_token'].includes(name));
64
// Correct events are created
65
assert.deepStrictEqual(
66
names.filter(name => !name.startsWith('engine.') && name !== 'log').sort(),
67
['conversation.message', 'conversation.messageText'].sort()
68
);
69
// Correct properties are attached to the message events
70
assert.ok(
71
messages
72
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.message')
73
.every(message => message.data.baseData.properties.source === source)
74
);
75
assert.ok(
76
messages
77
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.message')
78
.every(message => message.data.baseData.properties.turnIndex === turnIndex.toString())
79
);
80
// Correct measurements are attached to the message events
81
assert.ok(
82
messages
83
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.message')
84
.every(message => message.data.baseData.measurements.messageCharLen === messageLen)
85
);
86
assert.ok(
87
messages
88
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.message')
89
.every(message => message.data.baseData.measurements.promptCharLen === prompt.length)
90
);
91
assert.ok(
92
messages
93
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.message')
94
.every(message => message.data.baseData.measurements.intentClassifierScore === intentClassifierScore)
95
);
96
assert.ok(
97
messages
98
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.message')
99
.every(message => message.data.baseData.measurements.intentClassifierLatency === intentClassifierLatency)
100
);
101
// Correct properties are attached to the messageText events
102
assert.ok(
103
messages
104
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.messageText')
105
.every(message => message.data.baseData.properties.messageText === messageText)
106
);
107
});
108
109
test('Test telemetryUserAction', async function () {
110
// Set up inputs
111
const testingServiceCollection = createPlatformServices();
112
const document = undefined;
113
const rating = 'positive';
114
const messageId = '12345';
115
const name = 'conversation.messageRating';
116
117
// Call function
118
const [messages] = await withTelemetryCapture(testingServiceCollection, async accessor => {
119
const telemetryData = sendUserActionTelemetry(accessor.get(ITelemetryService), document, { rating: rating, messageId: messageId }, {}, name);
120
121
// Check that properties and measurements for standard telemetry are correct
122
assert.strictEqual(telemetryData.properties.rating, rating);
123
assert.strictEqual(telemetryData.properties.messageId, messageId);
124
});
125
126
// All of the below adapted from the ghostText telemetry integration tests
127
assert.ok(allEvents(messages));
128
const names = messages
129
.map(message => message.data.baseData.name.split('/')[1])
130
// In case we need a new Copilot token, we don't care about the messages that triggers
131
.filter(name => !['auth.new_login', 'auth.new_token'].includes(name));
132
// Correct events are created
133
assert.deepStrictEqual(
134
names.filter(name => !name.startsWith('engine.') && name !== 'log').sort(),
135
['conversation.messageRating'].sort()
136
);
137
// Correct properties are attached to the message events
138
assert.ok(
139
messages
140
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.messageRating')
141
.every(message => message.data.baseData.properties.rating === rating)
142
);
143
assert.ok(
144
messages
145
.filter(message => message.data.baseData.name.split('/')[1] === 'conversation.messageRating')
146
.every(message => message.data.baseData.properties.messageId === messageId)
147
);
148
});
149
150
test('Test getCodeBlocks with no code', async function () {
151
// Set up inputs
152
const noCode = 'hello world';
153
154
// Test no code case
155
const noCodeResult = getCodeBlocks(noCode);
156
assert.strictEqual(noCodeResult.length, 0, 'Length of no code result should be 0');
157
});
158
159
test('Test getCodeBlocks with one code block, no language', async function () {
160
// Set up inputs
161
const basicNoLang = '\n```\nhello world\n```';
162
163
// Test basic no lang case
164
const basicNoLangResult = getCodeBlocks(basicNoLang);
165
assert.deepEqual(basicNoLangResult, [''], 'Basic no lang result should be an array with an empty string');
166
});
167
168
test('Test getCodeBlocks with one code block with language', async function () {
169
// Set up inputs
170
const basicWithLang = '\n```python\nhello world\n```';
171
172
// Test basic with lang case
173
const basicWithLangResult = getCodeBlocks(basicWithLang);
174
assert.deepEqual(
175
basicWithLangResult,
176
['python'],
177
'Basic with lang result should be an array with a single string'
178
);
179
});
180
181
test('Test getCodeBlocks with nested code blocks', async function () {
182
// Set up inputs
183
const nested = '\n```\n```python\ndef hello_world():\n print("Hello, world!")\n```\n```\n\n';
184
185
// Test nested case
186
const nestedResult = getCodeBlocks(nested);
187
assert.deepEqual(
188
nestedResult,
189
[''],
190
'Nested result should be an array with one empty string, ignoring backticks within the code block'
191
);
192
});
193
194
test('Test getCodeBlocks with multiple nested code blocks', async function () {
195
// Set up inputs
196
const multiNested =
197
'\n```\n```python\ndef hello_world():\n print(\'Hello, world!\'\')\n```\n```\n\nThis will render as:\n\n```python\ndef hello_world():\n print(\'Hello, world!\'\')\n```';
198
199
// Test multi nested case
200
const multiNestedResult = getCodeBlocks(multiNested);
201
assert.deepEqual(
202
multiNestedResult,
203
['', ''],
204
'Multi nested result should be an array with two empty strings, ignoring backticks within the code block'
205
);
206
});
207
208
test('Test getCodeBlocks with escaped backticks', async function () {
209
// Set up inputs
210
const escaped =
211
'\n\n\\`\\`\\`python\nprint("Hello, world!")\n\\`\\`\\`\n\nThis will produce:\n\n```python\nprint("Hello, world!")\n```';
212
213
// Test escaped case
214
const escapedResult = getCodeBlocks(escaped);
215
assert.deepEqual(
216
escapedResult,
217
['python'],
218
'Escaped result should be an array with a single string, ignoring escaped backticks'
219
);
220
});
221
});
222
223