Generate a QL pack when you add a new database, if one is missing

This commit is contained in:
Elena Tanasoiu
2023-02-07 22:17:51 +00:00
parent 6cda6534d1
commit a8f36ee9e8
2 changed files with 77 additions and 9 deletions

View File

@@ -26,6 +26,7 @@ import { QueryRunner } from "./queryRunner";
import { pathsEqual } from "./pure/files"; import { pathsEqual } from "./pure/files";
import { redactableError } from "./pure/errors"; import { redactableError } from "./pure/errors";
import { isCodespacesTemplate } from "./config"; import { isCodespacesTemplate } from "./config";
import { QlPackGenerator, QueryLanguage } from "./qlpack-generator";
/** /**
* databases.ts * databases.ts
@@ -655,9 +656,26 @@ export class DatabaseManager extends DisposableObject {
return; return;
} }
await showBinaryChoiceDialog( const answer = await showBinaryChoiceDialog(
`We've noticed you don't have a QL pack downloaded to analyze this database. Can we set up a ${databaseItem.language} query pack for you`, `We've noticed you don't have a QL pack downloaded to analyze this database. Can we set up a query pack for you?`,
); );
if (!answer) {
return;
}
try {
const qlPackGenerator = new QlPackGenerator(
folderName,
databaseItem.language as QueryLanguage,
this.cli,
);
await qlPackGenerator.generate();
} catch (e: unknown) {
void this.logger.log(
`Could not create skeleton QL pack: ${getErrorMessage(e)}`,
);
}
} }
private async reregisterDatabases( private async reregisterDatabases(

View File

@@ -23,6 +23,7 @@ import { testDisposeHandler } from "../test-dispose-handler";
import { QueryRunner } from "../../../src/queryRunner"; import { QueryRunner } from "../../../src/queryRunner";
import * as helpers from "../../../src/helpers"; import * as helpers from "../../../src/helpers";
import { Setting } from "../../../src/config"; import { Setting } from "../../../src/config";
import { QlPackGenerator } from "../../../src/qlpack-generator";
describe("databases", () => { describe("databases", () => {
const MOCK_DB_OPTIONS: FullDatabaseOptions = { const MOCK_DB_OPTIONS: FullDatabaseOptions = {
@@ -37,6 +38,7 @@ describe("databases", () => {
let registerSpy: jest.Mock<Promise<void>, []>; let registerSpy: jest.Mock<Promise<void>, []>;
let deregisterSpy: jest.Mock<Promise<void>, []>; let deregisterSpy: jest.Mock<Promise<void>, []>;
let resolveDatabaseSpy: jest.Mock<Promise<DbInfo>, []>; let resolveDatabaseSpy: jest.Mock<Promise<DbInfo>, []>;
let packAddSpy: jest.Mock<any, []>;
let logSpy: jest.Mock<any, []>; let logSpy: jest.Mock<any, []>;
let showBinaryChoiceDialogSpy: jest.SpiedFunction< let showBinaryChoiceDialogSpy: jest.SpiedFunction<
@@ -52,6 +54,7 @@ describe("databases", () => {
registerSpy = jest.fn(() => Promise.resolve(undefined)); registerSpy = jest.fn(() => Promise.resolve(undefined));
deregisterSpy = jest.fn(() => Promise.resolve(undefined)); deregisterSpy = jest.fn(() => Promise.resolve(undefined));
resolveDatabaseSpy = jest.fn(() => Promise.resolve({} as DbInfo)); resolveDatabaseSpy = jest.fn(() => Promise.resolve({} as DbInfo));
packAddSpy = jest.fn();
logSpy = jest.fn(() => { logSpy = jest.fn(() => {
/* */ /* */
}); });
@@ -79,6 +82,7 @@ describe("databases", () => {
} as unknown as QueryRunner, } as unknown as QueryRunner,
{ {
resolveDatabase: resolveDatabaseSpy, resolveDatabase: resolveDatabaseSpy,
packAdd: packAddSpy,
} as unknown as CodeQLCliServer, } as unknown as CodeQLCliServer,
{ {
log: logSpy, log: logSpy,
@@ -589,20 +593,66 @@ describe("databases", () => {
describe("createSkeletonPacks", () => { describe("createSkeletonPacks", () => {
let mockDbItem: DatabaseItemImpl; let mockDbItem: DatabaseItemImpl;
let packfolderName: string;
let qlPackYamlFilePath: string;
let exampleQlFilePath: string;
let language: string;
beforeEach(() => {
language = "ruby";
const options: FullDatabaseOptions = {
dateAdded: 123,
ignoreSourceArchive: false,
language,
};
mockDbItem = createMockDB(options);
packfolderName = `codeql-custom-queries-${mockDbItem.language}`;
qlPackYamlFilePath = join(packfolderName, "qlpack.yml");
exampleQlFilePath = join(packfolderName, "example.ql");
});
afterEach(async () => {
try {
fs.rmdirSync(packfolderName, { recursive: true });
} catch (e) {
// ignore
}
});
describe("when the language is set", () => { describe("when the language is set", () => {
it("should offer the user to set up a skeleton QL pack", async () => { it("should offer the user to set up a skeleton QL pack", async () => {
const options: FullDatabaseOptions = {
dateAdded: 123,
ignoreSourceArchive: false,
language: "ruby",
};
mockDbItem = createMockDB(options);
await (databaseManager as any).createSkeletonPacks(mockDbItem); await (databaseManager as any).createSkeletonPacks(mockDbItem);
expect(showBinaryChoiceDialogSpy).toBeCalledTimes(1); expect(showBinaryChoiceDialogSpy).toBeCalledTimes(1);
}); });
it("should return early if the user refuses help", async () => {
showBinaryChoiceDialogSpy = jest
.spyOn(helpers, "showBinaryChoiceDialog")
.mockResolvedValue(false);
const generateSpy = jest.spyOn(QlPackGenerator.prototype, "generate");
await (databaseManager as any).createSkeletonPacks(mockDbItem);
expect(generateSpy).not.toBeCalled();
});
it("should create the skeleton QL pack for the user", async () => {
expect(fs.existsSync(packfolderName)).toBe(false);
expect(fs.existsSync(qlPackYamlFilePath)).toBe(false);
expect(fs.existsSync(exampleQlFilePath)).toBe(false);
await (databaseManager as any).createSkeletonPacks(mockDbItem);
expect(fs.existsSync(packfolderName)).toBe(true);
expect(fs.existsSync(qlPackYamlFilePath)).toBe(true);
expect(fs.existsSync(exampleQlFilePath)).toBe(true);
expect(packAddSpy).toHaveBeenCalledWith(packfolderName, language);
});
}); });
describe("when the language is not set", () => { describe("when the language is not set", () => {