Skip to content

Commit 346cc09

Browse files
committed
🌱 Refactor analysis wizard
Refactor analysis wizard to use the wizard pattern used by the generate assets wizard. - The wizard's state is managed centrally by the `useWizardReducer` hook - Each step has its own hook-form and is initialized from the central state - The `useTaskGroupManager` hook is used to manage the taskgroup lifecycle - The `useFormChangeHandler` hook is used by each hook-form/step to handle changes to the form and pushing it up the the wizard's state Pre-requisite for #2713 Signed-off-by: Scott J Dickerson <[email protected]>
1 parent c83a899 commit 346cc09

File tree

19 files changed

+1504
-917
lines changed

19 files changed

+1504
-917
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useEffect, useMemo } from "react";
2+
import {
3+
FieldPathValues,
4+
FieldValues,
5+
Path,
6+
UseFormReturn,
7+
useWatch,
8+
} from "react-hook-form";
9+
10+
/**
11+
* Generic form change handler that watches specific form fields and calls a state change callback.
12+
* @template TFormValues - The type of the form values
13+
* @template TState - The type of the state object
14+
* @template TWatchFields - The tuple type of field paths being watched
15+
*/
16+
export const useFormChangeHandler = <
17+
TFormValues extends FieldValues,
18+
TState,
19+
TWatchFields extends Path<TFormValues>[],
20+
>({
21+
form,
22+
onStateChanged,
23+
watchFields,
24+
mapValuesToState,
25+
}: {
26+
form: UseFormReturn<TFormValues>;
27+
onStateChanged: (state: TState) => void;
28+
watchFields: TWatchFields;
29+
mapValuesToState: (
30+
values: FieldPathValues<TFormValues, TWatchFields>,
31+
isFormValid: boolean
32+
) => TState;
33+
}) => {
34+
const {
35+
control,
36+
formState: { isValid },
37+
} = form;
38+
39+
const watchedValues = useWatch<TFormValues>({
40+
control,
41+
name: [...watchFields] as Path<TFormValues>[],
42+
}) as FieldPathValues<TFormValues, TWatchFields>;
43+
44+
const state = useMemo((): TState => {
45+
return mapValuesToState(watchedValues, isValid);
46+
}, [mapValuesToState, watchedValues, isValid]);
47+
48+
useEffect(() => {
49+
onStateChanged(state);
50+
}, [onStateChanged, state]);
51+
};

client/src/app/pages/applications/analysis-wizard/__tests__/utils.test.tsx

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Target, TargetLabel } from "@app/api/models";
1+
import { Application, Target, TargetLabel } from "@app/api/models";
22

3-
import { updateSelectedTargetLabels } from "../utils";
3+
import { isModeSupported, updateSelectedTargetLabels } from "../utils";
44

55
const TARGET_LABELS: TargetLabel[] = [
66
{
@@ -56,6 +56,140 @@ const TARGET_B: Target = {
5656
],
5757
};
5858

59+
describe("analysis-wizard utils - isModeSupported()", () => {
60+
it("should return true for binary-upload mode regardless of application state", () => {
61+
const application: Application = {
62+
id: 1,
63+
name: "Test App",
64+
migrationWave: null,
65+
};
66+
expect(isModeSupported(application, "binary-upload")).toBe(true);
67+
});
68+
69+
it("should return true for binary mode when application has valid binary format", () => {
70+
const application: Application = {
71+
id: 1,
72+
name: "Test App",
73+
binary: "group:artifact:version",
74+
migrationWave: null,
75+
};
76+
expect(isModeSupported(application, "binary")).toBe(true);
77+
});
78+
79+
it("should return false for binary mode when application binary is missing", () => {
80+
const application: Application = {
81+
id: 1,
82+
name: "Test App",
83+
migrationWave: null,
84+
};
85+
expect(isModeSupported(application, "binary")).toBe(false);
86+
});
87+
88+
it("should return false for binary mode when application binary is empty", () => {
89+
const application: Application = {
90+
id: 1,
91+
name: "Test App",
92+
binary: "",
93+
migrationWave: null,
94+
};
95+
expect(isModeSupported(application, "binary")).toBe(false);
96+
});
97+
98+
it("should return false for binary mode when application binary does not match pattern", () => {
99+
const application: Application = {
100+
id: 1,
101+
name: "Test App",
102+
binary: "invalid-format",
103+
migrationWave: null,
104+
};
105+
expect(isModeSupported(application, "binary")).toBe(false);
106+
});
107+
108+
it("should return true for source-code-deps mode when repository URL exists", () => {
109+
const application: Application = {
110+
id: 1,
111+
name: "Test App",
112+
repository: {
113+
url: "https://github.com/user/repo.git",
114+
},
115+
migrationWave: null,
116+
};
117+
expect(isModeSupported(application, "source-code-deps")).toBe(true);
118+
});
119+
120+
it("should return false for source-code-deps mode when repository URL is missing", () => {
121+
const application: Application = {
122+
id: 1,
123+
name: "Test App",
124+
migrationWave: null,
125+
};
126+
expect(isModeSupported(application, "source-code-deps")).toBe(false);
127+
});
128+
129+
it("should return false for source-code-deps mode when repository URL is empty", () => {
130+
const application: Application = {
131+
id: 1,
132+
name: "Test App",
133+
repository: {
134+
url: "",
135+
},
136+
migrationWave: null,
137+
};
138+
expect(isModeSupported(application, "source-code-deps")).toBe(false);
139+
});
140+
141+
it("should return true for source-code mode when repository URL exists", () => {
142+
const application: Application = {
143+
id: 1,
144+
name: "Test App",
145+
repository: {
146+
url: "https://github.com/user/repo.git",
147+
},
148+
migrationWave: null,
149+
};
150+
expect(isModeSupported(application, "source-code")).toBe(true);
151+
});
152+
153+
it("should return false for source-code mode when repository URL is missing", () => {
154+
const application: Application = {
155+
id: 1,
156+
name: "Test App",
157+
migrationWave: null,
158+
};
159+
expect(isModeSupported(application, "source-code")).toBe(false);
160+
});
161+
162+
it("should return false for source-code mode when repository URL is empty", () => {
163+
const application: Application = {
164+
id: 1,
165+
name: "Test App",
166+
repository: {
167+
url: "",
168+
},
169+
migrationWave: null,
170+
};
171+
expect(isModeSupported(application, "source-code")).toBe(false);
172+
});
173+
174+
it("should return false for undefined mode", () => {
175+
const application: Application = {
176+
id: 1,
177+
name: "Test App",
178+
migrationWave: null,
179+
};
180+
expect(isModeSupported(application, undefined)).toBe(false);
181+
});
182+
183+
it("should return false for unknown mode", () => {
184+
const application: Application = {
185+
id: 1,
186+
name: "Test App",
187+
migrationWave: null,
188+
};
189+
expect(isModeSupported(application, "unknown-mode")).toBe(false);
190+
});
191+
});
192+
59193
describe("analysis-wizard utils - updateSelectedTargetLabels()", () => {
60194
it("add a label to an empty list", () => {
61195
const theResult = updateSelectedTargetLabels(

0 commit comments

Comments
 (0)