Skip to content

Commit

Permalink
Allow downloading multiple databases from GitHub
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
koesie10 committed Nov 17, 2023
1 parent 636f8f1 commit d0488dd
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 69 deletions.
102 changes: 69 additions & 33 deletions extensions/ql-vscode/src/databases/github-database-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
import { Octokit } from "@octokit/rest";
import { showNeverAskAgainDialog } from "../common/vscode/dialog";
import { getLanguageDisplayName } from "../common/query-language";
import {
downloadGitHubDatabaseFromUrl,
promptForLanguage,
} from "./database-fetcher";
import { downloadGitHubDatabaseFromUrl } from "./database-fetcher";
import { withProgress } from "../common/vscode/progress";
import { DatabaseManager } from "./local-databases";
import { CodeQLCliServer } from "../codeql-cli/cli";
Expand Down Expand Up @@ -77,38 +74,46 @@ export async function promptGitHubDatabaseDownload(
return;
}

const language = await promptForLanguage(languages, undefined);
if (!language) {
const selectedDatabases = await promptForDatabases(databases);
if (selectedDatabases.length === 0) {
return;
}

const database = databases.find((database) => database.language === language);
if (!database) {
return;
}

await 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 commandManager.execute("codeQLDatabases.focus");
void window.showInformationMessage(
`Downloaded ${getLanguageDisplayName(language)} database from GitHub.`,
);
});
await Promise.all(
selectedDatabases.map((database) =>
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 commandManager.execute("codeQLDatabases.focus");
void window.showInformationMessage(
`Downloaded ${getLanguageDisplayName(
database.language,
)} database from GitHub.`,
);
},
{
title: `Adding ${getLanguageDisplayName(
database.language,
)} database from GitHub`,
},
),
),
);
}

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

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) ?? [];
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { faker } from "@faker-js/faker";
import { Octokit } from "@octokit/rest";
import { mockedObject } from "../../utils/mocking.helpers";
import { QuickPickItem, window } from "vscode";
import { mockedObject, mockedQuickPickItem } from "../../utils/mocking.helpers";
import {
CodeqlDatabase,
promptGitHubDatabaseDownload,
Expand Down Expand Up @@ -29,6 +30,7 @@ describe("promptGitHubDatabaseDownload", () => {
created_at: faker.date.past().toISOString(),
commit_oid: faker.git.commitSha(),
language: "swift",
size: 27389673,
url: faker.internet.url({
protocol: "https",
}),
Expand All @@ -38,9 +40,7 @@ describe("promptGitHubDatabaseDownload", () => {
let showNeverAskAgainDialogSpy: jest.SpiedFunction<
typeof dialog.showNeverAskAgainDialog
>;
let promptForLanguageSpy: jest.SpiedFunction<
typeof databaseFetcher.promptForLanguage
>;
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
let downloadGitHubDatabaseFromUrlSpy: jest.SpiedFunction<
typeof databaseFetcher.downloadGitHubDatabaseFromUrl
>;
Expand All @@ -56,9 +56,13 @@ describe("promptGitHubDatabaseDownload", () => {
showNeverAskAgainDialogSpy = jest
.spyOn(dialog, "showNeverAskAgainDialog")
.mockResolvedValue("Connect");
promptForLanguageSpy = jest
.spyOn(databaseFetcher, "promptForLanguage")
.mockResolvedValue(databases[0].language);
showQuickPickSpy = jest.spyOn(window, "showQuickPick").mockResolvedValue(
mockedQuickPickItem([
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
database: databases[0],
}),
]),
);
downloadGitHubDatabaseFromUrlSpy = jest
.spyOn(databaseFetcher, "downloadGitHubDatabaseFromUrl")
.mockResolvedValue(undefined);
Expand Down Expand Up @@ -93,7 +97,7 @@ describe("promptGitHubDatabaseDownload", () => {
true,
false,
);
expect(promptForLanguageSpy).toHaveBeenCalledWith(["swift"], undefined);
expect(showQuickPickSpy).not.toHaveBeenCalled();
expect(config.setDownload).not.toHaveBeenCalled();
});

Expand Down Expand Up @@ -180,28 +184,6 @@ describe("promptGitHubDatabaseDownload", () => {
});
});

describe("when not selecting language", () => {
beforeEach(() => {
promptForLanguageSpy.mockResolvedValue(undefined);
});

it("does not download the database", async () => {
await promptGitHubDatabaseDownload(
octokit,
owner,
repo,
databases,
config,
databaseManager,
storagePath,
cliServer,
commandManager,
);

expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
});
});

describe("when there are multiple languages", () => {
beforeEach(() => {
databases = [
Expand All @@ -210,6 +192,7 @@ describe("promptGitHubDatabaseDownload", () => {
created_at: faker.date.past().toISOString(),
commit_oid: faker.git.commitSha(),
language: "swift",
size: 27389673,
url: faker.internet.url({
protocol: "https",
}),
Expand All @@ -219,16 +202,23 @@ describe("promptGitHubDatabaseDownload", () => {
created_at: faker.date.past().toISOString(),
commit_oid: null,
language: "go",
size: 2930572385,
url: faker.internet.url({
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 promptGitHubDatabaseDownload(
octokit,
owner,
Expand Down Expand Up @@ -257,11 +247,117 @@ describe("promptGitHubDatabaseDownload", () => {
true,
false,
);
expect(promptForLanguageSpy).toHaveBeenCalledWith(
["swift", "go"],
undefined,
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(),
);
expect(config.setDownload).not.toHaveBeenCalled();
});

it("downloads multiple selected languages", async () => {
showQuickPickSpy.mockResolvedValue(
mockedQuickPickItem([
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
database: databases[0],
}),
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
database: databases[1],
}),
]),
);

await promptGitHubDatabaseDownload(
octokit,
owner,
repo,
databases,
config,
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(),
);
expect(config.setDownload).not.toHaveBeenCalled();
});

describe("when not selecting language", () => {
beforeEach(() => {
showQuickPickSpy.mockResolvedValue(undefined);
});

it("does not download the database", async () => {
await promptGitHubDatabaseDownload(
octokit,
owner,
repo,
databases,
config,
databaseManager,
storagePath,
cliServer,
commandManager,
);

expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
});
});
});
});

0 comments on commit d0488dd

Please sign in to comment.