Skip to content

refactor(console): decompose ModalCreateAction into focused subcomponents #277

Description

@r2dedios

Problem

console/src/app/Actions/Scheduler/components/ModalCreateAction.tsx is a 345-line component that handles the entire action creation flow in a single file:

  1. Operation selection (lines 66-74, 208-228): Scan / Power On / Power Off toggle group
  2. Account selection (lines 76-97): fetch accounts on open, AccountTypeaheadSelect wrapper
  3. Cluster selection (lines 99-126): fetch clusters when account changes, ClusterTypeaheadSelect wrapper
  4. Execution configuration (lines 128-141, 251-328): instant vs scheduled vs cron, datetime picker, cron input with validation
  5. Form validation (lines 49-64): cross-field validation combining operation, target, and execution state
  6. Action submission (lines 143-171): build request object, API call, error handling

All state (11 useState calls) and 3 useEffect calls live at the top level. The component is hard to reason about because changes to any section require understanding the full state tree.

Current State Map

ModalCreateAction
├── State (11 vars):
│   ├── actionOperation, actionType, description          — what to do
│   ├── selectedAccount, allAccounts                      — account target
│   ├── selectedCluster, allClusters                      — cluster target
│   ├── scheduledDateTime, showSchedule                   — when (scheduled)
│   ├── cronExpression, cronTouched                       — when (cron)
│   └── submitError                                       — submission feedback
├── Effects (3):
│   ├── Fetch accounts on modal open
│   ├── Fetch clusters when selectedAccount changes
│   └── Reset all state on modal close
├── Validation: isTargetValid, isExecutionValid, isFormValid
└── Submit handler: handlerConfirmActionCreation

Proposed Solution

Split into 3 focused form sections, keeping the parent as the state orchestrator:

1. OperationSelector.tsx (~40 lines)

The toggle group for Scan / Power On / Power Off. Receives value and onChange as props.

interface OperationSelectorProps {
  value: string;
  onChange: (operation: string) => void;
}

2. TargetSelector.tsx (~80 lines)

Account and cluster selection. Handles fetching accounts/clusters and renders the typeahead selects. Receives the modal's isOpen state to trigger fetches.

interface TargetSelectorProps {
  isOpen: boolean;
  isScan: boolean;
  selectedAccount: AccountResponseApi | null;
  onSelectAccount: (account: AccountResponseApi | null) => void;
  selectedCluster: ClusterResponseApi | null;
  onSelectCluster: (cluster: ClusterResponseApi | null) => void;
}

This component owns allAccounts and allClusters state and their fetch effects, since no other component needs them.

3. ExecutionConfig.tsx (~100 lines)

Schedule checkbox, specific time vs cron radio, datetime picker, cron input with validation. Owns showSchedule, cronTouched state internally.

interface ExecutionConfigProps {
  actionType: ActionTypes;
  onActionTypeChange: (type: ActionTypes) => void;
  scheduledDateTime: string;
  onScheduledDateTimeChange: (dt: string) => void;
  cronExpression: string;
  onCronExpressionChange: (expr: string) => void;
}

Result in ModalCreateAction

The parent shrinks to ~120 lines handling:

  • Top-level state for the values that cross section boundaries (operation, account, cluster, actionType, datetime, cron, description)
  • Cross-field validation (isFormValid)
  • Submit handler
  • Modal chrome (title, confirm/cancel buttons, error alert)
  • Composition of the 3 sections
<Modal ...>
  {submitError && <Alert ... />}
  <Form>
    <OperationSelector value={op} onChange={handleOperationChange} />
    <TargetSelector isOpen={isOpen} isScan={isScan} ... />
    <ExecutionConfig actionType={actionType} ... />
    <FormGroup label="Description">...</FormGroup>
  </Form>
</Modal>

Considerations

  • Validation stays in the parent: isFormValid depends on values from all 3 sections, so it cannot be pushed down.
  • Reset effect stays in the parent: it resets cross-section state on modal close.
  • handleOperationChange resets cluster, schedule, and cron state — it stays in the parent since it spans sections.
  • TargetSelector owns its own fetch effects because allAccounts and allClusters are only needed for the typeahead dropdowns. This keeps the parent's state footprint minimal.

Files to Create

  • console/src/app/Actions/Scheduler/components/OperationSelector.tsx
  • console/src/app/Actions/Scheduler/components/TargetSelector.tsx
  • console/src/app/Actions/Scheduler/components/ExecutionConfig.tsx

Files to Modify

  • console/src/app/Actions/Scheduler/components/ModalCreateAction.tsx — remove extracted code, import new components

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions