Merge remote-tracking branch 'origin/main' into koesie10/hide-type-models-non-canary

This commit is contained in:
Koen Vlaswinkel
2024-02-26 11:07:54 +01:00
12 changed files with 269 additions and 441 deletions

View File

@@ -977,6 +977,7 @@ async function activateWithInstalledDistribution(
const modelEditorModule = await ModelEditorModule.initialize( const modelEditorModule = await ModelEditorModule.initialize(
app, app,
dbm, dbm,
variantAnalysisManager,
cliServer, cliServer,
qs, qs,
tmpDir.name, tmpDir.name,

View File

@@ -31,6 +31,7 @@ import { getModelsAsDataLanguage } from "./languages";
import { INITIAL_MODE } from "./shared/mode"; import { INITIAL_MODE } from "./shared/mode";
import { isSupportedLanguage } from "./supported-languages"; import { isSupportedLanguage } from "./supported-languages";
import { DefaultNotifier, checkConsistency } from "./consistency-check"; import { DefaultNotifier, checkConsistency } from "./consistency-check";
import type { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager";
export class ModelEditorModule extends DisposableObject { export class ModelEditorModule extends DisposableObject {
private readonly queryStorageDir: string; private readonly queryStorageDir: string;
@@ -43,6 +44,7 @@ export class ModelEditorModule extends DisposableObject {
private constructor( private constructor(
private readonly app: App, private readonly app: App,
private readonly databaseManager: DatabaseManager, private readonly databaseManager: DatabaseManager,
private readonly variantAnalysisManager: VariantAnalysisManager,
private readonly cliServer: CodeQLCliServer, private readonly cliServer: CodeQLCliServer,
private readonly queryRunner: QueryRunner, private readonly queryRunner: QueryRunner,
baseQueryStorageDir: string, baseQueryStorageDir: string,
@@ -65,6 +67,7 @@ export class ModelEditorModule extends DisposableObject {
public static async initialize( public static async initialize(
app: App, app: App,
databaseManager: DatabaseManager, databaseManager: DatabaseManager,
variantAnalysisManager: VariantAnalysisManager,
cliServer: CodeQLCliServer, cliServer: CodeQLCliServer,
queryRunner: QueryRunner, queryRunner: QueryRunner,
queryStorageDir: string, queryStorageDir: string,
@@ -72,6 +75,7 @@ export class ModelEditorModule extends DisposableObject {
const modelEditorModule = new ModelEditorModule( const modelEditorModule = new ModelEditorModule(
app, app,
databaseManager, databaseManager,
variantAnalysisManager,
cliServer, cliServer,
queryRunner, queryRunner,
queryStorageDir, queryStorageDir,
@@ -240,6 +244,7 @@ export class ModelEditorModule extends DisposableObject {
this.modelingEvents, this.modelingEvents,
this.modelConfig, this.modelConfig,
this.databaseManager, this.databaseManager,
this.variantAnalysisManager,
this.cliServer, this.cliServer,
this.queryRunner, this.queryRunner,
this.queryStorageDir, this.queryStorageDir,

View File

@@ -60,6 +60,7 @@ import { runSuggestionsQuery } from "./suggestion-queries";
import { parseAccessPathSuggestionRowsToOptions } from "./suggestions-bqrs"; import { parseAccessPathSuggestionRowsToOptions } from "./suggestions-bqrs";
import { ModelEvaluator } from "./model-evaluator"; import { ModelEvaluator } from "./model-evaluator";
import type { ModelEvaluationRunState } from "./shared/model-evaluation-run-state"; import type { ModelEvaluationRunState } from "./shared/model-evaluation-run-state";
import type { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager";
export class ModelEditorView extends AbstractWebview< export class ModelEditorView extends AbstractWebview<
ToModelEditorMessage, ToModelEditorMessage,
@@ -78,6 +79,7 @@ export class ModelEditorView extends AbstractWebview<
private readonly modelingEvents: ModelingEvents, private readonly modelingEvents: ModelingEvents,
private readonly modelConfig: ModelConfigListener, private readonly modelConfig: ModelConfigListener,
private readonly databaseManager: DatabaseManager, private readonly databaseManager: DatabaseManager,
private readonly variantAnalysisManager: VariantAnalysisManager,
private readonly cliServer: CodeQLCliServer, private readonly cliServer: CodeQLCliServer,
private readonly queryRunner: QueryRunner, private readonly queryRunner: QueryRunner,
private readonly queryStorageDir: string, private readonly queryStorageDir: string,
@@ -116,9 +118,13 @@ export class ModelEditorView extends AbstractWebview<
this.languageDefinition = getModelsAsDataLanguage(language); this.languageDefinition = getModelsAsDataLanguage(language);
this.modelEvaluator = new ModelEvaluator( this.modelEvaluator = new ModelEvaluator(
this.app.logger,
this.cliServer,
modelingStore, modelingStore,
modelingEvents, modelingEvents,
this.variantAnalysisManager,
databaseItem, databaseItem,
language,
this.updateModelEvaluationRun.bind(this), this.updateModelEvaluationRun.bind(this),
); );
this.push(this.modelEvaluator); this.push(this.modelEvaluator);
@@ -803,6 +809,7 @@ export class ModelEditorView extends AbstractWebview<
this.modelingEvents, this.modelingEvents,
this.modelConfig, this.modelConfig,
this.databaseManager, this.databaseManager,
this.variantAnalysisManager,
this.cliServer, this.cliServer,
this.queryRunner, this.queryRunner,
this.queryStorageDir, this.queryStorageDir,

View File

@@ -3,14 +3,24 @@ import type { ModelingEvents } from "./modeling-events";
import type { DatabaseItem } from "../databases/local-databases"; import type { DatabaseItem } from "../databases/local-databases";
import type { ModelEvaluationRun } from "./model-evaluation-run"; import type { ModelEvaluationRun } from "./model-evaluation-run";
import { DisposableObject } from "../common/disposable-object"; import { DisposableObject } from "../common/disposable-object";
import { sleep } from "../common/time";
import type { ModelEvaluationRunState } from "./shared/model-evaluation-run-state"; import type { ModelEvaluationRunState } from "./shared/model-evaluation-run-state";
import type { BaseLogger } from "../common/logging";
import type { CodeQLCliServer } from "../codeql-cli/cli";
import type { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager";
import type { QueryLanguage } from "../common/query-language";
import { resolveCodeScanningQueryPack } from "../variant-analysis/code-scanning-pack";
import { withProgress } from "../common/vscode/progress";
import type { VariantAnalysis } from "../variant-analysis/shared/variant-analysis";
export class ModelEvaluator extends DisposableObject { export class ModelEvaluator extends DisposableObject {
public constructor( public constructor(
private readonly logger: BaseLogger,
private readonly cliServer: CodeQLCliServer,
private readonly modelingStore: ModelingStore, private readonly modelingStore: ModelingStore,
private readonly modelingEvents: ModelingEvents, private readonly modelingEvents: ModelingEvents,
private readonly variantAnalysisManager: VariantAnalysisManager,
private readonly dbItem: DatabaseItem, private readonly dbItem: DatabaseItem,
private readonly language: QueryLanguage,
private readonly updateView: ( private readonly updateView: (
run: ModelEvaluationRunState, run: ModelEvaluationRunState,
) => Promise<void>, ) => Promise<void>,
@@ -28,18 +38,48 @@ export class ModelEvaluator extends DisposableObject {
}; };
this.modelingStore.updateModelEvaluationRun(this.dbItem, evaluationRun); this.modelingStore.updateModelEvaluationRun(this.dbItem, evaluationRun);
// For now, just wait 5 seconds and then update the store. // Build pack
// In the future, this will be replaced with the actual evaluation process. const qlPack = await resolveCodeScanningQueryPack(
void sleep(5000).then(() => { this.logger,
const completedEvaluationRun: ModelEvaluationRun = { this.cliServer,
isPreparing: false, this.language,
variantAnalysisId: undefined, );
};
this.modelingStore.updateModelEvaluationRun( if (!qlPack) {
this.dbItem, this.modelingStore.updateModelEvaluationRun(this.dbItem, undefined);
completedEvaluationRun, throw new Error("Unable to trigger evaluation run");
); }
});
// Submit variant analysis and monitor progress
return withProgress(
async (progress, token) => {
let variantAnalysisId: number | undefined = undefined;
try {
variantAnalysisId =
await this.variantAnalysisManager.runVariantAnalysis(
qlPack,
progress,
token,
);
} catch (e) {
this.modelingStore.updateModelEvaluationRun(this.dbItem, undefined);
throw e;
}
if (variantAnalysisId) {
this.monitorVariantAnalysis(variantAnalysisId);
} else {
this.modelingStore.updateModelEvaluationRun(this.dbItem, undefined);
throw new Error(
"Unable to trigger variant analysis for evaluation run",
);
}
},
{
title: "Run Variant Analysis",
cancellable: true,
},
);
} }
public async stopEvaluation() { public async stopEvaluation() {
@@ -55,19 +95,51 @@ export class ModelEvaluator extends DisposableObject {
private registerToModelingEvents() { private registerToModelingEvents() {
this.push( this.push(
this.modelingEvents.onModelEvaluationRunChanged(async (event) => { this.modelingEvents.onModelEvaluationRunChanged(async (event) => {
if ( if (event.dbUri === this.dbItem.databaseUri.toString()) {
event.evaluationRun && if (!event.evaluationRun) {
event.dbUri === this.dbItem.databaseUri.toString() await this.updateView({
) { isPreparing: false,
const run: ModelEvaluationRunState = { variantAnalysis: undefined,
isPreparing: event.evaluationRun.isPreparing, });
} else {
// TODO: Get variant analysis from id. const variantAnalysis = await this.getVariantAnalysisForRun(
variantAnalysis: undefined, event.evaluationRun,
}; );
await this.updateView(run); const run: ModelEvaluationRunState = {
isPreparing: event.evaluationRun.isPreparing,
variantAnalysis,
};
await this.updateView(run);
}
} }
}), }),
); );
} }
private async getVariantAnalysisForRun(
evaluationRun: ModelEvaluationRun,
): Promise<VariantAnalysis | undefined> {
if (evaluationRun.variantAnalysisId) {
return await this.variantAnalysisManager.getVariantAnalysis(
evaluationRun.variantAnalysisId,
);
}
return undefined;
}
private monitorVariantAnalysis(variantAnalysisId: number) {
this.push(
this.variantAnalysisManager.onVariantAnalysisStatusUpdated(
async (variantAnalysis) => {
// Make sure it's the variant analysis we're interested in
if (variantAnalysisId === variantAnalysis.id) {
await this.updateView({
isPreparing: false,
variantAnalysis,
});
}
},
),
);
}
} }

View File

@@ -8,36 +8,39 @@
"extensions": { "extensions": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "$ref": "#/definitions/ModelExtension"
"properties": {
"addsTo": {
"type": "object",
"properties": {
"pack": {
"type": "string"
},
"extensible": {
"type": "string"
}
},
"required": ["pack", "extensible"]
},
"data": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/DataTuple"
}
}
}
},
"required": ["addsTo", "data"]
} }
} }
}, },
"required": ["extensions"] "required": ["extensions"]
}, },
"ModelExtension": {
"type": "object",
"properties": {
"addsTo": {
"type": "object",
"properties": {
"pack": {
"type": "string"
},
"extensible": {
"type": "string"
}
},
"required": ["pack", "extensible"]
},
"data": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/DataTuple"
}
}
}
},
"required": ["addsTo", "data"]
},
"DataTuple": { "DataTuple": {
"type": ["boolean", "number", "string"] "type": ["boolean", "number", "string"]
} }

View File

@@ -7,7 +7,7 @@ export type DataTuple = boolean | number | string;
type DataRow = DataTuple[]; type DataRow = DataTuple[];
type ModelExtension = { export type ModelExtension = {
addsTo: ExtensibleReference; addsTo: ExtensibleReference;
data: DataRow[]; data: DataRow[];
}; };

View File

@@ -378,7 +378,7 @@ export class ModelingStore extends DisposableObject {
public updateModelEvaluationRun( public updateModelEvaluationRun(
dbItem: DatabaseItem, dbItem: DatabaseItem,
evaluationRun: ModelEvaluationRun, evaluationRun: ModelEvaluationRun | undefined,
) { ) {
this.changeModelEvaluationRun(dbItem, (state) => { this.changeModelEvaluationRun(dbItem, (state) => {
state.modelEvaluationRun = evaluationRun; state.modelEvaluationRun = evaluationRun;

View File

@@ -16,7 +16,10 @@ import type {
import { getModelsAsDataLanguage } from "./languages"; import { getModelsAsDataLanguage } from "./languages";
import { Mode } from "./shared/mode"; import { Mode } from "./shared/mode";
import { assertNever } from "../common/helpers-pure"; import { assertNever } from "../common/helpers-pure";
import type { ModelExtensionFile } from "./model-extension-file"; import type {
ModelExtension,
ModelExtensionFile,
} from "./model-extension-file";
import type { QueryLanguage } from "../common/query-language"; import type { QueryLanguage } from "../common/query-language";
import modelExtensionFileSchema from "./model-extension-file.schema.json"; import modelExtensionFileSchema from "./model-extension-file.schema.json";
@@ -24,38 +27,22 @@ import modelExtensionFileSchema from "./model-extension-file.schema.json";
const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }); const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
const modelExtensionFileSchemaValidate = ajv.compile(modelExtensionFileSchema); const modelExtensionFileSchemaValidate = ajv.compile(modelExtensionFileSchema);
function createDataProperty<T>(
methods: readonly T[],
definition: ModelsAsDataLanguagePredicate<T>,
) {
if (methods.length === 0) {
return " []";
}
return `\n${methods
.map(
(method) =>
` - ${JSON.stringify(
definition.generateMethodDefinition(method),
)}`,
)
.join("\n")}`;
}
function createExtensions<T>( function createExtensions<T>(
language: QueryLanguage, language: QueryLanguage,
methods: readonly T[], methods: readonly T[],
definition: ModelsAsDataLanguagePredicate<T> | undefined, definition: ModelsAsDataLanguagePredicate<T> | undefined,
) { ): ModelExtension | undefined {
if (!definition) { if (!definition) {
return ""; return undefined;
} }
return ` - addsTo: return {
pack: codeql/${language}-all addsTo: {
extensible: ${definition.extensiblePredicate} pack: `codeql/${language}-all`,
data:${createDataProperty(methods, definition)} extensible: definition.extensiblePredicate,
`; },
data: methods.map((method) => definition.generateMethodDefinition(method)),
};
} }
export function createDataExtensionYaml( export function createDataExtensionYaml(
@@ -99,7 +86,7 @@ export function createDataExtensionYaml(
} }
const extensions = Object.keys(methodsByType) const extensions = Object.keys(methodsByType)
.map((typeKey) => { .map((typeKey): ModelExtension | undefined => {
const type = typeKey as keyof ModelsAsDataLanguagePredicates; const type = typeKey as keyof ModelsAsDataLanguagePredicates;
switch (type) { switch (type) {
@@ -137,10 +124,11 @@ export function createDataExtensionYaml(
assertNever(type); assertNever(type);
} }
}) })
.filter((extensions) => extensions !== ""); .filter(
(extension): extension is ModelExtension => extension !== undefined,
);
return `extensions: return modelExtensionFileToYaml({ extensions });
${extensions.join("\n")}`;
} }
export function createDataExtensionYamls( export function createDataExtensionYamls(
@@ -341,6 +329,36 @@ function validateModelExtensionFile(data: unknown): data is ModelExtensionFile {
return true; return true;
} }
/**
* Creates a string for the data extension YAML file from the
* structure of the data extension file. This should be used
* instead of creating a JSON string directly or dumping the
* YAML directly to ensure that the file is formatted correctly.
*
* @param data The data extension file
*/
function modelExtensionFileToYaml(data: ModelExtensionFile) {
const extensions = data.extensions
.map((extension) => {
const data =
extension.data.length === 0
? " []"
: `\n${extension.data
.map((row) => ` - ${JSON.stringify(row)}`)
.join("\n")}`;
return ` - addsTo:
pack: ${extension.addsTo.pack}
extensible: ${extension.addsTo.extensible}
data:${data}
`;
})
.filter((extensions) => extensions !== "");
return `extensions:
${extensions.join("\n")}`;
}
export function loadDataExtensionYaml( export function loadDataExtensionYaml(
data: unknown, data: unknown,
language: QueryLanguage, language: QueryLanguage,

View File

@@ -298,7 +298,7 @@ export class VariantAnalysisManager
qlPackDetails: QlPackDetails, qlPackDetails: QlPackDetails,
progress: ProgressCallback, progress: ProgressCallback,
token: CancellationToken, token: CancellationToken,
): Promise<void> { ): Promise<number | undefined> {
await saveBeforeStart(); await saveBeforeStart();
progress({ progress({
@@ -379,7 +379,7 @@ export class VariantAnalysisManager
} catch (e: unknown) { } catch (e: unknown) {
// If the error is handled by the handleRequestError function, we don't need to throw // If the error is handled by the handleRequestError function, we don't need to throw
if (e instanceof RequestError && handleRequestError(e, this.app.logger)) { if (e instanceof RequestError && handleRequestError(e, this.app.logger)) {
return; return undefined;
} }
throw e; throw e;
@@ -405,6 +405,8 @@ export class VariantAnalysisManager
"codeQL.monitorNewVariantAnalysis", "codeQL.monitorNewVariantAnalysis",
processedVariantAnalysis, processedVariantAnalysis,
); );
return processedVariantAnalysis.id;
} }
public async rehydrateVariantAnalysis(variantAnalysis: VariantAnalysis) { public async rehydrateVariantAnalysis(variantAnalysis: VariantAnalysis) {

View File

@@ -10,33 +10,45 @@ import { MultipleModeledMethodsPanel } from "../MultipleModeledMethodsPanel";
import { userEvent } from "@testing-library/user-event"; import { userEvent } from "@testing-library/user-event";
import type { ModeledMethod } from "../../../model-editor/modeled-method"; import type { ModeledMethod } from "../../../model-editor/modeled-method";
import { QueryLanguage } from "../../../common/query-language"; import { QueryLanguage } from "../../../common/query-language";
import type { ModelingStatus } from "../../../model-editor/shared/modeling-status";
describe(MultipleModeledMethodsPanel.name, () => { describe(MultipleModeledMethodsPanel.name, () => {
const render = (props: MultipleModeledMethodsPanelProps) =>
reactRender(<MultipleModeledMethodsPanel {...props} />);
const language = QueryLanguage.Java; const language = QueryLanguage.Java;
const isCanary = false;
const method = createMethod(); const method = createMethod();
const isModelingInProgress = false; const isModelingInProgress = false;
const isProcessedByAutoModel = false; const isProcessedByAutoModel = false;
const modelingStatus = "unmodeled"; const modelingStatus: ModelingStatus = "unmodeled";
const onChange = jest.fn<void, [string, ModeledMethod[]]>(); const onChange = jest.fn<void, [string, ModeledMethod[]]>();
const isCanary = false;
const baseProps = {
language,
method,
modelingStatus,
isModelingInProgress,
isCanary,
isProcessedByAutoModel,
onChange,
};
const createRender =
(modeledMethods: ModeledMethod[]) =>
(props: Partial<MultipleModeledMethodsPanelProps> = {}) =>
reactRender(
<MultipleModeledMethodsPanel
{...baseProps}
modeledMethods={modeledMethods}
{...props}
/>,
);
describe("with no modeled methods", () => { describe("with no modeled methods", () => {
const modeledMethods: ModeledMethod[] = []; const modeledMethods: ModeledMethod[] = [];
const render = createRender(modeledMethods);
it("renders the method modeling inputs once", () => { it("renders the method modeling inputs once", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
expect(screen.getAllByRole("combobox")).toHaveLength(4); expect(screen.getAllByRole("combobox")).toHaveLength(4);
expect( expect(
@@ -47,16 +59,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("disables all pagination", () => { it("disables all pagination", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
expect( expect(
screen screen
@@ -71,16 +74,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("cannot add or delete modeling", () => { it("cannot add or delete modeling", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
expect( expect(
screen screen
@@ -103,17 +97,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
}), }),
]; ];
const render = createRender(modeledMethods);
it("renders the method modeling inputs once", () => { it("renders the method modeling inputs once", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
expect(screen.getAllByRole("combobox")).toHaveLength(4); expect(screen.getAllByRole("combobox")).toHaveLength(4);
expect( expect(
@@ -124,16 +111,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("disables all pagination", () => { it("disables all pagination", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
expect( expect(
screen screen
@@ -147,16 +125,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("cannot delete modeling", () => { it("cannot delete modeling", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
expect( expect(
screen screen
@@ -166,16 +135,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can add modeling", async () => { it("can add modeling", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
await userEvent.click(screen.getByLabelText("Add modeling")); await userEvent.click(screen.getByLabelText("Add modeling"));
@@ -194,31 +154,16 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("changes selection to the newly added modeling", async () => { it("changes selection to the newly added modeling", async () => {
const { rerender } = render({ const { rerender } = render();
language,
isCanary,
method,
modeledMethods,
modelingStatus,
isModelingInProgress,
isProcessedByAutoModel,
onChange,
});
await userEvent.click(screen.getByLabelText("Add modeling")); await userEvent.click(screen.getByLabelText("Add modeling"));
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={ modeledMethods={
onChange.mock.calls[onChange.mock.calls.length - 1][1] onChange.mock.calls[onChange.mock.calls.length - 1][1]
} }
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -236,17 +181,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
}), }),
]; ];
const render = createRender(modeledMethods);
it("renders the method modeling inputs once", () => { it("renders the method modeling inputs once", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect(screen.getAllByRole("combobox")).toHaveLength(4); expect(screen.getAllByRole("combobox")).toHaveLength(4);
expect( expect(
@@ -257,16 +195,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("renders the pagination", () => { it("renders the pagination", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect(screen.getByLabelText("Previous modeling")).toBeInTheDocument(); expect(screen.getByLabelText("Previous modeling")).toBeInTheDocument();
expect(screen.getByLabelText("Next modeling")).toBeInTheDocument(); expect(screen.getByLabelText("Next modeling")).toBeInTheDocument();
@@ -274,16 +203,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("disables the correct pagination", async () => { it("disables the correct pagination", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect( expect(
screen screen
@@ -296,16 +216,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can use the pagination", async () => { it("can use the pagination", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Next modeling")); await userEvent.click(screen.getByLabelText("Next modeling"));
@@ -335,29 +246,14 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("correctly updates selected pagination index when the number of models decreases", async () => { it("correctly updates selected pagination index when the number of models decreases", async () => {
const { rerender } = render({ const { rerender } = render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Next modeling")); await userEvent.click(screen.getByLabelText("Next modeling"));
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={[modeledMethods[1]]} modeledMethods={[modeledMethods[1]]}
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -370,31 +266,13 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("does not show errors", () => { it("does not show errors", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect(screen.queryByRole("alert")).not.toBeInTheDocument(); expect(screen.queryByRole("alert")).not.toBeInTheDocument();
}); });
it("can update the first modeling", async () => { it("can update the first modeling", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
const modelTypeDropdown = screen.getByRole("combobox", { const modelTypeDropdown = screen.getByRole("combobox", {
name: "Model type", name: "Model type",
@@ -420,16 +298,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can update the second modeling", async () => { it("can update the second modeling", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Next modeling")); await userEvent.click(screen.getByLabelText("Next modeling"));
@@ -457,16 +326,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can delete modeling", async () => { it("can delete modeling", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Delete modeling")); await userEvent.click(screen.getByLabelText("Delete modeling"));
@@ -477,16 +337,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can add modeling", async () => { it("can add modeling", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Add modeling")); await userEvent.click(screen.getByLabelText("Add modeling"));
@@ -505,31 +356,16 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("shows an error when adding a neutral modeling", async () => { it("shows an error when adding a neutral modeling", async () => {
const { rerender } = render({ const { rerender } = render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Add modeling")); await userEvent.click(screen.getByLabelText("Add modeling"));
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={ modeledMethods={
onChange.mock.calls[onChange.mock.calls.length - 1][1] onChange.mock.calls[onChange.mock.calls.length - 1][1]
} }
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -543,16 +379,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={ modeledMethods={
onChange.mock.calls[onChange.mock.calls.length - 1][1] onChange.mock.calls[onChange.mock.calls.length - 1][1]
} }
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -564,16 +394,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={ modeledMethods={
onChange.mock.calls[onChange.mock.calls.length - 1][1] onChange.mock.calls[onChange.mock.calls.length - 1][1]
} }
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -584,16 +408,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("changes selection to the newly added modeling", async () => { it("changes selection to the newly added modeling", async () => {
const { rerender } = render({ const { rerender } = render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect(screen.getByText("1/2")).toBeInTheDocument(); expect(screen.getByText("1/2")).toBeInTheDocument();
@@ -601,16 +416,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={ modeledMethods={
onChange.mock.calls[onChange.mock.calls.length - 1][1] onChange.mock.calls[onChange.mock.calls.length - 1][1]
} }
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -637,17 +446,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
}), }),
]; ];
const render = createRender(modeledMethods);
it("can use the pagination", async () => { it("can use the pagination", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect( expect(
screen screen
@@ -731,29 +533,14 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("preserves selection when a modeling other than the selected modeling is removed", async () => { it("preserves selection when a modeling other than the selected modeling is removed", async () => {
const { rerender } = render({ const { rerender } = render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect(screen.getByText("1/3")).toBeInTheDocument(); expect(screen.getByText("1/3")).toBeInTheDocument();
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={modeledMethods.slice(0, 2)} modeledMethods={modeledMethods.slice(0, 2)}
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -761,16 +548,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("reduces selection when the selected modeling is removed", async () => { it("reduces selection when the selected modeling is removed", async () => {
const { rerender } = render({ const { rerender } = render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Next modeling")); await userEvent.click(screen.getByLabelText("Next modeling"));
await userEvent.click(screen.getByLabelText("Next modeling")); await userEvent.click(screen.getByLabelText("Next modeling"));
@@ -778,14 +556,8 @@ describe(MultipleModeledMethodsPanel.name, () => {
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={modeledMethods.slice(0, 2)} modeledMethods={modeledMethods.slice(0, 2)}
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -806,17 +578,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
}), }),
]; ];
const render = createRender(modeledMethods);
it("can add modeling", () => { it("can add modeling", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect( expect(
screen.getByLabelText("Add modeling").getElementsByTagName("input")[0], screen.getByLabelText("Add modeling").getElementsByTagName("input")[0],
@@ -824,16 +589,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can delete first modeling", async () => { it("can delete first modeling", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Delete modeling")); await userEvent.click(screen.getByLabelText("Delete modeling"));
@@ -844,16 +600,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can delete second modeling", async () => { it("can delete second modeling", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Next modeling")); await userEvent.click(screen.getByLabelText("Next modeling"));
await userEvent.click(screen.getByLabelText("Delete modeling")); await userEvent.click(screen.getByLabelText("Delete modeling"));
@@ -865,16 +612,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
}); });
it("can add modeling after deleting second modeling", async () => { it("can add modeling after deleting second modeling", async () => {
const { rerender } = render({ const { rerender } = render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
await userEvent.click(screen.getByLabelText("Next modeling")); await userEvent.click(screen.getByLabelText("Next modeling"));
await userEvent.click(screen.getByLabelText("Delete modeling")); await userEvent.click(screen.getByLabelText("Delete modeling"));
@@ -886,14 +624,8 @@ describe(MultipleModeledMethodsPanel.name, () => {
rerender( rerender(
<MultipleModeledMethodsPanel <MultipleModeledMethodsPanel
language={language} {...baseProps}
isCanary={isCanary}
method={method}
modeledMethods={modeledMethods.slice(0, 1)} modeledMethods={modeledMethods.slice(0, 1)}
isModelingInProgress={isModelingInProgress}
isProcessedByAutoModel={isProcessedByAutoModel}
modelingStatus={modelingStatus}
onChange={onChange}
/>, />,
); );
@@ -925,32 +657,16 @@ describe(MultipleModeledMethodsPanel.name, () => {
}), }),
]; ];
const render = createRender(modeledMethods);
it("shows errors", () => { it("shows errors", () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect(screen.getByRole("alert")).toBeInTheDocument(); expect(screen.getByRole("alert")).toBeInTheDocument();
}); });
it("shows the correct error message", async () => { it("shows the correct error message", async () => {
render({ render();
language,
isCanary,
method,
modeledMethods,
isModelingInProgress,
isProcessedByAutoModel,
modelingStatus,
onChange,
});
expect( expect(
screen.getByText("Error: Duplicated classification"), screen.getByText("Error: Duplicated classification"),

View File

@@ -1,4 +1,5 @@
[ [
"v2.16.3",
"v2.16.2", "v2.16.2",
"v2.15.5", "v2.15.5",
"v2.14.6", "v2.14.6",

View File

@@ -12,6 +12,7 @@ import { createMockModelingStore } from "../../../__mocks__/model-editor/modelin
import type { ModelConfigListener } from "../../../../src/config"; import type { ModelConfigListener } from "../../../../src/config";
import { createMockModelingEvents } from "../../../__mocks__/model-editor/modelingEventsMock"; import { createMockModelingEvents } from "../../../__mocks__/model-editor/modelingEventsMock";
import { QueryLanguage } from "../../../../src/common/query-language"; import { QueryLanguage } from "../../../../src/common/query-language";
import type { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
describe("ModelEditorView", () => { describe("ModelEditorView", () => {
const app = createMockApp({}); const app = createMockApp({});
@@ -21,6 +22,7 @@ describe("ModelEditorView", () => {
onDidChangeConfiguration: jest.fn(), onDidChangeConfiguration: jest.fn(),
}); });
const databaseManager = mockEmptyDatabaseManager(); const databaseManager = mockEmptyDatabaseManager();
const variantAnalysisManager = mockedObject<VariantAnalysisManager>({});
const cliServer = mockedObject<CodeQLCliServer>({}); const cliServer = mockedObject<CodeQLCliServer>({});
const queryRunner = mockedObject<QueryRunner>({}); const queryRunner = mockedObject<QueryRunner>({});
const queryStorageDir = "/a/b/c/d"; const queryStorageDir = "/a/b/c/d";
@@ -48,6 +50,7 @@ describe("ModelEditorView", () => {
modelingEvents, modelingEvents,
modelConfig, modelConfig,
databaseManager, databaseManager,
variantAnalysisManager,
cliServer, cliServer,
queryRunner, queryRunner,
queryStorageDir, queryStorageDir,