Merge pull request #3073 from github/koesie10/download-multiple-github-databases

Allow downloading multiple databases from GitHub
This commit is contained in:
Koen Vlaswinkel
2023-11-21 11:42:06 +01:00
committed by GitHub
2 changed files with 196 additions and 69 deletions

View File

@@ -2,10 +2,7 @@ import { window } from "vscode";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import { showNeverAskAgainDialog } from "../common/vscode/dialog"; import { showNeverAskAgainDialog } from "../common/vscode/dialog";
import { getLanguageDisplayName } from "../common/query-language"; import { getLanguageDisplayName } from "../common/query-language";
import { import { downloadGitHubDatabaseFromUrl } from "./database-fetcher";
downloadGitHubDatabaseFromUrl,
promptForLanguage,
} from "./database-fetcher";
import { withProgress } from "../common/vscode/progress"; import { withProgress } from "../common/vscode/progress";
import { DatabaseManager } from "./local-databases"; import { DatabaseManager } from "./local-databases";
import { CodeQLCliServer } from "../codeql-cli/cli"; import { CodeQLCliServer } from "../codeql-cli/cli";
@@ -66,40 +63,46 @@ export async function downloadDatabaseFromGitHub(
cliServer: CodeQLCliServer, cliServer: CodeQLCliServer,
commandManager: AppCommandManager, commandManager: AppCommandManager,
): Promise<void> { ): Promise<void> {
const languages = databases.map((database) => database.language); const selectedDatabases = await promptForDatabases(databases);
if (selectedDatabases.length === 0) {
const language = await promptForLanguage(languages, undefined);
if (!language) {
return; return;
} }
const database = databases.find((database) => database.language === language); await Promise.all(
if (!database) { selectedDatabases.map((database) =>
return; withProgress(
} async (progress) => {
await downloadGitHubDatabaseFromUrl(
database.url,
database.id,
database.created_at,
database.commit_oid ?? null,
owner,
repo,
octokit,
progress,
databaseManager,
storagePath,
cliServer,
true,
false,
);
await withProgress(async (progress) => { await commandManager.execute("codeQLDatabases.focus");
await downloadGitHubDatabaseFromUrl( void window.showInformationMessage(
database.url, `Downloaded ${getLanguageDisplayName(
database.id, database.language,
database.created_at, )} database from GitHub.`,
database.commit_oid ?? null, );
owner, },
repo, {
octokit, title: `Adding ${getLanguageDisplayName(
progress, database.language,
databaseManager, )} database from GitHub`,
storagePath, },
cliServer, ),
true, ),
false, );
);
await commandManager.execute("codeQLDatabases.focus");
void window.showInformationMessage(
`Downloaded ${getLanguageDisplayName(language)} database from GitHub.`,
);
});
} }
/** /**
@@ -126,3 +129,34 @@ function joinLanguages(languages: string[]): string {
return result; return result;
} }
async function promptForDatabases(
databases: CodeqlDatabase[],
): Promise<CodeqlDatabase[]> {
if (databases.length === 1) {
return databases;
}
const items = databases
.map((database) => {
const bytesToDisplayMB = `${(database.size / (1024 * 1024)).toFixed(
1,
)} MB`;
return {
label: getLanguageDisplayName(database.language),
description: bytesToDisplayMB,
database,
};
})
.sort((a, b) => a.label.localeCompare(b.label));
const selectedItems = await window.showQuickPick(items, {
title: "Select databases to download",
placeHolder: "Databases found in this repository",
ignoreFocusOut: true,
canPickMany: true,
});
return selectedItems?.map((selectedItem) => selectedItem.database) ?? [];
}

View File

@@ -1,6 +1,7 @@
import { faker } from "@faker-js/faker"; import { faker } from "@faker-js/faker";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import { mockedObject } from "../../utils/mocking.helpers"; import { QuickPickItem, window } from "vscode";
import { mockedObject, mockedQuickPickItem } from "../../utils/mocking.helpers";
import { import {
askForGitHubDatabaseDownload, askForGitHubDatabaseDownload,
downloadDatabaseFromGitHub, downloadDatabaseFromGitHub,
@@ -103,15 +104,14 @@ describe("downloadDatabaseFromGitHub", () => {
created_at: faker.date.past().toISOString(), created_at: faker.date.past().toISOString(),
commit_oid: faker.git.commitSha(), commit_oid: faker.git.commitSha(),
language: "swift", language: "swift",
size: 27389673,
url: faker.internet.url({ url: faker.internet.url({
protocol: "https", protocol: "https",
}), }),
}), }),
]; ];
let promptForLanguageSpy: jest.SpiedFunction< let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
typeof databaseFetcher.promptForLanguage
>;
let downloadGitHubDatabaseFromUrlSpy: jest.SpiedFunction< let downloadGitHubDatabaseFromUrlSpy: jest.SpiedFunction<
typeof databaseFetcher.downloadGitHubDatabaseFromUrl typeof databaseFetcher.downloadGitHubDatabaseFromUrl
>; >;
@@ -121,9 +121,13 @@ describe("downloadDatabaseFromGitHub", () => {
databaseManager = mockedObject<DatabaseManager>({}); databaseManager = mockedObject<DatabaseManager>({});
cliServer = mockedObject<CodeQLCliServer>({}); cliServer = mockedObject<CodeQLCliServer>({});
promptForLanguageSpy = jest showQuickPickSpy = jest.spyOn(window, "showQuickPick").mockResolvedValue(
.spyOn(databaseFetcher, "promptForLanguage") mockedQuickPickItem([
.mockResolvedValue(databases[0].language); mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
database: databases[0],
}),
]),
);
downloadGitHubDatabaseFromUrlSpy = jest downloadGitHubDatabaseFromUrlSpy = jest
.spyOn(databaseFetcher, "downloadGitHubDatabaseFromUrl") .spyOn(databaseFetcher, "downloadGitHubDatabaseFromUrl")
.mockResolvedValue(undefined); .mockResolvedValue(undefined);
@@ -157,28 +161,6 @@ describe("downloadDatabaseFromGitHub", () => {
true, true,
false, false,
); );
expect(promptForLanguageSpy).toHaveBeenCalledWith(["swift"], undefined);
});
describe("when not selecting language", () => {
beforeEach(() => {
promptForLanguageSpy.mockResolvedValue(undefined);
});
it("does not download the database", async () => {
await downloadDatabaseFromGitHub(
octokit,
owner,
repo,
databases,
databaseManager,
storagePath,
cliServer,
commandManager,
);
expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
});
}); });
describe("when there are multiple languages", () => { describe("when there are multiple languages", () => {
@@ -189,6 +171,7 @@ describe("downloadDatabaseFromGitHub", () => {
created_at: faker.date.past().toISOString(), created_at: faker.date.past().toISOString(),
commit_oid: faker.git.commitSha(), commit_oid: faker.git.commitSha(),
language: "swift", language: "swift",
size: 27389673,
url: faker.internet.url({ url: faker.internet.url({
protocol: "https", protocol: "https",
}), }),
@@ -198,16 +181,23 @@ describe("downloadDatabaseFromGitHub", () => {
created_at: faker.date.past().toISOString(), created_at: faker.date.past().toISOString(),
commit_oid: null, commit_oid: null,
language: "go", language: "go",
size: 2930572385,
url: faker.internet.url({ url: faker.internet.url({
protocol: "https", protocol: "https",
}), }),
}), }),
]; ];
promptForLanguageSpy.mockResolvedValue(databases[1].language);
}); });
it("downloads the correct database", async () => { it("downloads a single selected language", async () => {
showQuickPickSpy.mockResolvedValue(
mockedQuickPickItem([
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
database: databases[1],
}),
]),
);
await downloadDatabaseFromGitHub( await downloadDatabaseFromGitHub(
octokit, octokit,
owner, owner,
@@ -235,10 +225,113 @@ describe("downloadDatabaseFromGitHub", () => {
true, true,
false, false,
); );
expect(promptForLanguageSpy).toHaveBeenCalledWith( expect(showQuickPickSpy).toHaveBeenCalledWith(
["swift", "go"], [
undefined, expect.objectContaining({
label: "Go",
description: "2794.8 MB",
database: databases[1],
}),
expect.objectContaining({
label: "Swift",
description: "26.1 MB",
database: databases[0],
}),
],
expect.anything(),
); );
}); });
it("downloads multiple selected languages", async () => {
showQuickPickSpy.mockResolvedValue(
mockedQuickPickItem([
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
database: databases[0],
}),
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
database: databases[1],
}),
]),
);
await downloadDatabaseFromGitHub(
octokit,
owner,
repo,
databases,
databaseManager,
storagePath,
cliServer,
commandManager,
);
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledTimes(2);
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
databases[0].url,
databases[0].id,
databases[0].created_at,
databases[0].commit_oid,
owner,
repo,
octokit,
expect.anything(),
databaseManager,
storagePath,
cliServer,
true,
false,
);
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
databases[1].url,
databases[1].id,
databases[1].created_at,
databases[1].commit_oid,
owner,
repo,
octokit,
expect.anything(),
databaseManager,
storagePath,
cliServer,
true,
false,
);
expect(showQuickPickSpy).toHaveBeenCalledWith(
[
expect.objectContaining({
label: "Go",
description: "2794.8 MB",
database: databases[1],
}),
expect.objectContaining({
label: "Swift",
description: "26.1 MB",
database: databases[0],
}),
],
expect.anything(),
);
});
describe("when not selecting language", () => {
beforeEach(() => {
showQuickPickSpy.mockResolvedValue(undefined);
});
it("does not download the database", async () => {
await downloadDatabaseFromGitHub(
octokit,
owner,
repo,
databases,
databaseManager,
storagePath,
cliServer,
commandManager,
);
expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
});
});
}); });
}); });