Skip to content

Commit 41ce86d

Browse files
refactor(frontend): refactor ScanRangeInput
1 parent 8e6b33c commit 41ce86d

File tree

4 files changed

+103
-58
lines changed

4 files changed

+103
-58
lines changed

frontend/workflows-lib/lib/components/template/controls/ScanRangeInput.tsx

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import { InfoOutlined } from "@mui/icons-material";
1010
import { validateScanRange } from "../../../utils/validationUtils";
1111
import { ScanRange } from "../../../types";
1212

13+
export interface RawScanRange {
14+
excludedRaw: string;
15+
start: string;
16+
end: string;
17+
}
18+
1319
export interface ScanRangeInputProps {
1420
name: string;
1521
value: ScanRange;
@@ -41,13 +47,13 @@ const ScanRangeInput = ({
4147
end: string;
4248
excluded: string;
4349
};
44-
excludedRaw: string;
45-
start: string;
46-
end: string;
50+
rawRange: RawScanRange;
4751
}
4852

53+
type ScanRangeType = "start" | "end" | "excluded";
54+
4955
interface ScanRangeAction {
50-
type: "start" | "end" | "excluded";
56+
type: ScanRangeType;
5157
payload: string;
5258
}
5359

@@ -59,23 +65,19 @@ const ScanRangeInput = ({
5965

6066
switch (action.type) {
6167
case "start":
62-
newState.start = action.payload;
68+
newState.rawRange.start = action.payload;
6369
break;
6470
case "end":
65-
newState.end = action.payload;
71+
newState.rawRange.end = action.payload;
6672
break;
6773
case "excluded":
68-
newState.excludedRaw = action.payload;
74+
newState.rawRange.excludedRaw = action.payload;
6975
break;
7076
default:
7177
return state;
7278
}
7379

74-
const result = validateScanRange(
75-
newState.start,
76-
newState.end,
77-
newState.excludedRaw,
78-
);
80+
const result = validateScanRange(newState.rawRange);
7981
newState.componentError = result.errors;
8082

8183
if (result.parsed) {
@@ -90,19 +92,31 @@ const ScanRangeInput = ({
9092
return newState;
9193
}
9294

93-
const [scanRange, handleScanRange] = useReducer(scanRangeReducer, {
95+
const [scanRange, dispatch] = useReducer(scanRangeReducer, {
9496
componentError: {
9597
start: "",
9698
end: "",
9799
excluded: "",
98100
},
99-
excludedRaw: Array.isArray(value.excluded) ? value.excluded.join(", ") : "",
100-
start: String(value.start || ""),
101-
end: String(value.end || ""),
101+
rawRange: {
102+
excludedRaw: Array.isArray(value.excluded)
103+
? value.excluded.join(", ")
104+
: "",
105+
start: String(value.start || ""),
106+
end: String(value.end || ""),
107+
},
102108
});
103109

104110
if (!visible) return null;
105111

112+
const handleScanRangeChange =
113+
(
114+
type: ScanRangeType,
115+
): React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> =>
116+
(event) => {
117+
dispatch({ type: type, payload: event.target.value });
118+
};
119+
106120
return (
107121
<Box
108122
id={id}
@@ -137,15 +151,13 @@ const ScanRangeInput = ({
137151
<TextField
138152
type="number"
139153
label="Start"
140-
value={scanRange.start}
141-
onChange={(event) => {
142-
handleScanRange({ type: "start", payload: event.target.value });
143-
}}
154+
value={scanRange.rawRange.start}
155+
onChange={handleScanRangeChange("start")}
144156
error={!!scanRange.componentError.start}
145157
helperText={scanRange.componentError.start || " "}
146158
disabled={!enabled}
147159
slotProps={{
148-
htmlInput: { step: 1 },
160+
htmlInput: { step: 1, min: 0 },
149161
formHelperText: {
150162
sx: { minHeight: "3em", whiteSpace: "pre-wrap" },
151163
},
@@ -159,15 +171,13 @@ const ScanRangeInput = ({
159171
<TextField
160172
type="number"
161173
label="End"
162-
value={scanRange.end}
163-
onChange={(event) => {
164-
handleScanRange({ type: "end", payload: event.target.value });
165-
}}
174+
value={scanRange.rawRange.end}
175+
onChange={handleScanRangeChange("end")}
166176
error={!!scanRange.componentError.end}
167177
helperText={scanRange.componentError.end || " "}
168178
disabled={!enabled}
169179
slotProps={{
170-
htmlInput: { step: 1 },
180+
htmlInput: { step: 1, min: 0 },
171181
formHelperText: {
172182
sx: { minHeight: "3em", whiteSpace: "pre-wrap" },
173183
},
@@ -180,10 +190,8 @@ const ScanRangeInput = ({
180190

181191
<TextField
182192
label="Excluded"
183-
value={scanRange.excludedRaw}
184-
onChange={(event) => {
185-
handleScanRange({ type: "excluded", payload: event.target.value });
186-
}}
193+
value={scanRange.rawRange.excludedRaw}
194+
onChange={handleScanRangeChange("excluded")}
187195
error={!!scanRange.componentError.excluded}
188196
helperText={scanRange.componentError.excluded || " "}
189197
disabled={!enabled}

frontend/workflows-lib/lib/components/template/jsonforms/JsonFormsScanRangeRenderer.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { withJsonFormsControlProps } from "@jsonforms/react";
55
import { ControlProps } from "@jsonforms/core";
66
import ScanRangeInput from "../controls/ScanRangeInput";
77
import { ScanRange } from "../../../types";
8-
import { useMemo } from "react";
98

109
const ScanRangeControl = ({
1110
data,
@@ -23,18 +22,14 @@ const ScanRangeControl = ({
2322
handleChange(path, value);
2423
};
2524

26-
const localValue: ScanRange = useMemo(() => {
27-
return {
28-
start: data?.start,
29-
end: data?.end,
30-
excluded: data?.excluded ?? [],
31-
};
32-
}, [data]);
33-
3425
return (
3526
<ScanRangeInput
3627
name={path}
37-
value={localValue}
28+
value={{
29+
start: data?.start,
30+
end: data?.end,
31+
excluded: data?.excluded ?? [],
32+
}}
3833
handleChange={handleBufferedChange}
3934
label={label}
4035
description={description}

frontend/workflows-lib/lib/utils/validationUtils.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RawScanRange } from "../components/template/controls/ScanRangeInput";
12
import { ScanRange } from "../types";
23

34
export const isStrictPositiveInteger = (value: string): boolean =>
@@ -50,22 +51,22 @@ export interface ScanRangeValidationResult {
5051
}
5152

5253
export const validateScanRange = (
53-
startStr: string,
54-
endStr: string,
55-
excludedRaw: string,
54+
scanRange: RawScanRange,
5655
): ScanRangeValidationResult => {
57-
const validStart = isStrictPositiveInteger(startStr);
58-
const validEnd = isStrictPositiveInteger(endStr);
56+
const validStart = isStrictPositiveInteger(scanRange.start);
57+
const validEnd = isStrictPositiveInteger(scanRange.end);
5958

60-
const newStart = parseInt(startStr, 10);
61-
const newEnd = parseInt(endStr, 10);
59+
const newStart = parseInt(scanRange.start, 10);
60+
const newEnd = parseInt(scanRange.end, 10);
6261

6362
let excludedScans: number[] = [];
6463
let excludedError = "";
6564

6665
try {
6766
excludedScans =
68-
excludedRaw.trim() === "" ? [] : parseExcludedScans(excludedRaw);
67+
scanRange.excludedRaw.trim() === ""
68+
? []
69+
: parseExcludedScans(scanRange.excludedRaw);
6970
const outOfRange = excludedScans.filter(
7071
(num) => num < newStart || num > newEnd,
7172
);

frontend/workflows-lib/tests/functions/validationUtils.test.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RawScanRange } from "../../lib/components/template/controls/ScanRangeInput";
12
import {
23
isStrictPositiveInteger,
34
parseExcludedScans,
@@ -65,60 +66,100 @@ describe("parseExcludedScans", () => {
6566

6667
describe("validateScanRange", () => {
6768
it("validates correct input", () => {
68-
const result = validateScanRange("1", "10", "3, 4, 5");
69+
const result = validateScanRange({
70+
start: "1",
71+
end: "10",
72+
excludedRaw: "3, 4, 5",
73+
} as RawScanRange);
6974
expect(result.errors).toEqual({ start: "", end: "", excluded: "" });
7075
expect(result.parsed).toEqual({ start: 1, end: 10, excluded: [3, 4, 5] });
7176
});
7277

7378
it("returns error for invalid start", () => {
74-
const result = validateScanRange("abc", "10", "3, 4");
79+
const result = validateScanRange({
80+
start: "abc",
81+
end: "10",
82+
excludedRaw: "3, 4",
83+
} as RawScanRange);
7584
expect(result.errors.start).toBe("Start must be a positive integer");
7685
expect(result.parsed).toBeUndefined();
7786
});
7887

7988
it("returns error for invalid end", () => {
80-
const result = validateScanRange("1", "abc", "3, 4");
89+
const result = validateScanRange({
90+
start: "1",
91+
end: "abc",
92+
excludedRaw: "3, 4",
93+
} as RawScanRange);
8194
expect(result.errors.end).toBe("End must be a positive integer");
8295
expect(result.parsed).toBeUndefined();
8396
});
8497

8598
it("returns error when end < start", () => {
86-
const result = validateScanRange("10", "5", "3, 4");
99+
const result = validateScanRange({
100+
start: "10",
101+
end: "5",
102+
excludedRaw: "3, 4",
103+
} as RawScanRange);
87104
expect(result.errors.end).toBe("End cannot be less than start");
88105
expect(result.parsed).toBeUndefined();
89106
});
90107

91108
it("returns error for excluded values out of range", () => {
92-
const result = validateScanRange("1", "5", "2, 6");
109+
const result = validateScanRange({
110+
start: "1",
111+
end: "5",
112+
excludedRaw: "2, 6",
113+
} as RawScanRange);
93114
expect(result.errors.excluded).toBe("Excluded values out of range");
94115
expect(result.parsed).toBeUndefined();
95116
});
96117

97118
it("returns error for invalid excluded format", () => {
98-
const result = validateScanRange("1", "10", "abc");
119+
const result = validateScanRange({
120+
start: "1",
121+
end: "10",
122+
excludedRaw: "abc",
123+
} as RawScanRange);
99124
expect(result.errors.excluded).toBe("Must be positive integers or ranges");
100125
expect(result.parsed).toBeUndefined();
101126
});
102127

103128
it("returns empty excluded array for empty input", () => {
104-
const result = validateScanRange("1", "10", "");
129+
const result = validateScanRange({
130+
start: "1",
131+
end: "10",
132+
excludedRaw: "",
133+
} as RawScanRange);
105134
expect(result.errors.excluded).toBe("");
106135
expect(result.parsed?.excluded).toEqual([]);
107136
});
108137
it("handles duplicates", () => {
109-
const result = validateScanRange("1", "10", "1, 1, 9");
138+
const result = validateScanRange({
139+
start: "1",
140+
end: "10",
141+
excludedRaw: "1, 1, 9",
142+
} as RawScanRange);
110143
expect(result.errors).toEqual({ start: "", end: "", excluded: "" });
111144
expect(result.parsed?.excluded).toEqual([1, 9]);
112145
});
113146

114147
it("handles overlapping ranges", () => {
115-
const result = validateScanRange("1", "10", "3-5, 4-6");
148+
const result = validateScanRange({
149+
start: "1",
150+
end: "10",
151+
excludedRaw: "3-5, 4-6",
152+
});
116153
expect(result.errors).toEqual({ start: "", end: "", excluded: "" });
117154
expect(result.parsed?.excluded).toEqual([3, 4, 5, 6]);
118155
});
119156

120157
it("handles mixed excluded input", () => {
121-
const result = validateScanRange("1", "10", "3, 3-5, [4, 5]");
158+
const result = validateScanRange({
159+
start: "1",
160+
end: "10",
161+
excludedRaw: "3, 3-5, [4, 5]",
162+
});
122163
expect(result.errors).toEqual({ start: "", end: "", excluded: "" });
123164
expect(result.parsed?.excluded).toEqual([3, 4, 5]);
124165
});

0 commit comments

Comments
 (0)