Skip to content

Commit 026706c

Browse files
committed
Prompt non-codespace users for storage path
Offer non-codespace users the option to configure their storage folder for skeleton packs. Suggested here: #2310 (comment) 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.
1 parent ffa643c commit 026706c

3 files changed

Lines changed: 172 additions & 1 deletion

File tree

extensions/ql-vscode/src/config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,19 @@ export const CODESPACES_TEMPLATE = new Setting(
608608
export function isCodespacesTemplate() {
609609
return !!CODESPACES_TEMPLATE.getValue<boolean>();
610610
}
611+
612+
/**
613+
* The name of the folder where we want to create skeleton wizard QL packs.
614+
**/
615+
const SKELETON_WIZARD_FOLDER = new Setting(
616+
"folder",
617+
new Setting("skeletonWizard", ROOT_SETTING),
618+
);
619+
620+
export function getSkeletonWizardFolder(): string | undefined {
621+
return SKELETON_WIZARD_FOLDER.getValue<string>() || undefined;
622+
}
623+
624+
export async function setSkeletonWizardFolder(folder: string | undefined) {
625+
await SKELETON_WIZARD_FOLDER.updateValue(folder, ConfigurationTarget.Global);
626+
}

extensions/ql-vscode/src/skeleton-query-wizard.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import { QlPackGenerator } from "./qlpack-generator";
1010
import { DatabaseItem, DatabaseManager } from "./local-databases";
1111
import { ProgressCallback, UserCancellationException } from "./progress";
1212
import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher";
13+
import {
14+
getSkeletonWizardFolder,
15+
isCodespacesTemplate,
16+
setSkeletonWizardFolder,
17+
} from "./config";
18+
import { existsSync } from "fs-extra";
1319

1420
type QueryLanguagesToDatabaseMap = Record<string, string>;
1521

@@ -50,7 +56,7 @@ export class SkeletonQueryWizard {
5056
return;
5157
}
5258

53-
this.qlPackStoragePath = this.getFirstStoragePath();
59+
this.qlPackStoragePath = await this.determineStoragePath();
5460

5561
const skeletonPackAlreadyExists = isFolderAlreadyInWorkspace(
5662
this.folderName,
@@ -93,6 +99,38 @@ export class SkeletonQueryWizard {
9399
});
94100
}
95101

102+
public async determineStoragePath() {
103+
const firstStorageFolder = this.getFirstStoragePath();
104+
105+
if (isCodespacesTemplate()) {
106+
return firstStorageFolder;
107+
}
108+
109+
let storageFolder = getSkeletonWizardFolder();
110+
111+
if (storageFolder === undefined || !existsSync(storageFolder)) {
112+
storageFolder = await Window.showInputBox({
113+
title:
114+
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
115+
value: firstStorageFolder,
116+
ignoreFocusOut: true,
117+
});
118+
}
119+
120+
if (storageFolder === undefined) {
121+
throw new UserCancellationException("No storage folder entered.");
122+
}
123+
124+
if (!existsSync(storageFolder)) {
125+
throw new UserCancellationException(
126+
"Invalid folder. Must be a folder that already exists.",
127+
);
128+
}
129+
130+
await setSkeletonWizardFolder(storageFolder);
131+
return storageFolder;
132+
}
133+
96134
public getFirstStoragePath() {
97135
const workspaceFolders = workspace.workspaceFolders;
98136

extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import * as databaseFetcher from "../../../src/databaseFetcher";
2222
import { createMockDB } from "../../factories/databases/databases";
2323
import { asError } from "../../../src/pure/helpers-pure";
24+
import { Setting } from "../../../src/config";
2425

2526
describe("SkeletonQueryWizard", () => {
2627
let mockCli: CodeQLCliServer;
@@ -29,6 +30,7 @@ describe("SkeletonQueryWizard", () => {
2930
let dir: tmp.DirResult;
3031
let storagePath: string;
3132
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
33+
let showInputBoxSpy: jest.SpiedFunction<typeof window.showInputBox>;
3234
let generateSpy: jest.SpiedFunction<
3335
typeof QlPackGenerator.prototype.generate
3436
>;
@@ -93,6 +95,9 @@ describe("SkeletonQueryWizard", () => {
9395
quickPickSpy = jest
9496
.spyOn(window, "showQuickPick")
9597
.mockResolvedValueOnce(mockedQuickPickItem(chosenLanguage));
98+
showInputBoxSpy = jest
99+
.spyOn(window, "showInputBox")
100+
.mockResolvedValue(storagePath);
96101
generateSpy = jest
97102
.spyOn(QlPackGenerator.prototype, "generate")
98103
.mockResolvedValue(undefined);
@@ -493,4 +498,116 @@ describe("SkeletonQueryWizard", () => {
493498
});
494499
});
495500
});
501+
502+
describe("determineStoragePath", () => {
503+
it("should prompt the user to provide a storage path", async () => {
504+
const chosenPath = await wizard.determineStoragePath();
505+
506+
expect(showInputBoxSpy).toHaveBeenCalledWith(
507+
expect.objectContaining({ value: storagePath }),
508+
);
509+
expect(chosenPath).toEqual(storagePath);
510+
});
511+
512+
it("should write the chosen folder to settings", async () => {
513+
const updateValueSpy = jest.spyOn(Setting.prototype, "updateValue");
514+
515+
await wizard.determineStoragePath();
516+
517+
expect(updateValueSpy).toHaveBeenCalledWith(storagePath, 1);
518+
});
519+
520+
describe("when the user is using the codespace template", () => {
521+
let originalValue: any;
522+
let storedPath: string;
523+
524+
beforeEach(async () => {
525+
storedPath = join(dir.name, "pickles-folder");
526+
ensureDirSync(storedPath);
527+
528+
originalValue = workspace
529+
.getConfiguration("codeQL.skeletonWizard")
530+
.get("folder");
531+
532+
// Set isCodespacesTemplate to true to indicate we are in the codespace template
533+
await workspace
534+
.getConfiguration("codeQL")
535+
.update("codespacesTemplate", true);
536+
});
537+
538+
afterEach(async () => {
539+
await workspace
540+
.getConfiguration("codeQL")
541+
.update("codespacesTemplate", originalValue);
542+
});
543+
544+
it("should not prompt the user", async () => {
545+
const chosenPath = await wizard.determineStoragePath();
546+
547+
expect(showInputBoxSpy).not.toHaveBeenCalled();
548+
expect(chosenPath).toEqual(storagePath);
549+
});
550+
});
551+
552+
describe("when there is already a saved storage path in settings", () => {
553+
describe("when the saved storage path exists", () => {
554+
let originalValue: any;
555+
let storedPath: string;
556+
557+
beforeEach(async () => {
558+
storedPath = join(dir.name, "pickles-folder");
559+
ensureDirSync(storedPath);
560+
561+
originalValue = workspace
562+
.getConfiguration("codeQL.skeletonWizard")
563+
.get("folder");
564+
await workspace
565+
.getConfiguration("codeQL.skeletonWizard")
566+
.update("folder", storedPath);
567+
});
568+
569+
afterEach(async () => {
570+
await workspace
571+
.getConfiguration("codeQL.skeletonWizard")
572+
.update("folder", originalValue);
573+
});
574+
575+
it("should return it and not prompt the user", async () => {
576+
const chosenPath = await wizard.determineStoragePath();
577+
578+
expect(showInputBoxSpy).not.toHaveBeenCalled();
579+
expect(chosenPath).toEqual(storedPath);
580+
});
581+
});
582+
583+
describe("when the saved storage path does not exist", () => {
584+
let originalValue: any;
585+
let storedPath: string;
586+
587+
beforeEach(async () => {
588+
storedPath = join(dir.name, "this-folder-does-not-exist");
589+
590+
originalValue = workspace
591+
.getConfiguration("codeQL.skeletonWizard")
592+
.get("folder");
593+
await workspace
594+
.getConfiguration("codeQL.skeletonWizard")
595+
.update("folder", storedPath);
596+
});
597+
598+
afterEach(async () => {
599+
await workspace
600+
.getConfiguration("codeQL.skeletonWizard")
601+
.update("folder", originalValue);
602+
});
603+
604+
it("should prompt the user for to provide a new folder name", async () => {
605+
const chosenPath = await wizard.determineStoragePath();
606+
607+
expect(showInputBoxSpy).toHaveBeenCalled();
608+
expect(chosenPath).toEqual(storagePath);
609+
});
610+
});
611+
});
612+
});
496613
});

0 commit comments

Comments
 (0)