Skip to content

Commit eb5659a

Browse files
authored
Merge branch 'main' into koesie10/move-database-commands
2 parents bed56ef + f196e34 commit eb5659a

File tree

4 files changed

+178
-2
lines changed

4 files changed

+178
-2
lines changed

extensions/ql-vscode/src/extension.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import {
6969
showInformationMessageWithAction,
7070
tmpDir,
7171
tmpDirDisposal,
72+
prepareCodeTour,
7273
} from "./helpers";
7374
import {
7475
asError,
@@ -527,6 +528,14 @@ async function installOrUpdateThenTryActivate(
527528
): Promise<CodeQLExtensionInterface | Record<string, never>> {
528529
await installOrUpdateDistribution(ctx, distributionManager, config);
529530

531+
try {
532+
await prepareCodeTour();
533+
} catch (e: unknown) {
534+
void extLogger.log(
535+
`Could not open tutorial workspace automatically: ${getErrorMessage(e)}`,
536+
);
537+
}
538+
530539
// Display the warnings even if the extension has already activated.
531540
const distributionResult =
532541
await getDistributionDisplayingDistributionWarnings(distributionManager);

extensions/ql-vscode/src/helpers.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
window as Window,
1717
workspace,
1818
env,
19+
commands,
1920
} from "vscode";
2021
import { CodeQLCliServer, QlpacksInfo } from "./cli";
2122
import { UserCancellationException } from "./commandRunner";
@@ -25,6 +26,7 @@ import { telemetryListener } from "./telemetry";
2526
import { RedactableError } from "./pure/errors";
2627
import { getQlPackPath } from "./pure/ql";
2728
import { dbSchemeToLanguage } from "./common/query-language";
29+
import { isCodespacesTemplate } from "./config";
2830

2931
// Shared temporary folder for the extension.
3032
export const tmpDir = dirSync({
@@ -266,6 +268,51 @@ export function isFolderAlreadyInWorkspace(folderName: string) {
266268
);
267269
}
268270

271+
/** Check if the current workspace is the CodeTour and open the workspace folder.
272+
* Without this, we can't run the code tour correctly.
273+
**/
274+
export async function prepareCodeTour(): Promise<void> {
275+
if (workspace.workspaceFolders?.length) {
276+
const currentFolder = workspace.workspaceFolders[0].uri.fsPath;
277+
278+
const tutorialWorkspacePath = join(
279+
currentFolder,
280+
"tutorial.code-workspace",
281+
);
282+
const toursFolderPath = join(currentFolder, ".tours");
283+
284+
/** We're opening the tutorial workspace, if we detect it.
285+
* This will only happen if the following three conditions are met:
286+
* - the .tours folder exists
287+
* - the tutorial.code-workspace file exists
288+
* - the CODESPACES_TEMPLATE setting doesn't exist (it's only set if the user has already opened
289+
* the tutorial workspace so it's a good indicator that the user is in the folder but has ignored
290+
* the prompt to open the workspace)
291+
*/
292+
if (
293+
(await pathExists(tutorialWorkspacePath)) &&
294+
(await pathExists(toursFolderPath)) &&
295+
!isCodespacesTemplate()
296+
) {
297+
const answer = await showBinaryChoiceDialog(
298+
"We've detected you're in the CodeQL Tour repo. We will need to open the workspace file to continue. Reload?",
299+
);
300+
301+
if (!answer) {
302+
return;
303+
}
304+
305+
const tutorialWorkspaceUri = Uri.parse(tutorialWorkspacePath);
306+
307+
void extLogger.log(
308+
`In prepareCodeTour() method, going to open the tutorial workspace file: ${tutorialWorkspacePath}`,
309+
);
310+
311+
await commands.executeCommand("vscode.openFolder", tutorialWorkspaceUri);
312+
}
313+
}
314+
}
315+
269316
/**
270317
* Provides a utility method to invoke a function only if a minimum time interval has elapsed since
271318
* the last invocation of that function.

extensions/ql-vscode/supported_cli_versions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
2-
"v2.12.4",
2+
"v2.12.5",
33
"v2.11.6",
44
"v2.7.6",
55
"v2.8.5",

extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
commands,
23
EnvironmentVariableCollection,
34
EnvironmentVariableMutator,
45
Event,
@@ -15,7 +16,14 @@ import {
1516
import { dump } from "js-yaml";
1617
import * as tmp from "tmp";
1718
import { join } from "path";
18-
import { writeFileSync, mkdirSync, ensureDirSync, symlinkSync } from "fs-extra";
19+
import {
20+
writeFileSync,
21+
mkdirSync,
22+
ensureDirSync,
23+
symlinkSync,
24+
writeFile,
25+
mkdir,
26+
} from "fs-extra";
1927
import { DirResult } from "tmp";
2028

2129
import {
@@ -24,13 +32,15 @@ import {
2432
isFolderAlreadyInWorkspace,
2533
isLikelyDatabaseRoot,
2634
isLikelyDbLanguageFolder,
35+
prepareCodeTour,
2736
showBinaryChoiceDialog,
2837
showBinaryChoiceWithUrlDialog,
2938
showInformationMessageWithAction,
3039
walkDirectory,
3140
} from "../../../src/helpers";
3241
import { reportStreamProgress } from "../../../src/commandRunner";
3342
import { QueryLanguage } from "../../../src/common/query-language";
43+
import { Setting } from "../../../src/config";
3444

3545
describe("helpers", () => {
3646
describe("Invocation rate limiter", () => {
@@ -559,3 +569,113 @@ describe("isFolderAlreadyInWorkspace", () => {
559569
expect(isFolderAlreadyInWorkspace("/third/path")).toBe(false);
560570
});
561571
});
572+
573+
describe("prepareCodeTour", () => {
574+
let dir: tmp.DirResult;
575+
let showInformationMessageSpy: jest.SpiedFunction<
576+
typeof window.showInformationMessage
577+
>;
578+
579+
beforeEach(() => {
580+
dir = tmp.dirSync();
581+
582+
const mockWorkspaceFolders = [
583+
{
584+
uri: Uri.file(dir.name),
585+
name: "test",
586+
index: 0,
587+
},
588+
] as WorkspaceFolder[];
589+
590+
jest
591+
.spyOn(workspace, "workspaceFolders", "get")
592+
.mockReturnValue(mockWorkspaceFolders);
593+
594+
showInformationMessageSpy = jest
595+
.spyOn(window, "showInformationMessage")
596+
.mockResolvedValue({ title: "Yes" });
597+
});
598+
599+
afterEach(() => {
600+
dir.removeCallback();
601+
});
602+
603+
describe("if we're in the tour repo", () => {
604+
describe("if the workspace is not already open", () => {
605+
it("should open the tutorial workspace", async () => {
606+
// set up directory to have a 'tutorial.code-workspace' file
607+
const tutorialWorkspacePath = join(dir.name, "tutorial.code-workspace");
608+
await writeFile(tutorialWorkspacePath, "{}");
609+
610+
// set up a .tours directory to indicate we're in the tour codespace
611+
const tourDirPath = join(dir.name, ".tours");
612+
await mkdir(tourDirPath);
613+
614+
// spy that we open the workspace file by calling the 'vscode.openFolder' command
615+
const commandSpy = jest.spyOn(commands, "executeCommand");
616+
commandSpy.mockImplementation(() => Promise.resolve());
617+
618+
await prepareCodeTour();
619+
620+
expect(showInformationMessageSpy).toHaveBeenCalled();
621+
expect(commandSpy).toHaveBeenCalledWith(
622+
"vscode.openFolder",
623+
expect.objectContaining({
624+
path: Uri.parse(tutorialWorkspacePath).fsPath,
625+
}),
626+
);
627+
});
628+
});
629+
630+
describe("if the workspace is already open", () => {
631+
it("should not open the tutorial workspace", async () => {
632+
// Set isCodespaceTemplate to true to indicate the workspace has already been opened
633+
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(true);
634+
635+
// set up directory to have a 'tutorial.code-workspace' file
636+
const tutorialWorkspacePath = join(dir.name, "tutorial.code-workspace");
637+
await writeFile(tutorialWorkspacePath, "{}");
638+
639+
// set up a .tours directory to indicate we're in the tour codespace
640+
const tourDirPath = join(dir.name, ".tours");
641+
await mkdir(tourDirPath);
642+
643+
// spy that we open the workspace file by calling the 'vscode.openFolder' command
644+
const commandSpy = jest.spyOn(commands, "executeCommand");
645+
commandSpy.mockImplementation(() => Promise.resolve());
646+
647+
await prepareCodeTour();
648+
649+
expect(commandSpy).not.toHaveBeenCalled();
650+
});
651+
});
652+
});
653+
654+
describe("if we're in a different tour repo", () => {
655+
it("should not open the tutorial workspace", async () => {
656+
// set up a .tours directory
657+
const tourDirPath = join(dir.name, ".tours");
658+
await mkdir(tourDirPath);
659+
660+
// spy that we open the workspace file by calling the 'vscode.openFolder' command
661+
const commandSpy = jest.spyOn(commands, "executeCommand");
662+
commandSpy.mockImplementation(() => Promise.resolve());
663+
664+
await prepareCodeTour();
665+
666+
expect(commandSpy).not.toHaveBeenCalled();
667+
});
668+
});
669+
670+
describe("if we're in a different repo with no tour", () => {
671+
it("should not open the tutorial workspace", async () => {
672+
// spy that we open the workspace file by calling the 'vscode.openFolder' command
673+
const commandSpy = jest.spyOn(commands, "executeCommand");
674+
commandSpy.mockImplementation(() => Promise.resolve());
675+
676+
await prepareCodeTour();
677+
678+
expect(commandSpy).not.toHaveBeenCalled();
679+
});
680+
});
681+
});

0 commit comments

Comments
 (0)