Skip to content

Commit e27c447

Browse files
authored
feat: log telemetry for downloads from Colab servers (#576)
1 parent 26543dd commit e27c447

5 files changed

Lines changed: 128 additions & 29 deletions

File tree

src/colab/content-browser/commands.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -114,38 +114,47 @@ export async function newFolder(vs: typeof vscode, contextItem: ContentItem) {
114114
* @param contextItem - The tree view context item.
115115
*/
116116
export async function download(vs: typeof vscode, contextItem: ContentItem) {
117-
if (contextItem.type !== vs.FileType.File) {
118-
return;
119-
}
117+
let outcome = Outcome.OUTCOME_CANCELLED;
118+
let downloadedBytes = 0;
119+
try {
120+
if (contextItem.type !== vs.FileType.File) {
121+
return;
122+
}
120123

121-
const fileName = contextItem.uri.path.split('/').pop() ?? 'file';
122-
const targetUri = await vs.window.showSaveDialog({
123-
defaultUri: vs.Uri.file(fileName),
124-
title: 'Download File',
125-
});
124+
const fileName = contextItem.uri.path.split('/').pop() ?? 'file';
125+
const targetUri = await vs.window.showSaveDialog({
126+
defaultUri: vs.Uri.file(fileName),
127+
title: 'Download File',
128+
});
126129

127-
if (!targetUri) {
128-
return;
129-
}
130+
if (!targetUri) {
131+
return;
132+
}
130133

131-
await vs.window.withProgress(
132-
{
133-
location: vs.ProgressLocation.Notification,
134-
title: `Downloading ${fileName}...`,
135-
cancellable: false,
136-
},
137-
async () => {
138-
try {
139-
const content = await vs.workspace.fs.readFile(contextItem.uri);
140-
await vs.workspace.fs.writeFile(targetUri, content);
141-
} catch (err: unknown) {
142-
const msg = err instanceof Error ? err.message : 'unknown error';
143-
void vs.window.showErrorMessage(
144-
`Failed to download ${fileName}: ${msg}`,
145-
);
146-
}
147-
},
148-
);
134+
await vs.window.withProgress(
135+
{
136+
location: vs.ProgressLocation.Notification,
137+
title: `Downloading ${fileName}...`,
138+
cancellable: false,
139+
},
140+
async () => {
141+
try {
142+
const content = await vs.workspace.fs.readFile(contextItem.uri);
143+
await vs.workspace.fs.writeFile(targetUri, content);
144+
downloadedBytes = content.byteLength;
145+
outcome = Outcome.OUTCOME_SUCCEEDED;
146+
} catch (err: unknown) {
147+
const msg = err instanceof Error ? err.message : 'unknown error';
148+
void vs.window.showErrorMessage(
149+
`Failed to download ${fileName}: ${msg}`,
150+
);
151+
outcome = Outcome.OUTCOME_FAILED;
152+
}
153+
},
154+
);
155+
} finally {
156+
telemetry.logDownload(outcome, downloadedBytes);
157+
}
149158
}
150159

151160
/**

src/colab/content-browser/commands.unit.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,64 @@ describe('Server Browser Commands', () => {
283283

284284
sinon.assert.notCalled(vsStub.window.showSaveDialog);
285285
});
286+
287+
describe('telemetry', () => {
288+
let logStub: SinonStubbedFunction<typeof telemetry.logDownload>;
289+
290+
beforeEach(() => {
291+
logStub = sinon.stub(telemetry, 'logDownload');
292+
});
293+
294+
afterEach(() => {
295+
logStub.restore();
296+
});
297+
298+
it('logs OUTCOME_SUCCEEDED with file size when a file is downloaded', async () => {
299+
const localUri = TestUri.file('/local/path/foo.txt');
300+
vsStub.window.showSaveDialog.resolves(localUri);
301+
vsStub.workspace.fs.readFile.resolves(new Uint8Array([1, 2, 3, 4, 5]));
302+
303+
await download(vs, FILE_ITEM);
304+
305+
sinon.assert.calledOnceWithExactly(
306+
logStub,
307+
Outcome.OUTCOME_SUCCEEDED,
308+
5,
309+
);
310+
});
311+
312+
it('logs OUTCOME_CANCELLED when the user dismisses the save dialog', async () => {
313+
vsStub.window.showSaveDialog.resolves(undefined);
314+
315+
await download(vs, FILE_ITEM);
316+
317+
sinon.assert.calledOnceWithExactly(
318+
logStub,
319+
Outcome.OUTCOME_CANCELLED,
320+
0,
321+
);
322+
});
323+
324+
it('logs OUTCOME_CANCELLED when the target item is not a file', async () => {
325+
await download(vs, CONTENT_ROOT);
326+
327+
sinon.assert.calledOnceWithExactly(
328+
logStub,
329+
Outcome.OUTCOME_CANCELLED,
330+
0,
331+
);
332+
});
333+
334+
it('logs OUTCOME_FAILED when the underlying read fails', async () => {
335+
const localUri = TestUri.file('/local/path/foo.txt');
336+
vsStub.window.showSaveDialog.resolves(localUri);
337+
vsStub.workspace.fs.readFile.rejects(new Error('fail'));
338+
339+
await download(vs, FILE_ITEM);
340+
341+
sinon.assert.calledOnceWithExactly(logStub, Outcome.OUTCOME_FAILED, 0);
342+
});
343+
});
286344
});
287345

288346
describe('renameFile', () => {

src/telemetry/api.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ export type ColabEvent =
6262
/** An event representing a content browser file or folder operation. */
6363
content_browser_file_operation_event: ContentBrowserFileOperationEvent;
6464
}
65+
| {
66+
/** An event representing a file download from a Colab server. */
67+
download_event: DownloadEvent;
68+
}
6569
| {
6670
/** An event representing an error. */
6771
error_event: ErrorEvent;
@@ -212,6 +216,17 @@ interface ContentBrowserFileOperationEvent {
212216
target: ContentBrowserTarget;
213217
}
214218

219+
/** An event representing a file download from a Colab server. */
220+
interface DownloadEvent {
221+
/**
222+
* The outcome of the download. `OUTCOME_CANCELLED` covers both the user
223+
* dismissing the save dialog and the target item not being a file.
224+
*/
225+
outcome: Outcome;
226+
/** The size, in bytes, of the file that was successfully downloaded. */
227+
downloaded_bytes: number;
228+
}
229+
215230
/** An event representing an error. */
216231
interface ErrorEvent {
217232
/** The name of the error. */

src/telemetry/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ export const telemetry = {
9191
content_browser_file_operation_event: { operation, outcome, target },
9292
});
9393
},
94+
logDownload: (outcome: Outcome, downloadedBytes: number) => {
95+
log({
96+
download_event: { outcome, downloaded_bytes: downloadedBytes },
97+
});
98+
},
9499
logError: (e: unknown) => {
95100
if (e instanceof Error) {
96101
log({

src/telemetry/telemetry.unit.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,18 @@ describe('Telemetry Module', () => {
295295
});
296296
});
297297

298+
it('logs on download', () => {
299+
telemetry.logDownload(Outcome.OUTCOME_SUCCEEDED, 2048);
300+
301+
sinon.assert.calledOnceWithExactly(logStub, {
302+
...baseLog,
303+
download_event: {
304+
outcome: Outcome.OUTCOME_SUCCEEDED,
305+
downloaded_bytes: 2048,
306+
},
307+
});
308+
});
309+
298310
it('logs on mount Drive snippet', () => {
299311
const source = CommandSource.COMMAND_SOURCE_COMMAND_PALETTE;
300312

0 commit comments

Comments
 (0)