Skip to content

Commit 27c4bd8

Browse files
Merge pull request #2910 from github/robertbrignull/multiple-models-method-row
Add ability for MethodRow to render multiple modelings of the same method
2 parents 623df4c + edd2aa5 commit 27c4bd8

File tree

9 files changed

+240
-95
lines changed

9 files changed

+240
-95
lines changed

extensions/ql-vscode/src/model-editor/methods-usage/methods-usage-data-provider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ export class MethodsUsageDataProvider
102102
const modeledMethod = this.modeledMethods[method.signature];
103103
const modifiedMethod = this.modifiedMethodSignatures.has(method.signature);
104104

105-
const status = getModelingStatus(modeledMethod, modifiedMethod);
105+
const status = getModelingStatus(
106+
modeledMethod ? [modeledMethod] : [],
107+
modifiedMethod,
108+
);
106109
switch (status) {
107110
case "unmodeled":
108111
return new ThemeIcon("error", new ThemeColor("errorForeground"));

extensions/ql-vscode/src/model-editor/shared/modeling-status.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { ModeledMethod } from "../modeled-method";
33
export type ModelingStatus = "unmodeled" | "unsaved" | "saved";
44

55
export function getModelingStatus(
6-
modeledMethod: ModeledMethod | undefined,
6+
modeledMethods: ModeledMethod[],
77
methodIsUnsaved: boolean,
88
): ModelingStatus {
9-
if (modeledMethod) {
9+
if (modeledMethods.length > 0) {
1010
if (methodIsUnsaved) {
1111
return "unsaved";
12-
} else if (modeledMethod.type !== "none") {
12+
} else if (modeledMethods.some((m) => m.type !== "none")) {
1313
return "saved";
1414
}
1515
}

extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { CallClassification, Method } from "../../model-editor/method";
77
import { ModeledMethod } from "../../model-editor/modeled-method";
88
import { VSCodeDataGrid } from "@vscode/webview-ui-toolkit/react";
99
import { GRID_TEMPLATE_COLUMNS } from "../../view/model-editor/ModeledMethodDataGrid";
10+
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
11+
import { createMockExtensionPack } from "../../../test/factories/model-editor/extension-pack";
12+
import { Mode } from "../../model-editor/shared/mode";
1013

1114
export default {
1215
title: "CodeQL Model Editor/Method Row",
@@ -66,51 +69,78 @@ const modeledMethod: ModeledMethod = {
6669
methodParameters: "()",
6770
};
6871

72+
const viewState: ModelEditorViewState = {
73+
extensionPack: createMockExtensionPack(),
74+
showFlowGeneration: true,
75+
showLlmButton: true,
76+
showMultipleModels: true,
77+
mode: Mode.Application,
78+
};
79+
6980
export const Unmodeled = Template.bind({});
7081
Unmodeled.args = {
7182
method,
72-
modeledMethod: undefined,
83+
modeledMethods: [],
7384
methodCanBeModeled: true,
85+
viewState,
7486
};
7587

7688
export const Source = Template.bind({});
7789
Source.args = {
7890
method,
79-
modeledMethod: { ...modeledMethod, type: "source" },
91+
modeledMethods: [{ ...modeledMethod, type: "source" }],
8092
methodCanBeModeled: true,
93+
viewState,
8194
};
8295

8396
export const Sink = Template.bind({});
8497
Sink.args = {
8598
method,
86-
modeledMethod: { ...modeledMethod, type: "sink" },
99+
modeledMethods: [{ ...modeledMethod, type: "sink" }],
87100
methodCanBeModeled: true,
101+
viewState,
88102
};
89103

90104
export const Summary = Template.bind({});
91105
Summary.args = {
92106
method,
93-
modeledMethod: { ...modeledMethod, type: "summary" },
107+
modeledMethods: [{ ...modeledMethod, type: "summary" }],
94108
methodCanBeModeled: true,
109+
viewState,
95110
};
96111

97112
export const Neutral = Template.bind({});
98113
Neutral.args = {
99114
method,
100-
modeledMethod: { ...modeledMethod, type: "neutral" },
115+
modeledMethods: [{ ...modeledMethod, type: "neutral" }],
101116
methodCanBeModeled: true,
117+
viewState,
102118
};
103119

104120
export const AlreadyModeled = Template.bind({});
105121
AlreadyModeled.args = {
106122
method: { ...method, supported: true },
107-
modeledMethod: undefined,
123+
modeledMethods: [],
124+
viewState,
108125
};
109126

110127
export const ModelingInProgress = Template.bind({});
111128
ModelingInProgress.args = {
112129
method,
113-
modeledMethod,
130+
modeledMethods: [modeledMethod],
114131
modelingInProgress: true,
115132
methodCanBeModeled: true,
133+
viewState,
134+
};
135+
136+
export const MultipleModelings = Template.bind({});
137+
MultipleModelings.args = {
138+
method,
139+
modeledMethods: [
140+
{ ...modeledMethod, type: "source" },
141+
{ ...modeledMethod, type: "sink" },
142+
{ ...modeledMethod },
143+
],
144+
methodCanBeModeled: true,
145+
viewState,
116146
};

extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element {
3030
const [isMethodModified, setIsMethodModified] = useState<boolean>(false);
3131

3232
const modelingStatus = useMemo(
33-
() => getModelingStatus(modeledMethod, isMethodModified),
33+
() =>
34+
getModelingStatus(modeledMethod ? [modeledMethod] : [], isMethodModified),
3435
[modeledMethod, isMethodModified],
3536
);
3637

extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export const LibraryRow = ({
232232
modeledMethods={modeledMethods}
233233
modifiedSignatures={modifiedSignatures}
234234
inProgressMethods={inProgressMethods}
235-
mode={viewState.mode}
235+
viewState={viewState}
236236
hideModeledMethods={hideModeledMethods}
237237
revealedMethodSignature={revealedMethodSignature}
238238
onChange={onChange}

extensions/ql-vscode/src/view/model-editor/MethodRow.tsx

Lines changed: 100 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,16 @@ import { MethodName } from "./MethodName";
2121
import { ModelTypeDropdown } from "./ModelTypeDropdown";
2222
import { ModelInputDropdown } from "./ModelInputDropdown";
2323
import { ModelOutputDropdown } from "./ModelOutputDropdown";
24+
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
2425

25-
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
26+
const MultiModelColumn = styled(VSCodeDataGridCell)`
27+
display: flex;
28+
flex-direction: column;
29+
gap: 0.5em;
30+
`;
31+
32+
const ApiOrMethodRow = styled.div`
33+
min-height: calc(var(--input-height) * 1px);
2634
display: flex;
2735
flex-direction: row;
2836
align-items: center;
@@ -55,10 +63,10 @@ const DataGridRow = styled(VSCodeDataGridRow)<{ focused?: boolean }>`
5563
export type MethodRowProps = {
5664
method: Method;
5765
methodCanBeModeled: boolean;
58-
modeledMethod: ModeledMethod | undefined;
66+
modeledMethods: ModeledMethod[];
5967
methodIsUnsaved: boolean;
6068
modelingInProgress: boolean;
61-
mode: Mode;
69+
viewState: ModelEditorViewState;
6270
revealedMethodSignature: string | null;
6371
onChange: (modeledMethod: ModeledMethod) => void;
6472
};
@@ -88,38 +96,44 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
8896
(props, ref) => {
8997
const {
9098
method,
91-
modeledMethod,
99+
modeledMethods: modeledMethodsProp,
92100
methodIsUnsaved,
93-
mode,
101+
viewState,
94102
revealedMethodSignature,
95103
onChange,
96104
} = props;
97105

106+
const modeledMethods = viewState.showMultipleModels
107+
? modeledMethodsProp
108+
: modeledMethodsProp.slice(0, 1);
109+
98110
const jumpToUsage = useCallback(
99111
() => sendJumpToUsageMessage(method),
100112
[method],
101113
);
102114

103-
const modelingStatus = getModelingStatus(modeledMethod, methodIsUnsaved);
115+
const modelingStatus = getModelingStatus(modeledMethods, methodIsUnsaved);
104116

105117
return (
106118
<DataGridRow
107119
data-testid="modelable-method-row"
108120
ref={ref}
109121
focused={revealedMethodSignature === method.signature}
110122
>
111-
<ApiOrMethodCell gridColumn={1}>
112-
<ModelingStatusIndicator status={modelingStatus} />
113-
<MethodClassifications method={method} />
114-
<MethodName {...props.method} />
115-
{mode === Mode.Application && (
116-
<UsagesButton onClick={jumpToUsage}>
117-
{method.usages.length}
118-
</UsagesButton>
119-
)}
120-
<ViewLink onClick={jumpToUsage}>View</ViewLink>
121-
{props.modelingInProgress && <ProgressRing />}
122-
</ApiOrMethodCell>
123+
<VSCodeDataGridCell gridColumn={1}>
124+
<ApiOrMethodRow>
125+
<ModelingStatusIndicator status={modelingStatus} />
126+
<MethodClassifications method={method} />
127+
<MethodName {...props.method} />
128+
{viewState.mode === Mode.Application && (
129+
<UsagesButton onClick={jumpToUsage}>
130+
{method.usages.length}
131+
</UsagesButton>
132+
)}
133+
<ViewLink onClick={jumpToUsage}>View</ViewLink>
134+
{props.modelingInProgress && <ProgressRing />}
135+
</ApiOrMethodRow>
136+
</VSCodeDataGridCell>
123137
{props.modelingInProgress && (
124138
<>
125139
<VSCodeDataGridCell gridColumn={2}>
@@ -138,34 +152,46 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
138152
)}
139153
{!props.modelingInProgress && (
140154
<>
141-
<VSCodeDataGridCell gridColumn={2}>
142-
<ModelTypeDropdown
143-
method={method}
144-
modeledMethod={modeledMethod}
145-
onChange={onChange}
146-
/>
147-
</VSCodeDataGridCell>
148-
<VSCodeDataGridCell gridColumn={3}>
149-
<ModelInputDropdown
150-
method={method}
151-
modeledMethod={modeledMethod}
152-
onChange={onChange}
153-
/>
154-
</VSCodeDataGridCell>
155-
<VSCodeDataGridCell gridColumn={4}>
156-
<ModelOutputDropdown
157-
method={method}
158-
modeledMethod={modeledMethod}
159-
onChange={onChange}
160-
/>
161-
</VSCodeDataGridCell>
162-
<VSCodeDataGridCell gridColumn={5}>
163-
<ModelKindDropdown
164-
method={method}
165-
modeledMethod={modeledMethod}
166-
onChange={onChange}
167-
/>
168-
</VSCodeDataGridCell>
155+
<MultiModelColumn gridColumn={2}>
156+
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
157+
<ModelTypeDropdown
158+
key={index}
159+
method={method}
160+
modeledMethod={modeledMethod}
161+
onChange={onChange}
162+
/>
163+
))}
164+
</MultiModelColumn>
165+
<MultiModelColumn gridColumn={3}>
166+
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
167+
<ModelInputDropdown
168+
key={index}
169+
method={method}
170+
modeledMethod={modeledMethod}
171+
onChange={onChange}
172+
/>
173+
))}
174+
</MultiModelColumn>
175+
<MultiModelColumn gridColumn={4}>
176+
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
177+
<ModelOutputDropdown
178+
key={index}
179+
method={method}
180+
modeledMethod={modeledMethod}
181+
onChange={onChange}
182+
/>
183+
))}
184+
</MultiModelColumn>
185+
<MultiModelColumn gridColumn={5}>
186+
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
187+
<ModelKindDropdown
188+
key={index}
189+
method={method}
190+
modeledMethod={modeledMethod}
191+
onChange={onChange}
192+
/>
193+
))}
194+
</MultiModelColumn>
169195
</>
170196
)}
171197
</DataGridRow>
@@ -178,7 +204,7 @@ const UnmodelableMethodRow = forwardRef<
178204
HTMLElement | undefined,
179205
MethodRowProps
180206
>((props, ref) => {
181-
const { method, mode, revealedMethodSignature } = props;
207+
const { method, viewState, revealedMethodSignature } = props;
182208

183209
const jumpToUsage = useCallback(
184210
() => sendJumpToUsageMessage(method),
@@ -191,17 +217,19 @@ const UnmodelableMethodRow = forwardRef<
191217
ref={ref}
192218
focused={revealedMethodSignature === method.signature}
193219
>
194-
<ApiOrMethodCell gridColumn={1}>
195-
<ModelingStatusIndicator status="saved" />
196-
<MethodName {...props.method} />
197-
{mode === Mode.Application && (
198-
<UsagesButton onClick={jumpToUsage}>
199-
{method.usages.length}
200-
</UsagesButton>
201-
)}
202-
<ViewLink onClick={jumpToUsage}>View</ViewLink>
203-
<MethodClassifications method={method} />
204-
</ApiOrMethodCell>
220+
<VSCodeDataGridCell gridColumn={1}>
221+
<ApiOrMethodRow>
222+
<ModelingStatusIndicator status="saved" />
223+
<MethodName {...props.method} />
224+
{viewState.mode === Mode.Application && (
225+
<UsagesButton onClick={jumpToUsage}>
226+
{method.usages.length}
227+
</UsagesButton>
228+
)}
229+
<ViewLink onClick={jumpToUsage}>View</ViewLink>
230+
<MethodClassifications method={method} />
231+
</ApiOrMethodRow>
232+
</VSCodeDataGridCell>
205233
<VSCodeDataGridCell gridColumn="span 4">
206234
Method already modeled
207235
</VSCodeDataGridCell>
@@ -218,3 +246,17 @@ function sendJumpToUsageMessage(method: Method) {
218246
usage: method.usages[0],
219247
});
220248
}
249+
250+
function forEachModeledMethod(
251+
modeledMethods: ModeledMethod[],
252+
renderer: (
253+
modeledMethod: ModeledMethod | undefined,
254+
index: number,
255+
) => JSX.Element,
256+
): JSX.Element | JSX.Element[] {
257+
if (modeledMethods.length === 0) {
258+
return renderer(undefined, 0);
259+
} else {
260+
return modeledMethods.map(renderer);
261+
}
262+
}

0 commit comments

Comments
 (0)