From a03d30647b9bb39bd488ff79a75123ed3aed31b2 Mon Sep 17 00:00:00 2001 From: selm3n <30338862+selm3n@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:41:00 +0100 Subject: [PATCH 1/9] feat(pci-instances): secure flavor list (#13542) ref: TAPC-1900 Signed-off-by: Selmen AKRMI --- .../project/flavors-list/flavors-list.service.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/manager/modules/pci/src/components/project/flavors-list/flavors-list.service.js b/packages/manager/modules/pci/src/components/project/flavors-list/flavors-list.service.js index 25e0b1b63c76..208004b14938 100644 --- a/packages/manager/modules/pci/src/components/project/flavors-list/flavors-list.service.js +++ b/packages/manager/modules/pci/src/components/project/flavors-list/flavors-list.service.js @@ -54,7 +54,7 @@ export default class FlavorsList { serviceName, currentRegion, catalogEndpoint = DEFAULT_CATALOG_ENDPOINT, - noCache + noCache, ) { if (noCache) { this.OvhApiCloudProjectFlavor.v6().resetQueryCache(); @@ -175,6 +175,10 @@ export default class FlavorsList { return groupBy(flavors, 'groupName'); } + static getIsNewTagsBlob(flavor) { + return flavor?.tagsBlob?.includes(TAGS_BLOB.IS_NEW); + } + static groupByCategory(flavors) { return CATEGORIES.map(({ category, title, pattern, isNew }) => { const filteredAndRearrangedFlavors = filter(flavors, (flavor) => @@ -187,10 +191,10 @@ export default class FlavorsList { isNew, flavors: [ ...filteredAndRearrangedFlavors.filter((flavor) => - flavor?.tagsBlob.includes(TAGS_BLOB.IS_NEW), + this.getIsNewTagsBlob(flavor), ), ...filteredAndRearrangedFlavors.filter( - (flavor) => !flavor?.tagsBlob.includes(TAGS_BLOB.IS_NEW), + (flavor) => !this.getIsNewTagsBlob(flavor), ), ], }; From 9062d4d19f9ec9e543808c2f87613d5b3d8cda82 Mon Sep 17 00:00:00 2001 From: Yann Lojewski Date: Thu, 31 Oct 2024 17:41:42 +0100 Subject: [PATCH 2/9] feat(pci.instances.add): add ssh US guide link (#13789) ref: TAPC-806 Signed-off-by: Yann Lojewski --- .../components/project/instance/ssh-keys/ssh-keys.constants.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/manager/modules/pci/src/components/project/instance/ssh-keys/ssh-keys.constants.js b/packages/manager/modules/pci/src/components/project/instance/ssh-keys/ssh-keys.constants.js index b572cbdc2939..f9aa2d32dd63 100644 --- a/packages/manager/modules/pci/src/components/project/instance/ssh-keys/ssh-keys.constants.js +++ b/packages/manager/modules/pci/src/components/project/instance/ssh-keys/ssh-keys.constants.js @@ -12,6 +12,8 @@ export const GUIDE_URLS = { PT: 'https://docs.ovh.com/pt/public-cloud/criacao-de-chaves-ssh/', SG: 'https://docs.ovh.com/sg/en/public-cloud/create-ssh-keys/', WORLD: 'https://docs.ovh.com/gb/en/public-cloud/create-ssh-keys/', + US: + 'https://support.us.ovhcloud.com/hc/en-us/articles/360002245164-Creating-and-Connecting-a-Public-Cloud-Instance', }; export default { From c9fd6e635bbced25ad49e17e56424922f3973717 Mon Sep 17 00:00:00 2001 From: Lio B Date: Thu, 31 Oct 2024 17:42:48 +0100 Subject: [PATCH 3/9] feat(pci-savings-plan): avoid exceeding max quantity (#13565) ref: TAPC-1697 Signed-off-by: Lionel Bueno --- .../CreatePlanForm/CreatePlanForm.tsx | 12 +++-- .../QuantitySelector.test.tsx | 54 +++++++++++++++++++ .../QuantitySelector/QuantitySelector.tsx | 6 ++- 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.test.tsx diff --git a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx index 2c93a876ed6a..0f280316afe0 100644 --- a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx +++ b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx @@ -39,7 +39,9 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { MutationStatus, useMutationState } from '@tanstack/react-query'; -import QuantitySelector from '@/components/QuantitySelector/QuantitySelector'; +import QuantitySelector, { + MAX_QUANTITY, +} from '@/components/QuantitySelector/QuantitySelector'; import useTechnicalInfo, { usePricingInfo } from '@/hooks/useCatalogCommercial'; import { getMutationKeyCreateSavingsPlan, @@ -334,8 +336,12 @@ const CreatePlanForm: FC = ({ setQuantity(quantity - 1)} - onPlusClick={() => setQuantity(quantity + 1)} + onMinusClick={() => setQuantity((prevQ) => prevQ - 1)} + onPlusClick={() => { + if (quantity <= MAX_QUANTITY) { + setQuantity((prevQ) => prevQ + 1); + } + }} onChangeQuantity={onChangeQuantity} /> diff --git a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.test.tsx b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.test.tsx new file mode 100644 index 000000000000..c5d766d5f81d --- /dev/null +++ b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.test.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { vi, describe, it, expect } from 'vitest'; +import { screen, fireEvent } from '@testing-library/react'; +import QuantitySelector, { MAX_QUANTITY } from './QuantitySelector'; +import '@testing-library/jest-dom'; + +import { render } from '@/utils/testProvider'; + +const defaultProps = { + quantity: 1, + onMinusClick: vi.fn(), + onPlusClick: vi.fn(), + onChangeQuantity: vi.fn(), +}; + +const setupSpecTest = async (props = defaultProps) => + render(); + +describe('QuantitySelector', () => { + it('should render the quantity input with the correct value', async () => { + await setupSpecTest(); + const input = screen.getByTestId('input-quantity'); + expect(input).toHaveValue(defaultProps.quantity); + }); + + it('should call onMinusClick when minus button is clicked', async () => { + await setupSpecTest(); + const minusButton = screen.getByTestId('minus-button'); + fireEvent.click(minusButton); + expect(defaultProps.onMinusClick).toHaveBeenCalled(); + }); + + it('should call onPlusClick when plus button is clicked', async () => { + await setupSpecTest(); + const plusButton = screen.getByTestId('plus-button'); + fireEvent.click(plusButton); + expect(defaultProps.onPlusClick).toHaveBeenCalled(); + }); + + it('should call onChangeQuantity with the correct value when input changes', async () => { + await setupSpecTest(); + const input = screen.getByTestId('input-quantity'); + fireEvent.change(input, { target: { value: '5' } }); + expect(defaultProps.onChangeQuantity).toHaveBeenCalledWith(5); + }); + it('should not call onChangeQuantity with a value greater than MAX_QUANTITY', async () => { + await setupSpecTest(); + const input = screen.getByTestId('input-quantity'); + fireEvent.change(input, { target: { value: MAX_QUANTITY + 1 } }); + expect(defaultProps.onChangeQuantity).not.toHaveBeenCalledWith( + MAX_QUANTITY + 1, + ); + }); +}); diff --git a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx index 29f83e1ea472..34f15da06893 100644 --- a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx +++ b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx @@ -23,6 +23,8 @@ type QuantitySelectorProps = { onChangeQuantity: (v?: number) => void; }; +export const MAX_QUANTITY = 1000; + const QuantitySelector: React.FC = ({ quantity, onMinusClick, @@ -31,6 +33,7 @@ const QuantitySelector: React.FC = ({ }) => ( = ({ = ({ value={quantity} onOdsValueChange={(e) => { const value = Number(e.detail.value); - if (value > 0) { + if (value > 0 && value <= MAX_QUANTITY) { onChangeQuantity(value); } }} From 67b31558a465786442c28eb331ef20e98efdf8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Vilcot?= <47554752+fredericvilcot@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:43:30 +0100 Subject: [PATCH 4/9] feat(pci): update discord link on dashboard page (#13833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref: TAPC-810 Signed-off-by: Frédéric Vilcot --- .../modules/pci/src/projects/project/project.constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/manager/modules/pci/src/projects/project/project.constants.js b/packages/manager/modules/pci/src/projects/project/project.constants.js index 26bf5c84df39..d1d32d6eb0eb 100644 --- a/packages/manager/modules/pci/src/projects/project/project.constants.js +++ b/packages/manager/modules/pci/src/projects/project/project.constants.js @@ -159,7 +159,7 @@ export const COMMUNITY_LINKS = [ { term: 'pci_projects_project_community_discord_term', description: 'pci_projects_project_community_discord_description', - href: 'https://discord.com/invite/C9VVJZxxd6', + href: 'https://discord.gg/ovhcloud', regions: ['EU', 'CA'], trackingName: `${PROJECT_PAGE_TRACKING_NAME}::community-discord`, }, From 63aece6b57e1274abc675d773f548fcde9a81015 Mon Sep 17 00:00:00 2001 From: Florian Renaut Date: Thu, 31 Oct 2024 17:44:09 +0100 Subject: [PATCH 5/9] fix(pci-private-network): add vrack creation (#13627) ref: DTCORE-2764 Signed-off-by: Florian Renaut --- .../translations/vrack/Messages_de_DE.json | 12 + .../translations/vrack/Messages_en_GB.json | 12 + .../translations/vrack/Messages_es_ES.json | 12 + .../translations/vrack/Messages_fr_CA.json | 12 + .../translations/vrack/Messages_fr_FR.json | 12 + .../translations/vrack/Messages_it_IT.json | 12 + .../translations/vrack/Messages_pl_PL.json | 12 + .../translations/vrack/Messages_pt_PT.json | 12 + .../apps/pci-private-network/setupTests.ts | 12 - .../pci-private-network/src/api/data/vrack.ts | 77 ++ .../src/api/hooks/useVrack.ts | 129 ++ .../delete/DeleteModal.component.spec.tsx | 4 - .../global-regions/DataGridNoresults.spec.tsx | 12 +- .../global-regions/DatagridBodyRow.spec.tsx | 28 +- .../global-regions/DatagridHeader.spec.tsx | 14 +- .../GlobalRegionsDatagrid.spec.tsx | 6 +- .../DeleteAction.component.spec.tsx | 4 - .../pages/onboarding/Onboarding.page.spec.tsx | 10 +- .../src/pages/onboarding/Onboarding.page.tsx | 147 ++- .../Onboarding.page.spec.tsx.snap | 1119 +++++++++++++++++ .../new/VrackCreation.page.spec.tsx | 51 + .../onboarding/new/VrackCreation.page.tsx | 220 ++++ .../VrackCreation.page.spec.tsx.snap | 56 + .../src/pages/onboarding/store.ts | 11 + .../apps/pci-private-network/src/routes.tsx | 8 + .../pci-private-network/src/setupTests.ts | 37 + .../apps/pci-private-network/vitest.config.js | 17 +- .../manager-pci-common/src/api/data/index.ts | 1 + .../src/api/data/operation.ts | 36 + .../manager-pci-common/src/api/hook/index.ts | 1 + .../src/api/hook/useOperation.ts | 68 + 31 files changed, 2074 insertions(+), 90 deletions(-) create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_de_DE.json create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_en_GB.json create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_es_ES.json create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_CA.json create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_FR.json create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_it_IT.json create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pl_PL.json create mode 100644 packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pt_PT.json delete mode 100644 packages/manager/apps/pci-private-network/setupTests.ts create mode 100644 packages/manager/apps/pci-private-network/src/api/data/vrack.ts create mode 100644 packages/manager/apps/pci-private-network/src/api/hooks/useVrack.ts create mode 100644 packages/manager/apps/pci-private-network/src/pages/onboarding/__snapshots__/Onboarding.page.spec.tsx.snap create mode 100644 packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.spec.tsx create mode 100644 packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.tsx create mode 100644 packages/manager/apps/pci-private-network/src/pages/onboarding/new/__snapshots__/VrackCreation.page.spec.tsx.snap create mode 100644 packages/manager/apps/pci-private-network/src/pages/onboarding/store.ts create mode 100644 packages/manager/modules/manager-pci-common/src/api/data/operation.ts create mode 100644 packages/manager/modules/manager-pci-common/src/api/hook/useOperation.ts diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_de_DE.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_de_DE.json new file mode 100644 index 000000000000..4a59cd4a1d60 --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_de_DE.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "vRack erstellen", + "pci_projects_project_network_private_vrack_create_new": "Neues vRack", + "pci_projects_project_network_private_vrack_create_existing": "Vorhandenes vRack", + "pci_projects_project_network_private_vrack_create_description": "Klicken Sie auf „Erstellen“, um Ihr neues vRack zu aktivieren", + "pci_projects_project_network_private_vrack_create_choose": "Vorhandenes vRack auswählen", + "pci_projects_project_network_private_vrack_create_action": "Erstellen", + "pci_projects_project_network_private_vrack_create_cancel": "Abbrechen", + "pci_projects_project_network_private_vrack_create_init_error": "Beim Laden der Informationen ist ein Fehler aufgetreten: {{ message }}", + "pci_projects_project_network_private_vrack_create_error": "Bei der Aktivierung des vRack ist ein Fehler aufgetreten: {{message}}", + "pci_projects_project_network_private_vrack_pending": "vRack wird aktiviert. Dieser Vorgang kann einige Minuten dauern" +} diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_en_GB.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_en_GB.json new file mode 100644 index 000000000000..cdff15f6ac95 --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_en_GB.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "Create a vRack", + "pci_projects_project_network_private_vrack_create_new": "New vRack", + "pci_projects_project_network_private_vrack_create_existing": "Existing vRack", + "pci_projects_project_network_private_vrack_create_description": "Click “Create” to enable your new vRack", + "pci_projects_project_network_private_vrack_create_choose": "Select an existing vRack", + "pci_projects_project_network_private_vrack_create_action": "Create", + "pci_projects_project_network_private_vrack_create_cancel": "Cancel", + "pci_projects_project_network_private_vrack_create_init_error": "An error has occurred loading the information {{ message }}", + "pci_projects_project_network_private_vrack_create_error": "An error has occurred enabling the vRack {{ message }}", + "pci_projects_project_network_private_vrack_pending": "Your vRack is being enabled. This operation may take several minutes." +} diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_es_ES.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_es_ES.json new file mode 100644 index 000000000000..2b6f4adab424 --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_es_ES.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "Crear un vRack", + "pci_projects_project_network_private_vrack_create_new": "Nuevo vRack", + "pci_projects_project_network_private_vrack_create_existing": "vRack existente", + "pci_projects_project_network_private_vrack_create_description": "Haga clic en «Crear» para activar el nuevo vRack", + "pci_projects_project_network_private_vrack_create_choose": "Seleccionar un vRack existente", + "pci_projects_project_network_private_vrack_create_action": "Crear", + "pci_projects_project_network_private_vrack_create_cancel": "Cancelar", + "pci_projects_project_network_private_vrack_create_init_error": "Se ha producido un error al cargar la información: {{ message }}.", + "pci_projects_project_network_private_vrack_create_error": "Se ha producido un error al activar el vRack: {{message}}.", + "pci_projects_project_network_private_vrack_pending": "Activando el vRack... Esta operación puede tardar unos minutos." +} diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_CA.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_CA.json new file mode 100644 index 000000000000..1e25edf632b9 --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_CA.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "Créez un vRack", + "pci_projects_project_network_private_vrack_create_new": "Nouveau vRack", + "pci_projects_project_network_private_vrack_create_existing": "vRack existant", + "pci_projects_project_network_private_vrack_create_description": "Cliquez sur \"Créer\" pour activer votre nouveau vRack", + "pci_projects_project_network_private_vrack_create_choose": "Choisir un vRack existant", + "pci_projects_project_network_private_vrack_create_action": "Créer", + "pci_projects_project_network_private_vrack_create_cancel": "Annuler", + "pci_projects_project_network_private_vrack_create_init_error": "Une erreur est survenue lors du chargement des informations {{ message }}", + "pci_projects_project_network_private_vrack_create_error": "Une erreur est survenue lors de l'activation du vRack {{ message }}", + "pci_projects_project_network_private_vrack_pending": "Votre vRack est en cours d'activation. L'opération peut prendre plusieurs minutes" +} diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_FR.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_FR.json new file mode 100644 index 000000000000..1e25edf632b9 --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_fr_FR.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "Créez un vRack", + "pci_projects_project_network_private_vrack_create_new": "Nouveau vRack", + "pci_projects_project_network_private_vrack_create_existing": "vRack existant", + "pci_projects_project_network_private_vrack_create_description": "Cliquez sur \"Créer\" pour activer votre nouveau vRack", + "pci_projects_project_network_private_vrack_create_choose": "Choisir un vRack existant", + "pci_projects_project_network_private_vrack_create_action": "Créer", + "pci_projects_project_network_private_vrack_create_cancel": "Annuler", + "pci_projects_project_network_private_vrack_create_init_error": "Une erreur est survenue lors du chargement des informations {{ message }}", + "pci_projects_project_network_private_vrack_create_error": "Une erreur est survenue lors de l'activation du vRack {{ message }}", + "pci_projects_project_network_private_vrack_pending": "Votre vRack est en cours d'activation. L'opération peut prendre plusieurs minutes" +} diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_it_IT.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_it_IT.json new file mode 100644 index 000000000000..e9b8789f1d24 --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_it_IT.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "Crea una vRack", + "pci_projects_project_network_private_vrack_create_new": "Nuova vRack", + "pci_projects_project_network_private_vrack_create_existing": "vRack esistente", + "pci_projects_project_network_private_vrack_create_description": "Per attivare la tua nuova vRack, clicca su “Crea”.", + "pci_projects_project_network_private_vrack_create_choose": "Seleziona una vRack esistente", + "pci_projects_project_network_private_vrack_create_action": "Crea", + "pci_projects_project_network_private_vrack_create_cancel": "Annulla", + "pci_projects_project_network_private_vrack_create_init_error": "Si è verificato un errore durante il caricamento delle informazioni: {{ message }}", + "pci_projects_project_network_private_vrack_create_error": "Si è verificato un errore durante l’attivazione della vRack: {{ message }}", + "pci_projects_project_network_private_vrack_pending": "La tua vRack è in corso di attivazione. L’operazione potrebbe richiedere qualche minuto." +} diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pl_PL.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pl_PL.json new file mode 100644 index 000000000000..56d592810cfb --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pl_PL.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "Utwórz vRack", + "pci_projects_project_network_private_vrack_create_new": "Nowy vRack", + "pci_projects_project_network_private_vrack_create_existing": "Istniejący vRack", + "pci_projects_project_network_private_vrack_create_description": "Kliknij „Utwórz”, aby aktywować Twój nowy vRack.", + "pci_projects_project_network_private_vrack_create_choose": "Wybierz istniejący vRack", + "pci_projects_project_network_private_vrack_create_action": "Utwórz", + "pci_projects_project_network_private_vrack_create_cancel": "Anuluj", + "pci_projects_project_network_private_vrack_create_init_error": "Wystąpił błąd podczas pobierania informacji {{ message }}.", + "pci_projects_project_network_private_vrack_create_error": "Wystąpił błąd podczas tworzenia sieci vRack: {{message}}.", + "pci_projects_project_network_private_vrack_pending": "Trwa tworzenie Twojej sieci vRack. Może to potrwać kilka minut." +} diff --git a/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pt_PT.json b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pt_PT.json new file mode 100644 index 000000000000..09ff96946c7f --- /dev/null +++ b/packages/manager/apps/pci-private-network/public/translations/vrack/Messages_pt_PT.json @@ -0,0 +1,12 @@ +{ + "pci_projects_project_network_private_vrack_create_heading": "Criar um vRack", + "pci_projects_project_network_private_vrack_create_new": "Novo vRack", + "pci_projects_project_network_private_vrack_create_existing": "vRack existente", + "pci_projects_project_network_private_vrack_create_description": "Clique em “Criar” para ativar o seu novo vRack", + "pci_projects_project_network_private_vrack_create_choose": "Selecionar um vRack existente", + "pci_projects_project_network_private_vrack_create_action": "Criar", + "pci_projects_project_network_private_vrack_create_cancel": "Anular", + "pci_projects_project_network_private_vrack_create_init_error": "Ocorreu um erro ao carregar as informações {{ message }}", + "pci_projects_project_network_private_vrack_create_error": "Ocorreu um erro ao ativar o vRack {{ message }}", + "pci_projects_project_network_private_vrack_pending": "A ativação do seu vRack está em curso. A operação pode demorar alguns minutos" +} diff --git a/packages/manager/apps/pci-private-network/setupTests.ts b/packages/manager/apps/pci-private-network/setupTests.ts deleted file mode 100644 index 39651aefe5e5..000000000000 --- a/packages/manager/apps/pci-private-network/setupTests.ts +++ /dev/null @@ -1,12 +0,0 @@ -import '@testing-library/jest-dom'; -import 'element-internals-polyfill'; -import { vi } from 'vitest'; - -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (translationKey: string) => translationKey, - i18n: { - changeLanguage: () => new Promise(() => {}), - }, - }), -})); diff --git a/packages/manager/apps/pci-private-network/src/api/data/vrack.ts b/packages/manager/apps/pci-private-network/src/api/data/vrack.ts new file mode 100644 index 000000000000..34a500e20e57 --- /dev/null +++ b/packages/manager/apps/pci-private-network/src/api/data/vrack.ts @@ -0,0 +1,77 @@ +import { fetchIcebergV6, v6 } from '@ovh-ux/manager-core-api'; + +export enum VrackTaskStatus { + Cancelled = 'cancelled', + Doing = 'doing', + Done = 'done', + Init = 'init', + Todo = 'todo', +} + +export type TVrackTask = { + function: string; + id: number; + lastUpdate: string | null; + orderId: number | null; + serviceName: string | null; + status: VrackTaskStatus; + targetDomain: string | null; + todoDate: string | null; +}; + +export type TVrack = { + name: string; + description: string; + iam?: { + id: string; + displayName: string; + tags: Record; + urn: string; + }; +}; + +export const getVrackTask = async (vrack: string, taskId: string) => { + try { + const { data } = await v6.get(`/vrack/${vrack}/task/${taskId}`); + return data; + } catch (err) { + if (err?.response?.status === 404) return null; + throw err; + } +}; + +export const getProjectVrack = async (projectId: string) => { + const { data } = await v6.get<{ + id: string; + name: string; + }>(`/cloud/project/${projectId}/vrack`); + return data; +}; + +export const getVracks = async (): Promise => { + const { data } = await fetchIcebergV6({ + route: `/vrack`, + disableCache: true, + }); + return data; +}; + +export const createVrack = async (projectId: string) => { + const { data } = await v6.post<{ id: string }>( + `/cloud/project/${projectId}/vrack`, + ); + return data; +}; + +export const associateVrack = async ( + serviceName: string, + projectId: string, +) => { + const { data } = await v6.post<{ id: number }>( + `/vrack/${serviceName}/cloudProject`, + { + project: projectId, + }, + ); + return data; +}; diff --git a/packages/manager/apps/pci-private-network/src/api/hooks/useVrack.ts b/packages/manager/apps/pci-private-network/src/api/hooks/useVrack.ts new file mode 100644 index 000000000000..e8a21150a598 --- /dev/null +++ b/packages/manager/apps/pci-private-network/src/api/hooks/useVrack.ts @@ -0,0 +1,129 @@ +import { useEffect, useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { + associateVrack, + createVrack, + getProjectVrack, + getVracks, + getVrackTask, + TVrackTask, + VrackTaskStatus, +} from '@/api/data/vrack'; + +export const VRACK_QUERY_KEY = 'vrack'; +export const PROJECT_VRACK_QUERY_KEY = 'project-vrack'; + +export const useProjectVrack = (projectId: string) => + useQuery({ + queryKey: [PROJECT_VRACK_QUERY_KEY], + queryFn: async () => getProjectVrack(projectId), + retry: false, + throwOnError: false, + }); + +export const useVracks = () => + useQuery({ + queryKey: [VRACK_QUERY_KEY], + queryFn: () => getVracks(), + select: (data) => + data?.map((vrack) => { + const vrackId = (vrack.iam?.urn?.match(/vrack:(.*)$/) || [])[1]; + return { + vrackId, + displayName: vrack.name || vrack.iam?.displayName || vrackId, + ...vrack, + }; + }), + }); + +export const useCreateVrack = ({ + onError, + onSuccess, +}: { + onError: (cause: Error) => void; + onSuccess: (operationId: string) => void; +}) => { + const mutation = useMutation({ + mutationFn: createVrack, + onError, + onSuccess: async ({ id }) => { + onSuccess(id); + }, + }); + return { + createVrack: (projectId: string) => mutation.mutate(projectId), + ...mutation, + }; +}; + +export const useAssociateVrack = ({ + projectId, + vrackId, + onError, + onSuccess, +}: { + projectId: string; + vrackId: string; + onError: (cause: Error) => void; + onSuccess: () => void; +}) => { + const queryClient = useQueryClient(); + const [taskId, setTaskId] = useState(null); + const [isPending, setIsPending] = useState(false); + + const mutation = useMutation({ + mutationFn: async () => { + setIsPending(true); + const { id } = await associateVrack(vrackId, projectId); + return id; + }, + onError, + onSuccess: setTaskId, + }); + + // polling of task association + const { data: task, error } = useQuery({ + queryKey: ['pci-vrack-task', vrackId, taskId], + queryFn: () => getVrackTask(vrackId, taskId), + enabled: (query) => + !!projectId && + !!taskId && + ![VrackTaskStatus.Cancelled, VrackTaskStatus.Done].includes( + query.state.data?.status, + ), + refetchInterval: 3000, + retry: 5, + }); + + // report an error if failling task polling after retries + useEffect(() => { + if (error && isPending) { + setIsPending(false); + onError(error); + } + }, [error, isPending]); + + // report after polling + useEffect(() => { + if (!isPending) return; + // if task is missing (404) it is assumed to be done + if (task === null || task?.status === VrackTaskStatus.Done) { + queryClient.invalidateQueries({ + queryKey: [VRACK_QUERY_KEY], + }); + queryClient.invalidateQueries({ + queryKey: [PROJECT_VRACK_QUERY_KEY], + }); + setIsPending(false); + onSuccess(); + } else if (task?.status === VrackTaskStatus.Cancelled) { + setIsPending(false); + onError(new Error(task.status)); + } + }, [task, isPending]); + + return { + associateVrack: mutation.mutate, + isPending, + }; +}; diff --git a/packages/manager/apps/pci-private-network/src/components/delete/DeleteModal.component.spec.tsx b/packages/manager/apps/pci-private-network/src/components/delete/DeleteModal.component.spec.tsx index acf73b1d6ab7..9af2fc248f5e 100644 --- a/packages/manager/apps/pci-private-network/src/components/delete/DeleteModal.component.spec.tsx +++ b/packages/manager/apps/pci-private-network/src/components/delete/DeleteModal.component.spec.tsx @@ -3,10 +3,6 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import DeleteModal from './DeleteModal.component'; -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key) => key }), -})); - describe('DeleteModal', () => { const mockOnClose = vi.fn(); const mockOnConfirm = vi.fn(); diff --git a/packages/manager/apps/pci-private-network/src/components/global-regions/DataGridNoresults.spec.tsx b/packages/manager/apps/pci-private-network/src/components/global-regions/DataGridNoresults.spec.tsx index 8ebb726b7d92..e712451908e4 100644 --- a/packages/manager/apps/pci-private-network/src/components/global-regions/DataGridNoresults.spec.tsx +++ b/packages/manager/apps/pci-private-network/src/components/global-regions/DataGridNoresults.spec.tsx @@ -2,13 +2,15 @@ import { describe, vi } from 'vitest'; import { render, screen } from '@testing-library/react'; import DataGridNoResults from '@/components/global-regions/DatagridNoResults'; -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key: string) => key }), -})); - describe('DataGridNoResults', () => { it('should render no results message', () => { - render(); + render( + + + + +
, + ); expect( screen.getByText('common_pagination_no_results'), ).toBeInTheDocument(); diff --git a/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridBodyRow.spec.tsx b/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridBodyRow.spec.tsx index b73de3452ac4..42a8799211c2 100644 --- a/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridBodyRow.spec.tsx +++ b/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridBodyRow.spec.tsx @@ -4,10 +4,6 @@ import { useHref } from 'react-router-dom'; import DataGridBodyRow from '@/components/global-regions/DatagridBodyRow'; import { TAggregatedNetwork } from '@/api/data/network'; -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key: string) => key }), -})); - vi.mock('react-router-dom', () => ({ useHref: vi.fn(), })); @@ -21,7 +17,11 @@ describe('DataGridBodyRow', () => { } as unknown) as TAggregatedNetwork; render( - , + + + + +
, ); expect(screen.getByText('mocked_vlanId')).toBeInTheDocument(); expect(screen.getByText('mocked_name')).toBeInTheDocument(); @@ -38,7 +38,11 @@ describe('DataGridBodyRow', () => { } as unknown) as TAggregatedNetwork; render( - , + + + + +
, ); expect(screen.getAllByText('mocked_region')).toHaveLength(1); expect(screen.getAllByText('mocked_cidr')).toHaveLength(1); @@ -61,7 +65,11 @@ describe('DataGridBodyRow', () => { } as unknown) as TAggregatedNetwork; const { getByTestId } = render( - , + + + + +
, ); const deleteButton = getByTestId('dataGridBodyRow-delete_button'); expect(deleteButton).toBeInTheDocument(); @@ -82,7 +90,11 @@ describe('DataGridBodyRow', () => { } as unknown) as TAggregatedNetwork; render( - , + + + + +
, ); const tooltip = screen.getByText( 'pci_projects_project_network_private_delete', diff --git a/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridHeader.spec.tsx b/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridHeader.spec.tsx index 044276c07947..e9a23487e99f 100644 --- a/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridHeader.spec.tsx +++ b/packages/manager/apps/pci-private-network/src/components/global-regions/DatagridHeader.spec.tsx @@ -1,14 +1,16 @@ -import { describe, vi } from 'vitest'; +import { describe } from 'vitest'; import { render, screen } from '@testing-library/react'; import DatagridHeader from '@/components/global-regions/DatagridHeader'; -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key) => key }), -})); - describe('DatagridHeader', () => { it('should render all headers correctly', () => { - render(); + render( + + + + +
, + ); expect( screen.getByText('pci_projects_project_network_private_vlan_id'), ).toBeInTheDocument(); diff --git a/packages/manager/apps/pci-private-network/src/components/global-regions/GlobalRegionsDatagrid.spec.tsx b/packages/manager/apps/pci-private-network/src/components/global-regions/GlobalRegionsDatagrid.spec.tsx index 5df196172724..16aef713f0fa 100644 --- a/packages/manager/apps/pci-private-network/src/components/global-regions/GlobalRegionsDatagrid.spec.tsx +++ b/packages/manager/apps/pci-private-network/src/components/global-regions/GlobalRegionsDatagrid.spec.tsx @@ -1,12 +1,8 @@ -import { describe, vi } from 'vitest'; +import { describe } from 'vitest'; import { render, screen } from '@testing-library/react'; import GlobalRegionsDatagrid from '@/components/global-regions/GlobalRegionsDatagrid'; import { TAggregatedNetwork } from '@/api/data/network'; -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key) => key }), -})); - describe('GlobalRegionsDatagrid', () => { it('should render DataGridNoResults when no networks', () => { render( diff --git a/packages/manager/apps/pci-private-network/src/components/local-zones/DeleteAction.component.spec.tsx b/packages/manager/apps/pci-private-network/src/components/local-zones/DeleteAction.component.spec.tsx index 20a5d1e432e5..d0596dc506e9 100644 --- a/packages/manager/apps/pci-private-network/src/components/local-zones/DeleteAction.component.spec.tsx +++ b/packages/manager/apps/pci-private-network/src/components/local-zones/DeleteAction.component.spec.tsx @@ -3,10 +3,6 @@ import { render, screen } from '@testing-library/react'; import { useHref } from 'react-router-dom'; import DeleteAction from '@/components/local-zones/DeleteAction.component'; -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key) => key }), -})); - vi.mock('react-router-dom', () => ({ useHref: vi.fn(), })); diff --git a/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.spec.tsx b/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.spec.tsx index 6052d45827b1..4c6b58e7ca16 100644 --- a/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.spec.tsx +++ b/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.spec.tsx @@ -1,7 +1,7 @@ import 'element-internals-polyfill'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { describe, expect, vi } from 'vitest'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { ShellContext, @@ -12,6 +12,10 @@ import OnBoardingPage from './Onboarding.page'; vi.mock('react-router-dom'); +vi.mock('./OnBoardingGuard', () => ({ + default: ({ children }) => <>{children}, +})); + const shellContext = { environment: { getUser: vi.fn(), @@ -43,6 +47,8 @@ describe('OnBoardingPage', () => { shell.navigation.getURL.mockResolvedValue('https://www.ovh.com'); vi.mocked(useParams).mockReturnValue({ projectId: '123' }); const { container } = render(, { wrapper }); - expect(container).toBeDefined(); + await waitFor(() => { + expect(container).toBeDefined(); + }); }); }); diff --git a/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.tsx b/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.tsx index 75431a21fce9..9266f077c881 100644 --- a/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.tsx +++ b/packages/manager/apps/pci-private-network/src/pages/onboarding/Onboarding.page.tsx @@ -1,3 +1,4 @@ +import { useQueryClient } from '@tanstack/react-query'; import { ShellContext } from '@ovh-ux/manager-react-shell-client'; import { Card, OnboardingLayout } from '@ovh-ux/manager-react-components'; import { @@ -8,8 +9,12 @@ import { ODS_TEXT_LEVEL, OdsBreadcrumbAttributeItem, } from '@ovhcloud/ods-components'; -import { OsdsBreadcrumb, OsdsText } from '@ovhcloud/ods-components/react'; -import { TProject } from '@ovh-ux/manager-pci-common'; +import { + OsdsBreadcrumb, + OsdsProgressBar, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import { TProject, useOperationProgress } from '@ovh-ux/manager-pci-common'; import { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -20,11 +25,19 @@ import { } from 'react-router-dom'; import { GUIDES } from './onboarding.constants'; import OnBoardingGuard from './OnboardingGuard'; +import { + PROJECT_VRACK_QUERY_KEY, + useProjectVrack, + VRACK_QUERY_KEY, +} from '@/api/hooks/useVrack'; +import { useVrackCreationOperation } from './store'; export default function OnBoardingPage() { const { t } = useTranslation('listing'); const { t: tOnboarding } = useTranslation('onboarding'); + const { t: tVrack } = useTranslation('vrack'); + const queryClient = useQueryClient(); const { projectId } = useParams(); const navigate = useNavigate(); const context = useContext(ShellContext); @@ -32,6 +45,24 @@ export default function OnBoardingPage() { const { ovhSubsidiary } = context.environment.getUser(); const project = useRouteLoaderData('private-networks') as TProject; const [urlProject, setUrlProject] = useState(''); + const { data: vrack, isPending } = useProjectVrack(projectId); + const { operationId, setOperationId } = useVrackCreationOperation(); + const vrackCreation = useOperationProgress(projectId, operationId, () => { + // add a small delay in order to let the progress bar completed + // visible for a short period of time + setTimeout(() => { + queryClient.invalidateQueries({ + queryKey: [VRACK_QUERY_KEY], + }); + queryClient.invalidateQueries({ + queryKey: [PROJECT_VRACK_QUERY_KEY], + }); + setOperationId(null); + }, 2000); + }); + + const isMissingVrack = !isPending && !vrack?.id; + const isVrackCreationPending = !!operationId && !!vrackCreation; useEffect(() => { navigation @@ -60,49 +91,79 @@ export default function OnBoardingPage() { title={tOnboarding('pci_projects_project_network_private')} description={ <> - - {tOnboarding( - 'pci_projects_project_network_private_vrack_empty', - )} - - - {tOnboarding( - 'pci_projects_project_network_private_vrack_deploy', - )} - - - {tOnboarding( - 'pci_projects_project_network_private_vrack_explanation_1', - )} - - - {tOnboarding( - 'pci_projects_project_network_private_vrack_explanation_2', - )} - + {isVrackCreationPending && ( + <> + + {tVrack( + 'pci_projects_project_network_private_vrack_pending', + )} + + + + )} + {!isVrackCreationPending && ( + <> + + {tOnboarding( + 'pci_projects_project_network_private_vrack_empty', + )} + + + {tOnboarding( + 'pci_projects_project_network_private_vrack_deploy', + )} + + + {tOnboarding( + 'pci_projects_project_network_private_vrack_explanation_1', + )} + + + {tOnboarding( + 'pci_projects_project_network_private_vrack_explanation_2', + )} + + + )} } - orderButtonLabel={t('pci_projects_project_network_private_create')} - onOrderButtonClick={() => navigate('../new')} + {...(!isVrackCreationPending && + !isPending && { + orderButtonLabel: isMissingVrack + ? tVrack( + 'pci_projects_project_network_private_vrack_create_heading', + ) + : t('pci_projects_project_network_private_create'), + })} + onOrderButtonClick={() => + isMissingVrack ? navigate('./new') : navigate('../new') + } > {GUIDES.map((guide) => { const card = { diff --git a/packages/manager/apps/pci-private-network/src/pages/onboarding/__snapshots__/Onboarding.page.spec.tsx.snap b/packages/manager/apps/pci-private-network/src/pages/onboarding/__snapshots__/Onboarding.page.spec.tsx.snap new file mode 100644 index 000000000000..d40c5ea7d99b --- /dev/null +++ b/packages/manager/apps/pci-private-network/src/pages/onboarding/__snapshots__/Onboarding.page.spec.tsx.snap @@ -0,0 +1,1119 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`OnBoardingPage > displays private network creation button if vrack exists on project 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ + + pci_projects_project_network_private + + + + pci_projects_project_network_private_vrack_empty + + + pci_projects_project_network_private_vrack_deploy + + + pci_projects_project_network_private_vrack_explanation_1 + + + pci_projects_project_network_private_vrack_explanation_2 + + +
+ + pci_projects_project_network_private_create + +
+
+ +
+
+ , + "container":
+
+
+ + + pci_projects_project_network_private + + + + pci_projects_project_network_private_vrack_empty + + + pci_projects_project_network_private_vrack_deploy + + + pci_projects_project_network_private_vrack_explanation_1 + + + pci_projects_project_network_private_vrack_explanation_2 + + +
+ + pci_projects_project_network_private_create + +
+
+ +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`OnBoardingPage > displays private vrack creation button if missing vrack on project 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ + + pci_projects_project_network_private + + + + pci_projects_project_network_private_vrack_empty + + + pci_projects_project_network_private_vrack_deploy + + + pci_projects_project_network_private_vrack_explanation_1 + + + pci_projects_project_network_private_vrack_explanation_2 + + +
+ + pci_projects_project_network_private_vrack_create_heading + +
+
+ +
+
+ , + "container":
+
+
+ + + pci_projects_project_network_private + + + + pci_projects_project_network_private_vrack_empty + + + pci_projects_project_network_private_vrack_deploy + + + pci_projects_project_network_private_vrack_explanation_1 + + + pci_projects_project_network_private_vrack_explanation_2 + + +
+ + pci_projects_project_network_private_vrack_create_heading + +
+
+ +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.spec.tsx b/packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.spec.tsx new file mode 100644 index 000000000000..5173563235ce --- /dev/null +++ b/packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.spec.tsx @@ -0,0 +1,51 @@ +import { + ShellContext, + ShellContextType, +} from '@ovh-ux/manager-react-shell-client'; +import { + QueryClient, + QueryClientProvider, + UseQueryResult, +} from '@tanstack/react-query'; +import { render } from '@testing-library/react'; +import { describe, it, vi } from 'vitest'; +import * as useVrackModule from '@/api/hooks/useVrack'; +import VrackCreation from './VrackCreation.page'; +import { TVrack } from '@/api/data/vrack'; + +const shellContext = { + environment: { + getUser: vi.fn(), + }, + shell: { + navigation: { + getURL: vi.fn(), + }, + }, +}; + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => ( + + + {children} + + +); + +describe('VrackCreationPage', () => { + it('renders correctly', () => { + vi.spyOn(useVrackModule, 'useVracks').mockReturnValue({ + data: [], + isPending: false, + } as UseQueryResult<(TVrack & { vrackId: string; displayName: string })[]>); + vi.spyOn(useVrackModule, 'useProjectVrack').mockReturnValue({ + data: null, + isPending: false, + } as UseQueryResult<{ id: string; name: string }>); + const { container } = render(, { wrapper }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.tsx b/packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.tsx new file mode 100644 index 000000000000..e1a8843ed864 --- /dev/null +++ b/packages/manager/apps/pci-private-network/src/pages/onboarding/new/VrackCreation.page.tsx @@ -0,0 +1,220 @@ +import { useNavigate, useParams } from 'react-router-dom'; +import { + ODS_BUTTON_VARIANT, + ODS_SELECT_SIZE, + ODS_SPINNER_SIZE, + ODS_TEXT_LEVEL, +} from '@ovhcloud/ods-components'; +import { + ODS_THEME_COLOR_INTENT, + ODS_THEME_TYPOGRAPHY_SIZE, +} from '@ovhcloud/ods-common-theming'; +import { + OsdsButton, + OsdsModal, + OsdsSelect, + OsdsSelectOption, + OsdsSpinner, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import { Translation, useTranslation } from 'react-i18next'; +import { useEffect, useState } from 'react'; +import { + Notifications, + useNotifications, +} from '@ovh-ux/manager-react-components'; +import { ApiError } from '@ovh-ux/manager-core-api'; +import { + useAssociateVrack, + useCreateVrack, + useProjectVrack, + useVracks, +} from '@/api/hooks/useVrack'; +import { useVrackCreationOperation } from '../store'; + +export default function VrackCreation() { + const { projectId } = useParams(); + const { t } = useTranslation('vrack'); + const { clearNotifications } = useNotifications(); + const navigate = useNavigate(); + const [isCreation, setIsCreation] = useState(true); + const [existingVrackCheck, setExistingVrackCheck] = useState(true); + const [vrackIndex, setVrackIndex] = useState('-'); + const { addError } = useNotifications(); + const { data: vracks, isPending: isVracksPending } = useVracks(); + const selectedVrack = vracks?.[vrackIndex]; + const { setOperationId } = useVrackCreationOperation(); + const { + data: projectVrack, + isPending: isProjectVrackPending, + } = useProjectVrack(projectId); + + const { createVrack, isPending: isCreationPending } = useCreateVrack({ + onSuccess: (operationId) => { + setOperationId(operationId); + navigate('..'); + }, + onError: (error: ApiError) => { + addError( + + {(_t) => + _t('pci_projects_project_network_private_vrack_create_init_error', { + message: error?.response?.data?.message || error?.message || null, + }) + } + , + true, + ); + }, + }); + + const { associateVrack, isPending: isAssociationPending } = useAssociateVrack( + { + projectId, + vrackId: selectedVrack?.vrackId, + onSuccess: () => { + navigate('..'); + }, + onError: (error: ApiError) => { + addError( + + {(_t) => + _t( + 'pci_projects_project_network_private_vrack_create_init_error', + { + message: + error?.response?.data?.message || error?.message || null, + }, + ) + } + , + true, + ); + }, + }, + ); + + useEffect(() => { + if (projectVrack && existingVrackCheck) { + navigate('..'); + } + }, [projectVrack, existingVrackCheck]); + + const isPending = + isVracksPending || + isCreationPending || + isAssociationPending || + isProjectVrackPending; + + return ( + navigate('..')} + headline={t('pci_projects_project_network_private_vrack_create_heading')} + > + + {isPending ? ( + + ) : ( +
+ setIsCreation(true)} + inline + > + {t('pci_projects_project_network_private_vrack_create_new')} + + setIsCreation(false)} + inline + > + {t('pci_projects_project_network_private_vrack_create_existing')} + +

+ {isCreation && ( + + {t( + 'pci_projects_project_network_private_vrack_create_description', + )} + + )} + {!isCreation && ( + { + if (typeof detail.value === 'string') { + setVrackIndex(detail.value); + } + }} + inline + > + + {t( + 'pci_projects_project_network_private_vrack_create_choose', + )} + + {vracks.map((vrack, index) => ( + + {vrack.displayName} + + ))} + + )} +

+
+ )} +
+ + navigate('..')} + disabled={isPending || undefined} + > + {t('pci_projects_project_network_private_vrack_create_cancel')} + + { + clearNotifications(); + setExistingVrackCheck(false); + if (isCreation) { + createVrack(projectId); + } else { + associateVrack(selectedVrack?.vrackId); + } + }} + disabled={isPending || (!isCreation && !selectedVrack) || undefined} + > + {t('pci_projects_project_network_private_vrack_create_action')} + +
+ ); +} diff --git a/packages/manager/apps/pci-private-network/src/pages/onboarding/new/__snapshots__/VrackCreation.page.spec.tsx.snap b/packages/manager/apps/pci-private-network/src/pages/onboarding/new/__snapshots__/VrackCreation.page.spec.tsx.snap new file mode 100644 index 000000000000..7b6dde73170d --- /dev/null +++ b/packages/manager/apps/pci-private-network/src/pages/onboarding/new/__snapshots__/VrackCreation.page.spec.tsx.snap @@ -0,0 +1,56 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`VrackCreationPage > renders correctly 1`] = ` +
+ + +
+ + pci_projects_project_network_private_vrack_create_new + + + pci_projects_project_network_private_vrack_create_existing + +

+ + pci_projects_project_network_private_vrack_create_description + +

+
+
+ + pci_projects_project_network_private_vrack_create_cancel + + + pci_projects_project_network_private_vrack_create_action + +
+
+`; diff --git a/packages/manager/apps/pci-private-network/src/pages/onboarding/store.ts b/packages/manager/apps/pci-private-network/src/pages/onboarding/store.ts new file mode 100644 index 000000000000..16ecfd2d7011 --- /dev/null +++ b/packages/manager/apps/pci-private-network/src/pages/onboarding/store.ts @@ -0,0 +1,11 @@ +import { create } from 'zustand'; + +interface VrackCreationState { + operationId: string; + setOperationId: (id: string) => void; +} + +export const useVrackCreationOperation = create((set) => ({ + operationId: null, + setOperationId: (id: string) => set({ operationId: id }), +})); diff --git a/packages/manager/apps/pci-private-network/src/routes.tsx b/packages/manager/apps/pci-private-network/src/routes.tsx index b94a71698554..d304112d6278 100644 --- a/packages/manager/apps/pci-private-network/src/routes.tsx +++ b/packages/manager/apps/pci-private-network/src/routes.tsx @@ -76,6 +76,14 @@ export default [ { path: ROUTE_PATHS.onboarding, ...lazyRouteConfig(() => import('@/pages/onboarding/Onboarding.page')), + children: [ + { + path: 'new', + ...lazyRouteConfig(() => + import('@/pages/onboarding/new/VrackCreation.page'), + ), + }, + ], }, { path: ROUTE_PATHS.new, diff --git a/packages/manager/apps/pci-private-network/src/setupTests.ts b/packages/manager/apps/pci-private-network/src/setupTests.ts index 39651aefe5e5..4f143628a03c 100644 --- a/packages/manager/apps/pci-private-network/src/setupTests.ts +++ b/packages/manager/apps/pci-private-network/src/setupTests.ts @@ -2,6 +2,29 @@ import '@testing-library/jest-dom'; import 'element-internals-polyfill'; import { vi } from 'vitest'; +vi.mock('react-router-dom', async () => { + const mod = await vi.importActual('react-router-dom'); + return { + ...mod, + useSearchParams: () => [new URLSearchParams({})], + useParams: () => ({ projectId: 'project-id', kubeId: 'kube-id' }), + useHref: vi.fn(), + useLocation: vi.fn(), + useNavigate: vi.fn(), + Navigate: () => null, + Outlet: vi.fn(() => 'Outlet'), + }; +}); + +vi.mock('@ovh-ux/manager-react-components', async () => { + const mod = await vi.importActual('@ovh-ux/manager-react-components'); + return { + ...mod, + useProjectUrl: vi.fn().mockReturnValue('mockProjectUrl'), + Notifications: vi.fn(), + }; +}); + vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (translationKey: string) => translationKey, @@ -10,3 +33,17 @@ vi.mock('react-i18next', () => ({ }, }), })); + +vi.mock('@ovh-ux/manager-core-api', async () => { + const mod = await vi.importActual('@ovh-ux/manager-core-api'); + return { + ...mod, + fetchIcebergV6: vi.fn(), + v6: { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + }, + }; +}); diff --git a/packages/manager/apps/pci-private-network/vitest.config.js b/packages/manager/apps/pci-private-network/vitest.config.js index 156daa6627e5..130783c024e2 100644 --- a/packages/manager/apps/pci-private-network/vitest.config.js +++ b/packages/manager/apps/pci-private-network/vitest.config.js @@ -2,9 +2,24 @@ import path from 'path'; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +function relativeImgPathImport() { + return { + name: 'relative-img-path-import', + transform(_code, id) { + if (/(jpg|jpeg|png|webp|gif|svg)$/.test(id)) { + const imgSrc = path.relative(process.cwd(), id); + return { + code: `export default '${imgSrc}'`, + }; + } + return undefined; + }, + }; +} + // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), relativeImgPathImport()], test: { globals: true, environment: 'jsdom', diff --git a/packages/manager/modules/manager-pci-common/src/api/data/index.ts b/packages/manager/modules/manager-pci-common/src/api/data/index.ts index fd2e50ec5688..057ecb0dfc2d 100644 --- a/packages/manager/modules/manager-pci-common/src/api/data/index.ts +++ b/packages/manager/modules/manager-pci-common/src/api/data/index.ts @@ -2,5 +2,6 @@ export * from './availability'; export * from './catalog'; export * from './flavors'; export * from './instance'; +export * from './operation'; export * from './project'; export * from './regions'; diff --git a/packages/manager/modules/manager-pci-common/src/api/data/operation.ts b/packages/manager/modules/manager-pci-common/src/api/data/operation.ts new file mode 100644 index 000000000000..43190f46735e --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/api/data/operation.ts @@ -0,0 +1,36 @@ +import { v6 } from '@ovh-ux/manager-core-api'; + +export enum OperationStatus { + Completed = 'completed', + Created = 'created', + InError = 'in-error', + InProgress = 'in-progress', + Unknown = 'unknown', +} + +export type TOperation = { + action: string; + completedAt: string | null; + createdAt: string; + id: string; + progress: number; + regions: string[]; + resourceId: string | null; + startedAt: string | null; + status: OperationStatus; +}; + +export const getOperation = async ( + projectId: string, + operationId: string, +): Promise => { + try { + const { data } = await v6.get( + `/cloud/project/${projectId}/operation/${operationId}`, + ); + return data; + } catch (err) { + if (err?.response?.status === 404) return null; + throw err; + } +}; diff --git a/packages/manager/modules/manager-pci-common/src/api/hook/index.ts b/packages/manager/modules/manager-pci-common/src/api/hook/index.ts index a45ab8372b0a..c85b51e59033 100644 --- a/packages/manager/modules/manager-pci-common/src/api/hook/index.ts +++ b/packages/manager/modules/manager-pci-common/src/api/hook/index.ts @@ -1,6 +1,7 @@ export * from './useAvailability'; export * from './useCatalog'; export * from './useFlavors'; +export * from './useOperation'; export * from './useProject'; export * from './useRegions'; export * from './useInstance'; diff --git a/packages/manager/modules/manager-pci-common/src/api/hook/useOperation.ts b/packages/manager/modules/manager-pci-common/src/api/hook/useOperation.ts new file mode 100644 index 000000000000..6387473c204d --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/api/hook/useOperation.ts @@ -0,0 +1,68 @@ +import { useEffect, useRef, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { getOperation, OperationStatus, TOperation } from '../data/operation'; + +export const useOperationProgress = ( + projectId: string, + operationId: string, + onCompleted: (status: OperationStatus) => void, +) => { + const [progress, setProgress] = useState({ + percentage: 0, + status: OperationStatus.Unknown, + }); + + const onCompletedRef = useRef(onCompleted); + const notifyCompletion = (status: OperationStatus) => { + if (onCompletedRef.current) { + onCompletedRef.current(status); + onCompletedRef.current = null; + } + }; + + const { data: operation, error } = useQuery({ + queryKey: ['pci-operation-progress', projectId, operationId], + queryFn: () => getOperation(projectId, operationId), + enabled: + !!projectId && + !!operationId && + progress.percentage < 100 && + ![OperationStatus.Completed, OperationStatus.InError].includes( + progress.status, + ), + refetchInterval: 3000, + retry: 5, + }); + + useEffect(() => { + // if operation doesn't exist (404) we assume it is done and successfull + if (operation === null) { + setProgress({ + percentage: 100, + status: OperationStatus.Completed, + }); + notifyCompletion(OperationStatus.Completed); + } else if (operation) { + const status = operation.status || OperationStatus.Unknown; + setProgress({ + percentage: operation.progress, + status, + }); + if (operation.progress === 100) { + notifyCompletion(status); + } + } + }, [operation]); + + useEffect(() => { + if (error) { + setProgress({ + percentage: 100, + status: OperationStatus.InError, + }); + notifyCompletion(OperationStatus.InError); + } + }, [error]); + + return progress; +}; From 4db956a9a0c0c29f4ad2219795dc74fa2d7ad681 Mon Sep 17 00:00:00 2001 From: Eric <35898232+Eric-ciccotti@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:45:28 +0100 Subject: [PATCH 6/9] feat(pci-rancher): add additional guides links (#13595) ref: TAPC-675 Signed-off-by: Eric Ciccotti Co-authored-by: CDS Translator Agent --- .../onboarding/Messages_de_DE.json | 6 +- .../onboarding/Messages_en_GB.json | 6 +- .../onboarding/Messages_es_ES.json | 6 +- .../onboarding/Messages_fr_CA.json | 4 + .../onboarding/Messages_fr_FR.json | 4 + .../onboarding/Messages_it_IT.json | 6 +- .../onboarding/Messages_pl_PL.json | 6 +- .../onboarding/Messages_pt_PT.json | 6 +- .../src/hooks/useGuideLink/useGuideLink.tsx | 94 +++++++++++++++++++ .../src/pages/onboarding/Onboarding.page.tsx | 38 ++++++-- .../src/pages/onboarding/onboarding.test.tsx | 35 ++++++- 11 files changed, 190 insertions(+), 21 deletions(-) diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_de_DE.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_de_DE.json index aaacbf63db44..e00c535c6767 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_de_DE.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_de_DE.json @@ -16,5 +16,9 @@ "guide3Description": "Wie Sie einen NAS über CIFS auf Windows Server mounten", "guide3Link": "https://help.ovhcloud.com/csm/de-public-cloud-storage-nas-cifs?id=kb_article_view&sysparm_article=KB0046663", "managedRancherServiceGettingStartedTitle": "Erste Schritte mit Managed Rancher Service", - "managedRancherServiceGettingStartedTitleDescription": "Wie Sie Managed Rancher Service bei OVHcloud verwenden" + "managedRancherServiceGettingStartedTitleDescription": "Wie Sie Managed Rancher Service bei OVHcloud verwenden", + "managedRancherServiceGettingStartedTitle2": "Managed Rancher Service erstellen, aktualisieren und darauf zugreifen", + "managedRancherServiceGettingStartedTitleDescription2": "Hier erfahren Sie, wie Sie einen Managed Rancher Service erstellen, aktualisieren und darauf zugreifen", + "managedRancherServiceGettingStartedTitle3": "Den Lebenszyklus von Managed Rancher Service verstehen", + "managedRancherServiceGettingStartedTitleDescription3": "Entdecken Sie den Lebenszyklus von Rancher-Versionen bei Managed Rancher Service" } diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_en_GB.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_en_GB.json index bf0f9cbe656f..0ac0e1eadb3d 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_en_GB.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_en_GB.json @@ -16,5 +16,9 @@ "guide3Description": "Find out how to mount a NAS on Windows Server via CIFS", "guide3Link": "https://help.ovhcloud.com/csm/en-gb-public-cloud-storage-nas-cifs?id=kb_article_view&sysparm_article=KB0046664", "managedRancherServiceGettingStartedTitle": "Getting started with the Managed Rancher Service", - "managedRancherServiceGettingStartedTitleDescription": "Find out how to use the Managed Rancher Service on OVHcloud" + "managedRancherServiceGettingStartedTitleDescription": "Find out how to use the Managed Rancher Service on OVHcloud", + "managedRancherServiceGettingStartedTitle2": "Create, update and access a Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription2": "Find out how to create, update and access a Managed Rancher Service", + "managedRancherServiceGettingStartedTitle3": "Understanding the Managed Rancher Service lifecycle", + "managedRancherServiceGettingStartedTitleDescription3": "Discover the Rancher version lifecycle on the Managed Rancher Service" } diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_es_ES.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_es_ES.json index 6f3c6197ac15..162a66e839d9 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_es_ES.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_es_ES.json @@ -16,5 +16,9 @@ "guide3Description": "Cómo montar un NAS en Windows Server a través del protocolo CIFS", "guide3Link": "https://help.ovhcloud.com/csm/es-es-public-cloud-storage-nas-cifs?id=kb_article_view&sysparm_article=KB0046668", "managedRancherServiceGettingStartedTitle": "Empezar con Managed Rancher Service", - "managedRancherServiceGettingStartedTitleDescription": "Descubra cómo utilizar Managed Rancher Service en OVHcloud" + "managedRancherServiceGettingStartedTitleDescription": "Descubra cómo utilizar Managed Rancher Service en OVHcloud", + "managedRancherServiceGettingStartedTitle2": "Crear, actualizar y acceder a un Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription2": "Cómo crear, actualizar y acceder a un Managed Rancher Service", + "managedRancherServiceGettingStartedTitle3": "Descripción del Ciclo de Vida de Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription3": "Descubra el ciclo de vida de las versiones de Rancher en Managed Rancher Service" } diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_CA.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_CA.json index 1b9306dde116..92cdc26080c6 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_CA.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_CA.json @@ -8,6 +8,10 @@ "guideCategory": "Tutoriel", "managedRancherServiceGettingStartedTitle": "Démarrer avec Managed Rancher Service", "managedRancherServiceGettingStartedTitleDescription": "Découvrez comment utiliser Managed Rancher Service sur OVHcloud", + "managedRancherServiceGettingStartedTitle2": "Créer, mettre à jour et accéder à un Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription2": "Découvrez comment créer, mettre à jour, et accéder à un Managed Rancher Service", + "managedRancherServiceGettingStartedTitle3": "Comprendre le cycle de vie de Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription3": "Découvrez le cycle de vie des versions de Rancher sur Managed Rancher Service", "guide2Title": "Monter votre NAS via un partage NFS", "guide2Description": "Découvrez comment monter un NAS via un partage NFS", "guide3Title": "Monter votre NAS sur Windows Server via CIFS", diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_FR.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_FR.json index 1b9306dde116..92cdc26080c6 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_FR.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_fr_FR.json @@ -8,6 +8,10 @@ "guideCategory": "Tutoriel", "managedRancherServiceGettingStartedTitle": "Démarrer avec Managed Rancher Service", "managedRancherServiceGettingStartedTitleDescription": "Découvrez comment utiliser Managed Rancher Service sur OVHcloud", + "managedRancherServiceGettingStartedTitle2": "Créer, mettre à jour et accéder à un Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription2": "Découvrez comment créer, mettre à jour, et accéder à un Managed Rancher Service", + "managedRancherServiceGettingStartedTitle3": "Comprendre le cycle de vie de Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription3": "Découvrez le cycle de vie des versions de Rancher sur Managed Rancher Service", "guide2Title": "Monter votre NAS via un partage NFS", "guide2Description": "Découvrez comment monter un NAS via un partage NFS", "guide3Title": "Monter votre NAS sur Windows Server via CIFS", diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_it_IT.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_it_IT.json index 153ecfcbb6a8..b35bfb1e28d8 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_it_IT.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_it_IT.json @@ -16,5 +16,9 @@ "guide3Description": "Come effettuare il mount di un NAS su Windows Server tramite il protocollo CIFS", "guide3Link": "https://help.ovhcloud.com/csm/it-public-cloud-storage-nas-cifs?id=kb_article_view&sysparm_article=KB0046671", "managedRancherServiceGettingStartedTitle": "Iniziare a utilizzare Managed Rancher Service", - "managedRancherServiceGettingStartedTitleDescription": "Scopri come utilizzare Managed Rancher Service in OVHcloud" + "managedRancherServiceGettingStartedTitleDescription": "Scopri come utilizzare Managed Rancher Service in OVHcloud", + "managedRancherServiceGettingStartedTitle2": "Creare, aggiornare e accedere a un Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription2": "Scopri come creare, aggiornare e accedere a un Managed Rancher Service", + "managedRancherServiceGettingStartedTitle3": "Comprendere il ciclo di vita del servizio Managed Rancher", + "managedRancherServiceGettingStartedTitleDescription3": "Scopri il ciclo di vita delle versioni di Rancher sul servizio Managed Rancher" } diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pl_PL.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pl_PL.json index 2185a14bc928..dbc808757643 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pl_PL.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pl_PL.json @@ -16,5 +16,9 @@ "guide3Description": "Dowiedz się, jak zamontować NAS na serwerze Windows Server przy użyciu protokołu CIFS", "guide3Link": "https://help.ovhcloud.com/csm/pl-public-cloud-storage-nas-cifs?id=kb_article_view&sysparm_article=KB0046679", "managedRancherServiceGettingStartedTitle": "Zacznij korzystać z Managed Rancher Service", - "managedRancherServiceGettingStartedTitleDescription": "Dowiedz się, jak korzystać z Managed Rancher Service od OVHcloud" + "managedRancherServiceGettingStartedTitleDescription": "Dowiedz się, jak korzystać z Managed Rancher Service od OVHcloud", + "managedRancherServiceGettingStartedTitle2": "Tworzenie, aktualizowanie i uzyskiwanie dostępu do Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription2": "Dowiedz się, jak tworzyć, aktualizować i uzyskiwać dostęp do Managed Rancher Service", + "managedRancherServiceGettingStartedTitle3": "Zrozum cykl życia Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription3": "Sprawdź cykl życia wersji Rancher w Managed Rancher Service" } diff --git a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pt_PT.json b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pt_PT.json index 4d92da24b8f2..16bcab063664 100644 --- a/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pt_PT.json +++ b/packages/manager/apps/pci-rancher/public/translations/onboarding/Messages_pt_PT.json @@ -16,5 +16,9 @@ "guide3Description": "Saiba como montar um NAS no Windows Server através do protocolo CIFS", "guide3Link": "https://help.ovhcloud.com/csm/pt-public-cloud-storage-nas-cifs?id=kb_article_view&sysparm_article=KB0046676", "managedRancherServiceGettingStartedTitle": "Começar com o Managed Rancher Service", - "managedRancherServiceGettingStartedTitleDescription": "Saiba como utilizar o Managed Rancher Service na OVHcloud" + "managedRancherServiceGettingStartedTitleDescription": "Saiba como utilizar o Managed Rancher Service na OVHcloud", + "managedRancherServiceGettingStartedTitle2": "Criar, atualizar e aceder a um Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription2": "Saiba como criar, atualizar e aceder a um Managed Rancher Service", + "managedRancherServiceGettingStartedTitle3": "Compreender o ciclo de vida do Managed Rancher Service", + "managedRancherServiceGettingStartedTitleDescription3": "Descubra o ciclo de vida das versões do Rancher no Managed Rancher Service" } diff --git a/packages/manager/apps/pci-rancher/src/hooks/useGuideLink/useGuideLink.tsx b/packages/manager/apps/pci-rancher/src/hooks/useGuideLink/useGuideLink.tsx index 5ba3fb3ade25..a50ffa89565d 100644 --- a/packages/manager/apps/pci-rancher/src/hooks/useGuideLink/useGuideLink.tsx +++ b/packages/manager/apps/pci-rancher/src/hooks/useGuideLink/useGuideLink.tsx @@ -52,6 +52,100 @@ const GUIDE_LIST: { [guideName: string]: Partial } = { WS: 'https://help.ovhcloud.com/csm/es-public-cloud-managed-rancher-service-getting-started?id=kb_article_view&sysparm_article=KB0061900', }, + MANAGED_RANCHER_SERVICE_CREATION: { + DEFAULT: + 'https://help.ovhcloud.com/csm/en-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064191', + ASIA: + 'https://help.ovhcloud.com/csm/asia-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064184', + AU: + 'https://help.ovhcloud.com/csm/en-au-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064190', + CA: + 'https://help.ovhcloud.com/csm/en-ca-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064197', + DE: + 'https://help.ovhcloud.com/csm/de-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064194', + ES: + 'https://help.ovhcloud.com/csm/es-es-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064193', + EU: + 'https://help.ovhcloud.com/csm/en-ie-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064183', + FR: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064198', + GB: + 'https://help.ovhcloud.com/csm/en-gb-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064189', + IE: + 'https://help.ovhcloud.com/csm/en-ie-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064183', + IN: + 'https://help.ovhcloud.com/csm/asia-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064184', + IT: + 'https://help.ovhcloud.com/csm/it-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064195', + MA: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064198', + NL: + 'https://help.ovhcloud.com/csm/en-nl-documentation-public-cloud?id=kb_browse_cat&kb_id=574a8325551974502d4c6e78b7421938', + PL: + 'https://help.ovhcloud.com/csm/pl-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064187', + PT: + 'https://help.ovhcloud.com/csm/pt-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064196', + QC: + 'https://help.ovhcloud.com/csm/fr-ca-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064186', + SG: + 'https://help.ovhcloud.com/csm/en-sg-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064192', + SN: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064198', + TN: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064198', + US: 'https://us.ovhcloud.com/support', + WE: + 'https://help.ovhcloud.com/csm/en-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064191', + WS: + 'https://help.ovhcloud.com/csm/es-public-cloud-managed-rancher-service-creation?id=kb_article_view&sysparm_article=KB0064185', + }, + MANAGED_RANCHER_SERVICE_LIFECYCLE_POLICY: { + DEFAULT: + 'https://help.ovhcloud.com/csm/en-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064598', + ASIA: + 'https://help.ovhcloud.com/csm/asia-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064602', + AU: + 'https://help.ovhcloud.com/csm/en-au-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064611', + CA: + 'https://help.ovhcloud.com/csm/en-ca-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064609', + DE: + 'https://help.ovhcloud.com/csm/de-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064600', + ES: + 'https://help.ovhcloud.com/csm/es-es-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064605', + EU: + 'https://help.ovhcloud.com/csm/en-ie-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064606', + FR: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064608', + GB: + 'https://help.ovhcloud.com/csm/en-gb-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064607', + IE: + 'https://help.ovhcloud.com/csm/en-ie-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064606', + IN: + 'https://help.ovhcloud.com/csm/asia-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064602', + IT: + 'https://help.ovhcloud.com/csm/it-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064612', + MA: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064608', + NL: + 'https://help.ovhcloud.com/csm/en-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064598', // DEFAULT en attendant mieux + PL: + 'https://help.ovhcloud.com/csm/pl-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064610', + PT: + 'https://help.ovhcloud.com/csm/pt-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064604', + QC: + 'https://help.ovhcloud.com/csm/fr-ca-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064599', + SG: + 'https://help.ovhcloud.com/csm/en-sg-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064603', + SN: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064608', + TN: + 'https://help.ovhcloud.com/csm/fr-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064608', + US: 'https://us.ovhcloud.com/support', + WE: + 'https://help.ovhcloud.com/csm/en-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064598', + WS: + 'https://help.ovhcloud.com/csm/es-public-cloud-managed-rancher-service-lifecycle-policy?id=kb_article_view&sysparm_article=KB0064601', + }, }; type GetGuideLinkProps = { diff --git a/packages/manager/apps/pci-rancher/src/pages/onboarding/Onboarding.page.tsx b/packages/manager/apps/pci-rancher/src/pages/onboarding/Onboarding.page.tsx index 7ae266e42fe6..1ada8e429fa3 100644 --- a/packages/manager/apps/pci-rancher/src/pages/onboarding/Onboarding.page.tsx +++ b/packages/manager/apps/pci-rancher/src/pages/onboarding/Onboarding.page.tsx @@ -30,21 +30,39 @@ export default function Onboarding() { navigate(getCreateRancherUrl(projectId)); }; - const tileList = [ + const tileData = [ { id: 1, - texts: { - title: t('managedRancherServiceGettingStartedTitle'), - description: t('managedRancherServiceGettingStartedTitleDescription'), - category: t('guideCategory'), - }, - href: link?.MANAGED_RANCHER_SERVICE_GETTING_STARTED as string, - - isExternalHref: true, - hoverable: true, + titleKey: 'managedRancherServiceGettingStartedTitle', + descriptionKey: 'managedRancherServiceGettingStartedTitleDescription', + hrefKey: 'MANAGED_RANCHER_SERVICE_GETTING_STARTED', + }, + { + id: 2, + titleKey: 'managedRancherServiceGettingStartedTitle2', + descriptionKey: 'managedRancherServiceGettingStartedTitleDescription2', + hrefKey: 'MANAGED_RANCHER_SERVICE_CREATION', + }, + { + id: 3, + titleKey: 'managedRancherServiceGettingStartedTitle3', + descriptionKey: 'managedRancherServiceGettingStartedTitleDescription3', + hrefKey: 'MANAGED_RANCHER_SERVICE_LIFECYCLE_POLICY', }, ]; + const tileList = tileData.map((item) => ({ + ...item, + texts: { + title: t(item.titleKey), + description: t(item.descriptionKey), + category: t('guideCategory'), + }, + href: link?.[item.hrefKey] as string, + isExternalHref: true, + hoverable: true, + })); + return ( diff --git a/packages/manager/apps/pci-rancher/src/pages/onboarding/onboarding.test.tsx b/packages/manager/apps/pci-rancher/src/pages/onboarding/onboarding.test.tsx index 45cc1f5a76af..db463a778d49 100644 --- a/packages/manager/apps/pci-rancher/src/pages/onboarding/onboarding.test.tsx +++ b/packages/manager/apps/pci-rancher/src/pages/onboarding/onboarding.test.tsx @@ -27,6 +27,8 @@ jest.mock('@ovh-ux/manager-react-shell-client', () => ({ jest.mock('@/hooks/useGuideLink/useGuideLink', () => ({ useGuideUtils: jest.fn(() => ({ MANAGED_RANCHER_SERVICE_GETTING_STARTED: 'https://example.com/guide1', + MANAGED_RANCHER_SERVICE_CREATION: 'https://example.com/guide2', + MANAGED_RANCHER_SERVICE_LIFECYCLE_POLICY: 'https://example.com/guide3', })), })); @@ -58,6 +60,12 @@ describe('Onboarding', () => { it('renders the guide tiles correctly', async () => { const screen = await setupSpecTest(); + const tileList = [ + { id: 1, href: 'https://example.com/guide1' }, + { id: 2, href: 'https://example.com/guide2' }, + { id: 3, href: 'https://example.com/guide3' }, + ]; + const guideTitle = screen.getByText( onboardingTranslation.managedRancherServiceGettingStartedTitle, ); @@ -68,15 +76,32 @@ describe('Onboarding', () => { expect(guideTitle).toBeInTheDocument(); expect(guideDescription).toBeInTheDocument(); - const card = screen.getByTestId('tileCard'); - expect(card).toHaveAttribute('href', 'https://example.com/guide1'); + const cards = screen.getAllByTestId('tileCard'); + cards.forEach((card, index) => { + expect(card).toHaveAttribute('href', tileList[index].href); + }); }); it('uses the guide link utility correctly', async () => { const guideUtils = useGuideUtils(); - expect(guideUtils.MANAGED_RANCHER_SERVICE_GETTING_STARTED).toBe( - 'https://example.com/guide1', - ); + const expectedResults = [ + { + key: 'MANAGED_RANCHER_SERVICE_GETTING_STARTED', + expectedValue: 'https://example.com/guide1', + }, + { + key: 'MANAGED_RANCHER_SERVICE_CREATION', + expectedValue: 'https://example.com/guide2', + }, + { + key: 'MANAGED_RANCHER_SERVICE_LIFECYCLE_POLICY', + expectedValue: 'https://example.com/guide3', + }, + ]; + + expectedResults.forEach((testCase) => { + expect(guideUtils[testCase.key]).toBe(testCase.expectedValue); + }); }); }); From 16ff953fabd4f93f64fca4d542183a5e88c85066 Mon Sep 17 00:00:00 2001 From: Lio B Date: Thu, 31 Oct 2024 17:52:44 +0100 Subject: [PATCH 7/9] fix(pci): hide savings plan banner for us (#13495) ref: TAPC-1885 Signed-off-by: Lionel Bueno --- .../src/projects/project/instances/add/add.controller.js | 6 ++++++ .../modules/pci/src/projects/project/instances/add/add.html | 1 + 2 files changed, 7 insertions(+) diff --git a/packages/manager/modules/pci/src/projects/project/instances/add/add.controller.js b/packages/manager/modules/pci/src/projects/project/instances/add/add.controller.js index 0cc975040d02..8f55e799d6ab 100644 --- a/packages/manager/modules/pci/src/projects/project/instances/add/add.controller.js +++ b/packages/manager/modules/pci/src/projects/project/instances/add/add.controller.js @@ -231,6 +231,12 @@ export default class PciInstancesAddController { return this.model.datacenter?.type === 'localzone'; } + get isSavingsPlanAvailable() { + return this.pciFeatures.isFeatureAvailable( + PCI_FEATURES.PRODUCTS.SAVINGS_PLAN, + ); + } + updateLocation(location, datacenters) { this.model.location = location; this.selectedMode = this.isLocalZone() ? this.modes[2] : this.modes[0]; diff --git a/packages/manager/modules/pci/src/projects/project/instances/add/add.html b/packages/manager/modules/pci/src/projects/project/instances/add/add.html index df11be1f25f9..5f5985e42dbe 100644 --- a/packages/manager/modules/pci/src/projects/project/instances/add/add.html +++ b/packages/manager/modules/pci/src/projects/project/instances/add/add.html @@ -69,6 +69,7 @@

>
From b6a27231b08782b79300833cca7f9448d1190e64 Mon Sep 17 00:00:00 2001 From: Simon Chaumet Date: Thu, 31 Oct 2024 18:00:25 +0100 Subject: [PATCH 8/9] feat(pci-block-storage): add exten beta (#13489) Extend volume capacity to 12To Add banner for beta extension Implement dependent regions on block tiles ref: TAPC-1609, TAPC-2071, TAPC-2072, TAPC-1139, TAPC-1505, TAPC-2063, TAPC-1711, TAPC-1716, TAPC-1728, TAPC-1938 Signed-off-by: Simon Chaumet Co-authored-by: Yann Lojewski Co-authored-by: CDS Translator Agent --- .../translations/common/Messages_de_DE.json | 3 +- .../translations/common/Messages_en_GB.json | 3 +- .../translations/common/Messages_es_ES.json | 3 +- .../translations/common/Messages_fr_CA.json | 4 +- .../translations/common/Messages_fr_FR.json | 4 +- .../translations/common/Messages_it_IT.json | 3 +- .../translations/common/Messages_pl_PL.json | 3 +- .../translations/common/Messages_pt_PT.json | 3 +- .../exten-banner-beta/Messages_de_DE.json | 3 + .../exten-banner-beta/Messages_en_GB.json | 3 + .../exten-banner-beta/Messages_es_ES.json | 3 + .../exten-banner-beta/Messages_fr_CA.json | 3 + .../exten-banner-beta/Messages_fr_FR.json | 3 + .../exten-banner-beta/Messages_it_IT.json | 3 + .../exten-banner-beta/Messages_pl_PL.json | 3 + .../exten-banner-beta/Messages_pt_PT.json | 3 + .../pci-block-storage/src/api/data/quota.ts | 39 +++++++++++- .../src/api/hooks/useQuota.ts | 6 +- .../exten-banner-beta/ExtenBannerBeta.tsx | 62 +++++++++++++++++++ .../apps/pci-block-storage/src/constants.ts | 1 - .../src/pages/edit/Edit.page.spec.tsx | 5 +- .../src/pages/edit/Edit.page.tsx | 31 +++++----- .../src/pages/new/New.page.tsx | 5 ++ .../new/components/CapacityStep.component.tsx | 24 ++++--- .../pages/new/components/PriceEstimate.tsx | 20 +++--- .../components/VolumeTypeStep.component.tsx | 20 +++++- .../KubeRegionSelector.component.tsx | 7 +-- .../src/api/data/availability.ts | 13 ++-- .../src/api/hook/useAvailability.ts | 23 +++---- 29 files changed, 232 insertions(+), 74 deletions(-) create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_de_DE.json create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_en_GB.json create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_es_ES.json create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_CA.json create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_FR.json create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_it_IT.json create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pl_PL.json create mode 100644 packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pt_PT.json create mode 100644 packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_de_DE.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_de_DE.json index 5f07570cc57b..e264c034e35a 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_de_DE.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_de_DE.json @@ -17,5 +17,6 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Mit Instanz verbinden", "pci_projects_project_storages_blocks_create_backup_label": "Backup erstellen", "pci_projects_project_storages_blocks_delete_label": "Löschen", - "pci_projects_project_storages_blocks_add_label": "Volume erstellen" + "pci_projects_project_storages_blocks_add_label": "Volume erstellen", + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_en_GB.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_en_GB.json index e10516a227bc..625409878633 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_en_GB.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_en_GB.json @@ -17,5 +17,6 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Attach to instance", "pci_projects_project_storages_blocks_create_backup_label": "Create a backup ", "pci_projects_project_storages_blocks_delete_label": "Delete", - "pci_projects_project_storages_blocks_add_label": "Create a volume " + "pci_projects_project_storages_blocks_add_label": "Create a volume ", + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_es_ES.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_es_ES.json index 27ea12d969e4..740f259aa655 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_es_ES.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_es_ES.json @@ -17,5 +17,6 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Asociar a la instancia", "pci_projects_project_storages_blocks_create_backup_label": "Crear una copia de seguridad", "pci_projects_project_storages_blocks_delete_label": "Eliminar", - "pci_projects_project_storages_blocks_add_label": "Crear un volumen" + "pci_projects_project_storages_blocks_add_label": "Crear un volumen", + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_CA.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_CA.json index 2f0ed9fa6c80..bd4accaa88c1 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_CA.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_CA.json @@ -18,5 +18,7 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Attacher à l'instance", "pci_projects_project_storages_blocks_create_backup_label": "Créer une sauvegarde", "pci_projects_project_storages_blocks_delete_label": "Supprimer", - "pci_projects_project_storages_blocks_add_label": "Créer un volume" + "pci_projects_project_storages_blocks_add_label": "Créer un volume", + + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_FR.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_FR.json index 2f0ed9fa6c80..bd4accaa88c1 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_FR.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_fr_FR.json @@ -18,5 +18,7 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Attacher à l'instance", "pci_projects_project_storages_blocks_create_backup_label": "Créer une sauvegarde", "pci_projects_project_storages_blocks_delete_label": "Supprimer", - "pci_projects_project_storages_blocks_add_label": "Créer un volume" + "pci_projects_project_storages_blocks_add_label": "Créer un volume", + + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_it_IT.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_it_IT.json index c702fa8f94e2..50f2025ea09f 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_it_IT.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_it_IT.json @@ -17,5 +17,6 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Associa all’istanza", "pci_projects_project_storages_blocks_create_backup_label": "Crea un backup", "pci_projects_project_storages_blocks_delete_label": "Eliminare", - "pci_projects_project_storages_blocks_add_label": "Crea un volume" + "pci_projects_project_storages_blocks_add_label": "Crea un volume", + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pl_PL.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pl_PL.json index c02cf76d2170..7dcdf9f17419 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pl_PL.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pl_PL.json @@ -17,5 +17,6 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Podłącz do instancji", "pci_projects_project_storages_blocks_create_backup_label": "Utwórz kopię zapasową", "pci_projects_project_storages_blocks_delete_label": "Usuń", - "pci_projects_project_storages_blocks_add_label": "Utwórz wolumen" + "pci_projects_project_storages_blocks_add_label": "Utwórz wolumen", + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pt_PT.json b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pt_PT.json index 8fafc43406c3..ad67692c1771 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pt_PT.json +++ b/packages/manager/apps/pci-block-storage/public/translations/common/Messages_pt_PT.json @@ -17,5 +17,6 @@ "pci_projects_project_storages_blocks_instance_attach_label": "Associar a instância", "pci_projects_project_storages_blocks_create_backup_label": "Criar uma cópia de segurança", "pci_projects_project_storages_blocks_delete_label": "Eliminar", - "pci_projects_project_storages_blocks_add_label": "Criar um volume" + "pci_projects_project_storages_blocks_add_label": "Criar um volume", + "pci_projects_project_storages_blocks_new": "Beta" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_de_DE.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_de_DE.json new file mode 100644 index 000000000000..5a749e3e4e37 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_de_DE.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "Optimieren Sie Ihre Performance mit Block Storage NVMe over Fabric!\nErhältlich in der Betaphase in den Regionen {{regions}}, verdoppeln Sie die Performance Ihrer Classic Volumes und steigern Sie die Effizienz Ihrer rechenintensiven Aufgaben." +} diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_en_GB.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_en_GB.json new file mode 100644 index 000000000000..3574b0d37949 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_en_GB.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "Optimize your performance with Block Storage NVMe over Fabric!\nAvailable in the beta phase in the {{regions}} regions, double the performance of your Classic volumes and improve the efficiency of your intensive workloads." +} diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_es_ES.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_es_ES.json new file mode 100644 index 000000000000..83b6e1e1b782 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_es_ES.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "¡Optimice su rendimiento con Block Storage NVMe over Fabric!\nDisponible en fase beta en las regiones {{regions}}, duplique el rendimiento de sus volúmenes Classic y mejore la eficiencia de sus cargas de trabajo intensivas." +} diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_CA.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_CA.json new file mode 100644 index 000000000000..95d56a4ccb4b --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_CA.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "Optimisez vos performances avec Block Storage NVMe over Fabric !\nDisponible en phase bêta dans les régions {{regions}}, doublez les performances de vos volumes Classic et améliorez l'efficacité de vos charges de travail intensives." +} diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_FR.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_FR.json new file mode 100644 index 000000000000..95d56a4ccb4b --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_fr_FR.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "Optimisez vos performances avec Block Storage NVMe over Fabric !\nDisponible en phase bêta dans les régions {{regions}}, doublez les performances de vos volumes Classic et améliorez l'efficacité de vos charges de travail intensives." +} diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_it_IT.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_it_IT.json new file mode 100644 index 000000000000..0a85b86848b8 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_it_IT.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "Ottimizza le performance con Block Storage NVMe over Fabric!\nDisponibile in fase beta nelle Region {{regions}}, raddoppia le performance dei volumi Classic e migliora l'efficacia dei carichi di lavoro intensivi." +} diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pl_PL.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pl_PL.json new file mode 100644 index 000000000000..37d2436dea97 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pl_PL.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "Zoptymalizuj swoją wydajność korzystając z Block Storage NVMe over Fabric!\nZwiększ wydajność wolumenów Classic i zwiększ wydajność w przypadku intensywnych obciążeń, dzięki czemu Twoje rozwiązania będą dostępne w fazie beta w regionach {{regions}}." +} diff --git a/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pt_PT.json b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pt_PT.json new file mode 100644 index 000000000000..e62224ecb664 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/public/translations/exten-banner-beta/Messages_pt_PT.json @@ -0,0 +1,3 @@ +{ + "exten_banner_description": "Otimize as suas performances com o Block Storage NVMe over Fabric!\nDisponível na fase beta nas regiões {{regions}}, duplique os desempenhos dos seus volumes Classic e melhore a eficácia das suas cargas de trabalho intensivas." +} diff --git a/packages/manager/apps/pci-block-storage/src/api/data/quota.ts b/packages/manager/apps/pci-block-storage/src/api/data/quota.ts index baae96f89eb9..2a2e16f1ae1a 100644 --- a/packages/manager/apps/pci-block-storage/src/api/data/quota.ts +++ b/packages/manager/apps/pci-block-storage/src/api/data/quota.ts @@ -1,4 +1,6 @@ import { v6 } from '@ovh-ux/manager-core-api'; +import { useFeatureAvailability } from '@ovh-ux/manager-react-components'; +import { useMemo } from 'react'; export interface RegionQuota { region: string; @@ -12,9 +14,40 @@ export interface RegionQuota { export const getRegionsQuota = async ( projectId: string, -): Promise => { - const { data } = await v6.get( - `/cloud/project/${projectId}/quota`, + region?: string, +): Promise => { + const { data } = await v6.get( + `/cloud/project/${projectId}/region/${region}/quota`, ); return data; }; + +function isLocalZone(region: string) { + return region.split('-')[2] === 'LZ'; +} + +function getVolumeMaxSize(region: string) { + return isLocalZone(region) ? 4 * 1000 : 12 * 1000; +} + +export const FA_VOLUME_EXTEND_12TO = 'pci-block-storage:volume-extend-12To'; +export const FA_EXTEN_BANNER = 'pci-block-storage:exten-banner'; + +export function useVolumeMaxSize(region: string | undefined) { + const { data, ...restApi } = useFeatureAvailability([ + FA_VOLUME_EXTEND_12TO, + FA_EXTEN_BANNER, + ]); + + const volumeMaxSize = useMemo(() => { + if (region && data?.[FA_VOLUME_EXTEND_12TO]) { + return getVolumeMaxSize(region); + } + return 4 * 1000; + }, [data, region]); + + return { + ...restApi, + volumeMaxSize, + }; +} diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useQuota.ts b/packages/manager/apps/pci-block-storage/src/api/hooks/useQuota.ts index d34244bcfe16..27e57480750a 100644 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useQuota.ts +++ b/packages/manager/apps/pci-block-storage/src/api/hooks/useQuota.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { getRegionsQuota } from '@/api/data/quota'; -export const useRegionsQuota = (projectId: string) => +export const useRegionsQuota = (projectId: string, region: string) => useQuery({ - queryKey: ['project', projectId, 'quota'], - queryFn: () => getRegionsQuota(projectId), + queryKey: ['project', projectId, 'region', region, 'quota'], + queryFn: () => getRegionsQuota(projectId, region), }); diff --git a/packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx b/packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx new file mode 100644 index 000000000000..8f18e6f7672c --- /dev/null +++ b/packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx @@ -0,0 +1,62 @@ +import { useFeatureAvailability } from '@ovh-ux/manager-react-components'; +import { OsdsMessage, OsdsText } from '@ovhcloud/ods-components/react'; +import { + ODS_MESSAGE_TYPE, + ODS_TEXT_COLOR_HUE, + ODS_TEXT_COLOR_INTENT, +} from '@ovhcloud/ods-components'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { useMemo } from 'react'; +import { FA_EXTEN_BANNER } from '@/api/data/quota'; +import { useProjectsAvailableVolumes } from '@/api/hooks/useProjectsAvailableVolumes'; + +const extenProducts = [ + 'volume.high-speed-BETA.consumption', + 'volume.classic-BETA.consumption', +]; + +function Banner() { + const { t } = useTranslation('exten-banner-beta'); + const { projectId } = useParams(); + const { data: availableVolumes } = useProjectsAvailableVolumes(projectId); + + const regionsString = useMemo( + () => + availableVolumes + ? [ + ...new Set( + availableVolumes.plans + .filter((p) => extenProducts.includes(p.code)) + .flatMap((p) => p.regions) + .map((r) => r.name), + ), + ].join(', ') + : '', + [availableVolumes], + ); + + if (!availableVolumes) return null; + + return ( + + + {t('exten_banner_description', { + regions: regionsString, + })} + + + ); +} + +export function ExtenBannerBeta() { + const { data } = useFeatureAvailability([FA_EXTEN_BANNER]); + + if (!data?.[FA_EXTEN_BANNER]) return null; + + return ; +} diff --git a/packages/manager/apps/pci-block-storage/src/constants.ts b/packages/manager/apps/pci-block-storage/src/constants.ts index 686d0dedbaef..c34b8e6b66ef 100644 --- a/packages/manager/apps/pci-block-storage/src/constants.ts +++ b/packages/manager/apps/pci-block-storage/src/constants.ts @@ -51,7 +51,6 @@ export const URL_INFO = { LOCAL_ZONE: LOCAL_ZONE_INFO_URL, }; -export const VOLUME_MAX_SIZE = 4 * 1000; // Should be 10 * 1024 (but API is wrong; export const VOLUME_MIN_SIZE = 10; // 10 Gio export const VOLUME_UNLIMITED_QUOTA = -1; // Should be 10 * 1024 (but API is wrong) export const ALPHA_CHARACTERS_REGEX = /^[a-zA-Z-]+$/; diff --git a/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.spec.tsx b/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.spec.tsx index 0504c4016484..a194a547576e 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.spec.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.spec.tsx @@ -23,6 +23,10 @@ vi.mock('@/core/HidePreloader', () => ({ default: () =>
HidePeloader
, })); +vi.mock('@/api/hooks/useQuota', () => ({ + useRegionsQuota: vi.fn().mockReturnValue({ data: [] }), +})); + vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => { const mod = await importOriginal< typeof import('@ovh-ux/manager-react-components') @@ -38,7 +42,6 @@ vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => { addSuccess: vi.fn(), }), useProjectLocalRegions: vi.fn().mockReturnValue({ data: [] }), - useProjectQuota: vi.fn().mockReturnValue({ data: [] }), useProjectUrl: vi.fn().mockReturnValue('/project-url'), useTranslatedMicroRegions: vi .fn() diff --git a/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx b/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx index cb3d8c728669..5cc5731cdce6 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx @@ -5,7 +5,6 @@ import { useCatalogPrice, useNotifications, useProjectLocalRegions, - useProjectQuota, useProjectUrl, useTranslatedMicroRegions, } from '@ovh-ux/manager-react-components'; @@ -38,11 +37,7 @@ import { OsdsText, } from '@ovhcloud/ods-components/react'; import { useProject } from '@ovh-ux/manager-pci-common'; -import { - VOLUME_MAX_SIZE, - VOLUME_MIN_SIZE, - VOLUME_UNLIMITED_QUOTA, -} from '@/constants'; +import { VOLUME_MIN_SIZE, VOLUME_UNLIMITED_QUOTA } from '@/constants'; import ChipRegion from '@/components/edit/ChipRegion.component'; import { TVolume } from '@/api/data/volume'; import { @@ -51,6 +46,8 @@ import { useVolume, } from '@/api/hooks/useVolume'; import HidePreloader from '@/core/HidePreloader'; +import { useVolumeMaxSize } from '@/api/data/quota'; +import { useRegionsQuota } from '@/api/hooks/useQuota'; type TFormState = { name: string; @@ -95,6 +92,8 @@ export default function EditPage() { isPending: isPendingVolume, } = useVolume(projectId, volumeId); + const { volumeMaxSize } = useVolumeMaxSize(volume?.region); + const { data: localRegions, isPending: isPendingLocal, @@ -106,7 +105,7 @@ export default function EditPage() { size: { value: volume?.size || VOLUME_MIN_SIZE, min: VOLUME_MIN_SIZE, - max: VOLUME_MAX_SIZE, + max: volumeMaxSize, }, bootable: volume?.bootable || false, isInitialized: false, @@ -152,25 +151,23 @@ export default function EditPage() { }, }); - const { data: projectQuota, isPending: isPendingQuota } = useProjectQuota( + const { data: regionQuota, isPending: isPendingQuota } = useRegionsQuota( projectId, + volume?.region, ); const getMaxSize = (_volume: TVolume) => { - if (projectQuota?.length > 0) { - const regionQuota = projectQuota.find( - ({ region }) => region === _volume.region, - ); + if (regionQuota) { if ( regionQuota.volume && regionQuota.volume.maxGigabytes !== VOLUME_UNLIMITED_QUOTA ) { const availableGigabytes = regionQuota.volume.maxGigabytes - regionQuota.volume.usedGigabytes; - return Math.min(_volume.size + availableGigabytes, VOLUME_MAX_SIZE); + return Math.min(_volume.size + availableGigabytes, volumeMaxSize); } } - return VOLUME_MAX_SIZE; + return volumeMaxSize; }; const getVolumePriceEstimationFromCatalog = ( @@ -194,7 +191,7 @@ export default function EditPage() { isPendingQuota; useEffect(() => { - if (volume && projectQuota) { + if (volume && regionQuota) { setFormState({ name: volume.name, size: { @@ -206,7 +203,7 @@ export default function EditPage() { isInitialized: true, }); } - }, [volume, projectQuota]); + }, [volume, regionQuota]); useEffect(() => { const { min, max, value } = formState.size; @@ -443,7 +440,7 @@ export default function EditPage() {
{!hasError && - (estimatedPrice?.monthly ? ( + (estimatedPrice?.monthly !== undefined ? (
+
+ +
+
(VOLUME_MIN_SIZE); const [maxSize, setMaxSize] = useState(0); - const { data: regionQuotas, isLoading } = useRegionsQuota(projectId); + const { + data: regionQuotas, + isLoading: isRegionQuotaLoading, + } = useRegionsQuota(projectId, region.name); const isCapacityValid = volumeCapacity >= VOLUME_MIN_SIZE && volumeCapacity <= maxSize; + const { volumeMaxSize, isLoading: isVolumeMaxSizeLoading } = useVolumeMaxSize( + region.name, + ); + + const isLoading = isRegionQuotaLoading && isVolumeMaxSizeLoading; + useEffect(() => { if (!isLoading) { - const quota = regionQuotas?.find(({ region: r }) => r === region?.name); - let availableGigabytes = VOLUME_MAX_SIZE; + let availableGigabytes = volumeMaxSize; if ( - quota?.volume && - quota.volume.maxGigabytes !== VOLUME_UNLIMITED_QUOTA + regionQuotas?.volume && + regionQuotas.volume.maxGigabytes !== VOLUME_UNLIMITED_QUOTA ) { availableGigabytes = Math.min( - VOLUME_MAX_SIZE, - quota.volume.maxGigabytes - quota.volume.usedGigabytes, + volumeMaxSize, + regionQuotas.volume.maxGigabytes - regionQuotas.volume.usedGigabytes, ); } setMaxSize(availableGigabytes); diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx index 2e2dabb66a0a..34efc4a64694 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx @@ -28,16 +28,14 @@ export function PriceEstimate({ const priceEstimate = price * volumeCapacity * ESTIMATE_MONTHLY_HOURS; return ( - !!priceEstimate && ( - - {t('pci_projects_project_storages_blocks_add_submit_price_text', { - price: getFormattedCatalogPrice(priceEstimate), - })} - - ) + + {t('pci_projects_project_storages_blocks_add_submit_price_text', { + price: getFormattedCatalogPrice(priceEstimate), + })} + ); } diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx index b1d5d719039c..8572d0e9b8ed 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx @@ -2,18 +2,23 @@ import { OsdsButton, OsdsSpinner, OsdsText, + OsdsChip, } from '@ovhcloud/ods-components/react'; import { ODS_THEME_COLOR_INTENT, ODS_THEME_TYPOGRAPHY_LEVEL, ODS_THEME_TYPOGRAPHY_SIZE, } from '@ovhcloud/ods-common-theming'; +import { + ODS_CHIP_SIZE, + ODS_BUTTON_SIZE, + ODS_SPINNER_SIZE, +} from '@ovhcloud/ods-components'; import { TilesInputComponent, useCatalogPrice, } from '@ovh-ux/manager-react-components'; -import { ODS_BUTTON_SIZE, ODS_SPINNER_SIZE } from '@ovhcloud/ods-components'; import { useTranslation } from 'react-i18next'; import { useState } from 'react'; import { TAddon, TCatalog } from '@ovh-ux/manager-pci-common'; @@ -37,6 +42,7 @@ export function VolumeTypeStep({ }: Readonly) { const { t } = useTranslation('add'); const { t: tStepper } = useTranslation('stepper'); + const { t: tCommon } = useTranslation('common'); const [volumeType, setVolumeType] = useState(undefined); const tBytes = useTranslateBytes(); const { getFormattedCatalogPrice } = useCatalogPrice(6, { @@ -62,7 +68,7 @@ export function VolumeTypeStep({ items={displayedTypes || []} label={(vType: TCatalog['addons'][0]) => (
-
+
{vType.blobs.technical.name} + {vType.blobs.tags.includes('is_new') && ( + + {tCommon('pci_projects_project_storages_blocks_new')} + + )}
) { - const { data: availability, isPending } = useProductAvailability( - projectId, - 'kubernetes', - ); + const { data: availability, isPending } = useProductAvailability(projectId, { + product: 'kubernetes', + }); if (isPending) { return ; } diff --git a/packages/manager/modules/manager-pci-common/src/api/data/availability.ts b/packages/manager/modules/manager-pci-common/src/api/data/availability.ts index 9a5e568df960..8f4abf8bd7c6 100644 --- a/packages/manager/modules/manager-pci-common/src/api/data/availability.ts +++ b/packages/manager/modules/manager-pci-common/src/api/data/availability.ts @@ -19,15 +19,18 @@ export type TProductAvailability = { }[]; }; +export type ProductAvailabilityFilter = { + addonFamily?: string; + planCode?: string; + planFamily?: string; + product?: string; +}; + export const getProductAvailability = async ( projectId: string, params: { - addonFamily?: string; ovhSubsidiary: string; - planCode?: string; - planFamily?: string; - product?: string; - }, + } & ProductAvailabilityFilter, ): Promise => { const { data } = await v6.get( `/cloud/project/${projectId}/capabilities/productAvailability`, diff --git a/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts b/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts index c03b41751fec..57ac5c298d61 100644 --- a/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts +++ b/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts @@ -1,29 +1,30 @@ import { useQuery } from '@tanstack/react-query'; import { useMe } from '@ovh-ux/manager-react-components'; -import { getProductAvailability } from '../data/availability'; +import { + getProductAvailability, + ProductAvailabilityFilter, +} from '../data/availability'; export const getProductAvailabilityQuery = ( projectId: string, ovhSubsidiary: string, - product?: string, + filter?: ProductAvailabilityFilter, ) => ({ - queryKey: [ - 'product-availability', - projectId, - ovhSubsidiary, - product || 'all', - ], + queryKey: ['product-availability', projectId, ovhSubsidiary, filter], queryFn: () => getProductAvailability(projectId, { ovhSubsidiary, - product, + ...filter, }), }); -export const useProductAvailability = (projectId: string, product?: string) => { +export const useProductAvailability = ( + projectId: string, + filter?: ProductAvailabilityFilter, +) => { const { me } = useMe(); return useQuery({ - ...getProductAvailabilityQuery(projectId, me?.ovhSubsidiary, product), + ...getProductAvailabilityQuery(projectId, me?.ovhSubsidiary, filter), enabled: !!me, }); }; From 2dac090605611e9e12e4fd4f09142f96033be035 Mon Sep 17 00:00:00 2001 From: Lio B Date: Tue, 5 Nov 2024 10:58:38 +0100 Subject: [PATCH 9/9] fix(svp): add max quantity (#13915) ref: TAPC-1697 Signed-off-by: Lionel Bueno --- .../CreatePlanForm/CreatePlanForm.tsx | 6 +- .../QuantitySelector/QuantitySelector.tsx | 105 ++++++++++-------- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx index 0f280316afe0..4ab6d9b049d0 100644 --- a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx +++ b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx @@ -336,10 +336,10 @@ const CreatePlanForm: FC = ({ setQuantity((prevQ) => prevQ - 1)} + onMinusClick={() => setQuantity(quantity - 1)} onPlusClick={() => { - if (quantity <= MAX_QUANTITY) { - setQuantity((prevQ) => prevQ + 1); + if (quantity < MAX_QUANTITY) { + setQuantity(quantity + 1); } }} onChangeQuantity={onChangeQuantity} diff --git a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx index 34f15da06893..f5bee8c1093c 100644 --- a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx +++ b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx @@ -24,58 +24,67 @@ type QuantitySelectorProps = { }; export const MAX_QUANTITY = 1000; - const QuantitySelector: React.FC = ({ quantity, onMinusClick, onPlusClick, onChangeQuantity, -}) => ( - - - - - { - const value = Number(e.detail.value); - if (value > 0 && value <= MAX_QUANTITY) { - onChangeQuantity(value); - } - }} - size={ODS_INPUT_SIZE.md} - > - - - - -); +}) => { + const isDisabledPlus = quantity >= MAX_QUANTITY; + + return ( + + + + + { + const value = Number(e.detail.value); + if (value > 0 && value <= MAX_QUANTITY) { + onChangeQuantity(value); + } + }} + size={ODS_INPUT_SIZE.md} + > + + + + + ); +}; export default QuantitySelector;