Prompt non-codespace users for storage path

Offer non-codespace users the option to configure their storage folder for skeleton packs.

Suggested here:
https://github.com/github/vscode-codeql/pull/2310#issuecomment-1507428217

At the moment we're choosing to create our skeleton packs in the first
folder in the workspace.

This is fine for the codespace template because we can control the
folder structure in that repo.

For users outside of this we'd like to offer them the option to choose
where to save their skeleton packs.
This commit is contained in:
Elena Tanasoiu
2023-04-17 14:45:29 +00:00
parent 72a3e8d44a
commit 57d48a7b04
3 changed files with 172 additions and 2 deletions

View File

@@ -619,3 +619,19 @@ export const ALLOW_HTTP_SETTING = new Setting(
export function allowHttp(): boolean {
return ALLOW_HTTP_SETTING.getValue<boolean>() || false;
}
/**
* The name of the folder where we want to create skeleton wizard QL packs.
**/
const SKELETON_WIZARD_FOLDER = new Setting(
"folder",
new Setting("skeletonWizard", ROOT_SETTING),
);
export function getSkeletonWizardFolder(): string | undefined {
return SKELETON_WIZARD_FOLDER.getValue<string>() || undefined;
}
export async function setSkeletonWizardFolder(folder: string | undefined) {
await SKELETON_WIZARD_FOLDER.updateValue(folder, ConfigurationTarget.Global);
}

View File

@@ -14,7 +14,12 @@ import { QlPackGenerator } from "./qlpack-generator";
import { DatabaseItem, DatabaseManager } from "./local-databases";
import { ProgressCallback, UserCancellationException } from "./progress";
import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher";
import { existsSync } from "fs";
import {
getSkeletonWizardFolder,
isCodespacesTemplate,
setSkeletonWizardFolder,
} from "./config";
import { existsSync } from "fs-extra";
type QueryLanguagesToDatabaseMap = Record<string, string>;
@@ -55,7 +60,7 @@ export class SkeletonQueryWizard {
return;
}
this.qlPackStoragePath = getFirstWorkspaceFolder();
this.qlPackStoragePath = await this.determineStoragePath();
const skeletonPackAlreadyExists =
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
@@ -98,6 +103,38 @@ export class SkeletonQueryWizard {
});
}
public async determineStoragePath() {
const firstStorageFolder = getFirstWorkspaceFolder();
if (isCodespacesTemplate()) {
return firstStorageFolder;
}
let storageFolder = getSkeletonWizardFolder();
if (storageFolder === undefined || !existsSync(storageFolder)) {
storageFolder = await Window.showInputBox({
title:
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
value: firstStorageFolder,
ignoreFocusOut: true,
});
}
if (storageFolder === undefined) {
throw new UserCancellationException("No storage folder entered.");
}
if (!existsSync(storageFolder)) {
throw new UserCancellationException(
"Invalid folder. Must be a folder that already exists.",
);
}
await setSkeletonWizardFolder(storageFolder);
return storageFolder;
}
private async chooseLanguage() {
this.progress({
message: "Choose language",

View File

@@ -21,6 +21,7 @@ import {
import * as databaseFetcher from "../../../src/databaseFetcher";
import { createMockDB } from "../../factories/databases/databases";
import { asError } from "../../../src/pure/helpers-pure";
import { Setting } from "../../../src/config";
describe("SkeletonQueryWizard", () => {
let mockCli: CodeQLCliServer;
@@ -29,6 +30,7 @@ describe("SkeletonQueryWizard", () => {
let dir: tmp.DirResult;
let storagePath: string;
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
let showInputBoxSpy: jest.SpiedFunction<typeof window.showInputBox>;
let generateSpy: jest.SpiedFunction<
typeof QlPackGenerator.prototype.generate
>;
@@ -93,6 +95,9 @@ describe("SkeletonQueryWizard", () => {
quickPickSpy = jest
.spyOn(window, "showQuickPick")
.mockResolvedValueOnce(mockedQuickPickItem(chosenLanguage));
showInputBoxSpy = jest
.spyOn(window, "showInputBox")
.mockResolvedValue(storagePath);
generateSpy = jest
.spyOn(QlPackGenerator.prototype, "generate")
.mockResolvedValue(undefined);
@@ -433,4 +438,116 @@ describe("SkeletonQueryWizard", () => {
});
});
});
describe("determineStoragePath", () => {
it("should prompt the user to provide a storage path", async () => {
const chosenPath = await wizard.determineStoragePath();
expect(showInputBoxSpy).toHaveBeenCalledWith(
expect.objectContaining({ value: storagePath }),
);
expect(chosenPath).toEqual(storagePath);
});
it("should write the chosen folder to settings", async () => {
const updateValueSpy = jest.spyOn(Setting.prototype, "updateValue");
await wizard.determineStoragePath();
expect(updateValueSpy).toHaveBeenCalledWith(storagePath, 1);
});
describe("when the user is using the codespace template", () => {
let originalValue: any;
let storedPath: string;
beforeEach(async () => {
storedPath = join(dir.name, "pickles-folder");
ensureDirSync(storedPath);
originalValue = workspace
.getConfiguration("codeQL.skeletonWizard")
.get("folder");
// Set isCodespacesTemplate to true to indicate we are in the codespace template
await workspace
.getConfiguration("codeQL")
.update("codespacesTemplate", true);
});
afterEach(async () => {
await workspace
.getConfiguration("codeQL")
.update("codespacesTemplate", originalValue);
});
it("should not prompt the user", async () => {
const chosenPath = await wizard.determineStoragePath();
expect(showInputBoxSpy).not.toHaveBeenCalled();
expect(chosenPath).toEqual(storagePath);
});
});
describe("when there is already a saved storage path in settings", () => {
describe("when the saved storage path exists", () => {
let originalValue: any;
let storedPath: string;
beforeEach(async () => {
storedPath = join(dir.name, "pickles-folder");
ensureDirSync(storedPath);
originalValue = workspace
.getConfiguration("codeQL.skeletonWizard")
.get("folder");
await workspace
.getConfiguration("codeQL.skeletonWizard")
.update("folder", storedPath);
});
afterEach(async () => {
await workspace
.getConfiguration("codeQL.skeletonWizard")
.update("folder", originalValue);
});
it("should return it and not prompt the user", async () => {
const chosenPath = await wizard.determineStoragePath();
expect(showInputBoxSpy).not.toHaveBeenCalled();
expect(chosenPath).toEqual(storedPath);
});
});
describe("when the saved storage path does not exist", () => {
let originalValue: any;
let storedPath: string;
beforeEach(async () => {
storedPath = join(dir.name, "this-folder-does-not-exist");
originalValue = workspace
.getConfiguration("codeQL.skeletonWizard")
.get("folder");
await workspace
.getConfiguration("codeQL.skeletonWizard")
.update("folder", storedPath);
});
afterEach(async () => {
await workspace
.getConfiguration("codeQL.skeletonWizard")
.update("folder", originalValue);
});
it("should prompt the user for to provide a new folder name", async () => {
const chosenPath = await wizard.determineStoragePath();
expect(showInputBoxSpy).toHaveBeenCalled();
expect(chosenPath).toEqual(storagePath);
});
});
});
});
});