diff --git a/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx b/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx index 9c2118914..2e15420a1 100644 --- a/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx +++ b/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx @@ -7,6 +7,9 @@ import { CallClassification, Method } from "../../model-editor/method"; import { ModeledMethod } from "../../model-editor/modeled-method"; import { VSCodeDataGrid } from "@vscode/webview-ui-toolkit/react"; import { GRID_TEMPLATE_COLUMNS } from "../../view/model-editor/ModeledMethodDataGrid"; +import { ModelEditorViewState } from "../../model-editor/shared/view-state"; +import { createMockExtensionPack } from "../../../test/factories/model-editor/extension-pack"; +import { Mode } from "../../model-editor/shared/mode"; export default { title: "CodeQL Model Editor/Method Row", @@ -66,51 +69,66 @@ const modeledMethod: ModeledMethod = { methodParameters: "()", }; +const viewState: ModelEditorViewState = { + extensionPack: createMockExtensionPack(), + showFlowGeneration: true, + showLlmButton: true, + showMultipleModels: true, + mode: Mode.Application, +}; + export const Unmodeled = Template.bind({}); Unmodeled.args = { method, - modeledMethod: undefined, + modeledMethods: [], methodCanBeModeled: true, + viewState, }; export const Source = Template.bind({}); Source.args = { method, - modeledMethod: { ...modeledMethod, type: "source" }, + modeledMethods: [{ ...modeledMethod, type: "source" }], methodCanBeModeled: true, + viewState, }; export const Sink = Template.bind({}); Sink.args = { method, - modeledMethod: { ...modeledMethod, type: "sink" }, + modeledMethods: [{ ...modeledMethod, type: "sink" }], methodCanBeModeled: true, + viewState, }; export const Summary = Template.bind({}); Summary.args = { method, - modeledMethod: { ...modeledMethod, type: "summary" }, + modeledMethods: [{ ...modeledMethod, type: "summary" }], methodCanBeModeled: true, + viewState, }; export const Neutral = Template.bind({}); Neutral.args = { method, - modeledMethod: { ...modeledMethod, type: "neutral" }, + modeledMethods: [{ ...modeledMethod, type: "neutral" }], methodCanBeModeled: true, + viewState, }; export const AlreadyModeled = Template.bind({}); AlreadyModeled.args = { method: { ...method, supported: true }, - modeledMethod: undefined, + modeledMethods: [], + viewState, }; export const ModelingInProgress = Template.bind({}); ModelingInProgress.args = { method, - modeledMethod, + modeledMethods: [modeledMethod], modelingInProgress: true, methodCanBeModeled: true, + viewState, }; diff --git a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx index 7d7e0c66d..32be92cda 100644 --- a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx @@ -23,6 +23,12 @@ import { ModelInputDropdown } from "./ModelInputDropdown"; import { ModelOutputDropdown } from "./ModelOutputDropdown"; import { ModelEditorViewState } from "../../model-editor/shared/view-state"; +const MultiModelColumn = styled(VSCodeDataGridCell)` + display: flex; + flex-direction: column; + gap: 0.5em; +`; + const ApiOrMethodRow = styled.div` min-height: calc(var(--input-height) * 1px); display: flex; @@ -57,7 +63,7 @@ const DataGridRow = styled(VSCodeDataGridRow)<{ focused?: boolean }>` export type MethodRowProps = { method: Method; methodCanBeModeled: boolean; - modeledMethod: ModeledMethod | undefined; + modeledMethods: ModeledMethod[]; methodIsUnsaved: boolean; modelingInProgress: boolean; viewState: ModelEditorViewState; @@ -90,22 +96,23 @@ const ModelableMethodRow = forwardRef( (props, ref) => { const { method, - modeledMethod, + modeledMethods: modeledMethodsArg, methodIsUnsaved, viewState, revealedMethodSignature, onChange, } = props; + const modeledMethods = viewState.showMultipleModels + ? modeledMethodsArg + : modeledMethodsArg.slice(0, 1); + const jumpToUsage = useCallback( () => sendJumpToUsageMessage(method), [method], ); - const modelingStatus = getModelingStatus( - modeledMethod ? [modeledMethod] : [], - methodIsUnsaved, - ); + const modelingStatus = getModelingStatus(modeledMethods, methodIsUnsaved); return ( ( )} {!props.modelingInProgress && ( <> - - - - - - - - - - - - + + {forEachModeledMethod(modeledMethods, (modeledMethod) => ( + + ))} + + + {forEachModeledMethod(modeledMethods, (modeledMethod) => ( + + ))} + + + {forEachModeledMethod(modeledMethods, (modeledMethod) => ( + + ))} + + + {forEachModeledMethod(modeledMethods, (modeledMethod) => ( + + ))} + )} @@ -227,3 +246,14 @@ function sendJumpToUsageMessage(method: Method) { usage: method.usages[0], }); } + +function forEachModeledMethod( + modeledMethods: ModeledMethod[], + renderer: (modeledMethod: ModeledMethod | undefined) => JSX.Element, +): JSX.Element | JSX.Element[] { + if (modeledMethods.length === 0) { + return renderer(undefined); + } else { + return modeledMethods.map(renderer); + } +} diff --git a/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx b/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx index 9ff27ad21..bf53b479c 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx @@ -84,22 +84,25 @@ export const ModeledMethodDataGrid = ({ Kind - {methodsWithModelability.map(({ method, methodCanBeModeled }) => ( - - ))} + {methodsWithModelability.map(({ method, methodCanBeModeled }) => { + const modeledMethod = modeledMethods[method.signature]; + return ( + + ); + })} )} { { it("shows the modeling status indicator when unmodeled", () => { render({ - modeledMethod: undefined, + modeledMethods: [], }); expect(screen.getByLabelText("Method not modeled")).toBeInTheDocument(); @@ -137,7 +137,7 @@ describe(MethodRow.name, () => { it("renders an unmodelable method", () => { render({ methodCanBeModeled: false, - modeledMethod: undefined, + modeledMethods: [], }); expect(screen.queryByRole("combobox")).not.toBeInTheDocument();