Skip to content

Commit 11b4de1

Browse files
authored
Merge branch 'main' into koesie10/typed-local-query-commands
2 parents 7950c1c + f196e34 commit 11b4de1

File tree

9 files changed

+212
-26
lines changed

9 files changed

+212
-26
lines changed

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [UNRELEASED]
44

55
- Show data flow paths of a variant analysis in a new tab
6+
- Show labels of entities in exported CSV results [#2170](https://github.com/github/vscode-codeql/pull/2170)
67

78
## 1.8.0 - 9 March 2023
89

extensions/ql-vscode/src/extension.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
showInformationMessageWithAction,
6565
tmpDir,
6666
tmpDirDisposal,
67+
prepareCodeTour,
6768
} from "./helpers";
6869
import {
6970
asError,
@@ -523,6 +524,14 @@ async function installOrUpdateThenTryActivate(
523524
): Promise<CodeQLExtensionInterface | Record<string, never>> {
524525
await installOrUpdateDistribution(ctx, distributionManager, config);
525526

527+
try {
528+
await prepareCodeTour();
529+
} catch (e: unknown) {
530+
void extLogger.log(
531+
`Could not open tutorial workspace automatically: ${getErrorMessage(e)}`,
532+
);
533+
}
534+
526535
// Display the warnings even if the extension has already activated.
527536
const distributionResult =
528537
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/src/query-history/query-history-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import {
4848
import {
4949
deserializeQueryHistory,
5050
serializeQueryHistory,
51-
} from "../query-serialization";
51+
} from "./store/query-history-store";
5252
import { pathExists } from "fs-extra";
5353
import { CliVersionConstraint } from "../cli";
5454
import { HistoryItemLabelProvider } from "./history-item-label-provider";

extensions/ql-vscode/src/query-serialization.ts renamed to extensions/ql-vscode/src/query-history/store/query-history-store.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { pathExists, readFile, remove, mkdir, writeFile } from "fs-extra";
22
import { dirname } from "path";
33

4-
import { showAndLogExceptionWithTelemetry } from "./helpers";
4+
import { showAndLogExceptionWithTelemetry } from "../../helpers";
55
import {
66
asError,
77
asyncFilter,
88
getErrorMessage,
99
getErrorStack,
10-
} from "./pure/helpers-pure";
11-
import { CompletedQueryInfo, LocalQueryInfo } from "./query-results";
12-
import { QueryHistoryInfo } from "./query-history/query-history-info";
13-
import { QueryEvaluationInfo } from "./run-queries-shared";
14-
import { QueryResultType } from "./pure/legacy-messages";
15-
import { redactableError } from "./pure/errors";
10+
} from "../../pure/helpers-pure";
11+
import { CompletedQueryInfo, LocalQueryInfo } from "../../query-results";
12+
import { QueryHistoryInfo } from "../query-history-info";
13+
import { QueryEvaluationInfo } from "../../run-queries-shared";
14+
import { QueryResultType } from "../../pure/legacy-messages";
15+
import { redactableError } from "../../pure/errors";
1616

1717
export async function deserializeQueryHistory(
1818
fsPath: string,

extensions/ql-vscode/src/run-queries-shared.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { nanoid } from "nanoid";
3030
import { CodeQLCliServer } from "./cli";
3131
import { SELECT_QUERY_NAME } from "./contextual/locationFinder";
3232
import { DatabaseManager } from "./local-databases";
33-
import { DecodedBqrsChunk } from "./pure/bqrs-cli-types";
33+
import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types";
3434
import { extLogger, Logger } from "./common";
3535
import { generateSummarySymbolsFile } from "./log-insights/summary-parser";
3636
import { getErrorMessage } from "./pure/helpers-pure";
@@ -351,11 +351,17 @@ export class QueryEvaluationInfo {
351351
chunk.tuples.forEach((tuple) => {
352352
out.write(
353353
`${tuple
354-
.map((v, i) =>
355-
chunk.columns[i].kind === "String"
356-
? `"${typeof v === "string" ? v.replaceAll('"', '""') : v}"`
357-
: v,
358-
)
354+
.map((v, i) => {
355+
if (chunk.columns[i].kind === "String") {
356+
return `"${
357+
typeof v === "string" ? v.replaceAll('"', '""') : v
358+
}"`;
359+
} else if (chunk.columns[i].kind === "Entity") {
360+
return (v as EntityValue).label;
361+
} else {
362+
return v;
363+
}
364+
})
359365
.join(",")}\n`,
360366
);
361367
});

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+
});

extensions/ql-vscode/test/vscode-tests/no-workspace/query-serialization.test.ts renamed to extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import {
22
deserializeQueryHistory,
33
serializeQueryHistory,
4-
} from "../../../src/query-serialization";
4+
} from "../../../../../src/query-history/store/query-history-store";
55
import { join } from "path";
66
import { writeFileSync, mkdirpSync, writeFile } from "fs-extra";
7-
import { LocalQueryInfo, InitialQueryInfo } from "../../../src/query-results";
8-
import { QueryWithResults } from "../../../src/run-queries-shared";
9-
import { DatabaseInfo } from "../../../src/pure/interface-types";
7+
import {
8+
LocalQueryInfo,
9+
InitialQueryInfo,
10+
} from "../../../../../src/query-results";
11+
import { QueryWithResults } from "../../../../../src/run-queries-shared";
12+
import { DatabaseInfo } from "../../../../../src/pure/interface-types";
1013
import { CancellationTokenSource, Uri } from "vscode";
11-
import { tmpDir } from "../../../src/helpers";
12-
import { QueryResultType } from "../../../src/pure/legacy-messages";
13-
import { QueryInProgress } from "../../../src/legacy-query-server/run-queries";
14-
import { VariantAnalysisHistoryItem } from "../../../src/query-history/variant-analysis-history-item";
15-
import { QueryHistoryInfo } from "../../../src/query-history/query-history-info";
16-
import { createMockVariantAnalysisHistoryItem } from "../../factories/query-history/variant-analysis-history-item";
14+
import { tmpDir } from "../../../../../src/helpers";
15+
import { QueryResultType } from "../../../../../src/pure/legacy-messages";
16+
import { QueryInProgress } from "../../../../../src/legacy-query-server/run-queries";
17+
import { VariantAnalysisHistoryItem } from "../../../../../src/query-history/variant-analysis-history-item";
18+
import { QueryHistoryInfo } from "../../../../../src/query-history/query-history-info";
19+
import { createMockVariantAnalysisHistoryItem } from "../../../../factories/query-history/variant-analysis-history-item";
1720
import { nanoid } from "nanoid";
1821

1922
describe("serialize and deserialize", () => {

0 commit comments

Comments
 (0)