diff --git a/apps/console/public/assets/sidebar-test/layer.svg b/apps/console/public/assets/sidebar-test/layer.svg deleted file mode 100644 index 1c776882883..00000000000 --- a/apps/console/public/assets/sidebar-test/layer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libs/domains/clusters/data-access/src/index.ts b/libs/domains/clusters/data-access/src/index.ts index 3a75ad8d8f5..8e8229f53d9 100644 --- a/libs/domains/clusters/data-access/src/index.ts +++ b/libs/domains/clusters/data-access/src/index.ts @@ -1,5 +1,4 @@ export * from './lib/domains-clusters-data-access' -export * from './lib/http/is-http-status' export * from './lib/platform-configuration/platform-configuration' export { isGcpCluster } from './lib/cluster-checks/is-gcp-cluster' export { isAwsCluster } from './lib/cluster-checks/is-aws-cluster' diff --git a/libs/domains/clusters/feature/src/lib/cluster-add/cluster-add.tsx b/libs/domains/clusters/feature/src/lib/cluster-add/cluster-add.tsx index d043f577350..8c7ab62ae48 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-add/cluster-add.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-add/cluster-add.tsx @@ -200,7 +200,7 @@ export function ClusterAdd() { { + const React = jest.requireActual('react') + const ui = jest.requireActual('@qovery/shared/ui') + return { + ...ui, + Icon: (props: { name?: string; iconName?: string }) => + React.createElement('span', { 'data-icon-name': props.name ?? props.iconName }), + } +}) + +describe('ClusterAvatar', () => { + it.each([ + ['AWS', 'AWS'], + ['GCP', 'GCP'], + ['ON_PREMISE', 'KUBERNETES'], + ['OVH', 'OVH_CLOUD'], + ['ORACLE', 'ORACLE_CLOUD'], + ['IBM', 'IBM_CLOUD'], + ] as const)('shows the %s provider icon', (cloudProvider, iconName) => { + const { container } = renderWithProviders() + + expect(container.querySelector(`[data-icon-name="${iconName}"]`)).toBeInTheDocument() + }) + + it('shows a demo icon for demo clusters', () => { + const { container } = renderWithProviders( + + ) + + expect(container.querySelector('[data-icon-name="laptop-code"]')).toBeInTheDocument() + }) +}) diff --git a/libs/domains/clusters/feature/src/lib/cluster-avatar/cluster-avatar.tsx b/libs/domains/clusters/feature/src/lib/cluster-avatar/cluster-avatar.tsx index 10ed95a8863..045ae72bd7c 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-avatar/cluster-avatar.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-avatar/cluster-avatar.tsx @@ -1,10 +1,11 @@ -import { type CloudProviderEnum, type Cluster, type ClusterOverviewResponse } from 'qovery-typescript-axios' +import { type CloudVendorEnum, type Cluster, type ClusterOverviewResponse } from 'qovery-typescript-axios' import { type ComponentPropsWithoutRef, type ElementRef, forwardRef } from 'react' import { match } from 'ts-pattern' +import { IconEnum } from '@qovery/shared/enums' import { Avatar, Icon } from '@qovery/shared/ui' export interface ClusterAvatarProps extends Omit, 'fallback'> { - cloudProvider?: CloudProviderEnum + cloudProvider?: CloudVendorEnum cluster?: Cluster | ClusterOverviewResponse } @@ -18,6 +19,9 @@ export const ClusterAvatar = forwardRef, ClusterAvatar )) .with({ localCloudProvider: 'ON_PREMISE' }, () => ) + .with({ localCloudProvider: 'OVH' }, () => ) + .with({ localCloudProvider: 'ORACLE' }, () => ) + .with({ localCloudProvider: 'IBM' }, () => ) .otherwise(() => ) return diff --git a/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.spec.tsx b/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.spec.tsx index ba0e8533049..d5aadc2dd5c 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.spec.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.spec.tsx @@ -3,6 +3,10 @@ import { renderWithProviders, screen } from '@qovery/shared/util-tests' import { useUpdateClusterOperator } from '../hooks/use-update-cluster-operator/use-update-cluster-operator' import { ClusterOperatorStatus } from './cluster-operator-status' +jest.mock('@tanstack/react-router', () => ({ + ...jest.requireActual('@tanstack/react-router'), + useParams: () => ({ organizationId: 'organization-id', clusterId: 'cluster-id' }), +})) jest.mock('../hooks/use-update-cluster-operator/use-update-cluster-operator') const mockUseUpdateClusterOperator = useUpdateClusterOperator as jest.Mock @@ -22,9 +26,7 @@ describe('ClusterOperatorStatus', () => { }) it('updates the Operator to the target versions', async () => { - const { userEvent } = renderWithProviders( - - ) + const { userEvent } = renderWithProviders() await userEvent.click(screen.getByRole('button', { name: 'Update Operator' })) @@ -38,9 +40,7 @@ describe('ClusterOperatorStatus', () => { it('does not start another update while one is in progress', async () => { mockUseUpdateClusterOperator.mockReturnValue({ mutate: mockUpdateOperator, isLoading: true }) - const { userEvent } = renderWithProviders( - - ) + const { userEvent } = renderWithProviders() const button = screen.getByRole('button', { name: 'Update Operator' }) expect(button).toBeDisabled() @@ -51,25 +51,13 @@ describe('ClusterOperatorStatus', () => { }) it('hides the update action while the Operator is not connected', () => { - renderWithProviders( - - ) + renderWithProviders() expect(screen.queryByRole('button', { name: 'Update Operator' })).not.toBeInTheDocument() }) it('disables the update action without a target chart version', () => { - renderWithProviders( - - ) + renderWithProviders() expect(screen.getByRole('button', { name: 'Update Operator' })).toBeDisabled() }) diff --git a/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.tsx b/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.tsx index e69e78c6f02..5e8599de14f 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-operator-status/cluster-operator-status.tsx @@ -1,4 +1,5 @@ import { type IconName } from '@fortawesome/fontawesome-common-types' +import { useParams } from '@tanstack/react-router' import { type ClusterOperatorFleetStatus, type ClusterOperatorStatusResponse } from 'qovery-typescript-axios' import { match } from 'ts-pattern' import { Badge, Button, StatusChip } from '@qovery/shared/ui' @@ -76,12 +77,11 @@ function Version({ installed, target }: { installed?: string | null; target?: st } export interface ClusterOperatorStatusProps { - organizationId: string - clusterId: string operatorStatus: ClusterOperatorStatusResponse } -export function ClusterOperatorStatus({ organizationId, clusterId, operatorStatus }: ClusterOperatorStatusProps) { +export function ClusterOperatorStatus({ operatorStatus }: ClusterOperatorStatusProps) { + const { organizationId = '', clusterId = '' } = useParams({ strict: false }) const { mutate: updateOperator, isLoading: isUpdating } = useUpdateClusterOperator() const display = getClusterOperatorStatusDisplay(operatorStatus.status) diff --git a/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-context.tsx b/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-context.tsx index 58038318120..0826c5c5353 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-context.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-context.tsx @@ -62,6 +62,17 @@ function countProfileChanges( return profileChanges.length + clusterInputChanges.length } +function omitSubmittedValues>>(currentValues: T, submittedValues: T) { + return Object.fromEntries( + Object.entries(currentValues).flatMap(([componentKey, values]) => { + const remainingValues = Object.fromEntries( + Object.entries(values).filter(([key, value]) => !equal(value, submittedValues[componentKey]?.[key])) + ) + return Object.keys(remainingValues).length ? [[componentKey, remainingValues]] : [] + }) + ) as T +} + // PUT replaces the whole configuration: unedited components and inputs are sent back as saved. function getConfigurationRequest( template: PlatformTemplateSummaryResponse, @@ -148,9 +159,9 @@ export function ClusterProfileProvider({ children }: PropsWithChildren) { clusterId, configurationRequest: getConfigurationRequest(selectedTemplate, configuration, profileValues, clusterInputs), }) - // The saved configuration now carries the edits, so the local copies can go. - setProfileValues({}) - setClusterInputs({}) + // The saved configuration now carries the submitted edits, so their local copies can go. + setProfileValues((currentValues) => omitSubmittedValues(currentValues, profileValues)) + setClusterInputs((currentValues) => omitSubmittedValues(currentValues, clusterInputs)) } const saveChanges = async () => { diff --git a/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-sidebar.tsx b/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-sidebar.tsx index 72f10878696..eb4843f8bb9 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-sidebar.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-profile/cluster-profile-sidebar.tsx @@ -31,17 +31,12 @@ interface LayerSectionRowProps { onSelectItem: (itemId: string) => void } -function LayerIcon({ className = '' }: { className?: string }) { - return ( -