Skip to content

Commit efbb271

Browse files
committed
refactor
1 parent 03ec511 commit efbb271

File tree

3 files changed

+175
-127
lines changed

3 files changed

+175
-127
lines changed

src/components/ComponentsSelection/ComponentsSelectionContainer.tsx

Lines changed: 4 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
1-
import React, { useEffect, useMemo, useState } from 'react';
1+
import React from 'react';
22
import { ComponentsSelection } from './ComponentsSelection.tsx';
3-
43
import IllustratedError from '../Shared/IllustratedError.tsx';
5-
import { sortVersions } from '../../utils/componentsVersions.ts';
6-
7-
import { ListManagedComponents } from '../../lib/api/types/crate/listManagedComponents.ts';
8-
import { useApiResource } from '../../lib/api/useApiResource.ts';
94
import Loading from '../Shared/Loading.tsx';
10-
import { ComponentsListItem, removeComponents } from '../../lib/api/types/crate/createManagedControlPlane.ts';
5+
import { ComponentsListItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';
116
import { useTranslation } from 'react-i18next';
12-
import { ManagedControlPlaneTemplate } from '../../lib/api/types/templates/mcpTemplate.ts';
7+
import { useComponentsSelection } from './ComponentsSelectionProvider.tsx';
138

149
export interface ComponentsSelectionProps {
1510
componentsList: ComponentsListItem[];
1611
setComponentsList: (components: ComponentsListItem[]) => void;
17-
setInitialComponentsList: (components: ComponentsListItem[]) => void;
18-
managedControlPlaneTemplate?: ManagedControlPlaneTemplate;
19-
initialSelection?: Record<string, { isSelected: boolean; version: string }>;
20-
isOnMcpPage?: boolean;
21-
initializedComponents: React.RefObject<boolean>;
2212
}
2313

2414
/**
@@ -34,123 +24,12 @@ export const getSelectedComponents = (components: ComponentsListItem[]) => {
3424
});
3525
};
3626

37-
type TemplateDefaultComponent = {
38-
name: string;
39-
version: string;
40-
removable?: boolean;
41-
versionChangeable?: boolean;
42-
};
43-
4427
export const ComponentsSelectionContainer: React.FC<ComponentsSelectionProps> = ({
4528
setComponentsList,
4629
componentsList,
47-
managedControlPlaneTemplate,
48-
initialSelection,
49-
isOnMcpPage,
50-
setInitialComponentsList,
51-
initializedComponents,
5230
}) => {
53-
const {
54-
data: availableManagedComponentsListData,
55-
error,
56-
isLoading,
57-
} = useApiResource(ListManagedComponents(), undefined, !!isOnMcpPage);
5831
const { t } = useTranslation();
59-
60-
const [templateDefaultsError, setTemplateDefaultsError] = useState<string | null>(null);
61-
const defaultComponents = useMemo<TemplateDefaultComponent[]>(
62-
() => managedControlPlaneTemplate?.spec?.spec?.components?.defaultComponents ?? [],
63-
[managedControlPlaneTemplate],
64-
);
65-
66-
useEffect(() => {
67-
if (
68-
initializedComponents.current ||
69-
!availableManagedComponentsListData?.items ||
70-
availableManagedComponentsListData.items.length === 0
71-
) {
72-
return;
73-
}
74-
75-
const newComponentsList = availableManagedComponentsListData.items
76-
.map((item) => {
77-
const versions = sortVersions(item.status?.versions ?? []);
78-
const template = defaultComponents.find((dc) => dc.name === (item.metadata?.name ?? ''));
79-
const templateVersion = template?.version;
80-
let selectedVersion = template
81-
? templateVersion && versions.includes(templateVersion)
82-
? templateVersion
83-
: ''
84-
: (versions[0] ?? '');
85-
let isSelected = !!template;
86-
87-
const initSel = initialSelection?.[item.metadata?.name ?? ''];
88-
if (initSel) {
89-
// Override selection and version from initial selection if provided
90-
isSelected = Boolean(initSel.isSelected);
91-
selectedVersion = initSel.version && versions.includes(initSel.version) ? initSel.version : '';
92-
}
93-
return {
94-
name: item.metadata?.name ?? '',
95-
versions,
96-
selectedVersion,
97-
isSelected,
98-
documentationUrl: '',
99-
};
100-
})
101-
.filter((component) => !removeComponents.find((item) => item === component.name));
102-
setInitialComponentsList(newComponentsList);
103-
setComponentsList(newComponentsList);
104-
initializedComponents.current = true;
105-
// eslint-disable-next-line react-hooks/exhaustive-deps
106-
}, [setComponentsList, defaultComponents, initialSelection, availableManagedComponentsListData?.items]);
107-
108-
useEffect(() => {
109-
const items = availableManagedComponentsListData?.items ?? [];
110-
if (items.length === 0 || !defaultComponents.length) {
111-
setTemplateDefaultsError(null);
112-
return;
113-
}
114-
115-
const errors: string[] = [];
116-
defaultComponents.forEach((dc: TemplateDefaultComponent) => {
117-
if (!dc?.name) return;
118-
const item = items.find((it) => it.metadata?.name === dc.name);
119-
if (!item) {
120-
errors.push(`Component "${dc.name}" from template is not available.`);
121-
return;
122-
}
123-
const versions: string[] = Array.isArray(item.status?.versions) ? (item.status?.versions as string[]) : [];
124-
if (dc.version && !versions.includes(dc.version)) {
125-
errors.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`);
126-
}
127-
});
128-
129-
setTemplateDefaultsError(errors.length ? errors.join('\n') : null);
130-
}, [availableManagedComponentsListData, defaultComponents]);
131-
132-
useEffect(() => {
133-
if (!initializedComponents.current) return;
134-
if (!defaultComponents?.length) return;
135-
if (!componentsList?.length) return;
136-
// If initialSelection is provided, do not auto-apply template defaults
137-
if (initialSelection && Object.keys(initialSelection).length > 0) return;
138-
139-
const anySelected = componentsList.some((c) => c.isSelected);
140-
if (anySelected) return;
141-
142-
const updated = componentsList.map((c) => {
143-
const template = defaultComponents.find((dc) => dc.name === c.name);
144-
if (!template) return c;
145-
const templateVersion = template.version;
146-
const selectedVersion =
147-
templateVersion && Array.isArray(c.versions) && c.versions.includes(templateVersion) ? templateVersion : '';
148-
return { ...c, isSelected: true, selectedVersion };
149-
});
150-
151-
setComponentsList(updated);
152-
// eslint-disable-next-line react-hooks/exhaustive-deps
153-
}, [defaultComponents, componentsList, setComponentsList, initialSelection]);
32+
const { isLoading, error, templateDefaultsError } = useComponentsSelection();
15433

15534
if (isLoading) {
15635
return <Loading />;
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
2+
import { useApiResource } from '../../lib/api/useApiResource.ts';
3+
import { ListManagedComponents } from '../../lib/api/types/crate/listManagedComponents.ts';
4+
import { sortVersions } from '../../utils/componentsVersions.ts';
5+
import { ComponentsListItem, removeComponents } from '../../lib/api/types/crate/createManagedControlPlane.ts';
6+
import { ManagedControlPlaneTemplate } from '../../lib/api/types/templates/mcpTemplate.ts';
7+
8+
export type ComponentsSelectionProviderProps = {
9+
componentsList: ComponentsListItem[];
10+
setComponentsList: (components: ComponentsListItem[]) => void;
11+
setInitialComponentsList: (components: ComponentsListItem[]) => void;
12+
managedControlPlaneTemplate?: ManagedControlPlaneTemplate;
13+
initialSelection?: Record<string, { isSelected: boolean; version: string }>;
14+
isOnMcpPage?: boolean;
15+
initializedComponents: React.RefObject<boolean>;
16+
children: React.ReactNode;
17+
};
18+
19+
export type ComponentsSelectionContextValue = {
20+
isLoading: boolean;
21+
error: unknown;
22+
templateDefaultsError: string | null;
23+
};
24+
25+
const ComponentsSelectionContext = createContext<ComponentsSelectionContextValue | undefined>(undefined);
26+
27+
export const useComponentsSelection = (): ComponentsSelectionContextValue => {
28+
const ctx = useContext(ComponentsSelectionContext);
29+
if (!ctx) {
30+
throw new Error('useComponentsSelection must be used within ComponentsSelectionProvider');
31+
}
32+
return ctx;
33+
};
34+
35+
export const ComponentsSelectionProvider: React.FC<ComponentsSelectionProviderProps> = ({
36+
componentsList,
37+
setComponentsList,
38+
setInitialComponentsList,
39+
managedControlPlaneTemplate,
40+
initialSelection,
41+
isOnMcpPage,
42+
initializedComponents,
43+
children,
44+
}) => {
45+
type TemplateDefaultComponent = {
46+
name: string;
47+
version: string;
48+
removable?: boolean;
49+
versionChangeable?: boolean;
50+
};
51+
52+
const {
53+
data: availableManagedComponentsListData,
54+
error,
55+
isLoading,
56+
} = useApiResource(ListManagedComponents(), undefined, !!isOnMcpPage);
57+
58+
const [templateDefaultsError, setTemplateDefaultsError] = useState<string | null>(null);
59+
60+
const defaultComponents = useMemo<TemplateDefaultComponent[]>(
61+
() => managedControlPlaneTemplate?.spec?.spec?.components?.defaultComponents ?? [],
62+
[managedControlPlaneTemplate],
63+
);
64+
65+
// Initialize components list from API + template/defaults/initialSelection
66+
useEffect(() => {
67+
if (
68+
initializedComponents.current ||
69+
!availableManagedComponentsListData?.items ||
70+
availableManagedComponentsListData.items.length === 0
71+
) {
72+
return;
73+
}
74+
75+
const newComponentsList = availableManagedComponentsListData.items
76+
.map((item) => {
77+
const versions = sortVersions(item.status?.versions ?? []);
78+
const template = defaultComponents.find((dc) => dc.name === (item.metadata?.name ?? ''));
79+
const templateVersion = template?.version;
80+
let selectedVersion = template
81+
? templateVersion && versions.includes(templateVersion)
82+
? templateVersion
83+
: ''
84+
: (versions[0] ?? '');
85+
let isSelected = !!template;
86+
87+
const initSel = initialSelection?.[item.metadata?.name ?? ''];
88+
if (initSel) {
89+
isSelected = Boolean(initSel.isSelected);
90+
selectedVersion = initSel.version && versions.includes(initSel.version) ? initSel.version : '';
91+
}
92+
return {
93+
name: item.metadata?.name ?? '',
94+
versions,
95+
selectedVersion,
96+
isSelected,
97+
documentationUrl: '',
98+
} as ComponentsListItem;
99+
})
100+
.filter((component) => !removeComponents.find((item) => item === component.name));
101+
102+
setInitialComponentsList(newComponentsList);
103+
setComponentsList(newComponentsList);
104+
initializedComponents.current = true;
105+
// eslint-disable-next-line react-hooks/exhaustive-deps
106+
}, [setComponentsList, defaultComponents, initialSelection, availableManagedComponentsListData?.items]);
107+
108+
// Validate template default components are available
109+
useEffect(() => {
110+
const items = availableManagedComponentsListData?.items ?? [];
111+
if (items.length === 0 || !defaultComponents.length) {
112+
setTemplateDefaultsError(null);
113+
return;
114+
}
115+
116+
const errors: string[] = [];
117+
defaultComponents.forEach((dc: TemplateDefaultComponent) => {
118+
if (!dc?.name) return;
119+
const item = items.find((it) => it.metadata?.name === dc.name);
120+
if (!item) {
121+
errors.push(`Component "${dc.name}" from template is not available.`);
122+
return;
123+
}
124+
const versions: string[] = Array.isArray(item.status?.versions) ? (item.status?.versions as string[]) : [];
125+
if (dc.version && !versions.includes(dc.version)) {
126+
errors.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`);
127+
}
128+
});
129+
130+
setTemplateDefaultsError(errors.length ? errors.join('\n') : null);
131+
}, [availableManagedComponentsListData, defaultComponents]);
132+
133+
// Auto-apply template defaults if none selected and no initialSelection
134+
useEffect(() => {
135+
if (!initializedComponents.current) return;
136+
if (!defaultComponents?.length) return;
137+
if (!componentsList?.length) return;
138+
if (initialSelection && Object.keys(initialSelection).length > 0) return;
139+
140+
const anySelected = componentsList.some((c) => c.isSelected);
141+
if (anySelected) return;
142+
143+
const updated = componentsList.map((c) => {
144+
const template = defaultComponents.find((dc) => dc.name === c.name);
145+
if (!template) return c;
146+
const templateVersion = template.version;
147+
const selectedVersion =
148+
templateVersion && Array.isArray(c.versions) && c.versions.includes(templateVersion) ? templateVersion : '';
149+
return { ...c, isSelected: true, selectedVersion } as ComponentsListItem;
150+
});
151+
152+
setComponentsList(updated);
153+
// eslint-disable-next-line react-hooks/exhaustive-deps
154+
}, [defaultComponents, componentsList, setComponentsList, initialSelection]);
155+
156+
const value: ComponentsSelectionContextValue = {
157+
isLoading: Boolean(isLoading),
158+
error,
159+
templateDefaultsError,
160+
};
161+
162+
return <ComponentsSelectionContext.Provider value={value}>{children}</ComponentsSelectionContext.Provider>;
163+
};

src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { APIError } from '../../../lib/api/error.ts';
4444
import { MetadataForm } from '../../Dialogs/MetadataForm.tsx';
4545
import { EditMembers } from '../../Members/EditMembers.tsx';
4646
import { ComponentsSelectionContainer } from '../../ComponentsSelection/ComponentsSelectionContainer.tsx';
47+
import { ComponentsSelectionProvider } from '../../ComponentsSelection/ComponentsSelectionProvider.tsx';
4748
import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner.tsx';
4849
import { ManagedControlPlaneTemplate, noTemplateValue } from '../../../lib/api/types/templates/mcpTemplate.ts';
4950
import { stripIdpPrefix } from '../../../utils/stripIdpPrefix.ts';
@@ -599,15 +600,20 @@ export const CreateManagedControlPlaneWizardContainer: FC<CreateManagedControlPl
599600
>
600601
{/* this condition is to remount the component from scratch to fix a bug with data loading */}
601602
{selectedStep === 'componentSelection' && (
602-
<ComponentsSelectionContainer
603+
<ComponentsSelectionProvider
603604
componentsList={componentsList ?? []}
604605
setComponentsList={setComponentsList}
605606
initialSelection={initialSelection}
606607
managedControlPlaneTemplate={selectedTemplate}
607608
isOnMcpPage={isOnMcpPage}
608609
setInitialComponentsList={setInitialComponentsListHandler}
609610
initializedComponents={initializedComponents}
610-
/>
611+
>
612+
<ComponentsSelectionContainer
613+
componentsList={componentsList ?? []}
614+
setComponentsList={setComponentsList}
615+
/>
616+
</ComponentsSelectionProvider>
611617
)}
612618
</WizardStep>
613619
<WizardStep

0 commit comments

Comments
 (0)