diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index 3a2f4030..36cb4c18 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -27,6 +27,7 @@ import { import { YamlViewButtonWithLoader } from '../../Yaml/YamlViewButtonWithLoader.tsx'; import { useToast } from '../../../context/ToastContext.tsx'; +import { canConnectToMCP } from '../controlPlanes.ts'; interface Props { controlPlane: ListControlPlanesType; @@ -59,6 +60,8 @@ export function ControlPlaneCard({ const name = controlPlane.metadata.name; const namespace = controlPlane.metadata.namespace; + const isConnectButtonEnabled = canConnectToMCP(controlPlane); + return ( <> @@ -106,7 +109,7 @@ export function ControlPlaneCard({ resourceType={'managedcontrolplanes'} /> , +): ControlPlaneStatusCondition => ({ + type: 'Unknown', + status: false, + reason: 'DefaultReason', + message: 'Default message', + lastTransitionTime: new Date().toISOString(), + ...overrides, +}); + +const createControlPlane = ( + conditions: ControlPlaneStatusCondition[], +): ControlPlaneType => ({ + metadata: { + name: '', + namespace: '', + }, + spec: { + components: { + crossplane: undefined, + btpServiceOperator: undefined, + externalSecretsOperator: undefined, + kyverno: undefined, + flux: undefined, + }, + }, + status: { + conditions, + status: ReadyStatus.Ready, + access: undefined, + }, +}); + +const baseConditions = [ + createCondition({ type: 'APIServerHealthy', status: 'True' }), + createCondition({ type: 'AuthenticationHealthy', status: 'True' }), + createCondition({ type: 'AuthorizationHealthy', status: 'True' }), +]; + +describe('canConnectToMCP', () => { + it('returns true when all required conditions are True', () => { + const controlPlane = createControlPlane([...baseConditions]); + expect(canConnectToMCP(controlPlane)).toBe(true); + }); + + it('returns false when one required condition is missing', () => { + const controlPlane = createControlPlane([ + createCondition({ type: 'APIServerHealthy', status: 'True' }), + createCondition({ type: 'AuthorizationHealthy', status: 'True' }), + ]); + expect(canConnectToMCP(controlPlane)).toBe(false); + }); + + it('returns false when one required condition has status "False"', () => { + const controlPlane = createControlPlane([ + createCondition({ type: 'APIServerHealthy', status: 'True' }), + createCondition({ type: 'AuthenticationHealthy', status: 'False' }), + createCondition({ type: 'AuthorizationHealthy', status: 'True' }), + ]); + expect(canConnectToMCP(controlPlane)).toBe(false); + }); + + it('returns true even if other conditions exist', () => { + const controlPlane = createControlPlane([ + ...baseConditions, + createCondition({ type: 'SomethingElse', status: 'True' }), + ]); + expect(canConnectToMCP(controlPlane)).toBe(true); + }); + + it('returns true when required statuses are lowercase', () => { + const controlPlane = createControlPlane( + baseConditions.map((c) => ({ + ...c, + status: 'true', + })), + ); + expect(canConnectToMCP(controlPlane)).toBe(true); + }); + + it('returns true when required statuses are boolean', () => { + const controlPlane = createControlPlane( + baseConditions.map((c) => ({ + ...c, + status: true, + })), + ); + expect(canConnectToMCP(controlPlane)).toBe(true); + }); + + it('returns false when there are no conditions', () => { + const controlPlane = createControlPlane([]); + expect(canConnectToMCP(controlPlane)).toBe(false); + }); + + it('returns false when status field is missing', () => { + const controlPlane = { + metadata: { + name: '', + namespace: '', + }, + spec: { + components: { + crossplane: undefined, + btpServiceOperator: undefined, + externalSecretsOperator: undefined, + kyverno: undefined, + flux: undefined, + }, + }, + } as ControlPlaneType; + expect(canConnectToMCP(controlPlane)).toBe(false); + }); +}); diff --git a/src/components/ControlPlanes/controlPlanes.ts b/src/components/ControlPlanes/controlPlanes.ts new file mode 100644 index 00000000..68017507 --- /dev/null +++ b/src/components/ControlPlanes/controlPlanes.ts @@ -0,0 +1,17 @@ +import { ControlPlaneType } from '../../lib/api/types/crate/controlPlanes'; + +export const canConnectToMCP = (controlPlane: ControlPlaneType): boolean => { + const conditions = controlPlane.status?.conditions ?? []; + + return [ + 'APIServerHealthy', + 'AuthenticationHealthy', + 'AuthorizationHealthy', + ].every((type) => + conditions.some( + (condition) => + condition.type === type && + String(condition.status).toLowerCase() === 'true', + ), + ); +}; diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index e31acc99..2a05b9fb 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -44,7 +44,7 @@ export interface ControlPlaneStatusType { export interface ControlPlaneStatusCondition { type: string; - status: boolean; + status: boolean | string; reason: string; message: string; lastTransitionTime: string;