diff --git a/src/components/Wizards/CreateManagedControlPlane/useComponentsSelectionData.spec.ts b/src/components/Wizards/CreateManagedControlPlane/useComponentsSelectionData.spec.ts new file mode 100644 index 00000000..e215f710 --- /dev/null +++ b/src/components/Wizards/CreateManagedControlPlane/useComponentsSelectionData.spec.ts @@ -0,0 +1,332 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { renderHook, waitFor } from '@testing-library/react'; +import { + isProviderComponent, + findInitialSelection, + findOriginalName, + determineSelectedVersion, + mapToComponentsListItem, + addCustomProviders, + sortComponents, + buildComponentsList, + validateTemplateDefaults, + useComponentsSelectionData, + InitialSelection, +} from './useComponentsSelectionData.ts'; +import { ManagedControlPlaneTemplate } from '../../../lib/api/types/templates/mcpTemplate.ts'; +import { ManagedComponent } from '../../../lib/api/types/crate/listManagedComponents.ts'; +import { ComponentsListItem } from '../../../lib/api/types/crate/createManagedControlPlane.ts'; + +const createManagedComponent = (name: string, versions: string[]): ManagedComponent => ({ + apiVersion: 'core.openmcp.cloud/v1alpha1', + kind: 'ManagedComponent', + metadata: { name }, + spec: {}, + status: { versions }, +}); + +const createTemplate = (defaultComponents: { name: string; version: string }[]): ManagedControlPlaneTemplate => + ({ + apiVersion: 'core.openmcp.cloud/v1alpha1', + kind: 'ManagedControlPlaneTemplate', + metadata: { name: 'test-template', namespace: 'default' }, + spec: { + meta: { + chargingTarget: { type: 'BTP', value: '' }, + displayName: {}, + name: {}, + }, + spec: { + authentication: { system: { changeable: true, enabled: true } }, + authorization: { defaultMembers: [] }, + components: { defaultComponents }, + }, + }, + }) as ManagedControlPlaneTemplate; + +const createComponentsListItem = (name: string, isProvider = false): ComponentsListItem => ({ + name, + versions: [], + selectedVersion: '', + isSelected: false, + documentationUrl: '', + isProvider, +}); + +const sampleInitialSelection: InitialSelection = { + crossplane: { isSelected: true, version: '1.20.1' }, + 'custom-provider': { isSelected: true, version: '1.0.0' }, + kubernetes: { isSelected: true, version: '0.15.0' }, + btp: { isSelected: true, version: '1.2.2' }, + flux: { isSelected: true, version: '2.16.2' }, +}; + +const sampleItems: ManagedComponent[] = [ + createManagedComponent('cert-manager', ['1.13.1', '1.16.1']), + createManagedComponent('crossplane', ['1.15.0', '1.19.0', '1.20.1']), + createManagedComponent('flux', ['2.15.0', '2.16.2']), + createManagedComponent('kyverno', ['3.2.4', '3.5.2']), + createManagedComponent('provider-btp', ['1.0.0', '1.2.2', '1.4.0']), + createManagedComponent('provider-kubernetes', ['0.14.0', '0.15.0']), +]; + +describe('isProviderComponent', () => { + it('returns true for provider-* names, false for crossplane and others', () => { + expect(isProviderComponent('provider-btp')).toBe(true); + expect(isProviderComponent('crossplane')).toBe(false); + expect(isProviderComponent('flux')).toBe(false); + }); +}); + +describe('findInitialSelection', () => { + it('finds by exact name or without provider- prefix', () => { + expect(findInitialSelection('crossplane', sampleInitialSelection)?.version).toBe('1.20.1'); + expect(findInitialSelection('provider-btp', sampleInitialSelection)?.version).toBe('1.2.2'); + expect(findInitialSelection('non-existent', sampleInitialSelection)).toBeUndefined(); + }); +}); + +describe('findOriginalName', () => { + it('returns undefined when initialSelection is undefined', () => { + expect(findOriginalName('crossplane', undefined)).toBeUndefined(); + }); + + it('returns the exact name when it exists in initialSelection', () => { + expect(findOriginalName('crossplane', sampleInitialSelection)).toBe('crossplane'); + expect(findOriginalName('flux', sampleInitialSelection)).toBe('flux'); + }); + + it('returns name without provider- prefix when that exists in initialSelection', () => { + expect(findOriginalName('provider-btp', sampleInitialSelection)).toBe('btp'); + expect(findOriginalName('provider-kubernetes', sampleInitialSelection)).toBe('kubernetes'); + }); + + it('returns undefined when name does not exist in initialSelection', () => { + expect(findOriginalName('non-existent', sampleInitialSelection)).toBeUndefined(); + expect(findOriginalName('provider-unknown', sampleInitialSelection)).toBeUndefined(); + }); + + it('returns exact name when both exact and prefixed versions could match', () => { + const selectionWithBoth: InitialSelection = { + 'provider-test': { isSelected: true, version: '1.0.0' }, + test: { isSelected: true, version: '2.0.0' }, + }; + // Exact match takes priority + expect(findOriginalName('provider-test', selectionWithBoth)).toBe('provider-test'); + }); + + it('handles names that do not start with provider- prefix', () => { + // When the name doesn't have provider- prefix, nameWithoutPrefix equals name + // so the second condition is skipped + expect(findOriginalName('crossplane', sampleInitialSelection)).toBe('crossplane'); + }); +}); + +describe('determineSelectedVersion', () => { + const versions = ['1.20.1', '1.19.0', '1.18.0']; + + it('prioritizes: initial selection > template default > first available', () => { + const initSel = { isSelected: true, version: '1.19.0' }; + const templateDefault = { name: 'test', version: '1.18.0' }; + + expect(determineSelectedVersion(versions, initSel, templateDefault)).toBe('1.19.0'); + expect(determineSelectedVersion(versions, undefined, templateDefault)).toBe('1.18.0'); + expect(determineSelectedVersion(versions, undefined, undefined)).toBe('1.20.1'); + }); + + it('returns initial selection version even if not in versions array', () => { + const initSel = { isSelected: true, version: '9.9.9' }; + expect(determineSelectedVersion(versions, initSel, undefined)).toBe('9.9.9'); + }); +}); + +describe('mapToComponentsListItem', () => { + it('maps component with sorted versions and applies initial selection', () => { + const item = createManagedComponent('crossplane', ['1.15.0', '1.20.1', '1.19.0']); + const initialSelection: InitialSelection = { + crossplane: { isSelected: true, version: '1.19.0' }, + }; + + const result = mapToComponentsListItem(item, initialSelection, undefined); + + expect(result.versions).toEqual(['1.20.1', '1.19.0', '1.15.0']); + expect(result.selectedVersion).toBe('1.19.0'); + expect(result.isSelected).toBe(true); + expect(result.isProvider).toBe(false); + }); + + it('adds initial selection version to versions array if not present', () => { + const item = createManagedComponent('crossplane', ['1.15.0', '1.19.0']); + const initialSelection: InitialSelection = { + crossplane: { isSelected: true, version: '1.20.1' }, + }; + + const result = mapToComponentsListItem(item, initialSelection, undefined); + + expect(result.versions).toContain('1.20.1'); + expect(result.versions).toEqual(['1.20.1', '1.19.0', '1.15.0']); + expect(result.selectedVersion).toBe('1.20.1'); + }); + + it('applies template default when no initial selection', () => { + const item = createManagedComponent('flux', ['2.15.0', '2.16.2']); + const template = createTemplate([{ name: 'flux', version: '2.15.0' }]); + + const result = mapToComponentsListItem(item, undefined, template); + + expect(result.selectedVersion).toBe('2.15.0'); + expect(result.isSelected).toBe(true); + }); + + it('sets originalName to btp for provider-btp when initial selection uses btp', () => { + const item = createManagedComponent('provider-btp', ['1.0.0', '1.2.2']); + const initialSelection: InitialSelection = { btp: { isSelected: true, version: '1.2.2' } }; + + const result = mapToComponentsListItem(item, initialSelection, undefined); + + expect(result.name).toBe('provider-btp'); + expect(result.originalName).toBe('btp'); + }); +}); + +describe('addCustomProviders', () => { + it('adds providers from initial selection not in available components', () => { + const components = [createComponentsListItem('crossplane')]; + const initialSelection: InitialSelection = { + 'custom-provider': { isSelected: true, version: '1.0.0' }, + }; + + const result = addCustomProviders(components, initialSelection); + + expect(result).toHaveLength(2); + expect(result[1].name).toBe('custom-provider'); + expect(result[1].isProvider).toBe(true); + }); + + it('skips if provider already exists or is not selected', () => { + const components = [createComponentsListItem('provider-btp', true)]; + + expect(addCustomProviders(components, { 'provider-btp': { isSelected: true, version: '1.0.0' } })).toHaveLength(1); + expect(addCustomProviders(components, { btp: { isSelected: true, version: '1.0.0' } })).toHaveLength(1); + expect(addCustomProviders([], { test: { isSelected: false, version: '1.0.0' } })).toHaveLength(0); + }); +}); + +describe('sortComponents', () => { + it('places providers after crossplane, both groups alphabetically', () => { + const components = [ + createComponentsListItem('flux'), + createComponentsListItem('crossplane'), + createComponentsListItem('provider-kubernetes', true), + createComponentsListItem('provider-btp', true), + ]; + + const result = sortComponents(components); + + expect(result.map((c) => c.name)).toEqual(['crossplane', 'provider-btp', 'provider-kubernetes', 'flux']); + }); +}); + +describe('buildComponentsList', () => { + it('builds complete list: maps, filters cert-manager, adds custom providers, sorts', () => { + const result = buildComponentsList(sampleItems, sampleInitialSelection, undefined); + + expect(result.find((c) => c.name === 'cert-manager')).toBeUndefined(); + expect(result.find((c) => c.name === 'custom-provider')).toBeDefined(); + expect(result[0].name).toBe('crossplane'); + + const crossplane = result.find((c) => c.name === 'crossplane'); + expect(crossplane?.isSelected).toBe(true); + expect(crossplane?.selectedVersion).toBe('1.20.1'); + + const providerBtp = result.find((c) => c.name === 'provider-btp'); + expect(providerBtp?.selectedVersion).toBe('1.2.2'); + }); + + it('returns empty array for empty items', () => { + expect(buildComponentsList([], undefined, undefined)).toEqual([]); + }); +}); + +describe('validateTemplateDefaults', () => { + it('returns null for valid defaults or empty inputs', () => { + const validTemplate = createTemplate([{ name: 'crossplane', version: '1.20.1' }]); + + expect(validateTemplateDefaults(sampleItems, validTemplate)).toBeNull(); + expect(validateTemplateDefaults([], validTemplate)).toBeNull(); + expect(validateTemplateDefaults(sampleItems, undefined)).toBeNull(); + }); + + it('returns error messages for missing component or version', () => { + const missingComponent = createTemplate([{ name: 'non-existent', version: '1.0.0' }]); + const missingVersion = createTemplate([{ name: 'crossplane', version: '99.99.99' }]); + + expect(validateTemplateDefaults(sampleItems, missingComponent)).toContain('non-existent'); + expect(validateTemplateDefaults(sampleItems, missingVersion)).toContain('99.99.99'); + }); +}); + +describe('useComponentsSelectionData', () => { + const mockSetValue = vi.fn(); + const mockOnComponentsInitialized = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('calls setValue with built components list and onComponentsInitialized callback', async () => { + const mockUseComponentsQuery = vi.fn().mockReturnValue({ + components: { items: sampleItems }, + error: undefined, + isLoading: false, + }); + + renderHook(() => + useComponentsSelectionData( + undefined, + sampleInitialSelection, + mockSetValue, + mockOnComponentsInitialized, + mockUseComponentsQuery, + ), + ); + + await waitFor(() => { + expect(mockSetValue).toHaveBeenCalledWith('componentsList', expect.any(Array), { shouldValidate: false }); + expect(mockOnComponentsInitialized).toHaveBeenCalled(); + }); + }); + + it('returns loading and error states from query', () => { + const mockError = new Error('Test error'); + const mockUseComponentsQuery = vi.fn().mockReturnValue({ + components: undefined, + error: mockError, + isLoading: true, + }); + + const { result } = renderHook(() => + useComponentsSelectionData(undefined, undefined, mockSetValue, undefined, mockUseComponentsQuery), + ); + + expect(result.current.isLoading).toBe(true); + expect(result.current.error).toBe(mockError); + }); + + it('returns templateDefaultsError for invalid template defaults', async () => { + const invalidTemplate = createTemplate([{ name: 'non-existent', version: '1.0.0' }]); + const mockUseComponentsQuery = vi.fn().mockReturnValue({ + components: { items: sampleItems }, + error: undefined, + isLoading: false, + }); + + const { result } = renderHook(() => + useComponentsSelectionData(invalidTemplate, undefined, mockSetValue, undefined, mockUseComponentsQuery), + ); + + await waitFor(() => { + expect(result.current.templateDefaultsError).toContain('non-existent'); + }); + }); +}); diff --git a/src/components/Wizards/CreateManagedControlPlane/useComponentsSelectionData.ts b/src/components/Wizards/CreateManagedControlPlane/useComponentsSelectionData.ts index ef57aa18..debcd7ce 100644 --- a/src/components/Wizards/CreateManagedControlPlane/useComponentsSelectionData.ts +++ b/src/components/Wizards/CreateManagedControlPlane/useComponentsSelectionData.ts @@ -3,6 +3,7 @@ import { ManagedControlPlaneTemplate } from '../../../lib/api/types/templates/mc import { ComponentsListItem, removeComponents } from '../../../lib/api/types/crate/createManagedControlPlane.ts'; import { sortVersions } from '../../../utils/componentsVersions.ts'; import { useComponentsQuery as _useComponentsQuery } from '../../../hooks/useComponentsQuery.ts'; +import { ManagedComponent } from '../../../lib/api/types/crate/listManagedComponents.ts'; export type ComponentsHookResult = { isLoading: boolean; @@ -10,9 +11,202 @@ export type ComponentsHookResult = { templateDefaultsError: string | null; }; +export type InitialSelection = Record; + +export type DefaultComponent = { + name: string; + version?: string; +}; + +export const isProviderComponent = (name: string): boolean => { + return name.startsWith('provider-') && name !== 'crossplane'; +}; + +// Checks both exact name and without 'provider-' prefix +export const findInitialSelection = ( + name: string, + initialSelection: InitialSelection | undefined, +): { isSelected: boolean; version: string } | undefined => { + if (!initialSelection) return undefined; + return initialSelection[name] ?? initialSelection[name.replace('provider-', '')]; +}; + +// Gets the original name from initial selection, considering 'provider-' prefix +export const findOriginalName = (name: string, initialSelection: InitialSelection | undefined): string | undefined => { + if (!initialSelection) return undefined; + + if (name in initialSelection) return name; + + const nameWithoutPrefix = name.replace('provider-', ''); + if (nameWithoutPrefix !== name && nameWithoutPrefix in initialSelection) { + return nameWithoutPrefix; + } + + return undefined; +}; + +export const findTemplateDefault = ( + name: string, + selectedTemplate: ManagedControlPlaneTemplate | undefined, +): DefaultComponent | undefined => { + return selectedTemplate?.spec?.spec?.components?.defaultComponents?.find((dc) => dc.name === name); +}; + +// Priority: initial selection > template default > first available version +export const determineSelectedVersion = ( + versions: string[], + initSel: { isSelected: boolean; version: string } | undefined, + templateDefault: DefaultComponent | undefined, +): string => { + if (initSel?.version) { + return initSel.version; + } + + if (templateDefault?.version && versions.includes(templateDefault.version)) { + return templateDefault.version; + } + + return versions[0] ?? ''; +}; + +export const determineIsSelected = ( + initSel: { isSelected: boolean; version: string } | undefined, + templateDefault: DefaultComponent | undefined, +): boolean => { + if (initSel) { + return Boolean(initSel.isSelected); + } + return Boolean(templateDefault); +}; + +export const mapToComponentsListItem = ( + item: ManagedComponent, + initialSelection: InitialSelection | undefined, + selectedTemplate: ManagedControlPlaneTemplate | undefined, +): ComponentsListItem => { + const rawVersions = Array.isArray(item.status?.versions) ? (item.status?.versions as string[]) : []; + let versions = sortVersions(rawVersions); + const name = item.metadata?.name ?? ''; + + const initSel = findInitialSelection(name, initialSelection); + + const templateDefault = findTemplateDefault(name, selectedTemplate); + + // Add initial selection version if not in available versions + if (initSel?.version && !versions.includes(initSel.version)) { + versions = sortVersions([...rawVersions, initSel.version]); + } + + const isSelected = determineIsSelected(initSel, templateDefault); + const selectedVersion = determineSelectedVersion(versions, initSel, templateDefault); + + return { + name, + versions, + selectedVersion, + isSelected, + documentationUrl: '', + originalName: findOriginalName(name, initialSelection), + isProvider: isProviderComponent(name), + }; +}; + +export const filterRemovedComponents = (components: ComponentsListItem[]): ComponentsListItem[] => { + return components.filter((component) => !removeComponents.includes(component.name)); +}; + +// Adds providers from initial selection that don't exist in the available components list +export const addCustomProviders = ( + componentsList: ComponentsListItem[], + initialSelection: InitialSelection | undefined, +): ComponentsListItem[] => { + if (!initialSelection) return componentsList; + + const result = [...componentsList]; + const existingNames = new Set(result.map((c) => c.name)); + + Object.entries(initialSelection).forEach(([name, selection]) => { + const hasExactMatch = existingNames.has(name); + const hasProviderPrefixMatch = existingNames.has(`provider-${name}`); + + if (!hasExactMatch && !hasProviderPrefixMatch && selection.isSelected && selection.version) { + result.push({ + name, + versions: [selection.version], + selectedVersion: selection.version, + isSelected: true, + documentationUrl: '', + isProvider: true, + }); + } + }); + + return result; +}; + +// Sorts: non-providers alphabetically, then providers after 'crossplane' +export const sortComponents = (componentsList: ComponentsListItem[]): ComponentsListItem[] => { + const nonProviders = componentsList.filter((c) => !c.isProvider).sort((a, b) => a.name.localeCompare(b.name)); + + const crossplaneProviders = componentsList.filter((c) => c.isProvider).sort((a, b) => a.name.localeCompare(b.name)); + + const crossplaneIndex = nonProviders.findIndex((c) => c.name === 'crossplane'); + const insertIndex = crossplaneIndex !== -1 ? crossplaneIndex + 1 : nonProviders.length; + + return [...nonProviders.slice(0, insertIndex), ...crossplaneProviders, ...nonProviders.slice(insertIndex)]; +}; + +export const buildComponentsList = ( + items: ManagedComponent[], + initialSelection: InitialSelection | undefined, + selectedTemplate: ManagedControlPlaneTemplate | undefined, +): ComponentsListItem[] => { + if (!items || items.length === 0) { + return []; + } + + const mappedComponents = items.map((item) => mapToComponentsListItem(item, initialSelection, selectedTemplate)); + const filteredComponents = filterRemovedComponents(mappedComponents); + const withCustomProviders = addCustomProviders(filteredComponents, initialSelection); + + return sortComponents(withCustomProviders); +}; + +export const validateTemplateDefaults = ( + items: ManagedComponent[], + selectedTemplate: ManagedControlPlaneTemplate | undefined, +): string | null => { + const defaults = selectedTemplate?.spec?.spec?.components?.defaultComponents ?? []; + + if (!items.length || !defaults.length) { + return null; + } + + const errors: string[] = []; + + defaults.forEach((dc) => { + if (!dc?.name) return; + + const item = items.find((it) => it.metadata?.name === dc.name); + + if (!item) { + errors.push(`Component "${dc.name}" from template is not available.`); + return; + } + + const versions: string[] = Array.isArray(item.status?.versions) ? (item.status?.versions as string[]) : []; + + if (dc.version && !versions.includes(dc.version)) { + errors.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`); + } + }); + + return errors.length ? errors.join('\n') : null; +}; + export const useComponentsSelectionData = ( selectedTemplate: ManagedControlPlaneTemplate | undefined, - initialSelection: Record | undefined, + initialSelection: InitialSelection | undefined, setValue: (name: 'componentsList', value: ComponentsListItem[], options?: { shouldValidate?: boolean }) => void, onComponentsInitialized?: (components: ComponentsListItem[]) => void, useComponentsQuery: typeof _useComponentsQuery = _useComponentsQuery, @@ -21,96 +215,22 @@ export const useComponentsSelectionData = ( useEffect(() => { const items = data?.items ?? []; - if (!items || items.length === 0) { - setValue('componentsList', [], { shouldValidate: false }); - return; - } - const newComponentsList: ComponentsListItem[] = items - .map((item) => { - const rawVersions = Array.isArray(item.status?.versions) ? (item.status?.versions as string[]) : []; - const versions = sortVersions(rawVersions); - const name = item.metadata?.name ?? ''; - const initSel = initialSelection?.[name]; - const templateDefault = selectedTemplate?.spec?.spec?.components?.defaultComponents?.find( - (dc) => dc.name === name, - ); - let isSelected = Boolean(initSel?.isSelected); - let selectedVersion = initSel?.version && versions.includes(initSel.version) ? initSel.version : ''; - if (!initSel) { - isSelected = Boolean(templateDefault); - const templateVersion = templateDefault?.version; - selectedVersion = templateVersion && versions.includes(templateVersion) ? templateVersion : ''; - } - if (!initSel && !templateDefault) { - selectedVersion = versions[0] ?? ''; - } - return { - name, - versions, - selectedVersion, - isSelected, - documentationUrl: '', - isProvider: name.includes('provider') && name !== 'crossplane', - } as ComponentsListItem; - }) - .filter((component) => !removeComponents.find((item) => item === component.name)); - - // Add custom providers from initialSelection that don't exist in the available components list - if (initialSelection) { - const existingNames = new Set(newComponentsList.map((c) => c.name)); - Object.entries(initialSelection).forEach(([name, selection]) => { - if (!existingNames.has(name) && selection.isSelected && selection.version) { - newComponentsList.push({ - name, - versions: [selection.version], - selectedVersion: selection.version, - isSelected: true, - documentationUrl: '', - isProvider: true, - }); - } - }); - } - - // Sort components alphabetically, then crossplane providers alphabetically after 'crossplane' - const components = newComponentsList.filter((c) => !c.isProvider).sort((a, b) => a.name.localeCompare(b.name)); - const crossplaneProviders = newComponentsList - .filter((c) => c.isProvider) - .sort((a, b) => a.name.localeCompare(b.name)); - - const crossplaneIndex = components.findIndex((c) => c.name === 'crossplane'); - const insertIndex = crossplaneIndex !== -1 ? crossplaneIndex + 1 : components.length; - const sortedList = [...components.slice(0, insertIndex), ...crossplaneProviders, ...components.slice(insertIndex)]; + const sortedList = buildComponentsList(items, initialSelection, selectedTemplate); setValue('componentsList', sortedList, { shouldValidate: false }); - if (onComponentsInitialized) { + + if (onComponentsInitialized && sortedList.length > 0) { onComponentsInitialized(sortedList); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(data?.items), selectedTemplate, initialSelection]); const [defaultsError, setDefaultsError] = useState(null); + useEffect(() => { const items = data?.items ?? []; - const defaults = selectedTemplate?.spec?.spec?.components?.defaultComponents ?? []; - if (!items.length || !defaults.length) { - setDefaultsError(null); - return; - } - const errors: string[] = []; - defaults.forEach((dc) => { - if (!dc?.name) return; - const item = items.find((it) => it.metadata?.name === dc.name); - if (!item) { - errors.push(`Component "${dc.name}" from template is not available.`); - return; - } - const versions: string[] = Array.isArray(item.status?.versions) ? (item.status?.versions as string[]) : []; - if (dc.version && !versions.includes(dc.version)) { - errors.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`); - } - }); - setDefaultsError(errors.length ? errors.join('\n') : null); + const error = validateTemplateDefaults(items, selectedTemplate); + setDefaultsError(error); }, [data, selectedTemplate]); return { isLoading: Boolean(isLoading), error, templateDefaultsError: defaultsError }; diff --git a/src/lib/api/types/crate/createManagedControlPlane.ts b/src/lib/api/types/crate/createManagedControlPlane.ts index e1f957a6..26033424 100644 --- a/src/lib/api/types/crate/createManagedControlPlane.ts +++ b/src/lib/api/types/crate/createManagedControlPlane.ts @@ -14,6 +14,7 @@ export interface ComponentsListItem { selectedVersion: string; documentationUrl: string; isProvider: boolean; + originalName?: string; } interface RoleBinding { @@ -102,8 +103,8 @@ export const CreateManagedControlPlane = ( const selectedProviders: Provider[] = optional?.componentsList ?.filter(({ isSelected, isProvider }) => isProvider && isSelected) - .map(({ name, selectedVersion }) => ({ - name: name, + .map(({ name, selectedVersion, originalName }) => ({ + name: originalName ?? name, version: selectedVersion, })) ?? []; const crossplaneWithProviders = {