Skip to content

Commit 57d48a7

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 72a3e8d commit 57d48a7

File tree

3 files changed

+172
-2
lines changed

3 files changed

+172
-2
lines changed

extensions/ql-vscode/src/config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,3 +619,19 @@ export const ALLOW_HTTP_SETTING = new Setting(
619619
export function allowHttp(): boolean {
620620
return ALLOW_HTTP_SETTING.getValue<boolean>() || false;
621621
}
622+
623+
/**
624+
* The name of the folder where we want to create skeleton wizard QL packs.
625+
**/
626+
const SKELETON_WIZARD_FOLDER = new Setting(
627+
"folder",
628+
new Setting("skeletonWizard", ROOT_SETTING),
629+
);
630+
631+
export function getSkeletonWizardFolder(): string | undefined {
632+
return SKELETON_WIZARD_FOLDER.getValue<string>() || undefined;
633+
}
634+
635+
export async function setSkeletonWizardFolder(folder: string | undefined) {
636+
await SKELETON_WIZARD_FOLDER.updateValue(folder, ConfigurationTarget.Global);
637+
}

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

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import { QlPackGenerator } from "./qlpack-generator";
1414
import { DatabaseItem, DatabaseManager } from "./local-databases";
1515
import { ProgressCallback, UserCancellationException } from "./progress";
1616
import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher";
17-
import { existsSync } from "fs";
17+
import {
18+
getSkeletonWizardFolder,
19+
isCodespacesTemplate,
20+
setSkeletonWizardFolder,
21+
} from "./config";
22+
import { existsSync } from "fs-extra";
1823

1924
type QueryLanguagesToDatabaseMap = Record<string, string>;
2025

@@ -55,7 +60,7 @@ export class SkeletonQueryWizard {
5560
return;
5661
}
5762

58-
this.qlPackStoragePath = getFirstWorkspaceFolder();
63+
this.qlPackStoragePath = await this.determineStoragePath();
5964

6065
const skeletonPackAlreadyExists =
6166
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
@@ -98,6 +103,38 @@ export class SkeletonQueryWizard {
98103
});
99104
}
100105

106+
public async determineStoragePath() {
107+
const firstStorageFolder = getFirstWorkspaceFolder();
108+
109+
if (isCodespacesTemplate()) {
110+
return firstStorageFolder;
111+
}
112+
113+
let storageFolder = getSkeletonWizardFolder();
114+
115+
if (storageFolder === undefined || !existsSync(storageFolder)) {
116+
storageFolder = await Window.showInputBox({
117+
title:
118+
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
119+
value: firstStorageFolder,
120+
ignoreFocusOut: true,
121+
});
122+
}
123+
124+
if (storageFolder === undefined) {
125+
throw new UserCancellationException("No storage folder entered.");
126+
}
127+
128+
if (!existsSync(storageFolder)) {
129+
throw new UserCancellationException(
130+
"Invalid folder. Must be a folder that already exists.",
131+
);
132+
}
133+
134+
await setSkeletonWizardFolder(storageFolder);
135+
return storageFolder;
136+
}
137+
101138
private async chooseLanguage() {
102139
this.progress({
103140
message: "Choose language",

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);
@@ -433,4 +438,116 @@ describe("SkeletonQueryWizard", () => {
433438
});
434439
});
435440
});
441+
442+
describe("determineStoragePath", () => {
443+
it("should prompt the user to provide a storage path", async () => {
444+
const chosenPath = await wizard.determineStoragePath();
445+
446+
expect(showInputBoxSpy).toHaveBeenCalledWith(
447+
expect.objectContaining({ value: storagePath }),
448+
);
449+
expect(chosenPath).toEqual(storagePath);
450+
});
451+
452+
it("should write the chosen folder to settings", async () => {
453+
const updateValueSpy = jest.spyOn(Setting.prototype, "updateValue");
454+
455+
await wizard.determineStoragePath();
456+
457+
expect(updateValueSpy).toHaveBeenCalledWith(storagePath, 1);
458+
});
459+
460+
describe("when the user is using the codespace template", () => {
461+
let originalValue: any;
462+
let storedPath: string;
463+
464+
beforeEach(async () => {
465+
storedPath = join(dir.name, "pickles-folder");
466+
ensureDirSync(storedPath);
467+
468+
originalValue = workspace
469+
.getConfiguration("codeQL.skeletonWizard")
470+
.get("folder");
471+
472+
// Set isCodespacesTemplate to true to indicate we are in the codespace template
473+
await workspace
474+
.getConfiguration("codeQL")
475+
.update("codespacesTemplate", true);
476+
});
477+
478+
afterEach(async () => {
479+
await workspace
480+
.getConfiguration("codeQL")
481+
.update("codespacesTemplate", originalValue);
482+
});
483+
484+
it("should not prompt the user", async () => {
485+
const chosenPath = await wizard.determineStoragePath();
486+
487+
expect(showInputBoxSpy).not.toHaveBeenCalled();
488+
expect(chosenPath).toEqual(storagePath);
489+
});
490+
});
491+
492+
describe("when there is already a saved storage path in settings", () => {
493+
describe("when the saved storage path exists", () => {
494+
let originalValue: any;
495+
let storedPath: string;
496+
497+
beforeEach(async () => {
498+
storedPath = join(dir.name, "pickles-folder");
499+
ensureDirSync(storedPath);
500+
501+
originalValue = workspace
502+
.getConfiguration("codeQL.skeletonWizard")
503+
.get("folder");
504+
await workspace
505+
.getConfiguration("codeQL.skeletonWizard")
506+
.update("folder", storedPath);
507+
});
508+
509+
afterEach(async () => {
510+
await workspace
511+
.getConfiguration("codeQL.skeletonWizard")
512+
.update("folder", originalValue);
513+
});
514+
515+
it("should return it and not prompt the user", async () => {
516+
const chosenPath = await wizard.determineStoragePath();
517+
518+
expect(showInputBoxSpy).not.toHaveBeenCalled();
519+
expect(chosenPath).toEqual(storedPath);
520+
});
521+
});
522+
523+
describe("when the saved storage path does not exist", () => {
524+
let originalValue: any;
525+
let storedPath: string;
526+
527+
beforeEach(async () => {
528+
storedPath = join(dir.name, "this-folder-does-not-exist");
529+
530+
originalValue = workspace
531+
.getConfiguration("codeQL.skeletonWizard")
532+
.get("folder");
533+
await workspace
534+
.getConfiguration("codeQL.skeletonWizard")
535+
.update("folder", storedPath);
536+
});
537+
538+
afterEach(async () => {
539+
await workspace
540+
.getConfiguration("codeQL.skeletonWizard")
541+
.update("folder", originalValue);
542+
});
543+
544+
it("should prompt the user for to provide a new folder name", async () => {
545+
const chosenPath = await wizard.determineStoragePath();
546+
547+
expect(showInputBoxSpy).toHaveBeenCalled();
548+
expect(chosenPath).toEqual(storagePath);
549+
});
550+
});
551+
});
552+
});
436553
});

0 commit comments

Comments
 (0)