Skip to content

Commit d0488dd

Browse files
committed
Allow downloading multiple databases from GitHub
This adds the option to download multiple databases from GitHub in the initial GitHub database download prompt. The databases will be downloaded concurrently. Unfortunately it doesn't seem possible to change the "OK" text in the quick pick to "Download", so I've left it as "OK" for now.
1 parent 636f8f1 commit d0488dd

2 files changed

Lines changed: 201 additions & 69 deletions

File tree

extensions/ql-vscode/src/databases/github-database-prompt.ts

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
33
import { Octokit } from "@octokit/rest";
44
import { showNeverAskAgainDialog } from "../common/vscode/dialog";
55
import { getLanguageDisplayName } from "../common/query-language";
6-
import {
7-
downloadGitHubDatabaseFromUrl,
8-
promptForLanguage,
9-
} from "./database-fetcher";
6+
import { downloadGitHubDatabaseFromUrl } from "./database-fetcher";
107
import { withProgress } from "../common/vscode/progress";
118
import { DatabaseManager } from "./local-databases";
129
import { CodeQLCliServer } from "../codeql-cli/cli";
@@ -77,38 +74,46 @@ export async function promptGitHubDatabaseDownload(
7774
return;
7875
}
7976

80-
const language = await promptForLanguage(languages, undefined);
81-
if (!language) {
77+
const selectedDatabases = await promptForDatabases(databases);
78+
if (selectedDatabases.length === 0) {
8279
return;
8380
}
8481

85-
const database = databases.find((database) => database.language === language);
86-
if (!database) {
87-
return;
88-
}
89-
90-
await withProgress(async (progress) => {
91-
await downloadGitHubDatabaseFromUrl(
92-
database.url,
93-
database.id,
94-
database.created_at,
95-
database.commit_oid ?? null,
96-
owner,
97-
repo,
98-
octokit,
99-
progress,
100-
databaseManager,
101-
storagePath,
102-
cliServer,
103-
true,
104-
false,
105-
);
106-
107-
await commandManager.execute("codeQLDatabases.focus");
108-
void window.showInformationMessage(
109-
`Downloaded ${getLanguageDisplayName(language)} database from GitHub.`,
110-
);
111-
});
82+
await Promise.all(
83+
selectedDatabases.map((database) =>
84+
withProgress(
85+
async (progress) => {
86+
await downloadGitHubDatabaseFromUrl(
87+
database.url,
88+
database.id,
89+
database.created_at,
90+
database.commit_oid ?? null,
91+
owner,
92+
repo,
93+
octokit,
94+
progress,
95+
databaseManager,
96+
storagePath,
97+
cliServer,
98+
true,
99+
false,
100+
);
101+
102+
await commandManager.execute("codeQLDatabases.focus");
103+
void window.showInformationMessage(
104+
`Downloaded ${getLanguageDisplayName(
105+
database.language,
106+
)} database from GitHub.`,
107+
);
108+
},
109+
{
110+
title: `Adding ${getLanguageDisplayName(
111+
database.language,
112+
)} database from GitHub`,
113+
},
114+
),
115+
),
116+
);
112117
}
113118

114119
/**
@@ -135,3 +140,34 @@ function joinLanguages(languages: string[]): string {
135140

136141
return result;
137142
}
143+
144+
async function promptForDatabases(
145+
databases: CodeqlDatabase[],
146+
): Promise<CodeqlDatabase[]> {
147+
if (databases.length === 1) {
148+
return databases;
149+
}
150+
151+
const items = databases
152+
.map((database) => {
153+
const bytesToDisplayMB = `${(database.size / (1024 * 1024)).toFixed(
154+
1,
155+
)} MB`;
156+
157+
return {
158+
label: getLanguageDisplayName(database.language),
159+
description: bytesToDisplayMB,
160+
database,
161+
};
162+
})
163+
.sort((a, b) => a.label.localeCompare(b.label));
164+
165+
const selectedItems = await window.showQuickPick(items, {
166+
title: "Select databases to download",
167+
placeHolder: "Databases found in this repository",
168+
ignoreFocusOut: true,
169+
canPickMany: true,
170+
});
171+
172+
return selectedItems?.map((selectedItem) => selectedItem.database) ?? [];
173+
}

extensions/ql-vscode/test/vscode-tests/no-workspace/databases/github-database-prompt.test.ts

Lines changed: 132 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { faker } from "@faker-js/faker";
22
import { Octokit } from "@octokit/rest";
3-
import { mockedObject } from "../../utils/mocking.helpers";
3+
import { QuickPickItem, window } from "vscode";
4+
import { mockedObject, mockedQuickPickItem } from "../../utils/mocking.helpers";
45
import {
56
CodeqlDatabase,
67
promptGitHubDatabaseDownload,
@@ -29,6 +30,7 @@ describe("promptGitHubDatabaseDownload", () => {
2930
created_at: faker.date.past().toISOString(),
3031
commit_oid: faker.git.commitSha(),
3132
language: "swift",
33+
size: 27389673,
3234
url: faker.internet.url({
3335
protocol: "https",
3436
}),
@@ -38,9 +40,7 @@ describe("promptGitHubDatabaseDownload", () => {
3840
let showNeverAskAgainDialogSpy: jest.SpiedFunction<
3941
typeof dialog.showNeverAskAgainDialog
4042
>;
41-
let promptForLanguageSpy: jest.SpiedFunction<
42-
typeof databaseFetcher.promptForLanguage
43-
>;
43+
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
4444
let downloadGitHubDatabaseFromUrlSpy: jest.SpiedFunction<
4545
typeof databaseFetcher.downloadGitHubDatabaseFromUrl
4646
>;
@@ -56,9 +56,13 @@ describe("promptGitHubDatabaseDownload", () => {
5656
showNeverAskAgainDialogSpy = jest
5757
.spyOn(dialog, "showNeverAskAgainDialog")
5858
.mockResolvedValue("Connect");
59-
promptForLanguageSpy = jest
60-
.spyOn(databaseFetcher, "promptForLanguage")
61-
.mockResolvedValue(databases[0].language);
59+
showQuickPickSpy = jest.spyOn(window, "showQuickPick").mockResolvedValue(
60+
mockedQuickPickItem([
61+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
62+
database: databases[0],
63+
}),
64+
]),
65+
);
6266
downloadGitHubDatabaseFromUrlSpy = jest
6367
.spyOn(databaseFetcher, "downloadGitHubDatabaseFromUrl")
6468
.mockResolvedValue(undefined);
@@ -93,7 +97,7 @@ describe("promptGitHubDatabaseDownload", () => {
9397
true,
9498
false,
9599
);
96-
expect(promptForLanguageSpy).toHaveBeenCalledWith(["swift"], undefined);
100+
expect(showQuickPickSpy).not.toHaveBeenCalled();
97101
expect(config.setDownload).not.toHaveBeenCalled();
98102
});
99103

@@ -180,28 +184,6 @@ describe("promptGitHubDatabaseDownload", () => {
180184
});
181185
});
182186

183-
describe("when not selecting language", () => {
184-
beforeEach(() => {
185-
promptForLanguageSpy.mockResolvedValue(undefined);
186-
});
187-
188-
it("does not download the database", async () => {
189-
await promptGitHubDatabaseDownload(
190-
octokit,
191-
owner,
192-
repo,
193-
databases,
194-
config,
195-
databaseManager,
196-
storagePath,
197-
cliServer,
198-
commandManager,
199-
);
200-
201-
expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
202-
});
203-
});
204-
205187
describe("when there are multiple languages", () => {
206188
beforeEach(() => {
207189
databases = [
@@ -210,6 +192,7 @@ describe("promptGitHubDatabaseDownload", () => {
210192
created_at: faker.date.past().toISOString(),
211193
commit_oid: faker.git.commitSha(),
212194
language: "swift",
195+
size: 27389673,
213196
url: faker.internet.url({
214197
protocol: "https",
215198
}),
@@ -219,16 +202,23 @@ describe("promptGitHubDatabaseDownload", () => {
219202
created_at: faker.date.past().toISOString(),
220203
commit_oid: null,
221204
language: "go",
205+
size: 2930572385,
222206
url: faker.internet.url({
223207
protocol: "https",
224208
}),
225209
}),
226210
];
227-
228-
promptForLanguageSpy.mockResolvedValue(databases[1].language);
229211
});
230212

231-
it("downloads the correct database", async () => {
213+
it("downloads a single selected language", async () => {
214+
showQuickPickSpy.mockResolvedValue(
215+
mockedQuickPickItem([
216+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
217+
database: databases[1],
218+
}),
219+
]),
220+
);
221+
232222
await promptGitHubDatabaseDownload(
233223
octokit,
234224
owner,
@@ -257,11 +247,117 @@ describe("promptGitHubDatabaseDownload", () => {
257247
true,
258248
false,
259249
);
260-
expect(promptForLanguageSpy).toHaveBeenCalledWith(
261-
["swift", "go"],
262-
undefined,
250+
expect(showQuickPickSpy).toHaveBeenCalledWith(
251+
[
252+
expect.objectContaining({
253+
label: "Go",
254+
description: "2794.8 MB",
255+
database: databases[1],
256+
}),
257+
expect.objectContaining({
258+
label: "Swift",
259+
description: "26.1 MB",
260+
database: databases[0],
261+
}),
262+
],
263+
expect.anything(),
263264
);
264265
expect(config.setDownload).not.toHaveBeenCalled();
265266
});
267+
268+
it("downloads multiple selected languages", async () => {
269+
showQuickPickSpy.mockResolvedValue(
270+
mockedQuickPickItem([
271+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
272+
database: databases[0],
273+
}),
274+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
275+
database: databases[1],
276+
}),
277+
]),
278+
);
279+
280+
await promptGitHubDatabaseDownload(
281+
octokit,
282+
owner,
283+
repo,
284+
databases,
285+
config,
286+
databaseManager,
287+
storagePath,
288+
cliServer,
289+
commandManager,
290+
);
291+
292+
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledTimes(2);
293+
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
294+
databases[0].url,
295+
databases[0].id,
296+
databases[0].created_at,
297+
databases[0].commit_oid,
298+
owner,
299+
repo,
300+
octokit,
301+
expect.anything(),
302+
databaseManager,
303+
storagePath,
304+
cliServer,
305+
true,
306+
false,
307+
);
308+
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
309+
databases[1].url,
310+
databases[1].id,
311+
databases[1].created_at,
312+
databases[1].commit_oid,
313+
owner,
314+
repo,
315+
octokit,
316+
expect.anything(),
317+
databaseManager,
318+
storagePath,
319+
cliServer,
320+
true,
321+
false,
322+
);
323+
expect(showQuickPickSpy).toHaveBeenCalledWith(
324+
[
325+
expect.objectContaining({
326+
label: "Go",
327+
description: "2794.8 MB",
328+
database: databases[1],
329+
}),
330+
expect.objectContaining({
331+
label: "Swift",
332+
description: "26.1 MB",
333+
database: databases[0],
334+
}),
335+
],
336+
expect.anything(),
337+
);
338+
expect(config.setDownload).not.toHaveBeenCalled();
339+
});
340+
341+
describe("when not selecting language", () => {
342+
beforeEach(() => {
343+
showQuickPickSpy.mockResolvedValue(undefined);
344+
});
345+
346+
it("does not download the database", async () => {
347+
await promptGitHubDatabaseDownload(
348+
octokit,
349+
owner,
350+
repo,
351+
databases,
352+
config,
353+
databaseManager,
354+
storagePath,
355+
cliServer,
356+
commandManager,
357+
);
358+
359+
expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
360+
});
361+
});
266362
});
267363
});

0 commit comments

Comments
 (0)