diff --git a/packages/manager/apps/pci-cold-archive/src/api/hooks/useUsers.ts b/packages/manager/apps/pci-cold-archive/src/api/hooks/useUsers.ts index 0bab2276ad34..4f26b505331f 100644 --- a/packages/manager/apps/pci-cold-archive/src/api/hooks/useUsers.ts +++ b/packages/manager/apps/pci-cold-archive/src/api/hooks/useUsers.ts @@ -302,7 +302,7 @@ export const useImportPolicy = ({ setIsPending(true); const policy = await readFileAsJSON(files[0], t); - importUserPolicy(projectId, userId, policy); + await importUserPolicy(projectId, userId, policy); onSuccess(); } catch (e) { onError(e as Error); diff --git a/packages/manager/apps/pci-cold-archive/src/pages/containers/archive/Archive.page.tsx b/packages/manager/apps/pci-cold-archive/src/pages/containers/archive/Archive.page.tsx index e93f347f1cb5..e3c77d85bb63 100644 --- a/packages/manager/apps/pci-cold-archive/src/pages/containers/archive/Archive.page.tsx +++ b/packages/manager/apps/pci-cold-archive/src/pages/containers/archive/Archive.page.tsx @@ -52,7 +52,7 @@ export default function ArchivePage() { onError: (error: ApiError) => { addErrorMessage({ i18nKey: - 'pci_projects_project_storages_cold_archive_containers_container_archive_success_message', + 'pci_projects_project_storages_cold_archive_containers_container_archive_error_message', error, values: { containerName: archiveName, diff --git a/packages/manager/apps/pci-databases-analytics/src/pages/services/create/_components/OrderFunnel.component.tsx b/packages/manager/apps/pci-databases-analytics/src/pages/services/create/_components/OrderFunnel.component.tsx index 530fc8f9d9e9..13510301fb93 100644 --- a/packages/manager/apps/pci-databases-analytics/src/pages/services/create/_components/OrderFunnel.component.tsx +++ b/packages/manager/apps/pci-databases-analytics/src/pages/services/create/_components/OrderFunnel.component.tsx @@ -1,9 +1,7 @@ import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { - Alert, - AlertDescription, Button, Card, CardContent, @@ -66,6 +64,7 @@ const OrderFunnel = ({ suggestions, catalog, ); + const { projectId } = useParams(); const projectData = usePciProject(); const navigate = useNavigate(); const { toast } = useToast(); @@ -80,7 +79,9 @@ const OrderFunnel = ({ }); }, onSuccess: (service) => { - navigate(`../${service.id}`); + navigate( + `/pci/projects/${projectId}/databases-analytics/${service.category}/services/${service.id}`, + ); toast({ title: t('successCreatingService'), }); diff --git a/packages/manager/apps/pci-kubernetes/package.json b/packages/manager/apps/pci-kubernetes/package.json index fb7afefeb0ca..480e33350beb 100644 --- a/packages/manager/apps/pci-kubernetes/package.json +++ b/packages/manager/apps/pci-kubernetes/package.json @@ -7,18 +7,15 @@ "scripts": { "build": "tsc --project tsconfig.build.json && vite build", "build:strict": "tsc --project tsconfig.json && vite build", - "coverage": "vitest run --coverage", + "coverage": "manager-test run --coverage", "lint": "eslint ./src", "lint:modern": "manager-lint --config eslint.config.mjs ./src", "lint:modern:fix": "manager-lint --fix --config eslint.config.mjs ./src", "start": "vite", - "test": "vitest run", - "test:watch": "vitest" + "test": "manager-test run", + "test:watch": "manager-test" }, "dependencies": { - "@datatr-ux/ods-tailwind-config": "1.0.3", - "@datatr-ux/ovhcloud-types": "1.0.8", - "@datatr-ux/uxlib": "0.0.10", "@hookform/resolvers": "^3.3.4", "@ovh-ux/manager-common-translations": "^0.19.2", "@ovh-ux/manager-config": "^8.6.4", @@ -34,7 +31,9 @@ "@ovhcloud/ods-common-stencil": "17.2.2", "@ovhcloud/ods-common-theming": "17.2.2", "@ovhcloud/ods-components": "17.2.2", + "@ovhcloud/ods-react": "^19.0.2", "@ovhcloud/ods-theme-blue-jeans": "17.2.2", + "@ovhcloud/ods-themes": "^19.0.2", "@tanstack/react-query": "^5.51.21", "@tanstack/react-table": "^8.17.3", "clsx": "^2.1.1", @@ -58,21 +57,14 @@ "@ovh-ux/manager-static-analysis-kit": "^0.11.0", "@ovh-ux/manager-vite-config": "^0.13.3", "@tanstack/react-query-devtools": "^5.51.21", - "@testing-library/dom": "^10.1.0", - "@testing-library/jest-dom": "^6.4.5", - "@testing-library/react": "^16.0.0", - "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", "@types/react": "^18.2.79", "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.0", - "@vitest/coverage-v8": "^2.1.9", "autoprefixer": "^10.4.19", "postcss": "^8.4.38", "rollup": "^4.18.0", "tailwindcss": "^3.4.4", - "typescript": "^5.4.5", - "vitest": "^2.1.9" + "typescript": "^5.4.5" }, "regions": [ "CA", diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_de_DE.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_de_DE.json index 03ef657615fc..c5b433228ff9 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_de_DE.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_de_DE.json @@ -38,5 +38,6 @@ "kube_common_node_pool_deploy_description": "Bei einem über drei Availability Zones (AZ) verteilten Kubernetes-Cluster wird empfohlen, Node-Pools je Zone einzusetzen, um Resilienz und Hochverfügbarkeit zu gewährleisten. Diese Konfiguration ermöglicht eine gleichmäßige Verteilung Ihrer Workloads auf die AZs. Das erhöht die Fehlertoleranz und sorgt für eine optimale Betriebskontinuität.", "kube_node_pool": "Node-Pool konfigurieren", "kube_common_estimation_price_free": "Kostenlos", - "kube_common_estimation_total_price": "Geschätzter Gesamtpreis:" + "kube_common_estimation_total_price": "Geschätzter Gesamtpreis:", + "kube_common_node_pool_estimation_cost_tile": "Monatliche Schätzung" } diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_en_GB.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_en_GB.json index c639e0556f72..5460744ab07e 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_en_GB.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_en_GB.json @@ -38,5 +38,6 @@ "kube_common_node_pool_deploy_description": "To achieve high availability and resilience within a three-zone Kubernetes cluster, we recommend deploying one node pool per zone. This configuration allows you to distribute your workloads evenly across the AZ. In addition, it improves fault tolerance and ensures service uptime.", "kube_node_pool": "Configure node pool", "kube_common_estimation_price_free": "Free", - "kube_common_estimation_total_price": "Estimated total price:" + "kube_common_estimation_total_price": "Total estimated price:", + "kube_common_node_pool_estimation_cost_tile": "Monthly estimate" } diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_es_ES.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_es_ES.json index 82f995dcf4ec..b94710076596 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_es_ES.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_es_ES.json @@ -38,5 +38,6 @@ "kube_common_node_pool_deploy_description": "En un clúster Kubernetes repartido en tres zonas de disponibilidad (AZ), se recomienda utilizar pools de nodos por zona para garantizar la resiliencia y la alta disponibilidad. Esta configuración le permite distribuir de manera equilibrada sus cargas de trabajo entre las AZ. Mejora así la tolerancia a fallos y garantiza una continuidad del servicio óptima.", "kube_node_pool": "Configurar el pool de nodos", "kube_common_estimation_price_free": "Gratis", - "kube_common_estimation_total_price": "Precio total aproximado:" + "kube_common_estimation_total_price": "Precio total estimado:", + "kube_common_node_pool_estimation_cost_tile": "Estimación mensual" } diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_CA.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_CA.json index 8606c3ba20c4..db5d56a2ee66 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_CA.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_CA.json @@ -22,6 +22,7 @@ "kube_common_node_pool_configure_true": "Configurer des pools de nœuds", "kube_common_node_pool_configure_false": " Créer un cluster sans pool de nœuds", "kube_common_node_pool_estimated_cost": "Estimation coût mensuel du cluster :", + "kube_common_node_pool_estimation_cost_tile": "Estimation mensuelle", "kube_common_node_pool_estimation_text": "Cette estimation n'inclut pas une éventuelle réduction liée aux Savings Plans.", "kube_common_node_pool_estimation_price": "Pools de nœuds :", "kube_common_estimation_total_price": "Prix total estimé :", diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_FR.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_FR.json index 8606c3ba20c4..db5d56a2ee66 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_FR.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_fr_FR.json @@ -22,6 +22,7 @@ "kube_common_node_pool_configure_true": "Configurer des pools de nœuds", "kube_common_node_pool_configure_false": " Créer un cluster sans pool de nœuds", "kube_common_node_pool_estimated_cost": "Estimation coût mensuel du cluster :", + "kube_common_node_pool_estimation_cost_tile": "Estimation mensuelle", "kube_common_node_pool_estimation_text": "Cette estimation n'inclut pas une éventuelle réduction liée aux Savings Plans.", "kube_common_node_pool_estimation_price": "Pools de nœuds :", "kube_common_estimation_total_price": "Prix total estimé :", diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_it_IT.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_it_IT.json index f649ae33c777..d2f174e3fa50 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_it_IT.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_it_IT.json @@ -38,5 +38,6 @@ "kube_common_node_pool_deploy_description": "In un cluster Kubernetes ripartito su tre Availability Zone (AZ), l'utilizzo di pool di nodi per zona è consigliato per assicurare resilienza e alta disponibilità. Questa configurazione permette di distribuire in modo equilibrato i tuoi workload tra le AZ. Migliora la tolleranza ai guasti e garantisce una continuità di servizio ottimale.", "kube_node_pool": "Configurare il pool di nodi", "kube_common_estimation_price_free": "Gratis", - "kube_common_estimation_total_price": "Prezzo totale stimato:" + "kube_common_estimation_total_price": "Prezzo totale stimato:", + "kube_common_node_pool_estimation_cost_tile": "Stima mensile" } diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pl_PL.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pl_PL.json index 0b8735deb602..7574add89824 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pl_PL.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pl_PL.json @@ -38,5 +38,6 @@ "kube_common_node_pool_deploy_description": "W klastrze Kubernetes rozproszonym w trzech strefach dostępności (AZ) zaleca się używanie pul węzłów w każdej strefie, w celu zapewnienia odporności i wysokiej dostępności. Dzięki tej konfiguracji możesz równomiernie rozdzielić obciążenia między AZ, co poprawia tolerancję na awarie i zapewnia optymalną ciągłość usługi.", "kube_node_pool": "Konfiguracja puli węzłów", "kube_common_estimation_price_free": "Gratis", - "kube_common_estimation_total_price": "Szacunkowa cena całkowita:" + "kube_common_estimation_total_price": "Szacowana cena całkowita:", + "kube_common_node_pool_estimation_cost_tile": "Miesięczna wycena" } diff --git a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pt_PT.json b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pt_PT.json index 4b92f9991356..4f4d13169e37 100644 --- a/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pt_PT.json +++ b/packages/manager/apps/pci-kubernetes/public/translations/node-pool/Messages_pt_PT.json @@ -38,5 +38,6 @@ "kube_common_node_pool_deploy_description": "Num cluster Kubernetes repartido por três Availability Zones (AZ), é recomendada a utilização de pools de nós por zonas para assegurar a resiliência e a alta disponibilidade. Esta configuração permite distribuir de forma equilibrada as suas cargas de trabalho entre as AZ. Assim, melhora-se a tolerância a falhas e garante-se uma continuidade de serviço ideal.", "kube_node_pool": "Configurar o pool de nós", "kube_common_estimation_price_free": "Grátis", - "kube_common_estimation_total_price": "Preço total estimado:" + "kube_common_estimation_total_price": "Preço total estimado:", + "kube_common_node_pool_estimation_cost_tile": "Estimativa mensal" } diff --git a/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.component.spec.tsx b/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.component.spec.tsx new file mode 100644 index 000000000000..37eb5a11c759 --- /dev/null +++ b/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.component.spec.tsx @@ -0,0 +1,147 @@ +import { render } from '@testing-library/react'; +import { describe, it, vi } from 'vitest'; + +import { wrapper } from '@/wrapperRenders'; + +import BillingStep, { TBillingStepProps } from './BillingStep.component'; + +const defaultProps: TBillingStepProps = { + price: 0, + monthlyPrice: 0, + monthlyBilling: { + isComingSoon: false, + isChecked: false, + check: vi.fn(), + }, + warn: false, +}; + +vi.mock('@ovh-ux/manager-react-components', async () => ({ + ...(await vi.importActual('@ovh-ux/manager-react-components')), + useProjectUrl: vi.fn().mockReturnValue('mockProjectUrl'), + useCatalogPrice: () => ({ + getTextPrice: (price: number) => price, + getFormattedCatalogPrice: (price: number) => price, + getFormattedHourlyCatalogPrice: (price: number) => price + ' /Hour', + getFormattedMonthlyCatalogPrice: (price: number) => price + ' /Month', + }), +})); + +describe('BillingStep', () => { + describe('Hourly billing', () => { + it('should render hourly billing tile with price from props', () => { + const props = { + ...defaultProps, + price: 5248, + }; + const { getByTestId } = render(, { wrapper }); + + const hourlyTile = getByTestId('hourly_tile'); + + expect(hourlyTile.innerHTML).toContain('5248'); + // monthly price + expect(hourlyTile.innerHTML).toContain('3831040'); + }); + it('should call getFormattedMonthlyCatalogPrice with the correct value', () => {}); + }); + describe('Monthly billing', () => { + it('should not show monthly billing tile when monthlyBilling.isComingSoon is true', () => { + const props = { + ...defaultProps, + monthlyPrice: undefined, + monthlyBilling: { + ...defaultProps.monthlyBilling, + isComingSoon: true, + }, + }; + const { queryByTestId } = render(, { + wrapper, + }); + + const monthlyTile = queryByTestId('monthly_tile'); + + expect(monthlyTile).not.toBeInTheDocument(); + }); + + it('should show monthly billing tile when monthlyBilling.isComingSoon is false', () => { + const props = { + ...defaultProps, + monthlyPrice: 15, + monthlyBilling: { + ...defaultProps.monthlyBilling, + isComingSoon: false, + }, + }; + const { queryByTestId } = render(, { + wrapper, + }); + + const monthlyTile = queryByTestId('monthly_tile'); + + expect(monthlyTile?.innerHTML).toContain('15'); + }); + + it('should show is coming soon message if monthly billing coming soon', () => { + const props = { + ...defaultProps, + monthlyBilling: { + ...defaultProps.monthlyBilling, + isComingSoon: true, + }, + }; + const { queryByTestId } = render(, { + wrapper, + }); + + const yesMessage = queryByTestId('coming_soon_message'); + const noMessage = queryByTestId('billing_description'); + + expect(yesMessage).toBeInTheDocument(); + expect(noMessage).not.toBeInTheDocument(); + }); + + it('should not show is coming soon message if monthly billing is not coming soon', () => { + const props = { + ...defaultProps, + monthlyBilling: { + ...defaultProps.monthlyBilling, + isComingSoon: false, + }, + }; + const { queryByTestId } = render(, { + wrapper, + }); + + const yesMessage = queryByTestId('coming_soon_message'); + const noMessage = queryByTestId('billing_description'); + + expect(yesMessage).not.toBeInTheDocument(); + expect(noMessage).toBeInTheDocument(); + }); + }); + describe('Warn message', () => { + it("should show warn message if it's enabled", () => { + const props = { + ...defaultProps, + warn: true, + }; + const { queryByTestId } = render(, { wrapper }); + + const warnMessage = queryByTestId('warn_message'); + + expect(warnMessage).toBeInTheDocument(); + }); + + it("should not show warn message if it's enabled", () => { + const props = { + ...defaultProps, + warn: false, + }; + const { queryByTestId } = render(, { wrapper }); + + const warnMessage = queryByTestId('warn_message'); + + expect(warnMessage).not.toBeInTheDocument(); + }); + }); +}); diff --git a/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.component.tsx b/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.component.tsx index 9d09936975a6..fdf741982173 100644 --- a/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.component.tsx +++ b/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.component.tsx @@ -25,7 +25,11 @@ import { OsdsTile, } from '@ovhcloud/ods-components/react'; -import { useCatalogPrice, useProjectUrl } from '@ovh-ux/manager-react-components'; +import { + convertHourlyPriceToMonthly, + useCatalogPrice, + useProjectUrl, +} from '@ovh-ux/manager-react-components'; import useSavingsPlanAvailable from '@/hooks/useSavingPlanAvailable'; @@ -47,9 +51,13 @@ export type TBillingStepProps = { }; export default function BillingStep(props: TBillingStepProps): ReactElement { - const { t } = useTranslation(['billing-anti-affinity', 'add']); - const { t: tFlavourBilling } = useTranslation('flavor-billing'); - const { getFormattedMonthlyCatalogPrice, getFormattedHourlyCatalogPrice } = useCatalogPrice(4, { + const { t } = useTranslation(['billing-anti-affinity', 'add', 'node-pool', 'flavor-billing']); + + const { getFormattedHourlyCatalogPrice } = useCatalogPrice(4, { + exclVat: true, + }); + + const { getFormattedMonthlyCatalogPrice } = useCatalogPrice(2, { exclVat: true, }); @@ -66,7 +74,7 @@ export default function BillingStep(props: TBillingStepProps): ReactElement { level={ODS_TEXT_LEVEL.heading} size={ODS_TEXT_SIZE._400} > - {tFlavourBilling('pci_projects_project_instances_configure_billing_type')} + {t('flavor-billing:pci_projects_project_instances_configure_billing_type')} {props.monthlyBilling.isComingSoon && showSavingPlan ? ( - {tFlavourBilling('pci_project_flavors_billing_hourly')} + {t('flavor-billing:pci_project_flavors_billing_hourly')}
- {tFlavourBilling('pci_project_flavors_billing_price_hourly_price_label')} + {t('flavor-billing:pci_project_flavors_billing_price_hourly_price_label')} {` ${getFormattedHourlyCatalogPrice(Number(props.price))}`} + + {t('node-pool:kube_common_node_pool_estimation_cost_tile')}: + {` ${getFormattedMonthlyCatalogPrice(convertHourlyPriceToMonthly(Number(props.price)))}`} + @@ -163,7 +179,7 @@ export default function BillingStep(props: TBillingStepProps): ReactElement { size={ODS_THEME_TYPOGRAPHY_SIZE._400} color={ODS_THEME_COLOR_INTENT.text} > - {tFlavourBilling('pci_project_flavors_billing_monthly')} + {t('flavor-billing:pci_project_flavors_billing_monthly')}
- {tFlavourBilling( - 'pci_project_flavors_billing_price_monthly_instance_price_label', + {t( + 'flavor-billing:pci_project_flavors_billing_price_monthly_instance_price_label', )} {` ${getFormattedMonthlyCatalogPrice(props.monthlyPrice ?? 0)}`} diff --git a/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.spec.tsx b/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.spec.tsx deleted file mode 100644 index d3b3f707f8d2..000000000000 --- a/packages/manager/apps/pci-kubernetes/src/components/create/BillingStep.spec.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { render } from '@testing-library/react'; -import { describe, it, vi } from 'vitest'; - -import { wrapper } from '@/wrapperRenders'; - -import BillingStep, { TBillingStepProps } from './BillingStep.component'; - -const defaultProps: TBillingStepProps = { - price: 0, - monthlyPrice: 0, - monthlyBilling: { - isComingSoon: false, - isChecked: false, - check: vi.fn(), - }, - warn: false, -}; - -vi.mock('@ovh-ux/manager-react-components', () => ({ - useProjectUrl: vi.fn().mockReturnValue('mockProjectUrl'), - useCatalogPrice: () => ({ - getTextPrice: (price: number) => price, - getFormattedCatalogPrice: (price: number) => price, - getFormattedHourlyCatalogPrice: (price: number) => price, - getFormattedMonthlyCatalogPrice: (price: number) => price, - }), -})); - -describe('BillingStep', () => { - describe('Hourly billing', () => { - it('should render hourly billing tile with price from props', () => { - const props = { - ...defaultProps, - price: 5248, - }; - const { getByTestId } = render(, { wrapper }); - - const hourlyTile = getByTestId('hourly_tile'); - - expect(hourlyTile.innerHTML).toContain('5248'); - }); - }); - describe('Monthly billing', () => { - describe('tile', () => { - it('should not show monthly billing tile when monthlyBilling.isComingSoon is true', () => { - const props = { - ...defaultProps, - monthlyPrice: undefined, - monthlyBilling: { - ...defaultProps.monthlyBilling, - isComingSoon: true, - }, - }; - const { queryByTestId } = render(, { - wrapper, - }); - - const monthlyTile = queryByTestId('monthly_tile'); - - expect(monthlyTile).not.toBeInTheDocument(); - }); - - it('should show monthly billing tile when monthlyBilling.isComingSoon is false', () => { - const props = { - ...defaultProps, - monthlyPrice: 15, - monthlyBilling: { - ...defaultProps.monthlyBilling, - isComingSoon: false, - }, - }; - const { queryByTestId } = render(, { - wrapper, - }); - - const monthlyTile = queryByTestId('monthly_tile'); - - expect(monthlyTile.innerHTML).toContain('15'); - }); - }); - - describe('Coming soon', () => { - it('should show is coming soon message if monthly billing coming soon', () => { - const props = { - ...defaultProps, - monthlyBilling: { - ...defaultProps.monthlyBilling, - isComingSoon: true, - }, - }; - const { queryByTestId } = render(, { - wrapper, - }); - - const yesMessage = queryByTestId('coming_soon_message'); - const noMessage = queryByTestId('billing_description'); - - expect(yesMessage).toBeInTheDocument(); - expect(noMessage).not.toBeInTheDocument(); - }); - - it('should not show is coming soon message if monthly billing is not coming soon', () => { - const props = { - ...defaultProps, - monthlyBilling: { - ...defaultProps.monthlyBilling, - isComingSoon: false, - }, - }; - const { queryByTestId } = render(, { - wrapper, - }); - - const yesMessage = queryByTestId('coming_soon_message'); - const noMessage = queryByTestId('billing_description'); - - expect(yesMessage).not.toBeInTheDocument(); - expect(noMessage).toBeInTheDocument(); - }); - }); - }); - describe('Warn message', () => { - it("should show warn message if it's enabled", () => { - const props = { - ...defaultProps, - warn: true, - }; - const { queryByTestId } = render(, { wrapper }); - - const warnMessage = queryByTestId('warn_message'); - - expect(warnMessage).toBeInTheDocument(); - }); - - it("should not show warn message if it's enabled", () => { - const props = { - ...defaultProps, - warn: false, - }; - const { queryByTestId } = render(, { wrapper }); - - const warnMessage = queryByTestId('warn_message'); - - expect(warnMessage).not.toBeInTheDocument(); - }); - }); -}); diff --git a/packages/manager/apps/pci-kubernetes/src/components/radio-tile/RadioTile.component.tsx b/packages/manager/apps/pci-kubernetes/src/components/radio-tile/RadioTile.component.tsx index 904bc9255f91..9dfc0fea610a 100644 --- a/packages/manager/apps/pci-kubernetes/src/components/radio-tile/RadioTile.component.tsx +++ b/packages/manager/apps/pci-kubernetes/src/components/radio-tile/RadioTile.component.tsx @@ -33,8 +33,9 @@ const RadioTile = ({ {...(!disabled && { tabIndex: 0 })} onKeyDown={handleLabelKeyDown} data-testid="radio-tile-container" + className={cn(tileClassName, 'h-full')} > -
+
(props.onChange ? props.onChange(e) : null)} className="hidden" @@ -48,13 +49,14 @@ const RadioTile = ({
; -}; - export default RadioTile; diff --git a/packages/manager/apps/pci-kubernetes/src/components/region-selector/KubeDeploymentTile.tsx b/packages/manager/apps/pci-kubernetes/src/components/region-selector/KubeDeploymentTile.tsx index d8815dae95b3..0cfcee66080f 100644 --- a/packages/manager/apps/pci-kubernetes/src/components/region-selector/KubeDeploymentTile.tsx +++ b/packages/manager/apps/pci-kubernetes/src/components/region-selector/KubeDeploymentTile.tsx @@ -32,10 +32,13 @@ export function KubeDeploymentTile({ value={title} checked={isSelected} > -
-
+
+

{title}

@@ -44,8 +47,8 @@ export function KubeDeploymentTile({
{pillLabel} -

{description}

-
+

{description}

+ ); } diff --git a/packages/manager/apps/pci-kubernetes/src/index.css b/packages/manager/apps/pci-kubernetes/src/index.css index f808ffa2f629..70c529404757 100644 --- a/packages/manager/apps/pci-kubernetes/src/index.css +++ b/packages/manager/apps/pci-kubernetes/src/index.css @@ -1,6 +1,3 @@ -@import '@datatr-ux/ods-tailwind-config/style.css'; -@import '@datatr-ux/uxlib/style.css'; - @tailwind base; @tailwind components; @tailwind utilities; @@ -9,6 +6,10 @@ @apply md:mx-12 mt-8; } +html { + font-family: var(--ods-font-family-default); +} + a:not([class]), .oui-link { font-family: 'Source Sans Pro', sans-serif; @@ -43,12 +44,12 @@ code { border-radius: 6px !important; } -osds-tile, -osds-input { - border-width: var(--ods-size-input-md-border-width); - border-color: var(--ods-color-primary-200); +.log-container { + color: var(--ods-color-text-500); } +osds-tile, osds-input { + border-width: var(--ods-size-input-md-border-width); border-color: var(--ods-color-primary-200); } diff --git a/packages/manager/apps/pci-kubernetes/src/main.tsx b/packages/manager/apps/pci-kubernetes/src/main.tsx index d80ba05d65ea..cd2e08f99fe5 100644 --- a/packages/manager/apps/pci-kubernetes/src/main.tsx +++ b/packages/manager/apps/pci-kubernetes/src/main.tsx @@ -2,6 +2,8 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import '@ovhcloud/ods-themes/default'; + import '@ovh-ux/manager-pci-common/dist/style.css'; import { ShellContext, initI18n, initShellContext } from '@ovh-ux/manager-react-shell-client'; diff --git a/packages/manager/apps/pci-kubernetes/src/pages/detail/log/Logs.page.tsx b/packages/manager/apps/pci-kubernetes/src/pages/detail/log/Logs.page.tsx index 6d6e489dc3df..c6384012fe16 100644 --- a/packages/manager/apps/pci-kubernetes/src/pages/detail/log/Logs.page.tsx +++ b/packages/manager/apps/pci-kubernetes/src/pages/detail/log/Logs.page.tsx @@ -1,4 +1,4 @@ -import { useNavigate, useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -12,7 +12,7 @@ import { } from '@ovhcloud/ods-components'; import { OsdsIcon, OsdsLink, OsdsText } from '@ovhcloud/ods-components/react'; -import { LogsView } from '@ovh-ux/manager-pci-common'; +import { LogsView, useParam } from '@ovh-ux/manager-pci-common'; import { Notifications, useMe } from '@ovh-ux/manager-react-components'; import { KubeLogsProvider } from './KubeLogsProvider'; @@ -20,10 +20,10 @@ import { LOGS_INFO } from './constants'; export default function LogsPage() { const { t } = useTranslation('logs'); - const { kubeId, projectId } = useParams(); + const { kubeId, projectId } = useParam('kubeId', 'projectId'); const navigate = useNavigate(); const ovhSubsidiary = useMe()?.me?.ovhSubsidiary; - const infoLink = LOGS_INFO[ovhSubsidiary] || LOGS_INFO.DEFAULT; + const infoLink = LOGS_INFO[ovhSubsidiary as keyof typeof LOGS_INFO] || LOGS_INFO.DEFAULT; return ( @@ -52,7 +52,9 @@ export default function LogsPage() { /> - navigate('./streams')} /> +
+ navigate('./streams')} /> +
); } diff --git a/packages/manager/apps/pci-kubernetes/src/pages/new/steps/LocationStep.component.tsx b/packages/manager/apps/pci-kubernetes/src/pages/new/steps/LocationStep.component.tsx index baf471ee3a15..76f8c26be1d0 100644 --- a/packages/manager/apps/pci-kubernetes/src/pages/new/steps/LocationStep.component.tsx +++ b/packages/manager/apps/pci-kubernetes/src/pages/new/steps/LocationStep.component.tsx @@ -167,7 +167,7 @@ export function LocationStep({ projectId, onSubmit, step }: Readonly
-
+
{tilesData.map(({ title, pillLabel, description, regionType, badge }) => ( { 'kube_common_node_pool_model_type_selector', 'kube_common_node_pool_size_title', 'kubernetes_node_pool_anti_affinity', - 'pci_projects_project_instances_configure_billing_type', + 'flavor-billing:pci_projects_project_instances_configure_billing_type', 'node-pool:kube_common_add_node_pool', ]; toTest.forEach((test) => expect(screen.getByText(test)).toBeInTheDocument()); diff --git a/packages/manager/apps/pci-kubernetes/src/pages/new/steps/PlanStep.component.tsx b/packages/manager/apps/pci-kubernetes/src/pages/new/steps/PlanStep.component.tsx index 6ff73e045fcb..f2c739f3557d 100644 --- a/packages/manager/apps/pci-kubernetes/src/pages/new/steps/PlanStep.component.tsx +++ b/packages/manager/apps/pci-kubernetes/src/pages/new/steps/PlanStep.component.tsx @@ -1,21 +1,18 @@ -import { FormEvent, useMemo, useState } from 'react'; +import { FormEvent, useCallback, useMemo, useState } from 'react'; -import { Button } from '@datatr-ux/uxlib'; -import { Check, Clock12 } from 'lucide-react'; +import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; import { - ODS_THEME_COLOR_INTENT, - ODS_THEME_TYPOGRAPHY_LEVEL, - ODS_THEME_TYPOGRAPHY_SIZE, -} from '@ovhcloud/ods-common-theming'; -import { - ODS_MESSAGE_TYPE, - ODS_SPINNER_SIZE, - ODS_TEXT_LEVEL, - ODS_TEXT_SIZE, -} from '@ovhcloud/ods-components'; -import { OsdsMessage, OsdsSpinner, OsdsText } from '@ovhcloud/ods-components/react'; + Badge, + Button, + Icon, + Message, + MessageBody, + MessageIcon, + Spinner, + Text, +} from '@ovhcloud/ods-react'; import { convertHourlyPriceToMonthly, useCatalogPrice } from '@ovh-ux/manager-react-components'; @@ -50,44 +47,47 @@ const PlanTile = ({ (isMonoDeploymentZone(type) && plan === TClusterPlanEnum.STANDARD) || (isMultiDeploymentZones(type) && plan === TClusterPlanEnum.FREE); - const getSortOrder = (typeRegion: string) => { - const priority = { - [DeploymentMode.MULTI_ZONES]: TClusterPlanEnum.STANDARD, - [DeploymentMode.MONO_ZONE]: TClusterPlanEnum.FREE, - }; - return priority[type as keyof typeof priority] - ? (a: { value: string }) => - a.value === priority[typeRegion as keyof typeof priority] ? -1 : 1 - : () => 0; - }; + const getSortOrder = useCallback( + (typeRegion: string) => { + const priority = { + [DeploymentMode.MULTI_ZONES]: TClusterPlanEnum.STANDARD, + [DeploymentMode.MONO_ZONE]: TClusterPlanEnum.FREE, + }; + return priority[type as keyof typeof priority] + ? (a: { value: string }) => + a.value === priority[typeRegion as keyof typeof priority] ? -1 : 1 + : () => 0; + }, + [type], + ); - const sortedPlans = useMemo(() => [...plans].sort(getSortOrder(type)), [type, plans]); + const sortedPlans = useMemo( + () => [...plans].sort(getSortOrder(type)), + [plans, getSortOrder, type], + ); return (
{!step.isLocked && } - - {t('kube_add_plan_subtitle')} - + {!step.isLocked && ( + + {t('kube_add_plan_subtitle')} + + )} -
+
{!step.isLocked && (isPendingPlans ? ( - + ) : ( - <> +
{sortedPlans.map((plan) => ( !planIsDisabled(plan.value) && setSelected(plan.value)} value={plan.value} checked={selected === plan.value} @@ -102,8 +102,16 @@ const PlanTile = ({ disabled={planIsDisabled(plan.value)} /> )} - -
+
+ +
+ +
))} - +
))} - {step.isLocked && } + {step.isLocked && ( +
+ +
+ )}
- {!step.isLocked && ( + {!step.isLocked && !isPendingPlans && ( @@ -142,18 +154,15 @@ PlanTile.Banner = function PlanTileBanner({ type }: { type: DeploymentMode }) { const [open, setOpen] = useState(true); return ( open && ( - setOpen(false)} - className="mt-4" - type={ODS_MESSAGE_TYPE.info} - color={ODS_THEME_COLOR_INTENT.info} + setOpen(false)} + className="mt-4 flex" + color="information" > - + + {isMultiDeploymentZones(type) ? t('kube_add_plan_content_unavailable_3AZ_banner', { plan: 'Free', @@ -161,8 +170,8 @@ PlanTile.Banner = function PlanTileBanner({ type }: { type: DeploymentMode }) { : t('kube_add_plan_content_unavailable_1AZ_banner', { plan: 'Standard', })} - - + + ) ); }; @@ -175,9 +184,12 @@ PlanTile.LockedView = function PlanTileLockedView({ value }: { value: TClusterPl if (!plan) return null; return ( - -
-
+ +
+
{t(plan.title)}
@@ -206,21 +218,25 @@ PlanTile.Header = function PlanTileHeader({ (value === TClusterPlanEnum.STANDARD && isMonoDeploymentZone(type))); return ( -
-
-
+
+
+
{t(title)}
-
- -
{displayWarningMessage && ( - - - {t('kube_add_plan_content_coming_very_soon')} - + + {t('kube_add_plan_content_coming_very_soon')} + )} - {t(description)} +
+ +
+ {t(description)}
); @@ -235,13 +251,15 @@ PlanTile.Content = function PlanTileContent({ }) { const { t } = useTranslation(['add']); return contents.map((text) => ( - - + + {t(text)} )); @@ -273,30 +291,20 @@ PlanTile.Footer = function PlanTileFooter({ return (
{isFreePlan ? ( -

+

{t(content)}

) : (
{hourlyPrice && ( - + {hourlyPrice} - + )} {monthlyPrice && ( - + ~ {monthlyPrice} - + )}
)} diff --git a/packages/manager/apps/pci-kubernetes/tailwind.config.mjs b/packages/manager/apps/pci-kubernetes/tailwind.config.mjs index 131bb244331d..0fe229da65e0 100644 --- a/packages/manager/apps/pci-kubernetes/tailwind.config.mjs +++ b/packages/manager/apps/pci-kubernetes/tailwind.config.mjs @@ -1,12 +1,12 @@ -import config from '@ovh-ux/manager-tailwind-config'; -import path from 'path'; import { createRequire } from 'node:module'; -import odsPlugin from '@datatr-ux/ods-tailwind-config'; +import path from 'path'; + +import config from '@ovh-ux/manager-tailwind-config'; const require = createRequire(import.meta.url); /** @type {import('tailwindcss').Config} */ -module.exports = { +export default { ...config, content: [ './src/**/*.{js,jsx,ts,tsx}', @@ -14,13 +14,9 @@ module.exports = { path.dirname(require.resolve('@ovh-ux/manager-react-components')), '**/*.{js,jsx,ts,tsx}', ), - path.join( - path.dirname(require.resolve('@ovh-ux/manager-pci-common')), - '**/*.{js,jsx,ts,tsx}', - ), + path.join(path.dirname(require.resolve('@ovh-ux/manager-pci-common')), '**/*.{js,jsx,ts,tsx}'), ], corePlugins: { preflight: false, }, - plugins: [odsPlugin], }; diff --git a/packages/manager/apps/pci-kubernetes/vitest.config.js b/packages/manager/apps/pci-kubernetes/vitest.config.js index d1fbf6c32d8b..d45bcdbfe359 100644 --- a/packages/manager/apps/pci-kubernetes/vitest.config.js +++ b/packages/manager/apps/pci-kubernetes/vitest.config.js @@ -1,38 +1,40 @@ import path from 'path'; -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - test: { - globals: true, - environment: 'jsdom', - setupFiles: './src/setupTests.ts', - coverage: { - include: ['src'], - exclude: [ - 'src/interface', - 'src/__tests__', - 'src/**/*constants.ts', - 'src/**/*enum.ts', - 'src/vite-*.ts', - 'src/App.tsx', - 'src/core/ShellRoutingSync.tsx', - 'src/core/HidePreloader.tsx', - 'src/i18n.ts', - 'src/main.tsx', - 'src/pages/Layout.tsx', - 'src/routes.tsx', - 'src/queryClient.ts', - 'src/wrapperRenders.tsx', - ], +import { + createConfig, + defaultExcludedFiles, + mergeConfig, + sharedConfig, +} from '@ovh-ux/manager-tests-setup'; + +export default mergeConfig( + sharedConfig, + createConfig({ + test: { + setupFiles: ['./src/setupTests.ts'], + server: { + deps: { + inline: [/@ovhcloud\/ods-react\/.*/i], + }, + }, + coverage: { + exclude: [ + ...defaultExcludedFiles, + // App-specific exclusions (not in shared config): + 'vite-*.ts', + 'App.tsx', + 'core/ShellRoutingSync.tsx', + 'main.tsx', + 'routes.tsx', + '__mocks__', + 'queryClient.ts', + ], + }, }, - }, - resolve: { - alias: { - '@': path.resolve(__dirname, 'src'), + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, }, - mainFields: ['module'], - }, -}); + }), +); diff --git a/packages/manager/apps/pci-object-storage/src/api/hooks/useUser.ts b/packages/manager/apps/pci-object-storage/src/api/hooks/useUser.ts index c0c5970764f8..05670d7e78dc 100644 --- a/packages/manager/apps/pci-object-storage/src/api/hooks/useUser.ts +++ b/packages/manager/apps/pci-object-storage/src/api/hooks/useUser.ts @@ -211,7 +211,7 @@ export const useImportPolicy = ({ setIsPending(true); const policy = await readFileAsJSON(files[0], t); - importUserPolicy(projectId, userId, policy); + await importUserPolicy(projectId, userId, policy); onSuccess(); } catch (e) { onError(e as Error); diff --git a/packages/manager/apps/public-cloud/src/links.json b/packages/manager/apps/public-cloud/src/links.json index b675ddeb8517..d37df52a0a15 100644 --- a/packages/manager/apps/public-cloud/src/links.json +++ b/packages/manager/apps/public-cloud/src/links.json @@ -357,7 +357,7 @@ }, "redirect": { "application": "public-cloud", - "path": "/pci/projects/{project}/databases-analytics/streaming/services/new?STEP_1=kafka" + "path": "/pci/projects/{project}/databases-analytics/all/services/new?STEP_1=kafka" } }, { @@ -367,7 +367,7 @@ }, "redirect": { "application": "public-cloud", - "path": "/pci/projects/{project}/databases-analytics/streaming/services/new?STEP_1=kafkaConnect" + "path": "/pci/projects/{project}/databases-analytics/all/services/new?STEP_1=kafkaConnect" } }, { @@ -377,7 +377,7 @@ }, "redirect": { "application": "public-cloud", - "path": "/pci/projects/{project}/databases-analytics/streaming/services/new?STEP_1=kafkaMirrorMaker" + "path": "/pci/projects/{project}/databases-analytics/all/services/new?STEP_1=kafkaMirrorMaker" } }, {