Skip to content

Conversation

@sjd78
Copy link
Member

@sjd78 sjd78 commented Nov 25, 2025

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Refactored Analysis Wizard with improved multi-step flow and state management for better form validation across analysis mode, targets, scope, custom rules, options, and review steps.
    • Enhanced file upload capability for custom rule files with validation support.
  • Refactor

    • Updated task naming convention in analysis workflow (.analyzer suffix).
    • Restructured internal wizard state management and component architecture for improved maintainability.

✏️ Tip: You can customize this high-level summary in your review settings.

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 konveyor#2713

Signed-off-by: Scott J Dickerson <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

Refactors the Analysis Wizard to use reducer-based state management instead of form context. Introduces new hooks for form watching, taskgroup lifecycle management, and centralized wizard state. Replaces context providers with explicit props, modularizes step components, consolidates schema definitions, and removes legacy context patterns.

Changes

Cohort / File(s) Change Summary
New Hooks
client/src/app/hooks/useFormChangeHandler.ts, client/src/app/pages/applications/analysis-wizard/useTaskGroupManager.ts, client/src/app/pages/applications/analysis-wizard/useWizardReducer.ts
Added three new React hooks: useFormChangeHandler watches form fields and triggers callbacks on state changes; useTaskGroupManager manages taskgroup create/submit/delete lifecycle with mutations and notifications; useWizardReducer provides centralized wizard state management using Immer with per-step action dispatchers.
Removed Context
client/src/app/pages/applications/analysis-wizard/components/TaskGroupContext.tsx
Deleted context-based taskgroup management module including TaskGroupContext, TaskGroupProvider, useTaskGroup hook, and related interfaces; functionality replaced by useTaskGroupManager hook.
Refactored Wizard Core
client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx
Major refactor: introduced StepId enum for step flow; integrated useWizardReducer and useTaskGroupManager for centralized state; replaced per-step form handling with step-driven architecture; changed task name suffix from windup to analyzer.
Schema Restructuring
client/src/app/pages/applications/analysis-wizard/schema.ts
Replaced monolithic AnalysisWizardFormValues with modular per-step schemas: added useAnalysisModeSchema, useSetTargetsSchema, useAnalysisScopeSchema, useCustomRulesSchema, useAdvancedOptionsSchema with corresponding state/values types; removed legacy per-step schema hooks.
New/Refactored Step Components
client/src/app/pages/applications/analysis-wizard/steps/analysis-mode.tsx, client/src/app/pages/applications/analysis-wizard/steps/analysis-scope.tsx, client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx, client/src/app/pages/applications/analysis-wizard/steps/custom-rules.tsx, client/src/app/pages/applications/analysis-wizard/steps/advanced-options.tsx (renamed from set-options.tsx)
Converted steps to accept explicit props (onStateChanged, initialState) instead of form context; each step now manages its own form instance with useForm + yupResolver; integrated useFormChangeHandler for state synchronization; removed form context dependencies.
Review Step Update
client/src/app/pages/applications/analysis-wizard/steps/review.tsx
Renamed IReview to ReviewProps and expanded props to receive all step values (targets, scope, customRules, options) explicitly; replaced form context watches with direct prop references.
Removed Legacy Component
client/src/app/pages/applications/analysis-wizard/set-mode.tsx
Deleted SetMode component; functionality merged into new AnalysisMode step component.
File Upload Components
client/src/app/pages/applications/analysis-wizard/components/upload-binary.tsx, client/src/app/pages/applications/analysis-wizard/components/upload-new-rules-files.tsx
UploadBinary refactored to accept explicit props (taskGroup, createTaskGroup, artifact, onArtifactChange) instead of form context; new UploadNewRulesFiles component added for managing custom rule file uploads with field array support.
Utilities
client/src/app/pages/applications/analysis-wizard/utils.ts, client/src/app/utils/utils.ts, client/src/app/pages/applications/analysis-wizard/__tests__/utils.test.tsx
Replaced mode-specific helper exports with unified isModeSupported function; added isNotEmptyString utility; added test suite for isModeSupported covering multiple analysis modes.
ApplicationsTable Update
client/src/app/pages/applications/applications-table/applications-table.tsx
Removed TaskGroupProvider wrapping; rendered AnalysisWizard directly; added navigation guards checking for required object presence before routing.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AnalysisWizard
    participant useWizardReducer as useWizardReducer<br/>(State Management)
    participant StepComponents as Step Components<br/>(AnalysisMode, SetTargets, etc.)
    participant useTaskGroupManager as useTaskGroupManager<br/>(Taskgroup Lifecycle)
    participant API as API

    User->>AnalysisWizard: Opens wizard
    AnalysisWizard->>useWizardReducer: Initialize state with initialRecipe
    useWizardReducer-->>AnalysisWizard: WizardState (mode, targets, scope, customRules, options, isReady)
    
    User->>StepComponents: Interacts with step (e.g., selects mode)
    StepComponents->>StepComponents: useForm validates with schema
    StepComponents->>AnalysisWizard: onStateChanged(stepState)
    AnalysisWizard->>useWizardReducer: setMode/setTargets/etc. dispatch
    useWizardReducer->>useWizardReducer: Draft update with Immer, compute isReady
    useWizardReducer-->>AnalysisWizard: Updated WizardState
    AnalysisWizard-->>StepComponents: Pass updated state to next steps
    
    User->>AnalysisWizard: Clicks Review & Submit
    AnalysisWizard->>useTaskGroupManager: createTaskGroup(taskgroupData from wizard state)
    useTaskGroupManager->>API: POST /taskgroups
    API-->>useTaskGroupManager: Taskgroup created
    useTaskGroupManager-->>AnalysisWizard: taskGroup object
    
    AnalysisWizard->>useTaskGroupManager: submitTaskGroup(taskGroup)
    useTaskGroupManager->>API: POST /taskgroups/{id}/submit
    API-->>useTaskGroupManager: Submission result
    useTaskGroupManager-->>AnalysisWizard: Success/Error notification
    AnalysisWizard-->>User: Navigate away / Show result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Core architectural change: Transition from context-based form state to reducer-based centralized state requires careful review of state flow and synchronization logic across multiple components
  • Multiple new hooks: Three new hooks (useFormChangeHandler, useTaskGroupManager, useWizardReducer) with distinct responsibilities; each requires understanding of their contract and side effects
  • Widespread refactoring: Step components systematically converted to prop-driven architecture (5 modified step files) with schema integration changes
  • Schema reshaping: Breaking down monolithic form schema into modular per-step schemas and their validation dependencies
  • Specific areas requiring attention:
    • State synchronization between useWizardReducer dispatch and step component callbacks in useFormChangeHandler integration
    • Taskgroup creation/submission flow changes in useTaskGroupManager and how errors propagate
    • Review step correctly receives and uses all prop data from new modular state structure
    • Immer draft mutations in useWizardReducer correctly compute isReady validation flag
    • Navigation guards in ApplicationsTable and potential null reference issues

Possibly related PRs

Suggested labels

cherry-pick/release-0.8

Suggested reviewers

  • ibolton336
  • rszwajko

Poem

🐰 From forms of old to reducer's dance,
State flows unified, no scattered stance,
Step by step the wizard glides,
Centralized logic, reducer guides,
Tasks take shape with analyzer's call,
The refactored flow enchants us all!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title ':seedling: Refactor analysis wizard' is clear, specific, and accurately describes the main change—refactoring the analysis wizard component with a new architectural pattern.
Description check ✅ Passed The PR description comprehensively explains the refactoring objectives, detailing the introduction of useWizardReducer for centralized state, individual hook-form instances per step, useTaskGroupManager for lifecycle management, and useFormChangeHandler for form updates.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
client/src/app/pages/applications/analysis-wizard/steps/advanced-options.tsx (1)

154-223: Target labels selector bound to wrong form field.

The HookFormPFGroupController on line 154-156 uses name="selectedSourceLabels" but renders a target labels selector. The component:

  1. Never calls onChange to update the form field
  2. Uses internal selectedTargetLabels state instead
  3. Only calls onSelectedTargetsChanged to update parent state

This means:

  • The form field selectedSourceLabels isn't being updated by this selector
  • The validation state from this field applies to the wrong UI element

Consider either:

  1. Removing HookFormPFGroupController wrapper since this isn't actually a form-controlled field
  2. Adding a separate form field for target labels if they need to be part of the form state
-      <HookFormPFGroupController
-        control={control}
-        name="selectedSourceLabels"
-        label={t("wizard.terms.target", { count: 2 })}
-        fieldId="target-labels"
-        renderInput={({
+      <FormGroup
+        label={t("wizard.terms.target", { count: 2 })}
+        fieldId="target-labels"
+      >
+        <Select
client/src/app/pages/applications/analysis-wizard/schema.ts (1)

155-164: Cross-step validation references non-existent fields in this schema.

The .when() clause references selectedTargetLabels and selectedTargets, but these fields don't exist in CustomRulesStepValues — they belong to SetTargetsValues. In the new per-step schema architecture, this validation will not function correctly because those fields are always undefined in this schema's context.

The TODO comment suggests passing these values via hook parameters. Consider refactoring to:

-export const useCustomRulesSchema = (): yup.SchemaOf<CustomRulesStepValues> => {
+export const useCustomRulesSchema = ({
+  hasTargets,
+}: {
+  hasTargets: boolean;
+}): yup.SchemaOf<CustomRulesStepValues> => {
   return yup.object({
     // ...
     customRulesFiles: yup
       .array()
       .of(UploadFileSchema)
       .when("rulesKind", {
         is: "manual",
-        then: yup.array().of(UploadFileSchema),
+        then: (schema) =>
+          hasTargets ? schema : schema.min(1, "At least 1 Rule File is required"),
         otherwise: (schema) => schema,
       })
-      .when(["selectedTargetLabels", "rulesKind", "selectedTargets"], {
-        // TODO: Rewrite this when clause, input the fields via hook params
-        is: (
-          labels: TargetLabel[],
-          rulesKind: string,
-          selectedTargets: number
-        ) =>
-          labels.length === 0 && rulesKind === "manual" && selectedTargets <= 0,
-        then: (schema) => schema.min(1, "At least 1 Rule File is required"),
-      }),
+      ,

Would you like me to generate a complete implementation or open an issue to track this?

🧹 Nitpick comments (9)
client/src/app/hooks/useFormChangeHandler.ts (1)

48-50: Document stability requirement for onStateChanged callback.

The onStateChanged callback is in the useEffect dependency array. If the caller doesn't wrap it in useCallback, this effect will fire on every render, potentially causing excessive updates or infinite loops. Consider either:

  1. Adding a JSDoc comment to warn callers that onStateChanged should be stable (memoized)
  2. Using a ref to store the callback to avoid re-triggering the effect
+/**
+ * Generic form change handler that watches specific form fields and calls a state change callback.
+ * @template TFormValues - The type of the form values
+ * @template TState - The type of the state object
+ * @template TWatchFields - The tuple type of field paths being watched
+ * 
+ * @note The `onStateChanged` callback should be wrapped in `useCallback` to prevent
+ * excessive effect executions on each render.
+ */
 export const useFormChangeHandler = <
client/src/app/pages/applications/analysis-wizard/components/upload-new-rules-files.tsx (1)

53-56: Acknowledge the TODO for orphaned file cleanup.

The TODO is a valid concern—files uploaded to hub before the user cancels will remain orphaned. This is acceptable as technical debt, but consider tracking it in an issue if it's not addressed in a follow-up.

Do you want me to open an issue to track implementing the cleanup of orphaned uploaded files when the modal is cancelled?

client/src/app/pages/applications/analysis-wizard/utils.ts (1)

10-26: LGTM! Consider combining duplicate case logic.

The refactored isModeSupported function consolidates the previous discrete validators into a single switch-based function, which improves maintainability.

The source-code-deps and source-code cases have identical logic and could be combined using case fallthrough:

     case "source-code-deps":
-      return isNotEmptyString(application?.repository?.url);
-
     case "source-code":
       return isNotEmptyString(application?.repository?.url);
client/src/app/pages/applications/analysis-wizard/steps/analysis-scope.tsx (1)

30-32: Consider moving packageNameSchema outside the component.

The schema is recreated on every render but never changes. Moving it to module scope would avoid unnecessary object allocation:

+const packageNameSchema = yup.string().matches(/^[a-z]+(.[a-z0-9]+)*$/, {
+  message: "Must be a valid Java package name",
+});
+
+export const AnalysisScope: React.FC<AnalysisScopeProps> = ({
   ...
-  const packageNameSchema = yup.string().matches(/^[a-z]+(.[a-z0-9]+)*$/, {
-    message: "Must be a valid Java package name",
-  });
client/src/app/pages/applications/analysis-wizard/steps/analysis-mode.tsx (1)

76-94: Consider using explicit type annotation for better type safety.

The filter(Boolean) pattern works but loses type information. An explicit type guard would be cleaner:

-  const analysisOptions: SelectOptionProps[] = [
+  const analysisOptions = [
     {
       value: "source-code-deps",
       children: "Source code + dependencies",
     },
     ...
     isSingleApp && {
       value: "binary-upload",
       children: "Upload a local binary",
     },
-  ].filter(Boolean);
+  ].filter((opt): opt is SelectOptionProps => Boolean(opt));
client/src/app/pages/applications/analysis-wizard/steps/custom-rules.tsx (2)

159-161: Potential stale initial tab state.

The activeTabKey is initialized using rulesKind from useWatch, but since useWatch subscribes to form values, the initial value during the first render may not reflect the form's defaultValues until after hydration. Consider using initialState.rulesKind directly for the initial state calculation:

-  const [activeTabKey, setActiveTabKey] = React.useState(() =>
-    rulesKind === "manual" ? 0 : rulesKind === "repository" ? 1 : 0
+  const [activeTabKey, setActiveTabKey] = React.useState(() =>
+    initialState.rulesKind === "manual" ? 0 : initialState.rulesKind === "repository" ? 1 : 0
   );

163-177: Consider triggering validation after programmatic value updates.

When using setValue programmatically (lines 169, 184), the form's isValid state may not immediately reflect the new values. Since useFormChangeHandler relies on isValid to propagate state to the parent wizard, consider adding { shouldValidate: true } to ensure validation runs:

-    setValue("customRulesFiles", newCustomRulesFiles);
+    setValue("customRulesFiles", newCustomRulesFiles, { shouldValidate: true });

This ensures the wizard's step-enabled logic stays in sync with form validity.

client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx (1)

119-127: Address TODO: Verify associatedCredentials matching logic.

The TODO comment on line 126 indicates uncertainty about the credentials matching logic. The current implementation matches by name, which should work if identity.name is unique and consistent with what's stored in wizardState.customRules.associatedCredentials.

Would you like me to help verify this logic or open an issue to track the verification?

client/src/app/pages/applications/analysis-wizard/schema.ts (1)

69-75: Consider adding explicit element types to array schemas.

The schema validates that fields are arrays but doesn't enforce element types via .of(). While yup.SchemaOf<SetTargetsValues> provides compile-time type safety, runtime validation is permissive. This may be intentional for a wizard step with controlled inputs.

If stricter runtime validation is desired:

 export const useSetTargetsSchema = (): yup.SchemaOf<SetTargetsValues> => {
   return yup.object({
-    selectedTargetLabels: yup.array(),
-    selectedTargets: yup.array(),
+    selectedTargetLabels: yup.array().of(
+      yup.object().shape({
+        name: yup.string().defined(),
+        label: yup.string().defined(),
+      })
+    ).defined(),
+    selectedTargets: yup.array().defined(),
     targetFilters: yup.object(),
   });
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c83a899 and 346cc09.

📒 Files selected for processing (19)
  • client/src/app/hooks/useFormChangeHandler.ts (1 hunks)
  • client/src/app/pages/applications/analysis-wizard/__tests__/utils.test.tsx (2 hunks)
  • client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx (5 hunks)
  • client/src/app/pages/applications/analysis-wizard/components/TaskGroupContext.tsx (0 hunks)
  • client/src/app/pages/applications/analysis-wizard/components/upload-binary.tsx (4 hunks)
  • client/src/app/pages/applications/analysis-wizard/components/upload-new-rules-files.tsx (1 hunks)
  • client/src/app/pages/applications/analysis-wizard/schema.ts (6 hunks)
  • client/src/app/pages/applications/analysis-wizard/set-mode.tsx (0 hunks)
  • client/src/app/pages/applications/analysis-wizard/steps/advanced-options.tsx (9 hunks)
  • client/src/app/pages/applications/analysis-wizard/steps/analysis-mode.tsx (1 hunks)
  • client/src/app/pages/applications/analysis-wizard/steps/analysis-scope.tsx (5 hunks)
  • client/src/app/pages/applications/analysis-wizard/steps/custom-rules.tsx (12 hunks)
  • client/src/app/pages/applications/analysis-wizard/steps/review.tsx (12 hunks)
  • client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx (8 hunks)
  • client/src/app/pages/applications/analysis-wizard/useTaskGroupManager.ts (1 hunks)
  • client/src/app/pages/applications/analysis-wizard/useWizardReducer.ts (1 hunks)
  • client/src/app/pages/applications/analysis-wizard/utils.ts (1 hunks)
  • client/src/app/pages/applications/applications-table/applications-table.tsx (6 hunks)
  • client/src/app/utils/utils.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • client/src/app/pages/applications/analysis-wizard/set-mode.tsx
  • client/src/app/pages/applications/analysis-wizard/components/TaskGroupContext.tsx
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2558
File: client/src/app/pages/applications/generate-assets-wizard/generate-assets-wizard.tsx:0-0
Timestamp: 2025-08-15T22:35:55.912Z
Learning: The Generate Assets Wizard in client/src/app/pages/applications/generate-assets-wizard/generate-assets-wizard.tsx is designed to accommodate additional wizard steps in the future, so the current conditional rendering of the Results step is intentional and appropriate for the planned architecture.
📚 Learning: 2025-08-15T22:35:55.912Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2558
File: client/src/app/pages/applications/generate-assets-wizard/generate-assets-wizard.tsx:0-0
Timestamp: 2025-08-15T22:35:55.912Z
Learning: The Generate Assets Wizard in client/src/app/pages/applications/generate-assets-wizard/generate-assets-wizard.tsx is designed to accommodate additional wizard steps in the future, so the current conditional rendering of the Results step is intentional and appropriate for the planned architecture.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/steps/analysis-mode.tsx
  • client/src/app/pages/applications/analysis-wizard/useWizardReducer.ts
  • client/src/app/pages/applications/applications-table/applications-table.tsx
  • client/src/app/pages/applications/analysis-wizard/schema.ts
  • client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx
  • client/src/app/pages/applications/analysis-wizard/steps/custom-rules.tsx
  • client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx
📚 Learning: 2025-10-03T14:26:45.291Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2631
File: client/src/app/api/rest/applications.ts:14-15
Timestamp: 2025-10-03T14:26:45.291Z
Learning: In the tackle2-ui codebase, delete and update REST functions in files under `client/src/app/api/rest/` return `AxiosPromise<void>` (which resolves to `AxiosResponse<void>`) rather than unwrapped `Promise<void>`. This return type is expected and correctly handled by the React Query mutation hooks in `client/src/app/queries/`, which accept promise-compatible types including AxiosPromise.

Applied to files:

  • client/src/app/pages/applications/applications-table/applications-table.tsx
📚 Learning: 2025-08-13T08:58:45.985Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2545
File: client/src/app/pages/source-platforms/useFetchPlatformsWithTasks.ts:42-59
Timestamp: 2025-08-13T08:58:45.985Z
Learning: In the groupPlatformTasks function in client/src/app/pages/source-platforms/useFetchPlatformsWithTasks.ts, the non-null assertion on task.platform!.id after filtering for tasks with platform is acceptable and safe according to the code maintainer sjd78.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/useTaskGroupManager.ts
  • client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx
📚 Learning: 2025-07-29T14:20:50.580Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2468
File: client/src/app/pages/source-platforms/usePlatformProviderList.ts:1-3
Timestamp: 2025-07-29T14:20:50.580Z
Learning: TargetCard component is focused on transformation targets, not source platform providers. It imports Target/TargetLabel models and operates independently from usePlatformProviderList. The codebase maintains clear separation between source platforms (managed by usePlatformProviderList) and transformation targets (managed by TargetCard).

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/steps/advanced-options.tsx
  • client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx
  • client/src/app/pages/applications/analysis-wizard/__tests__/utils.test.tsx
  • client/src/app/pages/applications/analysis-wizard/steps/review.tsx
📚 Learning: 2025-07-17T23:29:20.652Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2432
File: client/src/app/components/schema-defined-fields/utils.tsx:42-47
Timestamp: 2025-07-17T23:29:20.652Z
Learning: In the tackle2-ui codebase, when using yup.lazy for optional object validation, the correct TypeScript typing is `ReturnType<typeof yup.lazy<yup.AnySchema>>` instead of passing the generic type parameter directly to yup.lazy, due to the specific version of yup being used.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/schema.ts
📚 Learning: 2025-10-09T06:42:54.063Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2660
File: client/src/app/components/repository-fields/schema.ts:72-99
Timestamp: 2025-10-09T06:42:54.063Z
Learning: In the tackle2-ui repository, prefer the `when, is, then` style for Yup schemas (e.g., `.when(["field"], { is: ..., then: ... })`). Ignore Biome's `noThenProperty` warning in these circumstances.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/schema.ts
📚 Learning: 2025-07-29T14:20:50.580Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2468
File: client/src/app/pages/source-platforms/usePlatformProviderList.ts:1-3
Timestamp: 2025-07-29T14:20:50.580Z
Learning: The TargetCard component operates independently from the usePlatformProviderList hook and source platform functionality. References to cloud providers like "Azure" in TargetCard are unrelated to the platform provider list changes and serve a different purpose in the application.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx
📚 Learning: 2025-08-29T16:01:04.207Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2583
File: client/src/app/pages/identities/components/identity-form/kind-source-form.tsx:17-26
Timestamp: 2025-08-29T16:01:04.207Z
Learning: Skip i18n/internationalization suggestions for the file client/src/app/pages/identities/components/identity-form/kind-source-form.tsx - hardcoded strings like "Username/Password" and "Private Key/Passphrase" should remain as-is.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/steps/custom-rules.tsx
📚 Learning: 2025-08-20T21:09:47.113Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2534
File: client/src/app/pages/archetypes/components/tab-details-content.tsx:116-136
Timestamp: 2025-08-20T21:09:47.113Z
Learning: When dealing with singular/plural translation keys, prefer i18next pluralization (e.g., t("terms.stakeholder", { count: number })) over hardcoded plural keys like "terms.stakeholders" or keys with punctuation like "terms.stakeholder(s)". This provides better internationalization support and cleaner translation management.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/steps/review.tsx
📚 Learning: 2025-07-21T06:01:43.078Z
Learnt from: sjd78
Repo: konveyor/tackle2-ui PR: 2467
File: client/src/app/queries/generators.ts:20-20
Timestamp: 2025-07-21T06:01:43.078Z
Learning: The tackle2-ui codebase uses React Query v4 (tanstack/react-query v4.22.0), where the onError callback in query configurations is still valid and supported. The deprecation of onError callbacks only applies to React Query v5, so suggestions about removing them are premature until the codebase is upgraded.

Applied to files:

  • client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx
🧬 Code graph analysis (8)
client/src/app/pages/applications/analysis-wizard/steps/analysis-mode.tsx (7)
client/src/app/api/models.ts (2)
  • Application (145-170)
  • Taskgroup (499-506)
client/src/app/pages/applications/analysis-wizard/schema.ts (4)
  • AnalysisModeState (28-30)
  • AnalysisMode (21-21)
  • useAnalysisModeSchema (32-56)
  • AnalysisModeValues (23-26)
client/src/app/hooks/useFormChangeHandler.ts (1)
  • useFormChangeHandler (16-51)
client/src/app/pages/applications/analysis-wizard/utils.ts (1)
  • isModeSupported (10-26)
client/src/app/components/HookFormPFFields/HookFormPFGroupController.tsx (1)
  • HookFormPFGroupController (42-91)
client/src/app/components/SimpleSelectBasic.tsx (1)
  • SimpleSelectBasic (21-72)
client/src/app/pages/applications/analysis-wizard/components/upload-binary.tsx (1)
  • UploadBinary (43-216)
client/src/app/pages/applications/analysis-wizard/useWizardReducer.ts (1)
client/src/app/pages/applications/analysis-wizard/schema.ts (5)
  • AnalysisModeState (28-30)
  • SetTargetsState (65-67)
  • AnalysisScopeState (87-89)
  • CustomRulesStepState (124-126)
  • AdvancedOptionsState (198-200)
client/src/app/pages/applications/analysis-wizard/components/upload-new-rules-files.tsx (2)
client/src/app/api/models.ts (2)
  • Taskgroup (499-506)
  • UploadFile (600-609)
client/src/app/components/CustomRuleFilesUpload.tsx (1)
  • CustomRuleFilesUpload (52-248)
client/src/app/pages/applications/analysis-wizard/useTaskGroupManager.ts (4)
client/src/app/components/NotificationsContext.tsx (1)
  • NotificationsContext (31-33)
client/src/app/api/models.ts (2)
  • Taskgroup (499-506)
  • New (9-9)
client/src/app/queries/taskgroups.ts (3)
  • useCreateTaskgroupMutation (15-18)
  • useSubmitTaskgroupMutation (20-37)
  • useDeleteTaskgroupMutation (75-89)
client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx (1)
  • defaultTaskgroup (64-71)
client/src/app/pages/applications/analysis-wizard/schema.ts (3)
client/src/app/pages/applications/analysis-wizard/steps/analysis-mode.tsx (1)
  • AnalysisMode (34-155)
client/src/app/pages/applications/analysis-wizard/utils.ts (1)
  • useAnalyzableApplicationsByMode (42-55)
client/src/app/pages/applications/analysis-wizard/steps/analysis-scope.tsx (1)
  • AnalysisScope (23-155)
client/src/app/pages/applications/analysis-wizard/utils.ts (2)
client/src/app/api/models.ts (1)
  • Application (145-170)
client/src/app/utils/utils.ts (1)
  • isNotEmptyString (267-269)
client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx (6)
client/src/app/queries/targets.ts (1)
  • useFetchTargets (19-67)
client/src/app/components/FilterToolbar/FilterToolbar.tsx (1)
  • FilterSelectOptionProps (27-33)
client/src/app/utils/rules-utils.ts (1)
  • toLabelValue (84-84)
client/src/app/utils/utils.ts (1)
  • universalComparator (218-224)
client/src/app/pages/applications/analysis-wizard/schema.ts (3)
  • SetTargetsState (65-67)
  • useSetTargetsSchema (69-75)
  • SetTargetsValues (59-63)
client/src/app/hooks/useFormChangeHandler.ts (1)
  • useFormChangeHandler (16-51)
client/src/app/pages/applications/analysis-wizard/__tests__/utils.test.tsx (2)
client/src/app/api/models.ts (1)
  • Application (145-170)
client/src/app/pages/applications/analysis-wizard/utils.ts (1)
  • isModeSupported (10-26)
🪛 Biome (2.1.2)
client/src/app/pages/applications/analysis-wizard/schema.ts

[error] 53-53: Do not add then to an object.

(lint/suspicious/noThenProperty)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: unit-test
  • GitHub Check: build-and-upload-for-global-ci
🔇 Additional comments (21)
client/src/app/utils/utils.ts (1)

267-269: LGTM!

Clean utility function that properly handles undefined, null, and whitespace-only strings.

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

59-191: LGTM!

Comprehensive test coverage for isModeSupported with good edge case handling. Tests properly validate all mode branches including the default false return for unknown modes.

client/src/app/pages/applications/applications-table/applications-table.tsx (2)

1324-1330: LGTM!

Clean removal of the TaskGroupProvider wrapper. The AnalysisWizard now manages its own taskgroup lifecycle internally via useTaskGroupManager, which aligns with the PR's goal of centralizing state management within the wizard.


847-864: Good defensive guards added for navigation.

The added checks for application?.id and archetypeRefsToOverride?.length prevent potential navigation errors when these values are unexpectedly null/undefined.

client/src/app/pages/applications/analysis-wizard/useWizardReducer.ts (2)

69-103: LGTM! Well-structured reducer with Immer integration.

The reducer pattern is clean, with proper action handling and centralized isReady validation after each mutation.

The RESET case correctly returns the pre-computed firstInitialState (which has isReady already calculated in useImmerInitialState), so the validation logic is properly bypassed since it was already applied.


121-178: LGTM! Stable callback pattern is well-implemented.

The hook provides properly memoized callbacks via useCallback with correct dependencies. The initial state computation via useImmerInitialState ensures the recipe is only applied once, aligning with React 18's useReducer initialization pattern as noted in the comment.

client/src/app/pages/applications/analysis-wizard/steps/analysis-scope.tsx (1)

46-72: LGTM! Clean form-to-wizard state synchronization.

The useFormChangeHandler correctly maps all watched fields to state, and the separate useWatch for UI rendering keeps the component reactive. The optional chaining on watched values handles the initial undefined state correctly.

client/src/app/pages/applications/analysis-wizard/steps/analysis-mode.tsx (2)

34-66: LGTM! Clean integration with form and wizard state management.

The component correctly:

  • Initializes form state from initialState
  • Uses the schema hook for validation with proper i18n support
  • Syncs form changes to wizard state via useFormChangeHandler

145-152: LGTM! Clean UploadBinary integration.

The component correctly passes task group management props and wires the artifact change callback to update form state, maintaining proper data flow between the upload component and the wizard.

client/src/app/pages/applications/analysis-wizard/components/upload-binary.tsx (3)

53-59: Potential stale state if artifact prop changes externally.

The fileUploadProgress and fileUploadStatus states are initialized from the artifact prop but won't update if the prop changes after mount. If the artifact can be reset externally (e.g., via wizard reset), the UI state could become inconsistent.

Consider syncing state with prop changes:

+  React.useEffect(() => {
+    if (artifact) {
+      setFileUploadProgress(artifact.size > 0 ? 100 : undefined);
+      setFileUploadStatus("success");
+    } else {
+      setFileUploadProgress(undefined);
+      setFileUploadStatus(undefined);
+    }
+  }, [artifact]);

133-155: LGTM! Clean async task group creation pattern.

The lazy task group creation (taskGroup ? taskGroup.id : (await createTaskGroup()).id) correctly ensures a task group exists before uploading, and the error handling properly catches failures from both file reading and task group creation.


157-164: LGTM! Defensive check for task group existence.

The guard if (taskGroup) correctly handles the edge case where a user might try to clear the file before the upload completes (when no task group exists yet on the server).

client/src/app/pages/applications/analysis-wizard/steps/review.tsx (2)

48-55: LGTM!

The component signature cleanly accepts all required data via explicit props, removing the form context dependency. This aligns well with the new reducer-based wizard architecture.


128-130: I'm unable to access the repository at this moment due to persistent clone failures. However, I can assess the review comment based on the code logic and the concern raised.

The review comment raises a valid verification point about the withKnownLibs format assumption, but without access to the codebase, I cannot definitively confirm:

  • The actual type definition of AnalysisScopeValues.withKnownLibs
  • How the field is populated in the AnalysisScope step
  • Whether it's consistently formatted as a comma-separated string

withKnownLibs string format should be verified for consistency.

The code at lines 128-130 assumes scope.withKnownLibs is a comma-separated string (e.g., "app,oss,select"). Confirm this format aligns with:

  • The type definition of AnalysisScopeValues.withKnownLibs
  • How the field is populated in the AnalysisScope step component
  • Whether single values (without commas) are possible, and if so, whether split(",") handles them correctly

This assumption should be validated against the actual data structure and initialization logic.

client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx (1)

41-102: Good extraction of data fetching logic.

The useTargetsData hook cleanly encapsulates target fetching, tag categories, provider computation, and label options. This improves testability and separation of concerns.

client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx (2)

155-162: Address TODO: Verify includedPackages logic.

The TODO on line 156 questions whether the withKnownLibs.includes("select") check is correct for determining when to include packages. Based on the AnalysisScope step, this should be correct since "select" is the option for manually selecting packages. Consider removing the TODO after verification or clarifying the expected behavior.


81-88: LGTM!

The StepId enum provides clear, type-safe step identification. The numeric values enable the isStepEnabled logic to work correctly with sequential step navigation.

client/src/app/pages/applications/analysis-wizard/steps/advanced-options.tsx (1)

224-295: LGTM!

The sources selector is correctly implemented - it uses the form's value from the field prop, properly calls onChange to update the form state, and handles selection/deselection logic correctly.

client/src/app/pages/applications/analysis-wizard/schema.ts (3)

14-56: LGTM!

The analysis mode schema correctly validates mode compatibility based on analyzable applications and conditionally requires the artifact for binary-upload mode. The when/is/then pattern on line 53 is the preferred style in this codebase, so the Biome noThenProperty warning can be safely ignored. Based on learnings.


77-111: LGTM!

The scope schema correctly uses conditional validation. The withKnownLibs.includes("select") check works as intended since it's checking if the string value (e.g., "app,oss,select") contains the substring "select".


190-215: LGTM!

The advanced options schema correctly validates the nested TargetLabel shape for selectedSourceLabels and properly marks boolean fields as .defined().

Comment on lines +246 to +255
// For custom rules and options steps, we need additional state management
const [
selectedTargetLabelsForCustomRules,
setSelectedTargetLabelsForCustomRules,
] = React.useState(state.targets.selectedTargetLabels);

const handleTargetsChanged = (newTargetsState: typeof state.targets) => {
setTargets(newTargetsState);
setSelectedTargetLabelsForCustomRules(newTargetsState.selectedTargetLabels);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential state synchronization issue with selectedTargetLabelsForCustomRules.

The selectedTargetLabelsForCustomRules state is initialized from state.targets.selectedTargetLabels but only updated via handleTargetsChanged. However, the CustomRules component can also modify labels via onSelectedTargetLabelsChanged (line 372), which updates selectedTargetLabelsForCustomRules but doesn't sync back to state.targets.selectedTargetLabels.

This creates a divergence where:

  1. SetTargets updates → syncs to both
  2. CustomRules updates → only updates local state, not state.targets

Consider whether changes from CustomRules should also update the wizard's target state for consistency across steps.

🤖 Prompt for AI Agents
client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx around
lines 246-255: selectedTargetLabelsForCustomRules is seeded from
state.targets.selectedTargetLabels but can be mutated by CustomRules without
updating state.targets, causing state divergence; update the
onSelectedTargetLabelsChanged handler to both
setSelectedTargetLabelsForCustomRules(newLabels) and propagate the change into
the wizard target state by calling setTargets(prev => ({ ...prev,
selectedTargetLabels: newLabels })) (or consolidate into a single source of
truth by removing the duplicate local state and always reading/writing
selectedTargetLabels from state.targets).

onClose();
};

const isAddDisabled = !fields.every(({ status }) => status === "uploaded");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add button enabled when no files are uploaded.

When fields is empty, [].every(...) returns true, making isAddDisabled = false. This allows clicking "Add" with zero files, which is likely unintended.

-  const isAddDisabled = !fields.every(({ status }) => status === "uploaded");
+  const isAddDisabled =
+    fields.length === 0 ||
+    !fields.every(({ status }) => status === "uploaded");
🤖 Prompt for AI Agents
client/src/app/pages/applications/analysis-wizard/components/upload-new-rules-files.tsx
around line 63: the current expression uses [].every(...) which returns true for
an empty array and thus enables the Add button when no files exist; change the
logic to disable Add when fields is empty or when any field is not uploaded —
e.g., set isAddDisabled to true if fields.length === 0 OR not every status ===
"uploaded". Update the line to first check fields.length and then the every(...)
result so the Add button is only enabled when there is at least one field and
all fields have status "uploaded".

Comment on lines +98 to +103
onChangeRuleFile={(ruleFile) => {
const index = fields.findIndex(
(f) => f.fileName === ruleFile.fileName
);
update(index, ruleFile);
}}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against file not found in onChangeRuleFile.

If findIndex returns -1 (file not in fields), calling update(-1, ruleFile) may cause unexpected behavior with react-hook-form's useFieldArray.

        onChangeRuleFile={(ruleFile) => {
          const index = fields.findIndex(
            (f) => f.fileName === ruleFile.fileName
          );
-          update(index, ruleFile);
+          if (index >= 0) {
+            update(index, ruleFile);
+          }
        }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onChangeRuleFile={(ruleFile) => {
const index = fields.findIndex(
(f) => f.fileName === ruleFile.fileName
);
update(index, ruleFile);
}}
onChangeRuleFile={(ruleFile) => {
const index = fields.findIndex(
(f) => f.fileName === ruleFile.fileName
);
if (index >= 0) {
update(index, ruleFile);
}
}}
🤖 Prompt for AI Agents
In
client/src/app/pages/applications/analysis-wizard/components/upload-new-rules-files.tsx
around lines 98 to 103, onChangeRuleFile calls update(index, ruleFile) without
checking the result of fields.findIndex; if findIndex returns -1 this will call
update(-1, ...) and can break useFieldArray. Fix: guard the index by testing if
index !== -1 before calling update; if index === -1, decide and implement the
desired behavior (e.g., push the new ruleFile, ignore, or log/warn) so update is
only called with a valid field array index.

Comment on lines +135 to +138
// Track selected target labels internally for this step
const [selectedTargetLabels, setSelectedTargetLabels] = React.useState<
TargetLabel[]
>([]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Internal selectedTargetLabels state not initialized from props.

The selectedTargetLabels state is initialized to an empty array, but when navigating back to this step, the previously selected target labels won't be restored. Consider initializing from the parent's state or deriving from selectedTargets:

-  const [selectedTargetLabels, setSelectedTargetLabels] = React.useState<
-    TargetLabel[]
-  >([]);
+  const [selectedTargetLabels, setSelectedTargetLabels] = React.useState<
+    TargetLabel[]
+  >(() => {
+    // Derive initial labels from selectedTargets prop
+    return selectedTargets.flatMap((target) => target.labels ?? []);
+  });

Alternatively, this could be passed as a prop from the parent if it needs to be synchronized across steps.

🤖 Prompt for AI Agents
In client/src/app/pages/applications/analysis-wizard/steps/advanced-options.tsx
around lines 135 to 138, the internal state selectedTargetLabels is always
initialized to an empty array so previously selected labels are lost when
navigating back; initialize it from the incoming prop (e.g., selectedTargets or
a prop provided by the parent) and keep it in sync by either: (1) using
useState(() => initialSelectedTargets) where initialSelectedTargets comes from
props, or (2) adding a useEffect that updates setSelectedTargetLabels when the
prop changes, or (3) accept selectedTargetLabels and setSelectedTargetLabels as
props from the parent to fully synchronize across steps. Ensure you preserve the
same shape (TargetLabel[]) when initializing and syncing.

Comment on lines +141 to +150
useFormChangeHandler({
form,
onStateChanged,
watchFields: ["selectedTargetLabels", "selectedTargets"] as const,
mapValuesToState: ([selectedTargetLabels, selectedTargets], isValid) => ({
selectedTargetLabels,
selectedTargets,
isValid,
}),
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

targetFilters not propagated to wizard state.

The mapValuesToState function only maps selectedTargetLabels and selectedTargets, but the form's defaultValues includes targetFilters (line 135). This means filter state won't be preserved when navigating between wizard steps. Consider including targetFilters in the state propagation:

   useFormChangeHandler({
     form,
     onStateChanged,
-    watchFields: ["selectedTargetLabels", "selectedTargets"] as const,
-    mapValuesToState: ([selectedTargetLabels, selectedTargets], isValid) => ({
+    watchFields: ["selectedTargetLabels", "selectedTargets", "targetFilters"] as const,
+    mapValuesToState: ([selectedTargetLabels, selectedTargets, targetFilters], isValid) => ({
       selectedTargetLabels,
       selectedTargets,
+      targetFilters,
       isValid,
     }),
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useFormChangeHandler({
form,
onStateChanged,
watchFields: ["selectedTargetLabels", "selectedTargets"] as const,
mapValuesToState: ([selectedTargetLabels, selectedTargets], isValid) => ({
selectedTargetLabels,
selectedTargets,
isValid,
}),
});
useFormChangeHandler({
form,
onStateChanged,
watchFields: ["selectedTargetLabels", "selectedTargets", "targetFilters"] as const,
mapValuesToState: ([selectedTargetLabels, selectedTargets, targetFilters], isValid) => ({
selectedTargetLabels,
selectedTargets,
targetFilters,
isValid,
}),
});
🤖 Prompt for AI Agents
In client/src/app/pages/applications/analysis-wizard/steps/set-targets.tsx
around lines 141 to 150, the form change handler only propagates
selectedTargetLabels and selectedTargets so targetFilters from the form's
defaultValues isn't preserved; update useFormChangeHandler to also watch
"targetFilters" (add it to watchFields) and include targetFilters in
mapValuesToState (return { selectedTargetLabels, selectedTargets, targetFilters,
isValid }), ensuring any types/const assertions are adjusted accordingly so the
wizard state retains filter values when navigating steps.

Comment on lines +65 to +75
const createTaskGroup = async (
taskgroupData?: New<Taskgroup>
): Promise<Taskgroup> => {
if (!taskGroup) {
const newTaskGroup = await createTaskgroupAsync(
taskgroupData || defaultTaskgroup
);
return newTaskGroup;
}
return taskGroup;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential race condition in createTaskGroup.

If createTaskGroup is called multiple times in quick succession (e.g., rapid user clicks), the !taskGroup check on line 68 could pass for each call before the state updates from the first successful creation. This could result in multiple taskgroups being created.

Consider using a ref to track in-flight creation or returning early if a creation is already pending:

+import { useContext, useRef, useState } from "react";
 
 export const useTaskGroupManager = () => {
   const { pushNotification } = useContext(NotificationsContext);
   const [taskGroup, setTaskGroup] = useState<Taskgroup | null>(null);
+  const isCreatingRef = useRef(false);
 
   // ... mutations ...
 
   const createTaskGroup = async (
     taskgroupData?: New<Taskgroup>
   ): Promise<Taskgroup> => {
-    if (!taskGroup) {
+    if (!taskGroup && !isCreatingRef.current) {
+      isCreatingRef.current = true;
-      const newTaskGroup = await createTaskgroupAsync(
-        taskgroupData || defaultTaskgroup
-      );
-      return newTaskGroup;
+      try {
+        const newTaskGroup = await createTaskgroupAsync(
+          taskgroupData || defaultTaskgroup
+        );
+        return newTaskGroup;
+      } finally {
+        isCreatingRef.current = false;
+      }
     }
     return taskGroup;
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const createTaskGroup = async (
taskgroupData?: New<Taskgroup>
): Promise<Taskgroup> => {
if (!taskGroup) {
const newTaskGroup = await createTaskgroupAsync(
taskgroupData || defaultTaskgroup
);
return newTaskGroup;
}
return taskGroup;
};
const createTaskGroup = async (
taskgroupData?: New<Taskgroup>
): Promise<Taskgroup> => {
if (!taskGroup && !isCreatingRef.current) {
isCreatingRef.current = true;
try {
const newTaskGroup = await createTaskgroupAsync(
taskgroupData || defaultTaskgroup
);
return newTaskGroup;
} finally {
isCreatingRef.current = false;
}
}
return taskGroup;
};
🤖 Prompt for AI Agents
In client/src/app/pages/applications/analysis-wizard/useTaskGroupManager.ts
around lines 65 to 75, the createTaskGroup function has a race condition because
multiple concurrent calls can pass the !taskGroup check before state updates;
fix it by introducing an in-flight guard (e.g., a ref boolean isCreatingRef or
store a pending creation Promise) that you set before calling
createTaskgroupAsync and clear in a finally block, ensure subsequent calls
return the pending Promise or early-return while isCreatingRef is true, and
update taskGroup state once the single creation completes so only one taskgroup
is created.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant