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 { 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";
@@ -66,19 +63,15 @@ export async function downloadDatabaseFromGitHub(
cliServer: CodeQLCliServer,
commandManager: AppCommandManager,
): Promise<void> {
const languages = databases.map((database) => database.language);
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 Promise.all(
selectedDatabases.map((database) =>
withProgress(
async (progress) => {
await downloadGitHubDatabaseFromUrl(
database.url,
database.id,
@@ -97,9 +90,19 @@ export async function downloadDatabaseFromGitHub(
await commandManager.execute("codeQLDatabases.focus");
void window.showInformationMessage(
`Downloaded ${getLanguageDisplayName(language)} database from GitHub.`,
`Downloaded ${getLanguageDisplayName(
database.language,
)} database from GitHub.`,
);
},
{
title: `Adding ${getLanguageDisplayName(
database.language,
)} database from GitHub`,
},
),
),
);
});
}
/**
@@ -126,3 +129,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) ?? [];
}

View File

@@ -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 {
askForGitHubDatabaseDownload,
downloadDatabaseFromGitHub,
@@ -103,15 +104,14 @@ describe("downloadDatabaseFromGitHub", () => {
created_at: faker.date.past().toISOString(),
commit_oid: faker.git.commitSha(),
language: "swift",
size: 27389673,
url: faker.internet.url({
protocol: "https",
}),
}),
];
let promptForLanguageSpy: jest.SpiedFunction<
typeof databaseFetcher.promptForLanguage
>;
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
let downloadGitHubDatabaseFromUrlSpy: jest.SpiedFunction<
typeof databaseFetcher.downloadGitHubDatabaseFromUrl
>;
@@ -121,9 +121,13 @@ describe("downloadDatabaseFromGitHub", () => {
databaseManager = mockedObject<DatabaseManager>({});
cliServer = mockedObject<CodeQLCliServer>({});
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);
@@ -157,28 +161,6 @@ describe("downloadDatabaseFromGitHub", () => {
true,
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", () => {
@@ -189,6 +171,7 @@ describe("downloadDatabaseFromGitHub", () => {
created_at: faker.date.past().toISOString(),
commit_oid: faker.git.commitSha(),
language: "swift",
size: 27389673,
url: faker.internet.url({
protocol: "https",
}),
@@ -198,16 +181,23 @@ describe("downloadDatabaseFromGitHub", () => {
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 downloadDatabaseFromGitHub(
octokit,
owner,
@@ -235,10 +225,113 @@ describe("downloadDatabaseFromGitHub", () => {
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(),
);
});
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();
});
});
});
});