Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .cursor/plans/cluster_sizer_api_integration_4747313e.plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
name: Cluster Sizer API Integration
overview: Integrate the ClusterSizingWizard with the real AssessmentApi by replacing mock data with the calculateAssessmentClusterRequirements endpoint, using the existing DI container pattern.
todos:
- id: update-types
content: Update types.ts to re-export API types from api-client and keep only UI-specific types
status: completed
- id: update-wizard
content: Update ClusterSizingWizard to use AssessmentApi via DI container instead of mocks
status: completed
- id: update-result
content: Add null checks in SizingResult for optional resourceConsumption fields
status: completed
- id: cleanup-report
content: Remove unused onCalculate prop from ClusterSizingWizard usage in Report.tsx
status: completed
- id: remove-ha-text
content: "Remove \"High Availability: Yes\" text from SizingResult component"
status: completed
- id: update-default-overcommit
content: Change default overcommit ratio from 1:6 to 1:4 in constants.ts
status: completed
---

# Cluster Sizer API Integration Plan

## Overview

The `@migration-planner-ui/api-client` package now includes the `calculateAssessmentClusterRequirements` method in `AssessmentApi` along with the `ClusterRequirementsRequest` and `ClusterRequirementsResponse` types. The wizard will use the existing DI pattern (`useInjection` hook) to access the API.

## Key Files

- [`src/pages/report/cluster-sizer/ClusterSizingWizard.tsx`](src/pages/report/cluster-sizer/ClusterSizingWizard.tsx) - Main component to update
- [`src/pages/report/cluster-sizer/types.ts`](src/pages/report/cluster-sizer/types.ts) - Replace API types with api-client imports
- [`src/pages/report/cluster-sizer/SizingResult.tsx`](src/pages/report/cluster-sizer/SizingResult.tsx) - Add null checks for optional fields
- [`src/main/Symbols.ts`](src/main/Symbols.ts) - Already has `AssessmentApi` symbol registered

## API Details

The api-client provides:

- `AssessmentApi.calculateAssessmentClusterRequirements({ id, clusterRequirementsRequest })`
- Returns `ClusterRequirementsResponse` with `clusterSizing`, `resourceConsumption`, and `inventoryTotals`

Note: `resourceConsumption.limits` and `resourceConsumption.overCommitRatio` are optional in the api-client types.

## Implementation Steps

### 1. Update types.ts

Remove duplicate API types and re-export from api-client:

```typescript
// Re-export API types from api-client
export type {
ClusterRequirementsRequest,
ClusterRequirementsResponse,
ClusterSizing,
InventoryTotals,
SizingResourceConsumption,
} from '@migration-planner-ui/api-client/models';

// Keep UI-specific types (SizingFormValues, WorkerNodePreset, etc.)
```

### 2. Update ClusterSizingWizard.tsx

- Import `useInjection` from `@migration-planner-ui/ioc`
- Import `AssessmentApi` from `@migration-planner-ui/api-client/apis`
- Import `Symbols` from `@/main/Symbols`
- Remove `fetchMockClusterRequirements` import
- Remove `onCalculate` prop (no longer needed)
- Use `assessmentApi.calculateAssessmentClusterRequirements()` directly
```typescript
const assessmentApi = useInjection<AssessmentApi>(Symbols.AssessmentApi);

const result = await assessmentApi.calculateAssessmentClusterRequirements({
id: assessmentId,
clusterRequirementsRequest: request,
});
```


### 3. Update SizingResult.tsx

Add null checks for optional API response fields:

```typescript
sizerOutput.resourceConsumption.overCommitRatio?.cpu ?? 0
sizerOutput.resourceConsumption.limits?.cpu ?? 0
```

### 4. Clean up Report.tsx

Remove the unused `onCalculate` prop from `ClusterSizingWizard` usage.

### 5. Remove "High Availability: Yes" from SizingResult

Remove the hardcoded "High Availability: Yes" text from both:

- The `generatePlainTextRecommendation` function (clipboard copy text)
- The JSX render section in `SizingResult.tsx`

### 6. Change default overcommit ratio

In [`src/pages/report/cluster-sizer/constants.ts`](src/pages/report/cluster-sizer/constants.ts), update `DEFAULT_FORM_VALUES.overcommitRatio` from `6` (High Density 1:6) to `4` (Standard 1:4).

### 7. Mock files decision

Keep `src/pages/report/cluster-sizer/mocks/` folder for unit testing purposes, but remove the runtime dependency on `data.mock.ts`.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

# vscode config
# .vscode/*
.cursor/**/*
.cursor/*
!.cursor/plans/
!.cursor/plans/**


# ide config
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
},
"dependencies": {
"@emotion/css": "^11.13.0",
"@migration-planner-ui/api-client": "^0.0.36",
"@migration-planner-ui/api-client": "^0.0.37",
"@migration-planner-ui/ioc": "^0.0.36",
"@patternfly/react-charts": "7.4.9",
"@patternfly/react-core": "^6.2.2",
Expand Down
100 changes: 100 additions & 0 deletions specs/cluster-sizer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Cluster Sizer – Target Cluster Recommendations

## Overview
The Cluster Sizer feature helps users generate OpenShift cluster sizing recommendations based on their VMware inventory data and migration preferences. It presents a two-step wizard that collects user preferences and displays calculated requirements for the target OpenShift cluster.

## Related Jira Tickets
- **ECOPROJECT-3637**: Implement "Recommend me an OpenShift cluster" wizard
- **ECOPROJECT-3631**: Cluster requirements API integration (backend sizer library)

## Figma Mockups
- Configuration step: `node-id=6896-15678`
- Results step: `node-id=7181-9318`
- File: `Migration-assessment` design file

## Wizard Steps

### Step 1: Migration Preferences
Users configure their target cluster parameters:
- **Run workloads on control plane nodes** (checkbox): Whether to schedule VM workloads on control plane nodes
- **Worker node CPU cores** (dropdown, required): CPU cores per worker node (8, 16, 32, 64, 96, 128)
- **Worker node memory** (dropdown, required): Memory in GB per worker node (16, 32, 64, 128, 256, 512)
- **Over-commit ratio** (dropdown, required): Resource sharing factor (1:1, 1:2, 1:4, 1:6)
- 1:1 = No over-commit (dedicated)
- 1:2 = Low density
- 1:4 = Standard density
- 1:6 = High density

### Step 2: Review Cluster Recommendations
Displays calculated sizing based on inventory data and user preferences:
- **Inventory Summary**: Total VMs, CPU cores, and memory from source VMware cluster
- **Cluster Sizing**: Recommended worker nodes, control plane nodes, total nodes, total CPU, total memory
- **Resource Utilization**: CPU consumption %, memory consumption %, resource limits, over-commit ratios

## API Integration

### Endpoint
```
POST /api/v1/assessments/{id}/cluster-requirements
```

### Request Payload
```typescript
interface ClusterRequirementsRequest {
clusterId: string; // VMware cluster ID
overCommitRatio: "1:1" | "1:2" | "1:4" | "1:6";
workerNodeCPU: number; // CPU cores per worker
workerNodeMemory: number; // Memory in GB per worker
controlPlaneSchedulable: boolean;
}
```

### Response Payload
```typescript
interface ClusterRequirementsResponse {
clusterSizing: {
controlPlaneNodes: number;
totalCPU: number;
totalMemory: number;
totalNodes: number;
workerNodes: number;
};
inventoryTotals: {
totalCPU: number;
totalMemory: number;
totalVMs: number;
};
resourceConsumption: {
cpu: number; // percentage
memory: number; // percentage
limits: { cpu: number; memory: number };
overCommitRatio: { cpu: number; memory: number };
};
}
```

## File Structure
```
src/pages/report/cluster-sizer/
├── index.ts # Re-exports
├── types.ts # TypeScript interfaces and type definitions
├── constants.ts # Form options (CPU, memory, over-commit dropdowns)
├── ClusterSizingWizard.tsx # Main wizard modal component
├── SizingInputForm.tsx # Step 1: Migration preferences form
├── SizingResult.tsx # Step 2: Results display
└── mockData.ts # Mock API responses for development
```

## UX Behavior Notes
- Modal title: "Target cluster recommendations"
- Default values: Control plane schedulable = false, CPU = 32, Memory = 32GB, Over-commit = 1:6
- Footer buttons:
- Step 1: "Next" (primary) + "Cancel" (link)
- Step 2: "Close" (primary) + "Back" (link)
- Copy to clipboard: Results can be copied as plain text for sharing
- Loading state: Shows spinner while calculating recommendations
- Error handling: Displays error message if API call fails

## Integration Point
The wizard is triggered from the Report page (`src/pages/report/Report.tsx`) via a "Get cluster sizing recommendation" button. It receives the `assessmentId` as a prop to identify which VMware cluster inventory to use.

File renamed without changes.
27 changes: 21 additions & 6 deletions src/pages/report/Report.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { useDiscoverySources } from '../../migration-wizard/contexts/discovery-s
import { Provider as DiscoverySourcesProvider } from '../../migration-wizard/contexts/discovery-sources/Provider';
import { EnhancedDownloadButton } from '../../migration-wizard/steps/discovery/EnhancedDownloadButton';
import { ExportError, SnapshotLike } from '../../services/report-export/types';
import { openAssistedInstaller } from '../assessment/utils/functions';
import { parseLatestSnapshot } from '../assessment/utils/snapshotParser';
import { AgentStatusView } from '../environment/sources-table/AgentStatusView';

Expand All @@ -41,6 +40,7 @@ import {
ClusterOption,
} from './assessment-report/clusterView';
import { Dashboard } from './assessment-report/Dashboard';
import { ClusterSizingWizard } from './cluster-sizer/ClusterSizingWizard';

type AssessmentLike = {
id: string | number;
Expand All @@ -56,6 +56,7 @@ const Inner: React.FC = () => {
const [exportError, setExportError] = useState<ExportError | null>(null);
const [selectedClusterId, setSelectedClusterId] = useState<string>('all');
const [isClusterSelectOpen, setIsClusterSelectOpen] = useState(false);
const [isSizingWizardOpen, setIsSizingWizardOpen] = useState(false);

useMount(async () => {
if (
Expand Down Expand Up @@ -340,11 +341,17 @@ const Inner: React.FC = () => {
}`}
/>
</SplitItem>
<SplitItem>
<Button variant="primary" onClick={openAssistedInstaller}>
Create a target cluster
</Button>
</SplitItem>

{selectedClusterId !== 'all' ? (
<SplitItem>
<Button
variant="primary"
onClick={() => setIsSizingWizardOpen(true)}
>
View target cluster recommendations
</Button>
</SplitItem>
) : null}
</Split>
) : undefined
}
Expand All @@ -370,6 +377,14 @@ const Inner: React.FC = () => {
</Content>
</Bullseye>
)}

<ClusterSizingWizard
isOpen={isSizingWizardOpen}
onClose={() => setIsSizingWizardOpen(false)}
clusterName={clusterView.selectionLabel}
clusterId={selectedClusterId}
assessmentId={id || ''}
/>
</AppPage>
);
};
Expand Down
Loading