Skip to content

Commit 0523d2a

Browse files
authored
Merge pull request #3073 from github/koesie10/download-multiple-github-databases
Allow downloading multiple databases from GitHub
2 parents 8aae7d3 + 4f51445 commit 0523d2a

File tree

2 files changed

+196
-69
lines changed

2 files changed

+196
-69
lines changed

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

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { window } from "vscode";
22
import { Octokit } from "@octokit/rest";
33
import { showNeverAskAgainDialog } from "../common/vscode/dialog";
44
import { getLanguageDisplayName } from "../common/query-language";
5-
import {
6-
downloadGitHubDatabaseFromUrl,
7-
promptForLanguage,
8-
} from "./database-fetcher";
5+
import { downloadGitHubDatabaseFromUrl } from "./database-fetcher";
96
import { withProgress } from "../common/vscode/progress";
107
import { DatabaseManager } from "./local-databases";
118
import { CodeQLCliServer } from "../codeql-cli/cli";
@@ -66,40 +63,46 @@ export async function downloadDatabaseFromGitHub(
6663
cliServer: CodeQLCliServer,
6764
commandManager: AppCommandManager,
6865
): Promise<void> {
69-
const languages = databases.map((database) => database.language);
70-
71-
const language = await promptForLanguage(languages, undefined);
72-
if (!language) {
66+
const selectedDatabases = await promptForDatabases(databases);
67+
if (selectedDatabases.length === 0) {
7368
return;
7469
}
7570

76-
const database = databases.find((database) => database.language === language);
77-
if (!database) {
78-
return;
79-
}
71+
await Promise.all(
72+
selectedDatabases.map((database) =>
73+
withProgress(
74+
async (progress) => {
75+
await downloadGitHubDatabaseFromUrl(
76+
database.url,
77+
database.id,
78+
database.created_at,
79+
database.commit_oid ?? null,
80+
owner,
81+
repo,
82+
octokit,
83+
progress,
84+
databaseManager,
85+
storagePath,
86+
cliServer,
87+
true,
88+
false,
89+
);
8090

81-
await withProgress(async (progress) => {
82-
await downloadGitHubDatabaseFromUrl(
83-
database.url,
84-
database.id,
85-
database.created_at,
86-
database.commit_oid ?? null,
87-
owner,
88-
repo,
89-
octokit,
90-
progress,
91-
databaseManager,
92-
storagePath,
93-
cliServer,
94-
true,
95-
false,
96-
);
97-
98-
await commandManager.execute("codeQLDatabases.focus");
99-
void window.showInformationMessage(
100-
`Downloaded ${getLanguageDisplayName(language)} database from GitHub.`,
101-
);
102-
});
91+
await commandManager.execute("codeQLDatabases.focus");
92+
void window.showInformationMessage(
93+
`Downloaded ${getLanguageDisplayName(
94+
database.language,
95+
)} database from GitHub.`,
96+
);
97+
},
98+
{
99+
title: `Adding ${getLanguageDisplayName(
100+
database.language,
101+
)} database from GitHub`,
102+
},
103+
),
104+
),
105+
);
103106
}
104107

105108
/**
@@ -126,3 +129,34 @@ function joinLanguages(languages: string[]): string {
126129

127130
return result;
128131
}
132+
133+
async function promptForDatabases(
134+
databases: CodeqlDatabase[],
135+
): Promise<CodeqlDatabase[]> {
136+
if (databases.length === 1) {
137+
return databases;
138+
}
139+
140+
const items = databases
141+
.map((database) => {
142+
const bytesToDisplayMB = `${(database.size / (1024 * 1024)).toFixed(
143+
1,
144+
)} MB`;
145+
146+
return {
147+
label: getLanguageDisplayName(database.language),
148+
description: bytesToDisplayMB,
149+
database,
150+
};
151+
})
152+
.sort((a, b) => a.label.localeCompare(b.label));
153+
154+
const selectedItems = await window.showQuickPick(items, {
155+
title: "Select databases to download",
156+
placeHolder: "Databases found in this repository",
157+
ignoreFocusOut: true,
158+
canPickMany: true,
159+
});
160+
161+
return selectedItems?.map((selectedItem) => selectedItem.database) ?? [];
162+
}

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

Lines changed: 128 additions & 35 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
askForGitHubDatabaseDownload,
67
downloadDatabaseFromGitHub,
@@ -103,15 +104,14 @@ describe("downloadDatabaseFromGitHub", () => {
103104
created_at: faker.date.past().toISOString(),
104105
commit_oid: faker.git.commitSha(),
105106
language: "swift",
107+
size: 27389673,
106108
url: faker.internet.url({
107109
protocol: "https",
108110
}),
109111
}),
110112
];
111113

112-
let promptForLanguageSpy: jest.SpiedFunction<
113-
typeof databaseFetcher.promptForLanguage
114-
>;
114+
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
115115
let downloadGitHubDatabaseFromUrlSpy: jest.SpiedFunction<
116116
typeof databaseFetcher.downloadGitHubDatabaseFromUrl
117117
>;
@@ -121,9 +121,13 @@ describe("downloadDatabaseFromGitHub", () => {
121121
databaseManager = mockedObject<DatabaseManager>({});
122122
cliServer = mockedObject<CodeQLCliServer>({});
123123

124-
promptForLanguageSpy = jest
125-
.spyOn(databaseFetcher, "promptForLanguage")
126-
.mockResolvedValue(databases[0].language);
124+
showQuickPickSpy = jest.spyOn(window, "showQuickPick").mockResolvedValue(
125+
mockedQuickPickItem([
126+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
127+
database: databases[0],
128+
}),
129+
]),
130+
);
127131
downloadGitHubDatabaseFromUrlSpy = jest
128132
.spyOn(databaseFetcher, "downloadGitHubDatabaseFromUrl")
129133
.mockResolvedValue(undefined);
@@ -157,28 +161,6 @@ describe("downloadDatabaseFromGitHub", () => {
157161
true,
158162
false,
159163
);
160-
expect(promptForLanguageSpy).toHaveBeenCalledWith(["swift"], undefined);
161-
});
162-
163-
describe("when not selecting language", () => {
164-
beforeEach(() => {
165-
promptForLanguageSpy.mockResolvedValue(undefined);
166-
});
167-
168-
it("does not download the database", async () => {
169-
await downloadDatabaseFromGitHub(
170-
octokit,
171-
owner,
172-
repo,
173-
databases,
174-
databaseManager,
175-
storagePath,
176-
cliServer,
177-
commandManager,
178-
);
179-
180-
expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
181-
});
182164
});
183165

184166
describe("when there are multiple languages", () => {
@@ -189,6 +171,7 @@ describe("downloadDatabaseFromGitHub", () => {
189171
created_at: faker.date.past().toISOString(),
190172
commit_oid: faker.git.commitSha(),
191173
language: "swift",
174+
size: 27389673,
192175
url: faker.internet.url({
193176
protocol: "https",
194177
}),
@@ -198,16 +181,23 @@ describe("downloadDatabaseFromGitHub", () => {
198181
created_at: faker.date.past().toISOString(),
199182
commit_oid: null,
200183
language: "go",
184+
size: 2930572385,
201185
url: faker.internet.url({
202186
protocol: "https",
203187
}),
204188
}),
205189
];
206-
207-
promptForLanguageSpy.mockResolvedValue(databases[1].language);
208190
});
209191

210-
it("downloads the correct database", async () => {
192+
it("downloads a single selected language", async () => {
193+
showQuickPickSpy.mockResolvedValue(
194+
mockedQuickPickItem([
195+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
196+
database: databases[1],
197+
}),
198+
]),
199+
);
200+
211201
await downloadDatabaseFromGitHub(
212202
octokit,
213203
owner,
@@ -235,10 +225,113 @@ describe("downloadDatabaseFromGitHub", () => {
235225
true,
236226
false,
237227
);
238-
expect(promptForLanguageSpy).toHaveBeenCalledWith(
239-
["swift", "go"],
240-
undefined,
228+
expect(showQuickPickSpy).toHaveBeenCalledWith(
229+
[
230+
expect.objectContaining({
231+
label: "Go",
232+
description: "2794.8 MB",
233+
database: databases[1],
234+
}),
235+
expect.objectContaining({
236+
label: "Swift",
237+
description: "26.1 MB",
238+
database: databases[0],
239+
}),
240+
],
241+
expect.anything(),
242+
);
243+
});
244+
245+
it("downloads multiple selected languages", async () => {
246+
showQuickPickSpy.mockResolvedValue(
247+
mockedQuickPickItem([
248+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
249+
database: databases[0],
250+
}),
251+
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
252+
database: databases[1],
253+
}),
254+
]),
255+
);
256+
257+
await downloadDatabaseFromGitHub(
258+
octokit,
259+
owner,
260+
repo,
261+
databases,
262+
databaseManager,
263+
storagePath,
264+
cliServer,
265+
commandManager,
266+
);
267+
268+
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledTimes(2);
269+
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
270+
databases[0].url,
271+
databases[0].id,
272+
databases[0].created_at,
273+
databases[0].commit_oid,
274+
owner,
275+
repo,
276+
octokit,
277+
expect.anything(),
278+
databaseManager,
279+
storagePath,
280+
cliServer,
281+
true,
282+
false,
283+
);
284+
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
285+
databases[1].url,
286+
databases[1].id,
287+
databases[1].created_at,
288+
databases[1].commit_oid,
289+
owner,
290+
repo,
291+
octokit,
292+
expect.anything(),
293+
databaseManager,
294+
storagePath,
295+
cliServer,
296+
true,
297+
false,
298+
);
299+
expect(showQuickPickSpy).toHaveBeenCalledWith(
300+
[
301+
expect.objectContaining({
302+
label: "Go",
303+
description: "2794.8 MB",
304+
database: databases[1],
305+
}),
306+
expect.objectContaining({
307+
label: "Swift",
308+
description: "26.1 MB",
309+
database: databases[0],
310+
}),
311+
],
312+
expect.anything(),
241313
);
242314
});
315+
316+
describe("when not selecting language", () => {
317+
beforeEach(() => {
318+
showQuickPickSpy.mockResolvedValue(undefined);
319+
});
320+
321+
it("does not download the database", async () => {
322+
await downloadDatabaseFromGitHub(
323+
octokit,
324+
owner,
325+
repo,
326+
databases,
327+
databaseManager,
328+
storagePath,
329+
cliServer,
330+
commandManager,
331+
);
332+
333+
expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
334+
});
335+
});
243336
});
244337
});

0 commit comments

Comments
 (0)