Skip to content

Commit 037596c

Browse files
committed
Create skeleton QL pack if in CodeTour
We'd like to make it easier for a user going through the CodeQL Tour to write their queries. To help them along, we can generate skeleton QL packs once we know which database they're using, instead of expecting them to know how to create this themselves. We're then able to download the necessary dependencies for their CodeQL queries. This checks that we're running the CodeTour by looking for the `codeQL.codespacesTemplate` setting.
1 parent a85281e commit 037596c

2 files changed

Lines changed: 84 additions & 3 deletions

File tree

extensions/ql-vscode/src/databases.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
showAndLogInformationMessage,
1010
isLikelyDatabaseRoot,
1111
showAndLogExceptionWithTelemetry,
12+
isFolderAlreadyInWorkspace,
13+
showBinaryChoiceDialog,
1214
} from "./helpers";
1315
import { ProgressCallback, withProgress } from "./commandRunner";
1416
import {
@@ -23,6 +25,7 @@ import { asError, getErrorMessage } from "./pure/helpers-pure";
2325
import { QueryRunner } from "./queryRunner";
2426
import { pathsEqual } from "./pure/files";
2527
import { redactableError } from "./pure/errors";
28+
import { isCodespacesTemplate } from "./config";
2629

2730
/**
2831
* databases.ts
@@ -621,9 +624,38 @@ export class DatabaseManager extends DisposableObject {
621624
await this.addDatabaseItem(progress, token, databaseItem);
622625
await this.addDatabaseSourceArchiveFolder(databaseItem);
623626

627+
if (isCodespacesTemplate()) {
628+
await this.createSkeletonPacks(databaseItem);
629+
}
630+
624631
return databaseItem;
625632
}
626633

634+
public async createSkeletonPacks(databaseItem: DatabaseItem) {
635+
if (databaseItem === undefined) {
636+
void this.logger.log(
637+
"Could not create QL pack as no database is selected. Please select a database.",
638+
);
639+
return;
640+
}
641+
642+
if (databaseItem.language == "") {
643+
void this.logger.log(
644+
"Could not create skeleton QL pack because the selected database's language is not set.",
645+
);
646+
return;
647+
}
648+
649+
const folderName = `codeql-custom-queries-${databaseItem.language}`;
650+
if (isFolderAlreadyInWorkspace(folderName)) {
651+
return;
652+
}
653+
654+
await showBinaryChoiceDialog(
655+
"We've noticed you don't have QL packs downloaded to analyze this database. Can we set it up for you?",
656+
);
657+
}
658+
627659
private async reregisterDatabases(
628660
progress: ProgressCallback,
629661
token: vscode.CancellationToken,

extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from "../../../src/archive-filesystem-provider";
2121
import { testDisposeHandler } from "../test-dispose-handler";
2222
import { QueryRunner } from "../../../src/queryRunner";
23+
import * as helpers from "../../../src/helpers";
2324

2425
describe("databases", () => {
2526
const MOCK_DB_OPTIONS: FullDatabaseOptions = {
@@ -34,6 +35,11 @@ describe("databases", () => {
3435
let registerSpy: jest.Mock<Promise<void>, []>;
3536
let deregisterSpy: jest.Mock<Promise<void>, []>;
3637
let resolveDatabaseSpy: jest.Mock<Promise<DbInfo>, []>;
38+
let logSpy: jest.Mock<any, []>;
39+
40+
let showBinaryChoiceDialogSpy: jest.SpiedFunction<
41+
typeof helpers.showBinaryChoiceDialog
42+
>;
3743

3844
let dir: tmp.DirResult;
3945

@@ -44,6 +50,13 @@ describe("databases", () => {
4450
registerSpy = jest.fn(() => Promise.resolve(undefined));
4551
deregisterSpy = jest.fn(() => Promise.resolve(undefined));
4652
resolveDatabaseSpy = jest.fn(() => Promise.resolve({} as DbInfo));
53+
logSpy = jest.fn(() => {
54+
/* */
55+
});
56+
57+
showBinaryChoiceDialogSpy = jest
58+
.spyOn(helpers, "showBinaryChoiceDialog")
59+
.mockResolvedValue(true);
4760

4861
databaseManager = new DatabaseManager(
4962
{
@@ -66,9 +79,7 @@ describe("databases", () => {
6679
resolveDatabase: resolveDatabaseSpy,
6780
} as unknown as CodeQLCliServer,
6881
{
69-
log: () => {
70-
/**/
71-
},
82+
log: logSpy,
7283
} as unknown as Logger,
7384
);
7485

@@ -574,6 +585,44 @@ describe("databases", () => {
574585
});
575586
});
576587

588+
describe("createSkeletonPacks", () => {
589+
let mockDbItem: DatabaseItemImpl;
590+
591+
describe("when the language is set", () => {
592+
it("should offer the user to set up a skeleton QL pack", async () => {
593+
const options: FullDatabaseOptions = {
594+
dateAdded: 123,
595+
ignoreSourceArchive: false,
596+
language: "ruby",
597+
};
598+
mockDbItem = createMockDB(options);
599+
600+
await (databaseManager as any).createSkeletonPacks(mockDbItem);
601+
602+
expect(showBinaryChoiceDialogSpy).toBeCalledTimes(1);
603+
});
604+
});
605+
606+
describe("when the language is not set", () => {
607+
it("should fail gracefully", async () => {
608+
mockDbItem = createMockDB();
609+
await (databaseManager as any).createSkeletonPacks(mockDbItem);
610+
expect(logSpy).toHaveBeenCalledWith(
611+
"Could not create skeleton QL pack because the selected database's language is not set.",
612+
);
613+
});
614+
});
615+
616+
describe("when the databaseItem is not set", () => {
617+
it("should fail gracefully", async () => {
618+
await (databaseManager as any).createSkeletonPacks(undefined);
619+
expect(logSpy).toHaveBeenCalledWith(
620+
"Could not create QL pack as no database is selected. Please select a database.",
621+
);
622+
});
623+
});
624+
});
625+
577626
function createMockDB(
578627
mockDbOptions = MOCK_DB_OPTIONS,
579628
// source archive location must be a real(-ish) location since

0 commit comments

Comments
 (0)