Merge pull request #3033 from github/koesie10/generate-model
Add generation of Ruby models
This commit is contained in:
@@ -10,7 +10,11 @@ import tk from "tree-kill";
|
|||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { CancellationToken, Disposable, Uri } from "vscode";
|
import { CancellationToken, Disposable, Uri } from "vscode";
|
||||||
|
|
||||||
import { BQRSInfo, DecodedBqrsChunk } from "../common/bqrs-cli-types";
|
import {
|
||||||
|
BQRSInfo,
|
||||||
|
DecodedBqrs,
|
||||||
|
DecodedBqrsChunk,
|
||||||
|
} from "../common/bqrs-cli-types";
|
||||||
import { allowCanaryQueryServer, CliConfig } from "../config";
|
import { allowCanaryQueryServer, CliConfig } from "../config";
|
||||||
import {
|
import {
|
||||||
DistributionProvider,
|
DistributionProvider,
|
||||||
@@ -1040,6 +1044,18 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all results from a bqrs.
|
||||||
|
* @param bqrsPath The path to the bqrs.
|
||||||
|
*/
|
||||||
|
async bqrsDecodeAll(bqrsPath: string): Promise<DecodedBqrs> {
|
||||||
|
return await this.runJsonCodeQlCliCommand<DecodedBqrs>(
|
||||||
|
["bqrs", "decode"],
|
||||||
|
[bqrsPath],
|
||||||
|
"Reading all bqrs data",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async runInterpretCommand(
|
async runInterpretCommand(
|
||||||
format: string,
|
format: string,
|
||||||
additonalArgs: string[],
|
additonalArgs: string[],
|
||||||
|
|||||||
@@ -121,3 +121,5 @@ export interface DecodedBqrsChunk {
|
|||||||
next?: number;
|
next?: number;
|
||||||
columns: BqrsColumn[];
|
columns: BqrsColumn[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DecodedBqrs = Record<string, DecodedBqrsChunk>;
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export interface QueryConstraints {
|
|||||||
kind?: string;
|
kind?: string;
|
||||||
"tags contain"?: string[];
|
"tags contain"?: string[];
|
||||||
"tags contain all"?: string[];
|
"tags contain all"?: string[];
|
||||||
|
"query filename"?: string;
|
||||||
|
"query path"?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,6 +134,14 @@ export async function resolveQueries(
|
|||||||
`tagged all of "${constraints["tags contain all"].join(" ")}"`,
|
`tagged all of "${constraints["tags contain all"].join(" ")}"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (constraints["query filename"] !== undefined) {
|
||||||
|
humanConstraints.push(
|
||||||
|
`with query filename "${constraints["query filename"]}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (constraints["query path"] !== undefined) {
|
||||||
|
humanConstraints.push(`with query path "${constraints["query path"]}"`);
|
||||||
|
}
|
||||||
|
|
||||||
const joinedPacksToSearch = packsToSearch.join(", ");
|
const joinedPacksToSearch = packsToSearch.join(", ");
|
||||||
const error = redactableError`No ${name} queries (${humanConstraints.join(
|
const error = redactableError`No ${name} queries (${humanConstraints.join(
|
||||||
|
|||||||
162
extensions/ql-vscode/src/model-editor/generate-model-queries.ts
Normal file
162
extensions/ql-vscode/src/model-editor/generate-model-queries.ts
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import { CancellationToken } from "vscode";
|
||||||
|
import { DatabaseItem } from "../databases/local-databases";
|
||||||
|
import { QueryRunner } from "../query-server";
|
||||||
|
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
|
import {
|
||||||
|
NotificationLogger,
|
||||||
|
showAndLogExceptionWithTelemetry,
|
||||||
|
} from "../common/logging";
|
||||||
|
import { getModelsAsDataLanguage } from "./languages";
|
||||||
|
import { ProgressCallback } from "../common/vscode/progress";
|
||||||
|
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||||
|
import { ModeledMethod } from "./modeled-method";
|
||||||
|
import { redactableError } from "../common/errors";
|
||||||
|
import { telemetryListener } from "../common/vscode/telemetry";
|
||||||
|
import { runQuery } from "../local-queries/run-query";
|
||||||
|
import { resolveQueries } from "../local-queries";
|
||||||
|
import { QueryLanguage } from "../common/query-language";
|
||||||
|
import { DataTuple } from "./model-extension-file";
|
||||||
|
|
||||||
|
const GENERATE_MODEL_SUPPORTED_LANGUAGES = [QueryLanguage.Ruby];
|
||||||
|
|
||||||
|
export function isGenerateModelSupported(language: QueryLanguage): boolean {
|
||||||
|
return GENERATE_MODEL_SUPPORTED_LANGUAGES.includes(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateModelOptions = {
|
||||||
|
cliServer: CodeQLCliServer;
|
||||||
|
queryRunner: QueryRunner;
|
||||||
|
logger: NotificationLogger;
|
||||||
|
queryStorageDir: string;
|
||||||
|
databaseItem: DatabaseItem;
|
||||||
|
language: QueryLanguage;
|
||||||
|
progress: ProgressCallback;
|
||||||
|
token: CancellationToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
// resolve (100) + query (1000) + interpret (100)
|
||||||
|
const maxStep = 1200;
|
||||||
|
|
||||||
|
export async function runGenerateModelQuery({
|
||||||
|
cliServer,
|
||||||
|
queryRunner,
|
||||||
|
logger,
|
||||||
|
queryStorageDir,
|
||||||
|
databaseItem,
|
||||||
|
language,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
}: GenerateModelOptions): Promise<ModeledMethod[]> {
|
||||||
|
progress({
|
||||||
|
message: "Resolving generate model query",
|
||||||
|
step: 100,
|
||||||
|
maxStep,
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryPath = await resolveGenerateModelQuery(
|
||||||
|
cliServer,
|
||||||
|
logger,
|
||||||
|
databaseItem,
|
||||||
|
);
|
||||||
|
if (queryPath === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the query
|
||||||
|
const completedQuery = await runQuery({
|
||||||
|
queryRunner,
|
||||||
|
databaseItem,
|
||||||
|
queryPath,
|
||||||
|
queryStorageDir,
|
||||||
|
additionalPacks: getOnDiskWorkspaceFolders(),
|
||||||
|
extensionPacks: undefined,
|
||||||
|
progress: ({ step, message }) =>
|
||||||
|
progress({
|
||||||
|
message: `Generating models: ${message}`,
|
||||||
|
step: 100 + step,
|
||||||
|
maxStep,
|
||||||
|
}),
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!completedQuery) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
progress({
|
||||||
|
message: "Decoding results",
|
||||||
|
step: 1100,
|
||||||
|
maxStep,
|
||||||
|
});
|
||||||
|
|
||||||
|
const decodedBqrs = await cliServer.bqrsDecodeAll(
|
||||||
|
completedQuery.outputDir.bqrsPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
|
||||||
|
|
||||||
|
const modeledMethods: ModeledMethod[] = [];
|
||||||
|
|
||||||
|
for (const resultSetName in decodedBqrs) {
|
||||||
|
const definition = Object.values(modelsAsDataLanguage.predicates).find(
|
||||||
|
(definition) => definition.extensiblePredicate === resultSetName,
|
||||||
|
);
|
||||||
|
if (definition === undefined) {
|
||||||
|
void logger.log(`No predicate found for ${resultSetName}`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultSet = decodedBqrs[resultSetName];
|
||||||
|
|
||||||
|
if (
|
||||||
|
resultSet.tuples.some((tuple) =>
|
||||||
|
tuple.some((value) => typeof value === "object"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
void logger.log(
|
||||||
|
`Skipping ${resultSetName} because it contains undefined values`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
modeledMethods.push(
|
||||||
|
...resultSet.tuples.map((tuple) => {
|
||||||
|
const row = tuple.filter(
|
||||||
|
(value): value is DataTuple => typeof value !== "object",
|
||||||
|
);
|
||||||
|
|
||||||
|
return definition.readModeledMethod(row);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modeledMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveGenerateModelQuery(
|
||||||
|
cliServer: CodeQLCliServer,
|
||||||
|
logger: NotificationLogger,
|
||||||
|
databaseItem: DatabaseItem,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const packsToSearch = [`codeql/${databaseItem.language}-queries`];
|
||||||
|
|
||||||
|
const queries = await resolveQueries(
|
||||||
|
cliServer,
|
||||||
|
packsToSearch,
|
||||||
|
"generate model",
|
||||||
|
{
|
||||||
|
"query path": "queries/modeling/GenerateModel.ql",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (queries.length !== 1) {
|
||||||
|
void showAndLogExceptionWithTelemetry(
|
||||||
|
logger,
|
||||||
|
telemetryListener,
|
||||||
|
redactableError`Expected exactly one generate model query, got ${queries.length}`,
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return queries[0];
|
||||||
|
}
|
||||||
@@ -51,6 +51,10 @@ import { ModelingStore } from "./modeling-store";
|
|||||||
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
|
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
|
||||||
import { ModelingEvents } from "./modeling-events";
|
import { ModelingEvents } from "./modeling-events";
|
||||||
import { getModelsAsDataLanguage, ModelsAsDataLanguage } from "./languages";
|
import { getModelsAsDataLanguage, ModelsAsDataLanguage } from "./languages";
|
||||||
|
import {
|
||||||
|
isGenerateModelSupported,
|
||||||
|
runGenerateModelQuery,
|
||||||
|
} from "./generate-model-queries";
|
||||||
|
|
||||||
export class ModelEditorView extends AbstractWebview<
|
export class ModelEditorView extends AbstractWebview<
|
||||||
ToModelEditorMessage,
|
ToModelEditorMessage,
|
||||||
@@ -266,7 +270,11 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case "generateMethod":
|
case "generateMethod":
|
||||||
await this.generateModeledMethods();
|
if (isFlowModelGenerationSupported(this.language)) {
|
||||||
|
await this.generateModeledMethodsFromFlow();
|
||||||
|
} else if (isGenerateModelSupported(this.language)) {
|
||||||
|
await this.generateModeledMethodsFromGenerateModel();
|
||||||
|
}
|
||||||
void telemetryListener?.sendUIInteraction(
|
void telemetryListener?.sendUIInteraction(
|
||||||
"model-editor-generate-modeled-methods",
|
"model-editor-generate-modeled-methods",
|
||||||
);
|
);
|
||||||
@@ -369,9 +377,10 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async setViewState(): Promise<void> {
|
private async setViewState(): Promise<void> {
|
||||||
const showFlowGeneration =
|
const showGenerateButton =
|
||||||
this.modelConfig.flowGeneration &&
|
this.modelConfig.flowGeneration &&
|
||||||
isFlowModelGenerationSupported(this.language);
|
(isFlowModelGenerationSupported(this.language) ||
|
||||||
|
isGenerateModelSupported(this.language));
|
||||||
|
|
||||||
const showLlmButton =
|
const showLlmButton =
|
||||||
this.databaseItem.language === "java" && this.modelConfig.llmGeneration;
|
this.databaseItem.language === "java" && this.modelConfig.llmGeneration;
|
||||||
@@ -388,7 +397,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
viewState: {
|
viewState: {
|
||||||
extensionPack: this.extensionPack,
|
extensionPack: this.extensionPack,
|
||||||
language: this.language,
|
language: this.language,
|
||||||
showFlowGeneration,
|
showGenerateButton,
|
||||||
showLlmButton,
|
showLlmButton,
|
||||||
showMultipleModels: this.modelConfig.showMultipleModels,
|
showMultipleModels: this.modelConfig.showMultipleModels,
|
||||||
mode: this.modelingStore.getMode(this.databaseItem),
|
mode: this.modelingStore.getMode(this.databaseItem),
|
||||||
@@ -465,7 +474,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async generateModeledMethods(): Promise<void> {
|
protected async generateModeledMethodsFromFlow(): Promise<void> {
|
||||||
await withProgress(
|
await withProgress(
|
||||||
async (progress) => {
|
async (progress) => {
|
||||||
const tokenSource = new CancellationTokenSource();
|
const tokenSource = new CancellationTokenSource();
|
||||||
@@ -508,19 +517,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
databaseItem: addedDatabase ?? this.databaseItem,
|
databaseItem: addedDatabase ?? this.databaseItem,
|
||||||
language: this.language,
|
language: this.language,
|
||||||
onResults: async (modeledMethods) => {
|
onResults: async (modeledMethods) => {
|
||||||
const modeledMethodsByName: Record<string, ModeledMethod[]> = {};
|
this.addModeledMethodsFromArray(modeledMethods);
|
||||||
|
|
||||||
for (const modeledMethod of modeledMethods) {
|
|
||||||
if (!(modeledMethod.signature in modeledMethodsByName)) {
|
|
||||||
modeledMethodsByName[modeledMethod.signature] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
modeledMethodsByName[modeledMethod.signature].push(
|
|
||||||
modeledMethod,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addModeledMethods(modeledMethodsByName);
|
|
||||||
},
|
},
|
||||||
progress,
|
progress,
|
||||||
token: tokenSource.token,
|
token: tokenSource.token,
|
||||||
@@ -539,6 +536,38 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async generateModeledMethodsFromGenerateModel(): Promise<void> {
|
||||||
|
await withProgress(
|
||||||
|
async (progress) => {
|
||||||
|
const tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const modeledMethods = await runGenerateModelQuery({
|
||||||
|
cliServer: this.cliServer,
|
||||||
|
queryRunner: this.queryRunner,
|
||||||
|
logger: this.app.logger,
|
||||||
|
queryStorageDir: this.queryStorageDir,
|
||||||
|
databaseItem: this.databaseItem,
|
||||||
|
language: this.language,
|
||||||
|
progress,
|
||||||
|
token: tokenSource.token,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addModeledMethodsFromArray(modeledMethods);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
void showAndLogExceptionWithTelemetry(
|
||||||
|
this.app.logger,
|
||||||
|
this.app.telemetry,
|
||||||
|
redactableError(
|
||||||
|
asError(e),
|
||||||
|
)`Failed to generate models: ${getErrorMessage(e)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ cancellable: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async generateModeledMethodsFromLlm(
|
private async generateModeledMethodsFromLlm(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
methodSignatures: string[],
|
methodSignatures: string[],
|
||||||
@@ -757,6 +786,20 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addModeledMethodsFromArray(modeledMethods: ModeledMethod[]) {
|
||||||
|
const modeledMethodsByName: Record<string, ModeledMethod[]> = {};
|
||||||
|
|
||||||
|
for (const modeledMethod of modeledMethods) {
|
||||||
|
if (!(modeledMethod.signature in modeledMethodsByName)) {
|
||||||
|
modeledMethodsByName[modeledMethod.signature] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
modeledMethodsByName[modeledMethod.signature].push(modeledMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addModeledMethods(modeledMethodsByName);
|
||||||
|
}
|
||||||
|
|
||||||
private setModeledMethods(signature: string, methods: ModeledMethod[]) {
|
private setModeledMethods(signature: string, methods: ModeledMethod[]) {
|
||||||
this.modelingStore.updateModeledMethods(
|
this.modelingStore.updateModeledMethods(
|
||||||
this.databaseItem,
|
this.databaseItem,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { QueryLanguage } from "../../common/query-language";
|
|||||||
export interface ModelEditorViewState {
|
export interface ModelEditorViewState {
|
||||||
extensionPack: ExtensionPack;
|
extensionPack: ExtensionPack;
|
||||||
language: QueryLanguage;
|
language: QueryLanguage;
|
||||||
showFlowGeneration: boolean;
|
showGenerateButton: boolean;
|
||||||
showLlmButton: boolean;
|
showLlmButton: boolean;
|
||||||
showMultipleModels: boolean;
|
showMultipleModels: boolean;
|
||||||
mode: Mode;
|
mode: Mode;
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ LibraryRow.args = {
|
|||||||
modifiedSignatures: new Set(["org.sql2o.Sql2o#Sql2o(String)"]),
|
modifiedSignatures: new Set(["org.sql2o.Sql2o#Sql2o(String)"]),
|
||||||
inProgressMethods: new Set(),
|
inProgressMethods: new Set(),
|
||||||
viewState: createMockModelEditorViewState({
|
viewState: createMockModelEditorViewState({
|
||||||
showFlowGeneration: true,
|
showGenerateButton: true,
|
||||||
showLlmButton: true,
|
showLlmButton: true,
|
||||||
showMultipleModels: true,
|
showMultipleModels: true,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const modeledMethod: ModeledMethod = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const viewState = createMockModelEditorViewState({
|
const viewState = createMockModelEditorViewState({
|
||||||
showFlowGeneration: true,
|
showGenerateButton: true,
|
||||||
showLlmButton: true,
|
showLlmButton: true,
|
||||||
showMultipleModels: true,
|
showMultipleModels: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ ModelEditor.args = {
|
|||||||
extensionTargets: {},
|
extensionTargets: {},
|
||||||
dataExtensions: [],
|
dataExtensions: [],
|
||||||
},
|
},
|
||||||
showFlowGeneration: true,
|
showGenerateButton: true,
|
||||||
showLlmButton: true,
|
showLlmButton: true,
|
||||||
showMultipleModels: true,
|
showMultipleModels: true,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export const LibraryRow = ({
|
|||||||
Stop
|
Stop
|
||||||
</VSCodeButton>
|
</VSCodeButton>
|
||||||
)}
|
)}
|
||||||
{viewState.showFlowGeneration &&
|
{viewState.showGenerateButton &&
|
||||||
viewState.mode === Mode.Application && (
|
viewState.mode === Mode.Application && (
|
||||||
<VSCodeButton appearance="icon" onClick={handleModelFromSource}>
|
<VSCodeButton appearance="icon" onClick={handleModelFromSource}>
|
||||||
<Codicon name="code" label="Model from source" />
|
<Codicon name="code" label="Model from source" />
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ export function ModelEditor({
|
|||||||
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
|
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
|
||||||
Refresh
|
Refresh
|
||||||
</VSCodeButton>
|
</VSCodeButton>
|
||||||
{viewState.showFlowGeneration &&
|
{viewState.showGenerateButton &&
|
||||||
viewState.mode === Mode.Framework && (
|
viewState.mode === Mode.Framework && (
|
||||||
<VSCodeButton onClick={onGenerateFromSourceClick}>
|
<VSCodeButton onClick={onGenerateFromSourceClick}>
|
||||||
Generate
|
Generate
|
||||||
|
|||||||
@@ -57,11 +57,11 @@ describe(LibraryRow.name, () => {
|
|||||||
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders the row when flow generation is enabled", () => {
|
it("renders the row when generate button is enabled", () => {
|
||||||
render({
|
render({
|
||||||
viewState: {
|
viewState: {
|
||||||
...viewState,
|
...viewState,
|
||||||
showFlowGeneration: true,
|
showGenerateButton: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,11 +83,11 @@ describe(LibraryRow.name, () => {
|
|||||||
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders the row when flow generation and LLM are enabled", () => {
|
it("renders the row when generate button and LLM are enabled", () => {
|
||||||
render({
|
render({
|
||||||
viewState: {
|
viewState: {
|
||||||
...viewState,
|
...viewState,
|
||||||
showFlowGeneration: true,
|
showGenerateButton: true,
|
||||||
showLlmButton: true,
|
showLlmButton: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function createMockModelEditorViewState(
|
|||||||
return {
|
return {
|
||||||
language: QueryLanguage.Java,
|
language: QueryLanguage.Java,
|
||||||
mode: Mode.Application,
|
mode: Mode.Application,
|
||||||
showFlowGeneration: false,
|
showGenerateButton: false,
|
||||||
showLlmButton: false,
|
showLlmButton: false,
|
||||||
showMultipleModels: false,
|
showMultipleModels: false,
|
||||||
showModeSwitchButton: true,
|
showModeSwitchButton: true,
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||||
|
import {
|
||||||
|
DatabaseItem,
|
||||||
|
DatabaseKind,
|
||||||
|
} from "../../../../src/databases/local-databases";
|
||||||
|
import { file } from "tmp-promise";
|
||||||
|
import { QueryResultType } from "../../../../src/query-server/new-messages";
|
||||||
|
import { Mode } from "../../../../src/model-editor/shared/mode";
|
||||||
|
import { mockedObject, mockedUri } from "../../utils/mocking.helpers";
|
||||||
|
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
||||||
|
import { QueryRunner } from "../../../../src/query-server";
|
||||||
|
import { join } from "path";
|
||||||
|
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||||
|
import { QueryOutputDir } from "../../../../src/run-queries-shared";
|
||||||
|
import { runGenerateModelQuery } from "../../../../src/model-editor/generate-model-queries";
|
||||||
|
import { QueryLanguage } from "../../../../src/common/query-language";
|
||||||
|
|
||||||
|
describe("runGenerateModelQuery", () => {
|
||||||
|
it("should run the query and return the results", async () => {
|
||||||
|
const queryStorageDir = (await file()).path;
|
||||||
|
const outputDir = new QueryOutputDir(join(queryStorageDir, "1"));
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
mode: Mode.Application,
|
||||||
|
cliServer: mockedObject<CodeQLCliServer>({
|
||||||
|
resolveQueriesInSuite: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(["/a/b/c/GenerateModel.ql"]),
|
||||||
|
bqrsDecodeAll: jest.fn().mockResolvedValue({
|
||||||
|
sourceModel: {
|
||||||
|
columns: [
|
||||||
|
{ name: "type", kind: "String" },
|
||||||
|
{ name: "path", kind: "String" },
|
||||||
|
{ name: "kind", kind: "String" },
|
||||||
|
],
|
||||||
|
tuples: [],
|
||||||
|
},
|
||||||
|
sinkModel: {
|
||||||
|
columns: [
|
||||||
|
{ name: "type", kind: "String" },
|
||||||
|
{ name: "path", kind: "String" },
|
||||||
|
{ name: "kind", kind: "String" },
|
||||||
|
],
|
||||||
|
tuples: [],
|
||||||
|
},
|
||||||
|
typeVariableModel: {
|
||||||
|
columns: [
|
||||||
|
{ name: "name", kind: "String" },
|
||||||
|
{ name: "path", kind: "String" },
|
||||||
|
],
|
||||||
|
tuples: [],
|
||||||
|
},
|
||||||
|
typeModel: {
|
||||||
|
columns: [
|
||||||
|
{ name: "type1", kind: "String" },
|
||||||
|
{ name: "type2", kind: "String" },
|
||||||
|
{ name: "path", kind: "String" },
|
||||||
|
],
|
||||||
|
tuples: [
|
||||||
|
["Array", "SQLite3::ResultSet", "Method[types].ReturnValue"],
|
||||||
|
["Array", "SQLite3::ResultSet", "Method[columns].ReturnValue"],
|
||||||
|
["Array", "SQLite3::Statement", "Method[types].ReturnValue"],
|
||||||
|
["Array", "SQLite3::Statement", "Method[columns].ReturnValue"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
summaryModel: {
|
||||||
|
columns: [
|
||||||
|
{ name: "type", kind: "String" },
|
||||||
|
{ name: "path", kind: "String" },
|
||||||
|
{ name: "input", kind: "String" },
|
||||||
|
{ name: "output", kind: "String" },
|
||||||
|
{ name: "kind", kind: "String" },
|
||||||
|
],
|
||||||
|
tuples: [
|
||||||
|
[
|
||||||
|
"SQLite3::Database",
|
||||||
|
"Method[create_function]",
|
||||||
|
"Argument[self]",
|
||||||
|
"ReturnValue",
|
||||||
|
"value",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SQLite3::Value!",
|
||||||
|
"Method[new]",
|
||||||
|
"Argument[1]",
|
||||||
|
"ReturnValue",
|
||||||
|
"value",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
queryRunner: mockedObject<QueryRunner>({
|
||||||
|
createQueryRun: jest.fn().mockReturnValue({
|
||||||
|
evaluate: jest.fn().mockResolvedValue({
|
||||||
|
resultType: QueryResultType.SUCCESS,
|
||||||
|
outputDir,
|
||||||
|
}),
|
||||||
|
outputDir,
|
||||||
|
}),
|
||||||
|
logger: createMockLogger(),
|
||||||
|
}),
|
||||||
|
logger: createMockLogger(),
|
||||||
|
databaseItem: mockedObject<DatabaseItem>({
|
||||||
|
databaseUri: mockedUri("/a/b/c/src.zip"),
|
||||||
|
contents: {
|
||||||
|
kind: DatabaseKind.Database,
|
||||||
|
name: "foo",
|
||||||
|
datasetUri: mockedUri(),
|
||||||
|
},
|
||||||
|
language: "ruby",
|
||||||
|
getSourceLocationPrefix: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue("/home/runner/work/my-repo/my-repo"),
|
||||||
|
sourceArchive: mockedUri("/a/b/c/src.zip"),
|
||||||
|
}),
|
||||||
|
language: QueryLanguage.Ruby,
|
||||||
|
queryStorageDir: "/tmp/queries",
|
||||||
|
progress: jest.fn(),
|
||||||
|
token: new CancellationTokenSource().token,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runGenerateModelQuery(options);
|
||||||
|
expect(result.sort()).toEqual(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
input: "Argument[self]",
|
||||||
|
kind: "value",
|
||||||
|
methodName: "create_function",
|
||||||
|
methodParameters: "",
|
||||||
|
output: "ReturnValue",
|
||||||
|
packageName: "",
|
||||||
|
provenance: "manual",
|
||||||
|
signature: "SQLite3::Database#create_function",
|
||||||
|
type: "summary",
|
||||||
|
typeName: "SQLite3::Database",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "Argument[1]",
|
||||||
|
kind: "value",
|
||||||
|
methodName: "new",
|
||||||
|
methodParameters: "",
|
||||||
|
output: "ReturnValue",
|
||||||
|
packageName: "",
|
||||||
|
provenance: "manual",
|
||||||
|
signature: "SQLite3::Value!#new",
|
||||||
|
type: "summary",
|
||||||
|
typeName: "SQLite3::Value!",
|
||||||
|
},
|
||||||
|
].sort(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(options.queryRunner.createQueryRun).toHaveBeenCalledTimes(1);
|
||||||
|
expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith(
|
||||||
|
"/a/b/c/src.zip",
|
||||||
|
{
|
||||||
|
queryPath: "/a/b/c/GenerateModel.ql",
|
||||||
|
quickEvalPosition: undefined,
|
||||||
|
quickEvalCountOnly: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
"/tmp/queries",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user