Convert MethodRow to display multiple modelings

This commit is contained in:
Robert
2023-10-04 15:42:13 +01:00
parent 86d7d8345c
commit 252c7a20c4
4 changed files with 111 additions and 60 deletions

View File

@@ -7,6 +7,9 @@ import { CallClassification, Method } from "../../model-editor/method";
import { ModeledMethod } from "../../model-editor/modeled-method"; import { ModeledMethod } from "../../model-editor/modeled-method";
import { VSCodeDataGrid } from "@vscode/webview-ui-toolkit/react"; import { VSCodeDataGrid } from "@vscode/webview-ui-toolkit/react";
import { GRID_TEMPLATE_COLUMNS } from "../../view/model-editor/ModeledMethodDataGrid"; 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 { export default {
title: "CodeQL Model Editor/Method Row", title: "CodeQL Model Editor/Method Row",
@@ -66,51 +69,66 @@ const modeledMethod: ModeledMethod = {
methodParameters: "()", methodParameters: "()",
}; };
const viewState: ModelEditorViewState = {
extensionPack: createMockExtensionPack(),
showFlowGeneration: true,
showLlmButton: true,
showMultipleModels: true,
mode: Mode.Application,
};
export const Unmodeled = Template.bind({}); export const Unmodeled = Template.bind({});
Unmodeled.args = { Unmodeled.args = {
method, method,
modeledMethod: undefined, modeledMethods: [],
methodCanBeModeled: true, methodCanBeModeled: true,
viewState,
}; };
export const Source = Template.bind({}); export const Source = Template.bind({});
Source.args = { Source.args = {
method, method,
modeledMethod: { ...modeledMethod, type: "source" }, modeledMethods: [{ ...modeledMethod, type: "source" }],
methodCanBeModeled: true, methodCanBeModeled: true,
viewState,
}; };
export const Sink = Template.bind({}); export const Sink = Template.bind({});
Sink.args = { Sink.args = {
method, method,
modeledMethod: { ...modeledMethod, type: "sink" }, modeledMethods: [{ ...modeledMethod, type: "sink" }],
methodCanBeModeled: true, methodCanBeModeled: true,
viewState,
}; };
export const Summary = Template.bind({}); export const Summary = Template.bind({});
Summary.args = { Summary.args = {
method, method,
modeledMethod: { ...modeledMethod, type: "summary" }, modeledMethods: [{ ...modeledMethod, type: "summary" }],
methodCanBeModeled: true, methodCanBeModeled: true,
viewState,
}; };
export const Neutral = Template.bind({}); export const Neutral = Template.bind({});
Neutral.args = { Neutral.args = {
method, method,
modeledMethod: { ...modeledMethod, type: "neutral" }, modeledMethods: [{ ...modeledMethod, type: "neutral" }],
methodCanBeModeled: true, methodCanBeModeled: true,
viewState,
}; };
export const AlreadyModeled = Template.bind({}); export const AlreadyModeled = Template.bind({});
AlreadyModeled.args = { AlreadyModeled.args = {
method: { ...method, supported: true }, method: { ...method, supported: true },
modeledMethod: undefined, modeledMethods: [],
viewState,
}; };
export const ModelingInProgress = Template.bind({}); export const ModelingInProgress = Template.bind({});
ModelingInProgress.args = { ModelingInProgress.args = {
method, method,
modeledMethod, modeledMethods: [modeledMethod],
modelingInProgress: true, modelingInProgress: true,
methodCanBeModeled: true, methodCanBeModeled: true,
viewState,
}; };

View File

@@ -23,6 +23,12 @@ import { ModelInputDropdown } from "./ModelInputDropdown";
import { ModelOutputDropdown } from "./ModelOutputDropdown"; import { ModelOutputDropdown } from "./ModelOutputDropdown";
import { ModelEditorViewState } from "../../model-editor/shared/view-state"; import { ModelEditorViewState } from "../../model-editor/shared/view-state";
const MultiModelColumn = styled(VSCodeDataGridCell)`
display: flex;
flex-direction: column;
gap: 0.5em;
`;
const ApiOrMethodRow = styled.div` const ApiOrMethodRow = styled.div`
min-height: calc(var(--input-height) * 1px); min-height: calc(var(--input-height) * 1px);
display: flex; display: flex;
@@ -57,7 +63,7 @@ const DataGridRow = styled(VSCodeDataGridRow)<{ focused?: boolean }>`
export type MethodRowProps = { export type MethodRowProps = {
method: Method; method: Method;
methodCanBeModeled: boolean; methodCanBeModeled: boolean;
modeledMethod: ModeledMethod | undefined; modeledMethods: ModeledMethod[];
methodIsUnsaved: boolean; methodIsUnsaved: boolean;
modelingInProgress: boolean; modelingInProgress: boolean;
viewState: ModelEditorViewState; viewState: ModelEditorViewState;
@@ -90,22 +96,23 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
(props, ref) => { (props, ref) => {
const { const {
method, method,
modeledMethod, modeledMethods: modeledMethodsArg,
methodIsUnsaved, methodIsUnsaved,
viewState, viewState,
revealedMethodSignature, revealedMethodSignature,
onChange, onChange,
} = props; } = props;
const modeledMethods = viewState.showMultipleModels
? modeledMethodsArg
: modeledMethodsArg.slice(0, 1);
const jumpToUsage = useCallback( const jumpToUsage = useCallback(
() => sendJumpToUsageMessage(method), () => sendJumpToUsageMessage(method),
[method], [method],
); );
const modelingStatus = getModelingStatus( const modelingStatus = getModelingStatus(modeledMethods, methodIsUnsaved);
modeledMethod ? [modeledMethod] : [],
methodIsUnsaved,
);
return ( return (
<DataGridRow <DataGridRow
@@ -145,34 +152,46 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
)} )}
{!props.modelingInProgress && ( {!props.modelingInProgress && (
<> <>
<VSCodeDataGridCell gridColumn={2}> <MultiModelColumn gridColumn={2}>
<ModelTypeDropdown {forEachModeledMethod(modeledMethods, (modeledMethod) => (
method={method} <ModelTypeDropdown
modeledMethod={modeledMethod} key={JSON.stringify(modeledMethod)}
onChange={onChange} method={method}
/> modeledMethod={modeledMethod}
</VSCodeDataGridCell> onChange={onChange}
<VSCodeDataGridCell gridColumn={3}> />
<ModelInputDropdown ))}
method={method} </MultiModelColumn>
modeledMethod={modeledMethod} <MultiModelColumn gridColumn={3}>
onChange={onChange} {forEachModeledMethod(modeledMethods, (modeledMethod) => (
/> <ModelInputDropdown
</VSCodeDataGridCell> key={JSON.stringify(modeledMethod)}
<VSCodeDataGridCell gridColumn={4}> method={method}
<ModelOutputDropdown modeledMethod={modeledMethod}
method={method} onChange={onChange}
modeledMethod={modeledMethod} />
onChange={onChange} ))}
/> </MultiModelColumn>
</VSCodeDataGridCell> <MultiModelColumn gridColumn={4}>
<VSCodeDataGridCell gridColumn={5}> {forEachModeledMethod(modeledMethods, (modeledMethod) => (
<ModelKindDropdown <ModelOutputDropdown
method={method} key={JSON.stringify(modeledMethod)}
modeledMethod={modeledMethod} method={method}
onChange={onChange} modeledMethod={modeledMethod}
/> onChange={onChange}
</VSCodeDataGridCell> />
))}
</MultiModelColumn>
<MultiModelColumn gridColumn={5}>
{forEachModeledMethod(modeledMethods, (modeledMethod) => (
<ModelKindDropdown
key={JSON.stringify(modeledMethod)}
method={method}
modeledMethod={modeledMethod}
onChange={onChange}
/>
))}
</MultiModelColumn>
</> </>
)} )}
</DataGridRow> </DataGridRow>
@@ -227,3 +246,14 @@ function sendJumpToUsageMessage(method: Method) {
usage: method.usages[0], 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);
}
}

View File

@@ -84,22 +84,25 @@ export const ModeledMethodDataGrid = ({
Kind Kind
</VSCodeDataGridCell> </VSCodeDataGridCell>
</VSCodeDataGridRow> </VSCodeDataGridRow>
{methodsWithModelability.map(({ method, methodCanBeModeled }) => ( {methodsWithModelability.map(({ method, methodCanBeModeled }) => {
<MethodRow const modeledMethod = modeledMethods[method.signature];
key={method.signature} return (
method={method} <MethodRow
methodCanBeModeled={methodCanBeModeled} key={method.signature}
modeledMethod={modeledMethods[method.signature]} method={method}
methodIsUnsaved={modifiedSignatures.has(method.signature)} methodCanBeModeled={methodCanBeModeled}
modelingInProgress={inProgressMethods.hasMethod( modeledMethods={modeledMethod ? [modeledMethod] : []}
packageName, methodIsUnsaved={modifiedSignatures.has(method.signature)}
method.signature, modelingInProgress={inProgressMethods.hasMethod(
)} packageName,
viewState={viewState} method.signature,
revealedMethodSignature={revealedMethodSignature} )}
onChange={onChange} viewState={viewState}
/> revealedMethodSignature={revealedMethodSignature}
))} onChange={onChange}
/>
);
})}
</> </>
)} )}
<HiddenMethodsRow <HiddenMethodsRow

View File

@@ -46,7 +46,7 @@ describe(MethodRow.name, () => {
<MethodRow <MethodRow
method={method} method={method}
methodCanBeModeled={true} methodCanBeModeled={true}
modeledMethod={modeledMethod} modeledMethods={[modeledMethod]}
methodIsUnsaved={false} methodIsUnsaved={false}
modelingInProgress={false} modelingInProgress={false}
revealedMethodSignature={null} revealedMethodSignature={null}
@@ -120,7 +120,7 @@ describe(MethodRow.name, () => {
it("shows the modeling status indicator when unmodeled", () => { it("shows the modeling status indicator when unmodeled", () => {
render({ render({
modeledMethod: undefined, modeledMethods: [],
}); });
expect(screen.getByLabelText("Method not modeled")).toBeInTheDocument(); expect(screen.getByLabelText("Method not modeled")).toBeInTheDocument();
@@ -137,7 +137,7 @@ describe(MethodRow.name, () => {
it("renders an unmodelable method", () => { it("renders an unmodelable method", () => {
render({ render({
methodCanBeModeled: false, methodCanBeModeled: false,
modeledMethod: undefined, modeledMethods: [],
}); });
expect(screen.queryByRole("combobox")).not.toBeInTheDocument(); expect(screen.queryByRole("combobox")).not.toBeInTheDocument();