Merge remote-tracking branch 'origin/main' into koesie10/sort-usages-panel
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
- Fix a bug where the query server was restarted twice after configuration changes. [#2884](https://github.com/github/vscode-codeql/pull/2884).
|
||||
- Add support for the `telemetry.telemetryLevel` setting. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code). [#2824](https://github.com/github/vscode-codeql/pull/2824).
|
||||
- Fix syntax highlighting directly after import statements with instantiation arguments. [#2792](https://github.com/github/vscode-codeql/pull/2792)
|
||||
- The `debug.saveBeforeStart` setting is now respected when running variant analyses. [#2950](https://github.com/github/vscode-codeql/pull/2950)
|
||||
- The 'open database' button of the model editor was renamed to 'open source'. Also, it's now only available if the source archive is available as a workspace folder. [#2945](https://github.com/github/vscode-codeql/pull/2945)
|
||||
|
||||
## 1.9.1 - 29 September 2023
|
||||
|
||||
|
||||
@@ -507,7 +507,7 @@ interface SetMethodsMessage {
|
||||
|
||||
interface SetModeledMethodsMessage {
|
||||
t: "setModeledMethods";
|
||||
methods: Record<string, ModeledMethod>;
|
||||
methods: Record<string, ModeledMethod[]>;
|
||||
}
|
||||
|
||||
interface SetModifiedMethodsMessage {
|
||||
@@ -577,6 +577,12 @@ interface SetModeledMethodMessage {
|
||||
method: ModeledMethod;
|
||||
}
|
||||
|
||||
interface SetMultipleModeledMethodsMessage {
|
||||
t: "setMultipleModeledMethods";
|
||||
methodSignature: string;
|
||||
modeledMethods: ModeledMethod[];
|
||||
}
|
||||
|
||||
interface SetInModelingModeMessage {
|
||||
t: "setInModelingMode";
|
||||
inModelingMode: boolean;
|
||||
@@ -643,14 +649,14 @@ interface SetMethodModifiedMessage {
|
||||
interface SetSelectedMethodMessage {
|
||||
t: "setSelectedMethod";
|
||||
method: Method;
|
||||
modeledMethod?: ModeledMethod;
|
||||
modeledMethods: ModeledMethod[];
|
||||
isModified: boolean;
|
||||
}
|
||||
|
||||
export type ToMethodModelingMessage =
|
||||
| SetMethodModelingPanelViewStateMessage
|
||||
| SetMethodMessage
|
||||
| SetModeledMethodMessage
|
||||
| SetMultipleModeledMethodsMessage
|
||||
| SetMethodModifiedMessage
|
||||
| SetSelectedMethodMessage
|
||||
| SetInModelingModeMessage;
|
||||
|
||||
@@ -167,6 +167,15 @@ export class DatabaseItemImpl implements DatabaseItem {
|
||||
return encodeArchiveBasePath(sourceArchive.fsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the database's source archive is in the workspace.
|
||||
*/
|
||||
public hasSourceArchiveInExplorer(): boolean {
|
||||
return (vscode.workspace.workspaceFolders || []).some((folder) =>
|
||||
this.belongsToSourceArchiveExplorerUri(folder.uri),
|
||||
);
|
||||
}
|
||||
|
||||
public verifyZippedSources(): string | undefined {
|
||||
const sourceArchive = this.sourceArchive;
|
||||
if (sourceArchive === undefined) {
|
||||
|
||||
@@ -56,6 +56,11 @@ export interface DatabaseItem {
|
||||
*/
|
||||
getSourceArchiveExplorerUri(): vscode.Uri;
|
||||
|
||||
/**
|
||||
* Returns true if the database's source archive is in the workspace.
|
||||
*/
|
||||
hasSourceArchiveInExplorer(): boolean;
|
||||
|
||||
/**
|
||||
* Holds if `uri` belongs to this database's source archive.
|
||||
*/
|
||||
|
||||
@@ -14,10 +14,7 @@ import { assertNever } from "../../common/helpers-pure";
|
||||
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
|
||||
import { ModelConfigListener } from "../../config";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import {
|
||||
convertFromLegacyModeledMethod,
|
||||
convertToLegacyModeledMethod,
|
||||
} from "../shared/modeled-methods-legacy";
|
||||
import { convertFromLegacyModeledMethod } from "../shared/modeled-methods-legacy";
|
||||
|
||||
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
||||
ToMethodModelingMessage,
|
||||
@@ -77,9 +74,7 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
||||
await this.postMessage({
|
||||
t: "setSelectedMethod",
|
||||
method: selectedMethod.method,
|
||||
modeledMethod: convertToLegacyModeledMethod(
|
||||
selectedMethod.modeledMethods,
|
||||
),
|
||||
modeledMethods: selectedMethod.modeledMethods,
|
||||
isModified: selectedMethod.isModified,
|
||||
});
|
||||
}
|
||||
@@ -149,29 +144,23 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
||||
return;
|
||||
}
|
||||
|
||||
const views = this.editorViewTracker.getViews(
|
||||
const view = this.editorViewTracker.getView(
|
||||
this.databaseItem.databaseUri.toString(),
|
||||
);
|
||||
if (views.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(views.map((view) => view.revealMethod(method)));
|
||||
await view?.revealMethod(method);
|
||||
}
|
||||
|
||||
private registerToModelingStoreEvents(): void {
|
||||
this.push(
|
||||
this.modelingStore.onModeledMethodsChanged(async (e) => {
|
||||
if (this.webviewView && e.isActiveDb) {
|
||||
const modeledMethods = e.modeledMethods[this.method?.signature ?? ""];
|
||||
if (this.webviewView && e.isActiveDb && this.method) {
|
||||
const modeledMethods = e.modeledMethods[this.method.signature];
|
||||
if (modeledMethods) {
|
||||
const modeledMethod = convertToLegacyModeledMethod(modeledMethods);
|
||||
if (modeledMethod) {
|
||||
await this.postMessage({
|
||||
t: "setModeledMethod",
|
||||
method: modeledMethod,
|
||||
});
|
||||
}
|
||||
await this.postMessage({
|
||||
t: "setMultipleModeledMethods",
|
||||
methodSignature: this.method.signature,
|
||||
modeledMethods,
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -198,7 +187,7 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
||||
await this.postMessage({
|
||||
t: "setSelectedMethod",
|
||||
method: e.method,
|
||||
modeledMethod: convertToLegacyModeledMethod(e.modeledMethods),
|
||||
modeledMethods: e.modeledMethods,
|
||||
isModified: e.isModified,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,12 +68,12 @@ export function getArgumentsList(methodParameters: string): string[] {
|
||||
|
||||
export function canMethodBeModeled(
|
||||
method: Method,
|
||||
modeledMethod: ModeledMethod | undefined,
|
||||
modeledMethods: ModeledMethod[],
|
||||
methodIsUnsaved: boolean,
|
||||
): boolean {
|
||||
return (
|
||||
!method.supported ||
|
||||
(modeledMethod && modeledMethod?.type !== "none") ||
|
||||
modeledMethods.some((modeledMethod) => modeledMethod.type !== "none") ||
|
||||
methodIsUnsaved
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,6 +127,15 @@ export class ModelEditorModule extends DisposableObject {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingView = this.editorViewTracker.getView(
|
||||
db.databaseUri.toString(),
|
||||
);
|
||||
if (existingView) {
|
||||
await existingView.focusView();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return withProgress(
|
||||
async (progress) => {
|
||||
const maxStep = 4;
|
||||
@@ -191,6 +200,17 @@ export class ModelEditorModule extends DisposableObject {
|
||||
maxStep,
|
||||
});
|
||||
|
||||
// Check again just before opening the editor to ensure no model editor has been opened between
|
||||
// our first check and now.
|
||||
const existingView = this.editorViewTracker.getView(
|
||||
db.databaseUri.toString(),
|
||||
);
|
||||
if (existingView) {
|
||||
await existingView.focusView();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const view = new ModelEditorView(
|
||||
this.app,
|
||||
this.modelingStore,
|
||||
|
||||
@@ -9,33 +9,25 @@ interface ModelEditorViewInterface {
|
||||
export class ModelEditorViewTracker<
|
||||
T extends ModelEditorViewInterface = ModelEditorViewInterface,
|
||||
> {
|
||||
private readonly views = new Map<string, T[]>();
|
||||
private readonly views = new Map<string, T>();
|
||||
|
||||
constructor() {}
|
||||
|
||||
public registerView(view: T): void {
|
||||
const databaseUri = view.databaseUri;
|
||||
|
||||
if (!this.views.has(databaseUri)) {
|
||||
this.views.set(databaseUri, []);
|
||||
if (this.views.has(databaseUri)) {
|
||||
throw new Error(`View for database ${databaseUri} already registered`);
|
||||
}
|
||||
|
||||
this.views.get(databaseUri)?.push(view);
|
||||
this.views.set(databaseUri, view);
|
||||
}
|
||||
|
||||
public unregisterView(view: T): void {
|
||||
const views = this.views.get(view.databaseUri);
|
||||
if (!views) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = views.indexOf(view);
|
||||
if (index !== -1) {
|
||||
views.splice(index, 1);
|
||||
}
|
||||
this.views.delete(view.databaseUri);
|
||||
}
|
||||
|
||||
public getViews(databaseUri: string): T[] {
|
||||
return this.views.get(databaseUri) ?? [];
|
||||
public getView(databaseUri: string): T | undefined {
|
||||
return this.views.get(databaseUri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,7 @@ import { AutoModeler } from "./auto-modeler";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { ModelingStore } from "./modeling-store";
|
||||
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
|
||||
import {
|
||||
convertFromLegacyModeledMethod,
|
||||
convertToLegacyModeledMethods,
|
||||
} from "./shared/modeled-methods-legacy";
|
||||
import { convertFromLegacyModeledMethod } from "./shared/modeled-methods-legacy";
|
||||
|
||||
export class ModelEditorView extends AbstractWebview<
|
||||
ToModelEditorMessage,
|
||||
@@ -353,6 +350,10 @@ export class ModelEditorView extends AbstractWebview<
|
||||
return this.databaseItem.databaseUri.toString();
|
||||
}
|
||||
|
||||
public async focusView(): Promise<void> {
|
||||
this.panel?.reveal();
|
||||
}
|
||||
|
||||
public async revealMethod(method: Method): Promise<void> {
|
||||
this.panel?.reveal();
|
||||
|
||||
@@ -366,6 +367,9 @@ export class ModelEditorView extends AbstractWebview<
|
||||
const showLlmButton =
|
||||
this.databaseItem.language === "java" && this.modelConfig.llmGeneration;
|
||||
|
||||
const sourceArchiveAvailable =
|
||||
this.databaseItem.hasSourceArchiveInExplorer();
|
||||
|
||||
await this.postMessage({
|
||||
t: "setModelEditorViewState",
|
||||
viewState: {
|
||||
@@ -374,6 +378,7 @@ export class ModelEditorView extends AbstractWebview<
|
||||
showLlmButton,
|
||||
showMultipleModels: this.modelConfig.showMultipleModels,
|
||||
mode: this.modelingStore.getMode(this.databaseItem),
|
||||
sourceArchiveAvailable,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -526,6 +531,15 @@ export class ModelEditorView extends AbstractWebview<
|
||||
return;
|
||||
}
|
||||
|
||||
let existingView = this.viewTracker.getView(
|
||||
addedDatabase.databaseUri.toString(),
|
||||
);
|
||||
if (existingView) {
|
||||
await existingView.focusView();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const modelFile = await pickExtensionPack(
|
||||
this.cliServer,
|
||||
addedDatabase,
|
||||
@@ -538,6 +552,17 @@ export class ModelEditorView extends AbstractWebview<
|
||||
return;
|
||||
}
|
||||
|
||||
// Check again just before opening the editor to ensure no model editor has been opened between
|
||||
// our first check and now.
|
||||
existingView = this.viewTracker.getView(
|
||||
addedDatabase.databaseUri.toString(),
|
||||
);
|
||||
if (existingView) {
|
||||
await existingView.focusView();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const view = new ModelEditorView(
|
||||
this.app,
|
||||
this.modelingStore,
|
||||
@@ -646,7 +671,7 @@ export class ModelEditorView extends AbstractWebview<
|
||||
if (event.dbUri === this.databaseItem.databaseUri.toString()) {
|
||||
await this.postMessage({
|
||||
t: "setModeledMethods",
|
||||
methods: convertToLegacyModeledMethods(event.modeledMethods),
|
||||
methods: event.modeledMethods,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
import { ModeledMethod } from "../modeled-method";
|
||||
|
||||
/**
|
||||
* Converts a record of a single ModeledMethod indexed by signature to a record of ModeledMethod[] indexed by signature
|
||||
* for legacy usage. This function should always be used instead of the trivial conversion to track usages of this
|
||||
* conversion.
|
||||
*
|
||||
* This method should only be called inside a `postMessage` call. If it's used anywhere else, consider whether the
|
||||
* boundary is correct: the boundary should as close as possible to the extension host -> webview boundary.
|
||||
*
|
||||
* @param modeledMethods The record of a single ModeledMethod indexed by signature
|
||||
*/
|
||||
export function convertToLegacyModeledMethods(
|
||||
modeledMethods: Record<string, ModeledMethod[]>,
|
||||
): Record<string, ModeledMethod> {
|
||||
// Always take the first modeled method in the array
|
||||
return Object.fromEntries(
|
||||
Object.entries(modeledMethods)
|
||||
.map(([signature, modeledMethods]) => {
|
||||
const modeledMethod = convertToLegacyModeledMethod(modeledMethods);
|
||||
if (!modeledMethod) {
|
||||
return null;
|
||||
}
|
||||
return [signature, modeledMethod];
|
||||
})
|
||||
.filter((entry): entry is [string, ModeledMethod] => entry !== null),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single ModeledMethod to a ModeledMethod[] for legacy usage. This function should always be used instead
|
||||
* of the trivial conversion to track usages of this conversion.
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface ModelEditorViewState {
|
||||
showLlmButton: boolean;
|
||||
showMultipleModels: boolean;
|
||||
mode: Mode;
|
||||
sourceArchiveAvailable: boolean;
|
||||
}
|
||||
|
||||
export interface MethodModelingPanelViewState {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import * as React from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MultipleModeledMethodsPanel as MultipleModeledMethodsPanelComponent } from "../../view/method-modeling/MultipleModeledMethodsPanel";
|
||||
import { createMethod } from "../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
|
||||
export default {
|
||||
title: "Method Modeling/Multiple Modeled Methods Panel",
|
||||
component: MultipleModeledMethodsPanelComponent,
|
||||
} as Meta<typeof MultipleModeledMethodsPanelComponent>;
|
||||
|
||||
const Template: StoryFn<typeof MultipleModeledMethodsPanelComponent> = (
|
||||
args,
|
||||
) => {
|
||||
const [modeledMethods, setModeledMethods] = useState<ModeledMethod[]>(
|
||||
args.modeledMethods,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setModeledMethods(args.modeledMethods);
|
||||
}, [args.modeledMethods]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(modeledMethods: ModeledMethod[]) => {
|
||||
args.onChange(modeledMethods);
|
||||
setModeledMethods(modeledMethods);
|
||||
},
|
||||
[args],
|
||||
);
|
||||
|
||||
return (
|
||||
<MultipleModeledMethodsPanelComponent
|
||||
{...args}
|
||||
modeledMethods={modeledMethods}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const method = createMethod();
|
||||
|
||||
export const Unmodeled = Template.bind({});
|
||||
Unmodeled.args = {
|
||||
method,
|
||||
modeledMethods: [],
|
||||
};
|
||||
|
||||
export const Single = Template.bind({});
|
||||
Single.args = {
|
||||
method,
|
||||
modeledMethods: [createModeledMethod(method)],
|
||||
};
|
||||
|
||||
export const Multiple = Template.bind({});
|
||||
Multiple.args = {
|
||||
method,
|
||||
modeledMethods: [
|
||||
createModeledMethod(method),
|
||||
createModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -146,67 +146,77 @@ LibraryRow.args = {
|
||||
],
|
||||
},
|
||||
],
|
||||
modeledMethods: {
|
||||
"org.sql2o.Sql2o#Sql2o(String)": {
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
"org.sql2o.Connection#createQuery(String)": {
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "df-manual",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
"org.sql2o.Sql2o#open()": {
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
},
|
||||
"org.sql2o.Query#executeScalar(Class)": {
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
},
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": {
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String,String,String)",
|
||||
},
|
||||
modeledMethodsMap: {
|
||||
"org.sql2o.Sql2o#Sql2o(String)": [
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Connection#createQuery(String)": [
|
||||
{
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "df-manual",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Sql2o#open()": [
|
||||
{
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Query#executeScalar(Class)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String,String,String)",
|
||||
},
|
||||
],
|
||||
},
|
||||
modifiedSignatures: new Set(["org.sql2o.Sql2o#Sql2o(String)"]),
|
||||
inProgressMethods: new InProgressMethods(),
|
||||
@@ -216,6 +226,7 @@ LibraryRow.args = {
|
||||
showLlmButton: true,
|
||||
showMultipleModels: true,
|
||||
mode: Mode.Application,
|
||||
sourceArchiveAvailable: true,
|
||||
},
|
||||
hideModeledMethods: false,
|
||||
};
|
||||
|
||||
@@ -75,6 +75,7 @@ const viewState: ModelEditorViewState = {
|
||||
showLlmButton: true,
|
||||
showMultipleModels: true,
|
||||
mode: Mode.Application,
|
||||
sourceArchiveAvailable: true,
|
||||
};
|
||||
|
||||
export const Unmodeled = Template.bind({});
|
||||
|
||||
@@ -32,6 +32,7 @@ ModelEditor.args = {
|
||||
showLlmButton: true,
|
||||
showMultipleModels: true,
|
||||
mode: Mode.Application,
|
||||
sourceArchiveAvailable: true,
|
||||
},
|
||||
initialMethods: [
|
||||
{
|
||||
@@ -216,65 +217,75 @@ ModelEditor.args = {
|
||||
},
|
||||
],
|
||||
initialModeledMethods: {
|
||||
"org.sql2o.Sql2o#Sql2o(String)": {
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
"org.sql2o.Connection#createQuery(String)": {
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "df-manual",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
"org.sql2o.Sql2o#open()": {
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
},
|
||||
"org.sql2o.Query#executeScalar(Class)": {
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
},
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": {
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String,String,String)",
|
||||
},
|
||||
"org.sql2o.Sql2o#Sql2o(String)": [
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Connection#createQuery(String)": [
|
||||
{
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "df-manual",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Sql2o#open()": [
|
||||
{
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Query#executeScalar(Class)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
},
|
||||
],
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String,String,String)",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
processVariantAnalysisRepositoryTask,
|
||||
} from "./variant-analysis-processor";
|
||||
import PQueue from "p-queue";
|
||||
import { createTimestampFile } from "../run-queries-shared";
|
||||
import { createTimestampFile, saveBeforeStart } from "../run-queries-shared";
|
||||
import { readFile, remove, pathExists } from "fs-extra";
|
||||
import { EOL } from "os";
|
||||
import { cancelVariantAnalysis } from "./gh-api/gh-actions-api-client";
|
||||
@@ -199,6 +199,8 @@ export class VariantAnalysisManager
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
await saveBeforeStart();
|
||||
|
||||
progress({
|
||||
maxStep: 5,
|
||||
step: 0,
|
||||
|
||||
@@ -10,7 +10,6 @@ import { vscode } from "../vscode-api";
|
||||
import { NotInModelingMode } from "./NotInModelingMode";
|
||||
import { NoMethodSelected } from "./NoMethodSelected";
|
||||
import { MethodModelingPanelViewState } from "../../model-editor/shared/view-state";
|
||||
import { convertFromLegacyModeledMethod } from "../../model-editor/shared/modeled-methods-legacy";
|
||||
import { MethodAlreadyModeled } from "./MethodAlreadyModeled";
|
||||
|
||||
type Props = {
|
||||
@@ -25,19 +24,13 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element {
|
||||
|
||||
const [method, setMethod] = useState<Method | undefined>(undefined);
|
||||
|
||||
const [modeledMethod, setModeledMethod] = React.useState<
|
||||
ModeledMethod | undefined
|
||||
>(undefined);
|
||||
const [modeledMethods, setModeledMethods] = useState<ModeledMethod[]>([]);
|
||||
|
||||
const [isMethodModified, setIsMethodModified] = useState<boolean>(false);
|
||||
|
||||
const modelingStatus = useMemo(
|
||||
() =>
|
||||
getModelingStatus(
|
||||
convertFromLegacyModeledMethod(modeledMethod),
|
||||
isMethodModified,
|
||||
),
|
||||
[modeledMethod, isMethodModified],
|
||||
() => getModelingStatus(modeledMethods, isMethodModified),
|
||||
[modeledMethods, isMethodModified],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -54,15 +47,15 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element {
|
||||
case "setMethod":
|
||||
setMethod(msg.method);
|
||||
break;
|
||||
case "setModeledMethod":
|
||||
setModeledMethod(msg.method);
|
||||
case "setMultipleModeledMethods":
|
||||
setModeledMethods(msg.modeledMethods);
|
||||
break;
|
||||
case "setMethodModified":
|
||||
setIsMethodModified(msg.isModified);
|
||||
break;
|
||||
case "setSelectedMethod":
|
||||
setMethod(msg.method);
|
||||
setModeledMethod(msg.modeledMethod);
|
||||
setModeledMethods(msg.modeledMethods);
|
||||
setIsMethodModified(msg.isModified);
|
||||
break;
|
||||
default:
|
||||
@@ -89,7 +82,7 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element {
|
||||
return <NoMethodSelected />;
|
||||
}
|
||||
|
||||
if (!canMethodBeModeled(method, modeledMethod, isMethodModified)) {
|
||||
if (!canMethodBeModeled(method, modeledMethods, isMethodModified)) {
|
||||
return <MethodAlreadyModeled />;
|
||||
}
|
||||
|
||||
@@ -104,7 +97,7 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element {
|
||||
<MethodModeling
|
||||
modelingStatus={modelingStatus}
|
||||
method={method}
|
||||
modeledMethods={convertFromLegacyModeledMethod(modeledMethod)}
|
||||
modeledMethods={modeledMethods}
|
||||
showMultipleModels={viewState?.showMultipleModels}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { MethodModelingInputs } from "./MethodModelingInputs";
|
||||
import { Method } from "../../model-editor/method";
|
||||
@@ -23,6 +24,13 @@ export const ModeledMethodsPanel = ({
|
||||
showMultipleModels,
|
||||
onChange,
|
||||
}: ModeledMethodsPanelProps) => {
|
||||
const handleMultipleChange = useCallback(
|
||||
(modeledMethods: ModeledMethod[]) => {
|
||||
onChange(modeledMethods[0]);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
if (!showMultipleModels) {
|
||||
return (
|
||||
<SingleMethodModelingInputs
|
||||
@@ -37,7 +45,7 @@ export const ModeledMethodsPanel = ({
|
||||
<MultipleModeledMethodsPanel
|
||||
method={method}
|
||||
modeledMethods={modeledMethods}
|
||||
onChange={onChange}
|
||||
onChange={handleMultipleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Method } from "../../model-editor/method";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { styled } from "styled-components";
|
||||
@@ -10,7 +10,7 @@ import { Codicon } from "../common";
|
||||
export type MultipleModeledMethodsPanelProps = {
|
||||
method: Method;
|
||||
modeledMethods: ModeledMethod[];
|
||||
onChange: (modeledMethod: ModeledMethod) => void;
|
||||
onChange: (modeledMethods: ModeledMethod[]) => void;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
@@ -25,6 +25,7 @@ const Container = styled.div`
|
||||
const Footer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const PaginationActions = styled.div`
|
||||
@@ -33,6 +34,12 @@ const PaginationActions = styled.div`
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
const ModificationActions = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
export const MultipleModeledMethodsPanel = ({
|
||||
method,
|
||||
modeledMethods,
|
||||
@@ -47,19 +54,73 @@ export const MultipleModeledMethodsPanel = ({
|
||||
setSelectedIndex((previousIndex) => previousIndex + 1);
|
||||
}, []);
|
||||
|
||||
const handleAddClick = useCallback(() => {
|
||||
const newModeledMethod: ModeledMethod = {
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
};
|
||||
|
||||
const newModeledMethods = [...modeledMethods, newModeledMethod];
|
||||
|
||||
onChange(newModeledMethods);
|
||||
setSelectedIndex(newModeledMethods.length - 1);
|
||||
}, [onChange, modeledMethods, method]);
|
||||
|
||||
const handleRemoveClick = useCallback(() => {
|
||||
const newModeledMethods = modeledMethods.filter(
|
||||
(_, index) => index !== selectedIndex,
|
||||
);
|
||||
|
||||
const newSelectedIndex =
|
||||
selectedIndex === newModeledMethods.length
|
||||
? selectedIndex - 1
|
||||
: selectedIndex;
|
||||
|
||||
onChange(newModeledMethods);
|
||||
setSelectedIndex(newSelectedIndex);
|
||||
}, [onChange, modeledMethods, selectedIndex]);
|
||||
|
||||
const anyUnmodeled = useMemo(
|
||||
() =>
|
||||
modeledMethods.length === 0 ||
|
||||
modeledMethods.some((m) => m.type === "none"),
|
||||
[modeledMethods],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(modeledMethod: ModeledMethod) => {
|
||||
if (modeledMethods.length > 0) {
|
||||
const newModeledMethods = [...modeledMethods];
|
||||
newModeledMethods[selectedIndex] = modeledMethod;
|
||||
onChange(newModeledMethods);
|
||||
} else {
|
||||
onChange([modeledMethod]);
|
||||
}
|
||||
},
|
||||
[modeledMethods, selectedIndex, onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{modeledMethods.length > 0 ? (
|
||||
<MethodModelingInputs
|
||||
method={method}
|
||||
modeledMethod={modeledMethods[selectedIndex]}
|
||||
onChange={onChange}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
) : (
|
||||
<MethodModelingInputs
|
||||
method={method}
|
||||
modeledMethod={undefined}
|
||||
onChange={onChange}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
<Footer>
|
||||
@@ -89,6 +150,24 @@ export const MultipleModeledMethodsPanel = ({
|
||||
<Codicon name="chevron-right" />
|
||||
</VSCodeButton>
|
||||
</PaginationActions>
|
||||
<ModificationActions>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
aria-label="Delete modeling"
|
||||
onClick={handleRemoveClick}
|
||||
disabled={modeledMethods.length < 2}
|
||||
>
|
||||
<Codicon name="trash" />
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
aria-label="Add modeling"
|
||||
onClick={handleAddClick}
|
||||
disabled={anyUnmodeled}
|
||||
>
|
||||
<Codicon name="add" />
|
||||
</VSCodeButton>
|
||||
</ModificationActions>
|
||||
</Footer>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
reactRender(<MultipleModeledMethodsPanel {...props} />);
|
||||
|
||||
const method = createMethod();
|
||||
const onChange = jest.fn();
|
||||
const onChange = jest.fn<void, [ModeledMethod[]]>();
|
||||
|
||||
describe("with no modeled methods", () => {
|
||||
const modeledMethods: ModeledMethod[] = [];
|
||||
@@ -52,6 +52,23 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
expect(screen.queryByText("0/0")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("1/0")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("cannot add or delete modeling", () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByLabelText("Delete modeling")
|
||||
.getElementsByTagName("input")[0],
|
||||
).toBeDisabled();
|
||||
expect(
|
||||
screen.getByLabelText("Add modeling").getElementsByTagName("input")[0],
|
||||
).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with one modeled method", () => {
|
||||
@@ -97,6 +114,46 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
).toBeDisabled();
|
||||
expect(screen.queryByText("1/1")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("cannot delete modeling", () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByLabelText("Delete modeling")
|
||||
.getElementsByTagName("input")[0],
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
it("can add modeling", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByLabelText("Add modeling"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith([
|
||||
...modeledMethods,
|
||||
{
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with two modeled methods", () => {
|
||||
@@ -194,6 +251,106 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
}),
|
||||
).toHaveValue("source");
|
||||
});
|
||||
|
||||
it("can update the first modeling", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
const modelTypeDropdown = screen.getByRole("combobox", {
|
||||
name: "Model type",
|
||||
});
|
||||
|
||||
await userEvent.selectOptions(modelTypeDropdown, "source");
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith([
|
||||
{
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "source",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "value",
|
||||
provenance: "manual",
|
||||
},
|
||||
...modeledMethods.slice(1),
|
||||
]);
|
||||
});
|
||||
|
||||
it("can update the second modeling", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByLabelText("Next modeling"));
|
||||
|
||||
const modelTypeDropdown = screen.getByRole("combobox", {
|
||||
name: "Model type",
|
||||
});
|
||||
|
||||
await userEvent.selectOptions(modelTypeDropdown, "sink");
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith([
|
||||
...modeledMethods.slice(0, 1),
|
||||
{
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "value",
|
||||
provenance: "manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("can delete modeling", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByLabelText("Delete modeling"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(modeledMethods.slice(1));
|
||||
});
|
||||
|
||||
it("can add modeling", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByLabelText("Add modeling"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith([
|
||||
...modeledMethods,
|
||||
{
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with three modeled methods", () => {
|
||||
@@ -309,4 +466,100 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
).toHaveValue("remote");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with 1 modeled and 1 unmodeled method", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
output: "",
|
||||
kind: "path-injection",
|
||||
}),
|
||||
createModeledMethod({
|
||||
...method,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
}),
|
||||
];
|
||||
|
||||
it("cannot add modeling", () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByLabelText("Add modeling").getElementsByTagName("input")[0],
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
it("can delete first modeling", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByLabelText("Delete modeling"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(modeledMethods.slice(1));
|
||||
});
|
||||
|
||||
it("can delete second modeling", async () => {
|
||||
render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByLabelText("Next modeling"));
|
||||
await userEvent.click(screen.getByLabelText("Delete modeling"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(modeledMethods.slice(0, 1));
|
||||
});
|
||||
|
||||
it("can add modeling after deleting second modeling", async () => {
|
||||
const { rerender } = render({
|
||||
method,
|
||||
modeledMethods,
|
||||
onChange,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByLabelText("Next modeling"));
|
||||
await userEvent.click(screen.getByLabelText("Delete modeling"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(modeledMethods.slice(0, 1));
|
||||
|
||||
rerender(
|
||||
<MultipleModeledMethodsPanel
|
||||
method={method}
|
||||
modeledMethods={modeledMethods.slice(0, 1)}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
onChange.mockReset();
|
||||
await userEvent.click(screen.getByLabelText("Add modeling"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith([
|
||||
...modeledMethods.slice(0, 1),
|
||||
{
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ export type LibraryRowProps = {
|
||||
title: string;
|
||||
libraryVersion?: string;
|
||||
methods: Method[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modeledMethodsMap: Record<string, ModeledMethod[]>;
|
||||
modifiedSignatures: Set<string>;
|
||||
inProgressMethods: InProgressMethods;
|
||||
viewState: ModelEditorViewState;
|
||||
@@ -92,7 +92,7 @@ export const LibraryRow = ({
|
||||
title,
|
||||
libraryVersion,
|
||||
methods,
|
||||
modeledMethods,
|
||||
modeledMethodsMap,
|
||||
modifiedSignatures,
|
||||
inProgressMethods,
|
||||
viewState,
|
||||
@@ -231,7 +231,7 @@ export const LibraryRow = ({
|
||||
<ModeledMethodDataGrid
|
||||
packageName={title}
|
||||
methods={methods}
|
||||
modeledMethods={modeledMethods}
|
||||
modeledMethodsMap={modeledMethodsMap}
|
||||
modifiedSignatures={modifiedSignatures}
|
||||
inProgressMethods={inProgressMethods}
|
||||
viewState={viewState}
|
||||
|
||||
@@ -74,7 +74,7 @@ const ButtonsContainer = styled.div`
|
||||
type Props = {
|
||||
initialViewState?: ModelEditorViewState;
|
||||
initialMethods?: Method[];
|
||||
initialModeledMethods?: Record<string, ModeledMethod>;
|
||||
initialModeledMethods?: Record<string, ModeledMethod[]>;
|
||||
initialHideModeledMethods?: boolean;
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ export function ModelEditor({
|
||||
}, [hideModeledMethods]);
|
||||
|
||||
const [modeledMethods, setModeledMethods] = useState<
|
||||
Record<string, ModeledMethod>
|
||||
Record<string, ModeledMethod[]>
|
||||
>(initialModeledMethods);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -282,10 +282,12 @@ export function ModelEditor({
|
||||
<>{viewState.extensionPack.name}</>
|
||||
</HeaderRow>
|
||||
<HeaderRow>
|
||||
<LinkIconButton onClick={onOpenDatabaseClick}>
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
Open database
|
||||
</LinkIconButton>
|
||||
{viewState.sourceArchiveAvailable && (
|
||||
<LinkIconButton onClick={onOpenDatabaseClick}>
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
Open source
|
||||
</LinkIconButton>
|
||||
)}
|
||||
<LinkIconButton onClick={onOpenExtensionPackClick}>
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
Open extension pack
|
||||
@@ -329,7 +331,7 @@ export function ModelEditor({
|
||||
</ButtonsContainer>
|
||||
<ModeledMethodsList
|
||||
methods={methods}
|
||||
modeledMethods={modeledMethods}
|
||||
modeledMethodsMap={modeledMethods}
|
||||
modifiedSignatures={modifiedSignatures}
|
||||
inProgressMethods={inProgressMethods}
|
||||
viewState={viewState}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const GRID_TEMPLATE_COLUMNS = "0.5fr 0.125fr 0.125fr 0.125fr 0.125fr";
|
||||
export type ModeledMethodDataGridProps = {
|
||||
packageName: string;
|
||||
methods: Method[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modeledMethodsMap: Record<string, ModeledMethod[]>;
|
||||
modifiedSignatures: Set<string>;
|
||||
inProgressMethods: InProgressMethods;
|
||||
viewState: ModelEditorViewState;
|
||||
@@ -30,7 +30,7 @@ export type ModeledMethodDataGridProps = {
|
||||
export const ModeledMethodDataGrid = ({
|
||||
packageName,
|
||||
methods,
|
||||
modeledMethods,
|
||||
modeledMethodsMap,
|
||||
modifiedSignatures,
|
||||
inProgressMethods,
|
||||
viewState,
|
||||
@@ -45,11 +45,11 @@ export const ModeledMethodDataGrid = ({
|
||||
const methodsWithModelability = [];
|
||||
let numHiddenMethods = 0;
|
||||
for (const method of sortMethods(methods)) {
|
||||
const modeledMethod = modeledMethods[method.signature];
|
||||
const modeledMethods = modeledMethodsMap[method.signature] ?? [];
|
||||
const methodIsUnsaved = modifiedSignatures.has(method.signature);
|
||||
const methodCanBeModeled = canMethodBeModeled(
|
||||
method,
|
||||
modeledMethod,
|
||||
modeledMethods,
|
||||
methodIsUnsaved,
|
||||
);
|
||||
|
||||
@@ -60,7 +60,7 @@ export const ModeledMethodDataGrid = ({
|
||||
}
|
||||
}
|
||||
return [methodsWithModelability, numHiddenMethods];
|
||||
}, [hideModeledMethods, methods, modeledMethods, modifiedSignatures]);
|
||||
}, [hideModeledMethods, methods, modeledMethodsMap, modifiedSignatures]);
|
||||
|
||||
const someMethodsAreVisible = methodsWithModelability.length > 0;
|
||||
|
||||
@@ -86,13 +86,13 @@ export const ModeledMethodDataGrid = ({
|
||||
</VSCodeDataGridCell>
|
||||
</VSCodeDataGridRow>
|
||||
{methodsWithModelability.map(({ method, methodCanBeModeled }) => {
|
||||
const modeledMethod = modeledMethods[method.signature];
|
||||
const modeledMethods = modeledMethodsMap[method.signature] ?? [];
|
||||
return (
|
||||
<MethodRow
|
||||
key={method.signature}
|
||||
method={method}
|
||||
methodCanBeModeled={methodCanBeModeled}
|
||||
modeledMethods={modeledMethod ? [modeledMethod] : []}
|
||||
modeledMethods={modeledMethods}
|
||||
methodIsUnsaved={modifiedSignatures.has(method.signature)}
|
||||
modelingInProgress={inProgressMethods.hasMethod(
|
||||
packageName,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { InProgressMethods } from "../../model-editor/shared/in-progress-methods
|
||||
|
||||
export type ModeledMethodsListProps = {
|
||||
methods: Method[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modeledMethodsMap: Record<string, ModeledMethod[]>;
|
||||
modifiedSignatures: Set<string>;
|
||||
inProgressMethods: InProgressMethods;
|
||||
revealedMethodSignature: string | null;
|
||||
@@ -36,7 +36,7 @@ const libraryNameOverrides: Record<string, string> = {
|
||||
|
||||
export const ModeledMethodsList = ({
|
||||
methods,
|
||||
modeledMethods,
|
||||
modeledMethodsMap,
|
||||
modifiedSignatures,
|
||||
inProgressMethods,
|
||||
viewState,
|
||||
@@ -82,7 +82,7 @@ export const ModeledMethodsList = ({
|
||||
title={libraryNameOverrides[libraryName] ?? libraryName}
|
||||
libraryVersion={libraryVersions[libraryName]}
|
||||
methods={grouped[libraryName]}
|
||||
modeledMethods={modeledMethods}
|
||||
modeledMethodsMap={modeledMethodsMap}
|
||||
modifiedSignatures={modifiedSignatures}
|
||||
inProgressMethods={inProgressMethods}
|
||||
viewState={viewState}
|
||||
|
||||
@@ -22,6 +22,7 @@ describe(LibraryRow.name, () => {
|
||||
showLlmButton: false,
|
||||
showMultipleModels: false,
|
||||
extensionPack: createMockExtensionPack(),
|
||||
sourceArchiveAvailable: true,
|
||||
};
|
||||
|
||||
const render = (props: Partial<LibraryRowProps> = {}) =>
|
||||
@@ -30,15 +31,17 @@ describe(LibraryRow.name, () => {
|
||||
title="sql2o"
|
||||
libraryVersion="1.6.0"
|
||||
methods={[method]}
|
||||
modeledMethods={{
|
||||
[method.signature]: {
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
modeledMethodsMap={{
|
||||
[method.signature]: [
|
||||
{
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
],
|
||||
}}
|
||||
modifiedSignatures={new Set([method.signature])}
|
||||
inProgressMethods={new InProgressMethods()}
|
||||
|
||||
@@ -39,6 +39,7 @@ describe(MethodRow.name, () => {
|
||||
showLlmButton: false,
|
||||
showMultipleModels: false,
|
||||
extensionPack: createMockExtensionPack(),
|
||||
sourceArchiveAvailable: true,
|
||||
};
|
||||
|
||||
const render = (props: Partial<MethodRowProps> = {}) =>
|
||||
|
||||
@@ -49,6 +49,7 @@ describe(ModeledMethodDataGrid.name, () => {
|
||||
showLlmButton: false,
|
||||
showMultipleModels: false,
|
||||
extensionPack: createMockExtensionPack(),
|
||||
sourceArchiveAvailable: true,
|
||||
};
|
||||
|
||||
const render = (props: Partial<ModeledMethodDataGridProps> = {}) =>
|
||||
@@ -56,15 +57,17 @@ describe(ModeledMethodDataGrid.name, () => {
|
||||
<ModeledMethodDataGrid
|
||||
packageName="sql2o"
|
||||
methods={[method1, method2, method3]}
|
||||
modeledMethods={{
|
||||
[method1.signature]: {
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
modeledMethodsMap={{
|
||||
[method1.signature]: [
|
||||
{
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
],
|
||||
}}
|
||||
modifiedSignatures={new Set([method1.signature])}
|
||||
inProgressMethods={new InProgressMethods()}
|
||||
|
||||
@@ -50,21 +50,24 @@ describe(ModeledMethodsList.name, () => {
|
||||
showLlmButton: false,
|
||||
showMultipleModels: false,
|
||||
extensionPack: createMockExtensionPack(),
|
||||
sourceArchiveAvailable: true,
|
||||
};
|
||||
|
||||
const render = (props: Partial<ModeledMethodsListProps> = {}) =>
|
||||
reactRender(
|
||||
<ModeledMethodsList
|
||||
methods={[method1, method2, method3]}
|
||||
modeledMethods={{
|
||||
[method1.signature]: {
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
modeledMethodsMap={{
|
||||
[method1.signature]: [
|
||||
{
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
],
|
||||
}}
|
||||
modifiedSignatures={new Set([method1.signature])}
|
||||
inProgressMethods={new InProgressMethods()}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[
|
||||
"v2.15.0",
|
||||
"v2.14.6",
|
||||
"v2.13.5",
|
||||
"v2.12.7",
|
||||
|
||||
@@ -5,15 +5,15 @@ import { ModelEditorView } from "../../../src/model-editor/model-editor-view";
|
||||
export function createMockModelEditorViewTracker({
|
||||
registerView = jest.fn(),
|
||||
unregisterView = jest.fn(),
|
||||
getViews = jest.fn(),
|
||||
getView = jest.fn(),
|
||||
}: {
|
||||
registerView?: ModelEditorViewTracker["registerView"];
|
||||
unregisterView?: ModelEditorViewTracker["unregisterView"];
|
||||
getViews?: ModelEditorViewTracker["getViews"];
|
||||
getView?: ModelEditorViewTracker["getView"];
|
||||
} = {}): ModelEditorViewTracker<ModelEditorView> {
|
||||
return mockedObject<ModelEditorViewTracker<ModelEditorView>>({
|
||||
registerView,
|
||||
unregisterView,
|
||||
getViews,
|
||||
getView,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user