diff --git a/packages/manager/apps/pci-kubernetes/package.json b/packages/manager/apps/pci-kubernetes/package.json index 4e0a3aa53d4d..bc2cc9f04338 100644 --- a/packages/manager/apps/pci-kubernetes/package.json +++ b/packages/manager/apps/pci-kubernetes/package.json @@ -39,6 +39,7 @@ "i18next-http-backend": "^2.5.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.1.2", "react-hook-form": "^7.52.1", "react-i18next": "^14.1.2", "react-router-dom": "^6.3.0", diff --git a/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_en_GB.json b/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_en_GB.json index a1657eb349b4..27f94fd9e169 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_en_GB.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_en_GB.json @@ -2,7 +2,6 @@ "kube_service": "Service", "kube_service_error": "An error has occurred loading the information: {{message}}", "kube_service_name": "Name", - "kube_service_cluster_etcd_quota": "Utilisation de l'ETCD", "kube_service_description_information": "Below, you will find information about your Kubernetes service, and how to access it using the kubectl tool.", "kube_service_description_reset": "You can reset your Kubernetes cluster's configuration at any time.", "kube_service_offer_beta": "Managed Kubernetes Service (free)", diff --git a/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_fr_FR.json b/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_fr_FR.json index ffd30e2b62b9..41e45a09aedd 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_fr_FR.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/service/Messages_fr_FR.json @@ -93,6 +93,8 @@ "kube_service_cluster_network_vrack_default_gateway": "Passerelle par défaut du vRACK (DHCP)", "kube_service_cluster_network_vrack_customer_gateway": "Passerelle : {{ vRackGatewayIp }}", "kubernetes_add_private_network": "Configurer un réseau", - - "kube_service_network_edit": "Modifier les paramètres du réseau" + "kube_service_network_edit": "Modifier les paramètres du réseau", + "kube_service_cluster_etcd_quota": "Utilisation du quota ETCD", + "kube_service_cluster_etcd_quota_info": "Le quota Etcd représente l'espace alloué à la base de donnée ETCD de votre cluster managé. Veuillez consulter notre guide pour en savoir plus sur le quota ETCD.  {{link}}", + "kube_service_etcd_quota_error": "Ce cluster utilise plus de 80% de l'espace ETCD attribué. Consultez cette page pour éviter tout impact sur votre service: {{link}}" } diff --git a/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.spec.ts b/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.spec.ts index e405eb610ab3..fb053d638303 100644 --- a/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.spec.ts +++ b/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.spec.ts @@ -3,6 +3,7 @@ import { describe, it, vi } from 'vitest'; import * as ApiKubernetesModule from '@/api/data/kubernetes'; import { useAllKube, + useGetClusterEtcdUsage, useKubernetesCluster, useKubes, useRenameKubernetesCluster, @@ -176,3 +177,37 @@ describe('useUpdateKubePolicy', () => { expect(mockError).not.toHaveBeenCalled(); }); }); + +describe('useGetClusterEtcdUsage', () => { + afterEach(() => { + vi.clearAllMocks(); + vi.restoreAllMocks(); + }); + it('fetches etcd usage successfully', async () => { + const mockData = { usage: 500, quota: 1024 }; + vi.spyOn(ApiKubernetesModule, 'getKubeEtcdUsage').mockResolvedValueOnce( + mockData, + ); + + const { result } = renderHook( + () => useGetClusterEtcdUsage('project-valid', 'kube1'), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(result.current.data).toEqual(mockData); + }); + + it('handles error when fetching etcd usage', async () => { + vi.spyOn(ApiKubernetesModule, 'getKubeEtcdUsage').mockRejectedValueOnce( + new Error('Network Error'), + ); + + const { result } = renderHook( + () => useGetClusterEtcdUsage('project-error', 'kube1'), + { wrapper }, + ); + + expect(result.current).toBe(null); + }); +}); diff --git a/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.ts b/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.ts index 020f1675eb1c..a1d281a50447 100644 --- a/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.ts +++ b/packages/manager/apps/pci-kubernetes/src/api/hooks/useKubernetes.ts @@ -1,6 +1,6 @@ import { applyFilters, Filter } from '@ovh-ux/manager-core-api'; import { PaginationState } from '@ovh-ux/manager-react-components'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { TKube } from '@/types'; @@ -612,17 +612,11 @@ export const useCreateKubernetesCluster = ({ export const useGetClusterEtcdUsage = (projectId, kubeId) => { const queryKey = ['project', projectId, 'kube', kubeId, 'etcd', 'usage']; - const query = useQuery({ + return useSuspenseQuery({ queryKey, queryFn: async () => { const data = await getKubeEtcdUsage(projectId, kubeId); return data; }, - enabled: Boolean(projectId), - suspense: true, }); - - return { - ...query, - }; }; diff --git a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.spec.tsx b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.spec.tsx new file mode 100644 index 000000000000..1f23d802872b --- /dev/null +++ b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.spec.tsx @@ -0,0 +1,95 @@ +import { render, waitFor } from '@testing-library/react'; +import * as manager from '@ovh-ux/manager-react-components'; +import { vi } from 'vitest'; +import { UseSuspenseQueryResult } from '@tanstack/react-query'; +import ClusterEtcd from './ClusterETCD.component'; +import { wrapper } from '@/wrapperRenders'; +import * as useKubernetesModule from '@/api/hooks/useKubernetes'; +import { formatBytes, getColorByPercentage } from '@/helpers'; + +describe('ClusterEtcd', () => { + it('renders progress bar and usage text correctly', async () => { + const mockUsage = 500; + const mockQuota = 1000; + const mockPercentage = (mockUsage / mockQuota) * 100; + + vi.spyOn(useKubernetesModule, 'useGetClusterEtcdUsage').mockReturnValue(({ + data: { usage: mockUsage, quota: mockQuota }, + isPending: false, + } as unknown) as UseSuspenseQueryResult<{ usage: number; quota: number }>); + + const { getByText, container } = render(, { wrapper }); + + await waitFor(() => { + const progressBar = container.querySelector('osds-progress-bar'); + const progressBarColor = getColorByPercentage(mockPercentage); + + expect(progressBar).toBeInTheDocument(); + expect(progressBar?.getAttribute('color')).toBe(progressBarColor); + expect(progressBar?.getAttribute('value')).toBe( + mockPercentage.toString(), + ); + expect( + getByText(`${formatBytes(mockUsage)} / ${formatBytes(mockQuota)}`), + ).toBeInTheDocument(); + }); + }); + + it('applies correct styles based on progress percentage', async () => { + const mockUsage = 700; + const mockQuota = 1000; + const mockPercentage = (mockUsage / mockQuota) * 100; + + vi.spyOn(useKubernetesModule, 'useGetClusterEtcdUsage').mockReturnValue(({ + data: { usage: mockUsage, quota: mockQuota }, + isPending: false, + } as unknown) as UseSuspenseQueryResult<{ usage: number; quota: number }>); + + const { container } = render(, { wrapper }); + + await waitFor(() => { + const progressBar = container.querySelector('osds-progress-bar'); + const progressBarStyle = getColorByPercentage(mockPercentage); + + expect(progressBar).toBeInTheDocument(); + expect(progressBar).toHaveProperty('color', progressBarStyle); + }); + }); + + it('renders correct usage and quota information', async () => { + const mockUsage = 300; + const mockQuota = 600; + + vi.spyOn(useKubernetesModule, 'useGetClusterEtcdUsage').mockReturnValue(({ + data: { usage: mockUsage, quota: mockQuota }, + isPending: false, + } as unknown) as UseSuspenseQueryResult<{ usage: number; quota: number }>); + + const { getByText } = render(, { wrapper }); + + await waitFor(() => { + expect( + getByText(`${formatBytes(mockUsage)} / ${formatBytes(mockQuota)}`), + ).toBeInTheDocument(); + }); + }); + it('should call addError only once when percentage is over 80%', async () => { + const mockUsage = 550; + const mockQuota = 600; + const addWarning = vi.fn(); + vi.spyOn(manager, 'useNotifications').mockReturnValue({ + addWarning, + }); + + // Mock useGetClusterEtcdUsage to return usage leading to percentage > 80% + vi.spyOn(useKubernetesModule, 'useGetClusterEtcdUsage').mockReturnValue(({ + data: { usage: mockUsage, quota: mockQuota }, + isPending: false, + } as unknown) as UseSuspenseQueryResult<{ usage: number; quota: number }>); + + // Render the component + render(); + await waitFor(() => expect(addWarning).toHaveBeenCalledTimes(1)); + // Check that addWarning is called only once + }); +}); diff --git a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.test.tsx b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.test.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.tsx b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.tsx index b90da4faa2f8..4f3ee326871b 100644 --- a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.tsx +++ b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterETCD.component.tsx @@ -1,22 +1,83 @@ -import { OsdsProgressBar } from '@ovhcloud/ods-components/react'; +import { useEffect, useMemo } from 'react'; +import { + ODS_TEXT_COLOR_INTENT, + ODS_TEXT_LEVEL, + ODS_TEXT_SIZE, +} from '@ovhcloud/ods-components'; +import { Trans, useTranslation } from 'react-i18next'; +import { useNotifications } from '@ovh-ux/manager-react-components'; +import { OsdsProgressBar, OsdsText } from '@ovhcloud/ods-components/react'; import { useParams } from 'react-router-dom'; import { useGetClusterEtcdUsage } from '@/api/hooks/useKubernetes'; -import { formatBytes } from '@/helpers'; +import { formatBytes, getColorByPercentage, QUOTA_ERROR_URL } from '@/helpers'; -// TODO rajouter le seuil +const getProgressBarStyle = (color: string) => ` + progress[value] { + --progress: calc(var(--w) * (attr(value) / 100)); /* Largeur de la progression en fonction du pourcentage */ + --color: ${color}; + --background: lightgrey; /* Couleur de fond */ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: none; + background: var(--background); + } + progress[value]::-webkit-progress-bar { + background: var(--background); + } + progress[value]::-webkit-progress-value { + background: var(--color); + } + progress[value]::-moz-progress-bar { + background: var(--color); + } +`; function ClusterEtcd() { const { projectId, kubeId } = useParams(); const { data: { usage: used, quota: total }, } = useGetClusterEtcdUsage(projectId, kubeId); - const percentage = (used / total) * 100; + const percentage = useMemo(() => (used / total) * 100, []); + const { t } = useTranslation(['service']); + const { addWarning } = useNotifications(); + + useEffect(() => { + if (percentage > 80) { + addWarning( + }}> + {t('kube_service_etcd_quota_error', { + link: QUOTA_ERROR_URL, + })} + , + true, + ); + } + }, [percentage]); + + useEffect(() => { + const progressBarElement = document.querySelector('osds-progress-bar'); + const { shadowRoot } = progressBarElement; + const style = document.createElement('style'); + style.textContent = getProgressBarStyle(getColorByPercentage(percentage)); + shadowRoot.appendChild(style); + }, []); + return (
- -
+ + {formatBytes(used)} / {formatBytes(total)} -
+
); } diff --git a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.spec.tsx b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.spec.tsx index 301a77944bf0..c5bf3257e422 100644 --- a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.spec.tsx +++ b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.spec.tsx @@ -1,13 +1,19 @@ -import { render, screen } from '@testing-library/react'; -import { describe, it, expect } from 'vitest'; +import { render, screen, waitFor, act } from '@testing-library/react'; +import * as manager from '@ovh-ux/manager-react-components'; +import { describe, it, expect, vi } from 'vitest'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; import ClusterInformation from '@/components/service/ClusterInformation.component'; +import { wrapper } from '@/wrapperRenders'; import { TKube } from '@/types'; +import * as ApiKube from '@/api/data/kubernetes'; const renderClusterInformation = (kubeDetail) => - render(); + render(, { wrapper }); describe('ClusterInformation', () => { + const mockData = { usage: 500, quota: 1024 }; + vi.spyOn(ApiKube, 'getKubeEtcdUsage').mockResolvedValueOnce(mockData); + const kubeDetail = { id: '1', name: 'Cluster1', @@ -35,25 +41,41 @@ describe('ClusterInformation', () => { ], } as TKube; - it('renders cluster information correctly', () => { + it.skip('calls clearNotifications on unmount', async () => { + const { unmount } = renderClusterInformation(kubeDetail); + const mockClearNotifications = vi.fn(); + + vi.spyOn(manager, 'useNotifications').mockReturnValue({ + clearNotifications: mockClearNotifications, + }); + expect(mockClearNotifications).not.toHaveBeenCalled(); + act(() => unmount()); + // not working issue + // https://github.com/testing-library/react-hooks-testing-library/issues/847 + expect(mockClearNotifications).toHaveBeenCalledTimes(1); + }); + + it('renders cluster information correctly', async () => { renderClusterInformation(kubeDetail); - expect( - screen.getByText(/kube_service_cluster_information/i), - ).toBeInTheDocument(); - expect(screen.getByText('Cluster1')).toBeInTheDocument(); - expect( - screen.getByText('kube_service_cluster_status_READY'), - ).toBeInTheDocument(); - expect(screen.getByText('1.18')).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/kube_service_cluster_information/i), + ).toBeInTheDocument(); + expect(screen.getByText('Cluster1')).toBeInTheDocument(); + expect( + screen.getByText('kube_service_cluster_status_READY'), + ).toBeInTheDocument(); + expect(screen.getByText('1.18')).toBeInTheDocument(); - expect( - screen.getByTestId('admission-plugin-chip AlwaysPullImages'), - ).toHaveProperty('color', ODS_THEME_COLOR_INTENT.success); - expect( - screen.getByTestId('admission-plugin-chip NodeRestriction'), - ).toHaveProperty('color', ODS_THEME_COLOR_INTENT.warning); - expect(screen.getByText('Region1')).toBeInTheDocument(); + expect( + screen.getByTestId('admission-plugin-chip AlwaysPullImages'), + ).toHaveProperty('color', ODS_THEME_COLOR_INTENT.success); + expect( + screen.getByTestId('admission-plugin-chip NodeRestriction'), + ).toHaveProperty('color', ODS_THEME_COLOR_INTENT.warning); + expect(screen.getByText('Region1')).toBeInTheDocument(); + }); }); it('renders cluster ID with clipboard component', () => { diff --git a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.tsx b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.tsx index 7a628e641696..206491e954ac 100644 --- a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.tsx +++ b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterInformation.component.tsx @@ -1,4 +1,6 @@ import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; +import { useEffect } from 'react'; + import { ODS_TEXT_LEVEL, ODS_TEXT_SIZE, @@ -10,11 +12,10 @@ import { OsdsTile, } from '@ovhcloud/ods-components/react'; import { useTranslation } from 'react-i18next'; - -import ClusterEtcd from './ClusterETCD.component'; import { Clipboard, TileBlock as TileLine, + useNotifications, } from '@ovh-ux/manager-react-components'; import { TKube } from '@/types'; import ClusterStatus from './ClusterStatus.component'; @@ -22,6 +23,7 @@ import ClusterETCD from './ClusterETCD.component'; import AdmissionPlugins from './AdmissionPlugins.component'; import { isProcessing } from './ClusterManagement.component'; +import ClusterTile from './ClusterTile.component'; export type ClusterInformationProps = { kubeDetail: TKube; @@ -32,6 +34,9 @@ export default function ClusterInformation({ }: Readonly) { const { t } = useTranslation('service'); const { t: tDetail } = useTranslation('listing'); + const { clearNotifications } = useNotifications(); + + useEffect(() => clearNotifications, []); return ( - - {' '} - - {kubeDetail.name} - - - - - - - - + - - - {kubeDetail.name} - - - - - + + { + // hacky, need to use a help icon and a tooltip inside the string label + } + ) as unknown) as string}> + + ); diff --git a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterTile.component.spec.tsx b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterTile.component.spec.tsx new file mode 100644 index 000000000000..ab7265f86e3a --- /dev/null +++ b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterTile.component.spec.tsx @@ -0,0 +1,27 @@ +import { describe, it, vi, expect } from 'vitest'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { useTranslation } from 'react-i18next'; +import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; +import ClusterTile from '@/components/service/ClusterTile.component'; +import { QUOTA_ERROR_URL } from '@/helpers'; + +describe('ClusterTile', () => { + it('renders the cluster quota text', () => { + render(); + + // Check if the translated quota text is rendered + expect( + screen.getByText('kube_service_cluster_etcd_quota'), + ).toBeInTheDocument(); + }); + + it('renders the help icon with correct color and size', () => { + const { container } = render(); + const icon = container.querySelector('osds-icon'); // Assuming the icon has a role of 'img' + expect(icon).toHaveAttribute('color', ODS_THEME_COLOR_INTENT.primary); + fireEvent.click(icon); + expect( + screen.getByText('kube_service_cluster_etcd_quota_info'), + ).toBeVisible(); + }); +}); diff --git a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterTile.component.tsx b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterTile.component.tsx index 365332731bd9..46efca60778f 100644 --- a/packages/manager/apps/pci-kubernetes/src/components/service/ClusterTile.component.tsx +++ b/packages/manager/apps/pci-kubernetes/src/components/service/ClusterTile.component.tsx @@ -1,5 +1,6 @@ import { useTranslation, Trans } from 'react-i18next'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; +import { ErrorBoundary } from 'react-error-boundary'; import { ODS_ICON_NAME, ODS_ICON_SIZE, diff --git a/packages/manager/apps/pci-kubernetes/src/helpers/index.spec.ts b/packages/manager/apps/pci-kubernetes/src/helpers/index.spec.ts index 5337791a9979..a2e92de7be8f 100644 --- a/packages/manager/apps/pci-kubernetes/src/helpers/index.spec.ts +++ b/packages/manager/apps/pci-kubernetes/src/helpers/index.spec.ts @@ -1,10 +1,14 @@ -import { describe, it } from 'vitest'; +import { describe, it, vi } from 'vitest'; +import { z } from 'zod'; import { compareFunction, formatIP, getFormatedKubeVersion, isIPValid, paginateResults, + validateSchema, + formatBytes, + getColorByPercentage, } from '@/helpers/index'; describe('helper', () => { @@ -66,3 +70,77 @@ describe('helper', () => { expect(result).toBe(false); }); }); + +describe('validateSchema', () => { + const schema = z.object({ + name: z.string(), + age: z + .number() + .int() + .positive(), + }); + + it('should return validated data if data is valid', () => { + const data = { name: 'John', age: 30 }; + const result = validateSchema({ schema, data }); + expect(result).toEqual(data); + }); + + it('should return null and call onInvalid if data is invalid', () => { + const data = { name: 'John', age: -5 }; + const onInvalidMock = vi.fn(); + const result = validateSchema({ schema, data, onInvalid: onInvalidMock }); + + expect(result).toBeNull(); + expect(onInvalidMock).toHaveBeenCalledTimes(1); + expect(onInvalidMock.mock.calls[0][0]).toBeInstanceOf(z.ZodError); + }); + + it('should return null without calling onInvalid if data is invalid and onInvalid is not provided', () => { + const data = { name: 'John', age: -5 }; + const result = validateSchema({ schema, data }); + expect(result).toBeNull(); + }); +}); + +describe('formatBytes', () => { + it('should format bytes correctly for values under 1024', () => { + expect(formatBytes(512)).toBe('512 o'); + }); + + it('should format bytes correctly in KiB', () => { + expect(formatBytes(2048)).toBe('2 KiB'); + }); + + it('should format bytes correctly in MiB', () => { + expect(formatBytes(1048576)).toBe('1 MiB'); + }); + + it('should format bytes correctly in GiB', () => { + expect(formatBytes(1073741824)).toBe('1 GiB'); + }); + + it('should format bytes correctly in TiB', () => { + expect(formatBytes(1099511627776)).toBe('1 TiB'); + }); +}); + +describe('getColorByPercentage', () => { + it('should return primary color for percentage <= 69', () => { + expect(getColorByPercentage(50)).toBe('var(--ods-color-primary-500)'); + expect(getColorByPercentage(69)).toBe('var(--ods-color-primary-500)'); + }); + + it('should return warning color for percentage between 70 and 79', () => { + expect(getColorByPercentage(75)).toBe('var(--ods-color-warning-500)'); + }); + + it('should return error color for percentage between 80 and 100', () => { + expect(getColorByPercentage(85)).toBe('var(--ods-color-error-500)'); + expect(getColorByPercentage(100)).toBe('var(--ods-color-error-500)'); + }); + + it('should return last color in thresholds if percentage exceeds 100', () => { + expect(getColorByPercentage(120)).toBe('var(--ods-color-error-500)'); + }); +}); diff --git a/packages/manager/apps/pci-kubernetes/src/helpers/index.ts b/packages/manager/apps/pci-kubernetes/src/helpers/index.ts index f58b68077a7b..5dc7191ba023 100644 --- a/packages/manager/apps/pci-kubernetes/src/helpers/index.ts +++ b/packages/manager/apps/pci-kubernetes/src/helpers/index.ts @@ -1,6 +1,9 @@ import { PaginationState } from '@ovh-ux/manager-react-components'; import { z } from 'zod'; +export const QUOTA_ERROR_URL = + 'https://docs.ovh.com/gb/en/kubernetes/etcd-quota-error/'; + export const compareFunction = (key: keyof T) => (a: T, b: T) => { const aValue = a[key] || ''; const bValue = b[key] || ''; @@ -133,14 +136,19 @@ type ColorThreshold = { * Returns the appropriate color based on the provided percentage. * * @param percentage - The percentage value (0 to 100). - * @param thresholds - The color thresholds to use for determining color. * @returns - The color corresponding to the percentage. */ export function getColorByPercentage(percentage: number): string { const colorThresholds: ColorThreshold[] = [ - { threshold: 33.333, color: 'blue' }, // Color for less than or equal to 33.333% - { threshold: 66, color: 'yellow' }, // Color for between 33.333% and 66% - { threshold: 100, color: 'red' }, // Color for greater than 66% + { + threshold: 69, + color: 'var(--ods-color-primary-500)', + }, // Color for less than or equal to 33.333% + { + threshold: 79, + color: 'var(--ods-color-warning-500)', + }, // Color for between 33.333% and 66% + { threshold: 100, color: 'var(--ods-color-error-500)' }, // Color for greater than 80% ].sort((a, b) => a.threshold - b.threshold); // Sort thresholds to ensure they are in ascending order colorThresholds.sort((a, b) => a.threshold - b.threshold); @@ -154,5 +162,5 @@ export function getColorByPercentage(percentage: number): string { } // If percentage exceeds all thresholds, return the last color - return thresholds[thresholds.length - 1].color; + return colorThresholds[colorThresholds.length - 1].color; } diff --git a/packages/manager/apps/pci-kubernetes/src/wrapperRenders.tsx b/packages/manager/apps/pci-kubernetes/src/wrapperRenders.tsx index cc007cd0accd..199c6abd7cb6 100644 --- a/packages/manager/apps/pci-kubernetes/src/wrapperRenders.tsx +++ b/packages/manager/apps/pci-kubernetes/src/wrapperRenders.tsx @@ -4,6 +4,7 @@ import { ShellContextType, } from '@ovh-ux/manager-react-shell-client'; import { vi } from 'vitest'; +import { ErrorBoundary } from 'react-error-boundary'; export const shellContext = { environment: { @@ -16,14 +17,22 @@ export const shellContext = { }, }; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); export const wrapper = ({ children }) => ( - - - {children} - - + Error From Test}> + + + {children} + + + );