Skip to content

Commit aa8896e

Browse files
Merge pull request #2964 from github/robertbrignull/enable-add-multiple-models
Enable/disable the add/remove model buttons at the right times
2 parents 39a9f4c + bf828bc commit aa8896e

4 files changed

Lines changed: 156 additions & 14 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ModeledMethod } from "../modeled-method";
2+
3+
export function canAddNewModeledMethod(
4+
modeledMethods: ModeledMethod[],
5+
): boolean {
6+
// Disallow adding methods when there are no modeled methods or where there is a single unmodeled method.
7+
// In both of these cases the UI will already be showing the user inputs they can use for modeling.
8+
return (
9+
modeledMethods.length > 1 ||
10+
(modeledMethods.length === 1 && modeledMethods[0].type !== "none")
11+
);
12+
}
13+
14+
export function canRemoveModeledMethod(
15+
modeledMethods: ModeledMethod[],
16+
): boolean {
17+
// Don't allow removing the last modeled method. In this case the user is intended to
18+
// set the type to "none" instead.
19+
return modeledMethods.length > 1;
20+
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import * as React from "react";
22
import { useCallback, useMemo, useState } from "react";
33
import { Method } from "../../model-editor/method";
44
import { ModeledMethod } from "../../model-editor/modeled-method";
5+
import {
6+
canAddNewModeledMethod,
7+
canRemoveModeledMethod,
8+
} from "../../model-editor/shared/multiple-modeled-methods";
59
import { styled } from "styled-components";
610
import { MethodModelingInputs } from "./MethodModelingInputs";
711
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
@@ -171,18 +175,15 @@ export const MultipleModeledMethodsPanel = ({
171175
appearance="icon"
172176
aria-label="Delete modeling"
173177
onClick={handleRemoveClick}
174-
disabled={modeledMethods.length < 2}
178+
disabled={!canRemoveModeledMethod(modeledMethods)}
175179
>
176180
<Codicon name="trash" />
177181
</VSCodeButton>
178182
<VSCodeButton
179183
appearance="icon"
180184
aria-label="Add modeling"
181185
onClick={handleAddClick}
182-
disabled={
183-
modeledMethods.length === 0 ||
184-
(modeledMethods.length === 1 && modeledMethods[0].type === "none")
185-
}
186+
disabled={!canAddNewModeledMethod(modeledMethods)}
186187
>
187188
<Codicon name="add" />
188189
</VSCodeButton>

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { ModelInputDropdown } from "./ModelInputDropdown";
2424
import { ModelOutputDropdown } from "./ModelOutputDropdown";
2525
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
2626
import { Codicon } from "../common";
27+
import { canAddNewModeledMethod } from "../../model-editor/shared/multiple-modeled-methods";
2728

2829
const MultiModelColumn = styled(VSCodeDataGridCell)`
2930
display: flex;
@@ -132,6 +133,8 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
132133

133134
const modelingStatus = getModelingStatus(modeledMethods, methodIsUnsaved);
134135

136+
const addModelButtonDisabled = !canAddNewModeledMethod(modeledMethods);
137+
135138
return (
136139
<DataGridRow
137140
data-testid="modelable-method-row"
@@ -219,15 +222,26 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
219222
</MultiModelColumn>
220223
{viewState.showMultipleModels && (
221224
<MultiModelColumn gridColumn={6}>
222-
{modeledMethods.map((_, index) => (
223-
<CodiconRow key={index} appearance="icon" disabled={false}>
224-
{index === modeledMethods.length - 1 ? (
225-
<Codicon name="add" label="Add new model" />
226-
) : (
227-
<Codicon name="trash" label="Remove model" />
228-
)}
229-
</CodiconRow>
230-
))}
225+
{modeledMethods.map((_, index) =>
226+
index === modeledMethods.length - 1 ? (
227+
<CodiconRow
228+
key={index}
229+
appearance="icon"
230+
aria-label="Add new model"
231+
disabled={addModelButtonDisabled}
232+
>
233+
<Codicon name="add" />
234+
</CodiconRow>
235+
) : (
236+
<CodiconRow
237+
key={index}
238+
appearance="icon"
239+
aria-label="Remove model"
240+
>
241+
<Codicon name="trash" />
242+
</CodiconRow>
243+
),
244+
)}
231245
</MultiModelColumn>
232246
)}
233247
</>

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

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,111 @@ describe(MethodRow.name, () => {
251251
expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
252252
expect(screen.getByText("Method already modeled")).toBeInTheDocument();
253253
});
254+
255+
it("doesn't show add/remove buttons when multiple methods feature flag is disabled", async () => {
256+
render({
257+
modeledMethods: [modeledMethod],
258+
viewState: {
259+
...viewState,
260+
showMultipleModels: false,
261+
},
262+
});
263+
264+
expect(screen.queryByLabelText("Add new model")).not.toBeInTheDocument();
265+
expect(screen.queryByLabelText("Remove model")).not.toBeInTheDocument();
266+
});
267+
268+
it("shows disabled button add new model when there are no modeled methods", async () => {
269+
render({
270+
modeledMethods: [],
271+
viewState: {
272+
...viewState,
273+
showMultipleModels: true,
274+
},
275+
});
276+
277+
const addButton = screen.queryByLabelText("Add new model");
278+
expect(addButton).toBeInTheDocument();
279+
expect(addButton?.getElementsByTagName("input")[0]).toBeDisabled();
280+
281+
expect(screen.queryByLabelText("Remove model")).not.toBeInTheDocument();
282+
});
283+
284+
it("disabled button to add new model when there is one unmodeled method", async () => {
285+
render({
286+
modeledMethods: [{ ...modeledMethod, type: "none" }],
287+
viewState: {
288+
...viewState,
289+
showMultipleModels: true,
290+
},
291+
});
292+
293+
const addButton = screen.queryByLabelText("Add new model");
294+
expect(addButton).toBeInTheDocument();
295+
expect(addButton?.getElementsByTagName("input")[0]).toBeDisabled();
296+
297+
expect(screen.queryByLabelText("Remove model")).not.toBeInTheDocument();
298+
});
299+
300+
it("enabled button to add new model when there is one modeled method", async () => {
301+
render({
302+
modeledMethods: [modeledMethod],
303+
viewState: {
304+
...viewState,
305+
showMultipleModels: true,
306+
},
307+
});
308+
309+
const addButton = screen.queryByLabelText("Add new model");
310+
expect(addButton).toBeInTheDocument();
311+
expect(addButton?.getElementsByTagName("input")[0]).toBeEnabled();
312+
313+
expect(screen.queryByLabelText("Remove model")).not.toBeInTheDocument();
314+
});
315+
316+
it("enabled add/remove model buttons when there are multiple modeled methods", async () => {
317+
render({
318+
modeledMethods: [
319+
{ ...modeledMethod, type: "source" },
320+
{ ...modeledMethod, type: "none" },
321+
],
322+
viewState: {
323+
...viewState,
324+
showMultipleModels: true,
325+
},
326+
});
327+
328+
const addButton = screen.queryByLabelText("Add new model");
329+
expect(addButton).toBeInTheDocument();
330+
expect(addButton?.getElementsByTagName("input")[0]).toBeEnabled();
331+
332+
const removeButton = screen.queryByLabelText("Remove model");
333+
expect(removeButton).toBeInTheDocument();
334+
expect(removeButton?.getElementsByTagName("input")[0]).toBeEnabled();
335+
});
336+
337+
it("shows add model button on last row and remove model button on all other rows", async () => {
338+
render({
339+
modeledMethods: [
340+
{ ...modeledMethod, type: "source" },
341+
{ ...modeledMethod, type: "sink" },
342+
{ ...modeledMethod, type: "summary" },
343+
{ ...modeledMethod, type: "none" },
344+
],
345+
viewState: {
346+
...viewState,
347+
showMultipleModels: true,
348+
},
349+
});
350+
351+
const addButtons = screen.queryAllByLabelText("Add new model");
352+
expect(addButtons.length).toBe(1);
353+
expect(addButtons[0]?.getElementsByTagName("input")[0]).toBeEnabled();
354+
355+
const removeButtons = screen.queryAllByLabelText("Remove model");
356+
expect(removeButtons.length).toBe(3);
357+
for (const removeButton of removeButtons) {
358+
expect(removeButton?.getElementsByTagName("input")[0]).toBeEnabled();
359+
}
360+
});
254361
});

0 commit comments

Comments
 (0)