diff --git a/packages/manager/modules/manager-pci-common/src/api/data/regions.ts b/packages/manager/modules/manager-pci-common/src/api/data/regions.ts index cb9f3fa65fcb..4f74dc765953 100644 --- a/packages/manager/modules/manager-pci-common/src/api/data/regions.ts +++ b/packages/manager/modules/manager-pci-common/src/api/data/regions.ts @@ -2,7 +2,7 @@ import { fetchIcebergV6 } from '@ovh-ux/manager-core-api'; export type TRegion = { name: string; - type: string; + type: 'region' | 'localzone' | 'region-3-az' | string; status: string; continentCode: string; services: { diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/Region3AZChip.component.spec.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/Region3AZChip.component.spec.tsx new file mode 100644 index 000000000000..9e84bcd0e507 --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/Region3AZChip.component.spec.tsx @@ -0,0 +1,35 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it } from 'vitest'; +import { wrapper } from '@/wrapperRenders'; +import { Region3AZChip } from '@/components/region-selector/Region3AZChip.component'; +import { URL_INFO } from '@/components/region-selector/constants'; + +describe('Region3AZChip', () => { + it.each([undefined, false, true])( + 'should render chip with showTooltip %s with correct texts and links', + (showTooltip: boolean | undefined) => { + render(, { wrapper }); + + expect( + screen.getByText('pci_project_flavors_zone_3AZ'), + ).toBeInTheDocument(); + + if (showTooltip ?? true) { + expect( + screen.getByText('pci_project_flavors_zone_3AZ_tooltip'), + ).toBeInTheDocument(); + + const link = screen.getByText('pci_project_flavors_zone_tooltip_link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', URL_INFO.REGION_3AZ.DEFAULT); + } else { + expect( + screen.queryByText('pci_project_flavors_zone_3AZ_tooltip'), + ).not.toBeInTheDocument(); + expect( + screen.queryByText('pci_project_flavors_zone_tooltip_link'), + ).not.toBeInTheDocument(); + } + }, + ); +}); diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/Region3AZChip.component.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/Region3AZChip.component.tsx new file mode 100644 index 000000000000..ad3ec799739f --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/Region3AZChip.component.tsx @@ -0,0 +1,78 @@ +import { useContext } from 'react'; +import { Links, LinkType } from '@ovh-ux/manager-react-components'; +import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; +import { + ODS_CHIP_SIZE, + ODS_ICON_NAME, + ODS_ICON_SIZE, + ODS_TEXT_LEVEL, + ODS_TEXT_SIZE, +} from '@ovhcloud/ods-components'; +import { + OsdsChip, + OsdsIcon, + OsdsPopover, + OsdsPopoverContent, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import { OdsHTMLAnchorElementTarget } from '@ovhcloud/ods-common-core'; +import { useTranslation } from 'react-i18next'; +import { URL_INFO } from './constants'; + +export function Region3AZChip({ + showTooltip = true, +}: Readonly<{ + showTooltip?: boolean; +}>) { + const { t } = useTranslation('pci-region-selector'); + const context = useContext(ShellContext); + const { ovhSubsidiary } = context.environment.getUser(); + const documentURL = + URL_INFO.REGION_3AZ[ovhSubsidiary] || URL_INFO.REGION_3AZ.DEFAULT; + + const chip = ( + event.stopPropagation()} + > + + {t('pci_project_flavors_zone_3AZ')} + + {showTooltip && ( + + )} + + ); + + if (showTooltip) { + return ( + + {chip} + + + {t('pci_project_flavors_zone_3AZ_tooltip')} + +   + + + + ); + } + + return chip; +} diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.spec.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.spec.tsx index 7891fcddade3..835452fcf7e3 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.spec.tsx +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.spec.tsx @@ -1,12 +1,107 @@ import { render, screen } from '@testing-library/react'; +import { vi } from 'vitest'; import { RegionGlobalzoneChip } from './RegionGlobalzoneChip.component'; import { wrapper } from '@/wrapperRenders'; +import { URL_INFO } from '@/components/region-selector/constants'; +import { useHas3AZ } from '@/hooks/useHas3AZ/useHas3AZ'; +import { useIs1AZ } from '@/hooks/useIs1AZ/useIs1AZ'; + +vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => { + const module = await importOriginal< + typeof import('@ovh-ux/manager-react-components') + >(); + return { ...module, useFeatureAvailability: vi.fn() }; +}); + +vi.mock('@/hooks/useHas3AZ/useHas3AZ'); +vi.mock('@/hooks/useIs1AZ/useIs1AZ'); + +enum ExpectedType { + GLOBAL_REGIONS = 'GLOBAL_REGIONS', + '1AZ_REGIONS' = '1AZ_REGIONS', +} + +const EXPECTED_VALUES = { + [ExpectedType.GLOBAL_REGIONS]: { + label: 'pci_project_flavors_zone_global_region', + tooltip: 'pci_project_flavors_zone_globalregions_tooltip', + link: URL_INFO.GLOBAL_REGIONS.DEFAULT, + }, + [ExpectedType['1AZ_REGIONS']]: { + label: 'pci_project_flavors_zone_1AZ', + tooltip: 'pci_project_flavors_zone_1AZ_with_3AZ_tooltip', + link: URL_INFO['1AZ_REGIONS'].DEFAULT, + }, +}; describe('RegionGlobalzoneChip', () => { - it('renders chip with correct text', () => { - render(, { wrapper }); - expect( - screen.getByText('pci_project_flavors_zone_global_region'), - ).toBeInTheDocument(); - }); + it.each([ + [undefined, undefined, ExpectedType.GLOBAL_REGIONS], + [undefined, false, ExpectedType.GLOBAL_REGIONS], + [undefined, true, ExpectedType['1AZ_REGIONS']], + [false, undefined, ExpectedType.GLOBAL_REGIONS], + [false, false, ExpectedType.GLOBAL_REGIONS], + [false, true, ExpectedType['1AZ_REGIONS']], + [true, undefined, ExpectedType.GLOBAL_REGIONS], + [true, false, ExpectedType.GLOBAL_REGIONS], + [true, true, ExpectedType['1AZ_REGIONS']], + ])( + 'should render chip with showTooltip %s show1AZ %s with correct texts and links', + ( + showTooltip: boolean | undefined, + show1AZ: boolean | undefined, + expectedType, + ) => { + const expected = EXPECTED_VALUES[expectedType]; + + vi.mocked(useIs1AZ).mockReturnValue(show1AZ); + + render(, { wrapper }); + + expect(screen.getByText(expected.label)).toBeInTheDocument(); + + if (showTooltip ?? true) { + expect(screen.getByText(expected.tooltip)).toBeInTheDocument(); + + const link = screen.getByText('pci_project_flavors_zone_tooltip_link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', expected.link); + } else { + expect(screen.queryByText(expected.tooltip)).not.toBeInTheDocument(); + expect( + screen.queryByText('pci_project_flavors_zone_tooltip_link'), + ).not.toBeInTheDocument(); + } + }, + ); + + it.each([ + ['render', true, false], + ['not render', true, true], + ['not render', false, true], + ['not render', false, false], + ])( + 'should %s 3AZ tooltip text when feature availability is %s and 3AZ availability is %s', + (expected: string, show1AZ: boolean, has3AZ: boolean) => { + vi.mocked(useIs1AZ).mockReturnValue(show1AZ); + + vi.mocked(useHas3AZ).mockReturnValue(has3AZ); + + render(, { wrapper }); + + if (expected === 'render') { + expect( + screen.getByText('pci_project_flavors_zone_1AZ_with_3AZ_tooltip'), + ).toBeInTheDocument(); + } else if (show1AZ) { + expect( + screen.queryByText('pci_project_flavors_zone_1AZ_tooltip'), + ).toBeInTheDocument(); + } else { + expect( + screen.queryByText('pci_project_flavors_zone_globalregions_tooltip'), + ).toBeInTheDocument(); + } + }, + ); }); diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.tsx index 2c3816540d91..3c7c7c194e89 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.tsx +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionGlobalzoneChip.component.tsx @@ -19,54 +19,74 @@ import { import { OdsHTMLAnchorElementTarget } from '@ovhcloud/ods-common-core'; import { useTranslation } from 'react-i18next'; import { URL_INFO } from './constants'; +import { useHas3AZ } from '../../hooks/useHas3AZ/useHas3AZ'; +import { useIs1AZ } from '../../hooks/useIs1AZ/useIs1AZ'; -export function RegionGlobalzoneChip() { +export function RegionGlobalzoneChip({ + showTooltip = true, +}: Readonly<{ + showTooltip?: boolean; +}>) { const { t } = useTranslation('pci-region-selector'); + const is1AZ = useIs1AZ(); const context = useContext(ShellContext); const { ovhSubsidiary } = context.environment.getUser(); - const getDocumentUrl = (linkType: string) => - URL_INFO[linkType as keyof typeof URL_INFO][ovhSubsidiary] || - URL_INFO[linkType as keyof typeof URL_INFO].DEFAULT; - return ( - - - event.stopPropagation()} + > + + {t(`pci_project_flavors_zone_${is1AZ ? '1AZ' : 'global_region'}`)} + + {showTooltip && ( + event.stopPropagation()} - > + /> + )} + + ); + + if (showTooltip) { + return ( + + {chip} + - {t('pci_project_flavors_zone_global_region')} + {is1AZ && !has3AZ + ? t('pci_project_flavors_zone_1AZ_with_3AZ_tooltip') + : t( + `pci_project_flavors_zone_${ + is1AZ ? '1AZ' : 'globalregions' + }_tooltip`, + )} - - - - - - {t('pci_project_flavors_zone_globalregions_tooltip')} - -   - - - - ); + + + ); + } + + return chip; } diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.spec.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.spec.tsx index 773318733739..1cd75a4aadbf 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.spec.tsx +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.spec.tsx @@ -1,13 +1,50 @@ import { render, screen } from '@testing-library/react'; -import { describe, it } from 'vitest'; +import { describe, it, vi } from 'vitest'; import { RegionLocalzoneChip } from './RegionLocalzoneChip.component'; import { wrapper } from '@/wrapperRenders'; +import { URL_INFO } from '@/components/region-selector/constants'; +import { useIs1AZ } from '@/hooks/useIs1AZ/useIs1AZ'; + +vi.mock('@/hooks/useIs1AZ/useIs1AZ'); describe('RegionLocalzoneChip', () => { - it('renders chip with correct text', () => { - render(, { wrapper }); + it.each([undefined, false, true])( + 'should render chip with showTooltip %s with correct texts and links', + (showTooltip: boolean | undefined) => { + vi.mocked(useIs1AZ).mockReturnValue(false); + + render(, { wrapper }); + + expect( + screen.getByText('pci_project_flavors_zone_localzone'), + ).toBeInTheDocument(); + + if (showTooltip ?? true) { + expect( + screen.getByText('pci_project_flavors_zone_localzone_tooltip'), + ).toBeInTheDocument(); + + const link = screen.getByText('pci_project_flavors_zone_tooltip_link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', URL_INFO.LOCAL_ZONE.DEFAULT); + } else { + expect( + screen.queryByText('pci_project_flavors_zone_localzone_tooltip'), + ).not.toBeInTheDocument(); + expect( + screen.queryByText('pci_project_flavors_zone_tooltip_link'), + ).not.toBeInTheDocument(); + } + }, + ); + + it('should render chip with 3az link when is1AZ is enabled', () => { + vi.mocked(useIs1AZ).mockReturnValue(true); + + render(, { wrapper }); + expect( - screen.getByText('pci_project_flavors_zone_localzone'), + screen.getByText('pci_project_flavors_zone_localzone_1AZ_tooltip'), ).toBeInTheDocument(); }); }); diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.tsx index f3a61fe38a9f..61268a11c373 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.tsx +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionLocalzoneChip.component.tsx @@ -19,54 +19,66 @@ import { import { OdsHTMLAnchorElementTarget } from '@ovhcloud/ods-common-core'; import { useTranslation } from 'react-i18next'; import { URL_INFO } from './constants'; +import { useIs1AZ } from '../../hooks/useIs1AZ/useIs1AZ'; -export function RegionLocalzoneChip() { +export function RegionLocalzoneChip({ + showTooltip = true, +}: Readonly<{ + showTooltip?: boolean; +}>) { const { t } = useTranslation('pci-region-selector'); + const is1AZ = useIs1AZ(); const context = useContext(ShellContext); const { ovhSubsidiary } = context.environment.getUser(); const getDocumentUrl = (linkType: string) => URL_INFO[linkType as keyof typeof URL_INFO][ovhSubsidiary] || URL_INFO[linkType as keyof typeof URL_INFO].DEFAULT; - return ( - - - event.stopPropagation()} - > + const chip = ( + event.stopPropagation()} + > + + {t('pci_project_flavors_zone_localzone')} + + {showTooltip && ( + + )} + + ); + + if (showTooltip) + return ( + + {chip} + - {t('pci_project_flavors_zone_localzone')} + {t( + `pci_project_flavors_zone_localzone${ + is1AZ ? '_1AZ' : '' + }_tooltip`, + )} - - - - - - {t('pci_project_flavors_zone_localzone_tooltip')} - -   - - - - ); + + + ); + + return chip; } diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionTile.spec.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionTile.spec.tsx new file mode 100644 index 000000000000..34fe5cc177c5 --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionTile.spec.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { describe, it, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { ODS_TEXT_SIZE } from '@ovhcloud/ods-components'; +import { wrapper } from '@/wrapperRenders'; +import { TLocalisation } from '@/components/region-selector/useRegions'; +import { + RegionTile, + RegionTileProps, +} from '@/components/region-selector/RegionTile'; + +vi.mock('./RegionLocalzoneChip.component', () => ({ + RegionLocalzoneChip: () => `chip-localzone`, +})); +vi.mock('./RegionGlobalzoneChip.component', () => ({ + RegionGlobalzoneChip: () => `chip-region`, +})); +vi.mock('./Region3AZChip.component', () => ({ + Region3AZChip: () => `chip-region-3-az`, +})); + +const POSSIBLE_REGIONS: Pick< + RegionTileProps['region'], + 'isMacro' | 'macroLabel' | 'microLabel' | 'type' +>[] = [ + { isMacro: true, macroLabel: 'MacroRegion', microLabel: '', type: 'region' }, + { isMacro: false, macroLabel: '', microLabel: 'MicroRegion', type: 'region' }, + { isMacro: true, macroLabel: 'LocalZone', microLabel: '', type: 'localzone' }, + { + isMacro: true, + macroLabel: '3AZRegion', + microLabel: '', + type: 'region-3-az', + }, +]; + +const POSSIBLE_SELECTED: RegionTileProps['isSelected'][] = [true, false]; + +const POSSIBLE_COMPACT: RegionTileProps['isCompact'][] = [ + true, + false, + undefined, +]; + +describe('RegionTile', () => { + POSSIBLE_REGIONS.forEach((region) => { + POSSIBLE_SELECTED.forEach((isSelected) => { + POSSIBLE_COMPACT.forEach((isCompact) => { + it(`should render region ${region.macroLabel || + region.microLabel} with isSelected ${isSelected}, isCompact ${isCompact}`, () => { + render( + , + { wrapper }, + ); + const label = screen.getByText( + region.isMacro ? region.macroLabel : region.microLabel, + ); + expect(label).toBeInTheDocument(); + expect(label).toHaveAttribute( + 'size', + isSelected ? ODS_TEXT_SIZE._500 : ODS_TEXT_SIZE._400, + ); + + const chip = screen.queryByText(`chip-${region.type}`); + if (isCompact) { + expect(chip).not.toBeInTheDocument(); + } else { + expect(chip).toBeInTheDocument(); + } + }); + }); + }); + }); +}); diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionTile.tsx b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionTile.tsx index bd59cfe37f38..ee69be51ee92 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionTile.tsx +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/RegionTile.tsx @@ -5,6 +5,7 @@ import { OsdsText } from '@ovhcloud/ods-components/react'; import { TLocalisation } from './useRegions'; import { RegionLocalzoneChip } from './RegionLocalzoneChip.component'; import { RegionGlobalzoneChip } from './RegionGlobalzoneChip.component'; +import { Region3AZChip } from './Region3AZChip.component'; export interface RegionTileProps { region: TLocalisation; @@ -12,6 +13,19 @@ export interface RegionTileProps { isCompact?: boolean; } +export const RegionChip = ({ region }: Readonly<{ region: TLocalisation }>) => { + switch (region.type) { + case 'localzone': + return ; + case 'region': + return ; + case 'region-3-az': + return ; + default: + return null; + } +}; + export const RegionTile = ({ region, isSelected, @@ -31,11 +45,7 @@ export const RegionTile = ({ <>
- {region?.isLocalZone ? ( - - ) : ( - - )} +
)} diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/constants.ts b/packages/manager/modules/manager-pci-common/src/components/region-selector/constants.ts index ea2f009f2af7..a2e61346398e 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/constants.ts +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/constants.ts @@ -49,7 +49,54 @@ export const GLOBAL_REGIONS_INFO_URL: Record = { WE: 'https://ovhcloud.com/us-en/public-cloud/compute/', }; +export const EXPANSIONS_REGIONS_AZ = { + DEFAULT: + 'https://www.ovhcloud.com/en/about-us/global-infrastructure/expansion-regions-az/', + ASIA: + 'https://www.ovhcloud.com/asia/about-us/global-infrastructure/expansion-regions-az/', + AU: + ' https://www.ovhcloud.com/en-au/about-us/global-infrastructure/expansion-regions-az/', + CA: + 'https://www.ovhcloud.com/en-ca/about-us/global-infrastructure/expansion-regions-az/', + GB: + 'https://www.ovhcloud.com/en-gb/about-us/global-infrastructure/expansion-regions-az/', + IE: + 'https://www.ovhcloud.com/en-ie/about-us/global-infrastructure/expansion-regions-az/', + IN: + 'https://www.ovhcloud.com/en-in/about-us/global-infrastructure/expansion-regions-az/', + SG: + 'https://www.ovhcloud.com/en-sg/about-us/global-infrastructure/expansion-regions-az/', + DE: + 'https://www.ovhcloud.com/de/about-us/global-infrastructure/expansion-regions-az/', + ES: + 'https://www.ovhcloud.com/es-es/about-us/global-infrastructure/expansion-regions-az/', + FR: + 'https://www.ovhcloud.com/fr/about-us/global-infrastructure/expansion-regions-az/', + IT: + 'https://www.ovhcloud.com/it/about-us/global-infrastructure/expansion-regions-az/', + MA: + 'https://www.ovhcloud.com/fr-ma/about-us/global-infrastructure/expansion-regions-az/', + SN: + 'https://www.ovhcloud.com/fr-sn/about-us/global-infrastructure/expansion-regions-az/', + TN: + 'https://www.ovhcloud.com/fr-tn/about-us/global-infrastructure/expansion-regions-az/', + NL: + 'https://www.ovhcloud.com/nl/about-us/global-infrastructure/expansion-regions-az/', + PL: + 'https://www.ovhcloud.com/pl/about-us/global-infrastructure/expansion-regions-az/', + PT: + 'https://www.ovhcloud.com/pt/about-us/global-infrastructure/expansion-regions-az/', + QC: + 'https://www.ovhcloud.com/fr-ca/about-us/global-infrastructure/expansion-regions-az/', + US: + 'https://www.ovhcloud.com/en/about-us/global-infrastructure/expansion-regions-az/', + WS: + 'https://www.ovhcloud.com/es/about-us/global-infrastructure/expansion-regions-az/', +}; + export const URL_INFO = { GLOBAL_REGIONS: GLOBAL_REGIONS_INFO_URL, LOCAL_ZONE: LOCAL_ZONE_INFO_URL, + REGION_3AZ: EXPANSIONS_REGIONS_AZ, + '1AZ_REGIONS': EXPANSIONS_REGIONS_AZ, }; diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/index.ts b/packages/manager/modules/manager-pci-common/src/components/region-selector/index.ts index 6ecc4ea9184d..f282cf44f4f8 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/index.ts +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/index.ts @@ -3,5 +3,6 @@ export * from './RegionSummary.component'; export * from './RegionSelector.component'; export * from './RegionGlobalzoneChip.component'; export * from './RegionLocalzoneChip.component'; +export * from './Region3AZChip.component'; export * from './RegionTile'; export * from './useRegions'; diff --git a/packages/manager/modules/manager-pci-common/src/components/region-selector/style.scss b/packages/manager/modules/manager-pci-common/src/components/region-selector/style.scss index 65916addb92a..2210729455c6 100644 --- a/packages/manager/modules/manager-pci-common/src/components/region-selector/style.scss +++ b/packages/manager/modules/manager-pci-common/src/components/region-selector/style.scss @@ -3,3 +3,39 @@ .border-ods-primary-200 { border-color: var(--ods-color-primary-200); } + +.chip-3AZ { + background-color: var(--ods-color-primary-700); + + & osds-text { + color: var(--ods-color-primary-000); + } + + & osds-icon { + background-color: var(--ods-color-primary-000); + } +} + +.chip-1AZ { + background-color: var(--ods-color-primary-200); + + & osds-text { + color: var(--ods-color-primary-700); + } + + & osds-icon { + background-color: var(--ods-color-primary-700); + } +} + +.chip-LZ { + background-color: var(--ods-color-primary-100); + + & osds-text { + color: var(--ods-color-primary-700); + } + + & osds-icon { + background-color: var(--ods-color-primary-700); + } +} diff --git a/packages/manager/modules/manager-pci-common/src/contexts/PCICommonContext/PCICommonContext.ts b/packages/manager/modules/manager-pci-common/src/contexts/PCICommonContext/PCICommonContext.ts new file mode 100644 index 000000000000..9f511ca90307 --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/contexts/PCICommonContext/PCICommonContext.ts @@ -0,0 +1,20 @@ +import { createContext } from 'react'; + +export interface InternalMeta { + has3AZ?: boolean; +} + +/** + * This type is only used to add completion with LSP + */ +export type PCICommonMetaType = + | InternalMeta + | Record + | undefined; + +/** + * Use {usePCICommonContextFactory} for general usage. + * + * Use this to override previously set properties + */ +export const PCICommonContext = createContext(undefined); diff --git a/packages/manager/modules/manager-pci-common/src/hooks/index.ts b/packages/manager/modules/manager-pci-common/src/hooks/index.ts index eba3a1018e40..9eb940da0aec 100644 --- a/packages/manager/modules/manager-pci-common/src/hooks/index.ts +++ b/packages/manager/modules/manager-pci-common/src/hooks/index.ts @@ -1,3 +1,4 @@ export { useBytes } from './bytes/useBytes'; export { usePciUrl } from './url/usePciUrl'; export { usePricing } from './usePricing'; +export { usePCICommonContextFactory } from './usePCICommonContextFactory/usePCICommonContextFactory'; diff --git a/packages/manager/modules/manager-pci-common/src/hooks/useHas3AZ/useHas3AZ.spec.tsx b/packages/manager/modules/manager-pci-common/src/hooks/useHas3AZ/useHas3AZ.spec.tsx new file mode 100644 index 000000000000..e1831d4e4b70 --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/hooks/useHas3AZ/useHas3AZ.spec.tsx @@ -0,0 +1,32 @@ +import { renderHook } from '@testing-library/react'; +import { useHas3AZ } from './useHas3AZ'; +import { PCICommonContext } from '../../contexts/PCICommonContext/PCICommonContext'; +import { usePCICommonContextFactory } from '../usePCICommonContextFactory/usePCICommonContextFactory'; + +describe('useHas3AZ', () => { + it.each([ + [false, undefined], + [false, {}], + [false, { has3AZ: false }], + [false, { has3AZ: 'whatever' }], + [false, { has3AZ: null }], + [true, { has3AZ: true }], + ])( + 'should return %s when meta is %o', + (expected: boolean, meta: { has3AZ: boolean | string } | undefined) => { + const { result } = renderHook(() => useHas3AZ(), { + wrapper: ({ children }) => { + const pciCommonContext = usePCICommonContextFactory(meta); + + return ( + + {children} + + ); + }, + }); + + expect(result.current).toBe(expected); + }, + ); +}); diff --git a/packages/manager/modules/manager-pci-common/src/hooks/useHas3AZ/useHas3AZ.ts b/packages/manager/modules/manager-pci-common/src/hooks/useHas3AZ/useHas3AZ.ts new file mode 100644 index 000000000000..76deafa37225 --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/hooks/useHas3AZ/useHas3AZ.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { PCICommonContext } from '../../contexts/PCICommonContext/PCICommonContext'; + +export const useHas3AZ = (): boolean => { + const meta = useContext(PCICommonContext); + + return meta && 'has3AZ' in meta && typeof meta.has3AZ === 'boolean' + ? meta.has3AZ + : false; +}; diff --git a/packages/manager/modules/manager-pci-common/src/hooks/useIs1AZ/useIs1AZ.spec.ts b/packages/manager/modules/manager-pci-common/src/hooks/useIs1AZ/useIs1AZ.spec.ts new file mode 100644 index 000000000000..d12ecfe79bb8 --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/hooks/useIs1AZ/useIs1AZ.spec.ts @@ -0,0 +1,56 @@ +import { + useFeatureAvailability, + UseFeatureAvailabilityResult, +} from '@ovh-ux/manager-react-components'; +import { vi } from 'vitest'; +import { renderHook } from '@testing-library/react'; +import { FEATURE_REGION_1AZ, useIs1AZ } from './useIs1AZ'; + +vi.mock('@ovh-ux/manager-react-components'); + +describe('useIs1AZ', () => { + it('should return false if data is undefined', () => { + vi.mocked(useFeatureAvailability).mockImplementationOnce( + () => + ({ + data: undefined, + isLoading: true, + } as UseFeatureAvailabilityResult), + ); + + const { result } = renderHook(() => useIs1AZ()); + expect(result.current).toBe(false); + }); + + it('should return false if feature is set to false', () => { + vi.mocked(useFeatureAvailability).mockImplementationOnce( + (features) => + ({ + data: { + ...Object.fromEntries(features.map((feature) => [feature, false])), + [FEATURE_REGION_1AZ]: false, + }, + isLoading: false, + } as UseFeatureAvailabilityResult), + ); + + const { result } = renderHook(() => useIs1AZ()); + expect(result.current).toBe(false); + }); + + it('should return true if feature is set to true', () => { + vi.mocked(useFeatureAvailability).mockImplementationOnce( + (features) => + ({ + data: { + ...Object.fromEntries(features.map((feature) => [feature, false])), + [FEATURE_REGION_1AZ]: true, + }, + isLoading: false, + } as UseFeatureAvailabilityResult), + ); + + const { result } = renderHook(() => useIs1AZ()); + expect(result.current).toBe(true); + }); +}); diff --git a/packages/manager/modules/manager-pci-common/src/hooks/useIs1AZ/useIs1AZ.ts b/packages/manager/modules/manager-pci-common/src/hooks/useIs1AZ/useIs1AZ.ts new file mode 100644 index 000000000000..c05ce47f949c --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/hooks/useIs1AZ/useIs1AZ.ts @@ -0,0 +1,9 @@ +import { useFeatureAvailability } from '@ovh-ux/manager-react-components'; + +export const FEATURE_REGION_1AZ = 'public-cloud:region-1AZ'; + +export const useIs1AZ = () => { + const { data } = useFeatureAvailability([FEATURE_REGION_1AZ]); + + return data?.[FEATURE_REGION_1AZ] || false; +}; diff --git a/packages/manager/modules/manager-pci-common/src/hooks/usePCICommonContextFactory/usePCICommonContextFactory.spec.tsx b/packages/manager/modules/manager-pci-common/src/hooks/usePCICommonContextFactory/usePCICommonContextFactory.spec.tsx new file mode 100644 index 000000000000..92be56629f55 --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/hooks/usePCICommonContextFactory/usePCICommonContextFactory.spec.tsx @@ -0,0 +1,116 @@ +import { renderHook } from '@testing-library/react'; +import { PropsWithChildren, useContext } from 'react'; +import { PCICommonContext } from '@/contexts/PCICommonContext/PCICommonContext'; +import { usePCICommonContextFactory } from './usePCICommonContextFactory'; + +const myVarChildren = { + myVar: 'children', +}; + +const TestChildrenMerge = ({ children }: PropsWithChildren) => { + const pciCommonContext = usePCICommonContextFactory(myVarChildren); + + return ( + + {children} + + ); +}; + +const TestChildrenOverride = ({ children }: PropsWithChildren) => ( + + {children} + +); + +const ParentWithoutProvider = ({ children }: PropsWithChildren) => ( + {children} +); + +const myVarParent = { + myVar: 'parent', +}; + +const ParentWithMyVar = ({ children }: PropsWithChildren) => { + const pciCommonContext = usePCICommonContextFactory(myVarParent); + + return ( + + {children} + + ); +}; + +const myVar2Parent = { + myVar2: 'parent', +}; + +const ParentWithMyVar2 = ({ children }: PropsWithChildren) => { + const pciCommonContext = usePCICommonContextFactory(myVar2Parent); + + return ( + + {children} + + ); +}; + +const ParentWithEmptyFactory = ({ children }: PropsWithChildren) => { + const pciCommonContext = usePCICommonContextFactory(); + + return ( + + {children} + + ); +}; + +describe('usePCICommonContextFactory', () => { + it('should set value', () => { + const { result } = renderHook(() => useContext(PCICommonContext), { + wrapper: ParentWithoutProvider, + }); + + expect(result.current).toEqual({ myVar: 'children' }); + }); + + it('should override previously set value', () => { + const { result } = renderHook(() => useContext(PCICommonContext), { + wrapper: ParentWithMyVar, + }); + + expect(result.current).toEqual({ myVar: 'children' }); + }); + + it('should merge with old value', () => { + const { result } = renderHook(() => useContext(PCICommonContext), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current).toEqual({ myVar: 'children', myVar2: 'parent' }); + }); + + it('should override old value', () => { + const { result } = renderHook(() => useContext(PCICommonContext), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current).toEqual({ myVar: 'children' }); + }); + + it('should work with no values', () => { + const { result } = renderHook(() => useContext(PCICommonContext), { + wrapper: ParentWithEmptyFactory, + }); + + expect(result.current).toEqual({}); + }); +}); diff --git a/packages/manager/modules/manager-pci-common/src/hooks/usePCICommonContextFactory/usePCICommonContextFactory.ts b/packages/manager/modules/manager-pci-common/src/hooks/usePCICommonContextFactory/usePCICommonContextFactory.ts new file mode 100644 index 000000000000..4c913f94bc3c --- /dev/null +++ b/packages/manager/modules/manager-pci-common/src/hooks/usePCICommonContextFactory/usePCICommonContextFactory.ts @@ -0,0 +1,17 @@ +import { useContext, useMemo } from 'react'; +import { + PCICommonContext, + PCICommonMetaType, +} from '../../contexts/PCICommonContext/PCICommonContext'; + +/** + * Use this to merge meta properties with previously set properties (merge is only on level 1) + * + * To override all properties use the provider directly + * @param meta Be sure to create it outside your component or memoize it to avoid performance issues + */ +export function usePCICommonContextFactory(meta?: PCICommonMetaType) { + const base = useContext(PCICommonContext); + + return useMemo(() => ({ ...(base || {}), ...(meta || {}) }), [base, meta]); +} diff --git a/packages/manager/modules/manager-pci-common/src/index.ts b/packages/manager/modules/manager-pci-common/src/index.ts index d8d35f93be1e..1f79a11b3ed5 100644 --- a/packages/manager/modules/manager-pci-common/src/index.ts +++ b/packages/manager/modules/manager-pci-common/src/index.ts @@ -8,6 +8,7 @@ export * from './components/modal'; export * from './components/flavor-selector'; export * from './hooks'; export * from './constants'; +export { PCICommonContext } from './contexts/PCICommonContext/PCICommonContext'; export * from './components/quantity-selector'; export * from './components/Pricing'; export * from './components/shape-input/ShapeInput.component'; diff --git a/packages/manager/modules/manager-pci-common/src/translations/region-selector/Messages_fr_FR.json b/packages/manager/modules/manager-pci-common/src/translations/region-selector/Messages_fr_FR.json index 4a6023e0513a..a60a0d19e720 100644 --- a/packages/manager/modules/manager-pci-common/src/translations/region-selector/Messages_fr_FR.json +++ b/packages/manager/modules/manager-pci-common/src/translations/region-selector/Messages_fr_FR.json @@ -7,7 +7,13 @@ "pci_project_flavors_zone_compatible": "Compatible avec", "pci_project_flavors_zone_localzone": "Local Zones", "pci_project_flavors_zone_global_region": "Régions", + "pci_project_flavors_zone_1AZ": "1-AZ", + "pci_project_flavors_zone_3AZ": "3-AZ", "pci_project_flavors_zone_localzone_tooltip": "Les Local Zones sont un nouveau type de localisation, qui prennent en charge une partie de notre portefeuille de produits Public Cloud. Nous allons progressivement augmenter le nombre total de Local Zones dans le monde au cours des prochaines années.", + "pci_project_flavors_zone_localzone_1AZ_tooltip": "Une Local Zone correspond à un mode de déploiement des produits Public Cloud d’OVHcloud dans des datacenters situés au plus près des utilisateurs finaux.", "pci_project_flavors_zone_globalregions_tooltip": "Les Régions sont supportées par un ou plusieurs datacenters gérés par OVHCloud. Chaque région fournit une ou plusieurs Availability Zone avec le portefeuille complet de services OVHCloud.", + "pci_project_flavors_zone_1AZ_with_3AZ_tooltip": "Avec l'arrivée prochaine des régions 3-AZ, le type de déploiement 'Régions' change de nom ! Une région 1-AZ (zone de disponibilité) regroupe un ou plusieurs datacenters localisés sur un même site.", + "pci_project_flavors_zone_1AZ_tooltip": "Une région 1-AZ (zone de disponibilité) regroupe un ou plusieurs datacenters localisés sur un même site.", + "pci_project_flavors_zone_3AZ_tooltip": "Une région 3-AZ comprend trois zones de disponibilités situées dans une même aire métropolitaine, séparées par quelques kilomètres. Elle répond aux exigences les plus élevées en matière de résilience tout en garantissant une latence extrêmement faible entre les AZ.", "pci_project_flavors_zone_tooltip_link": "En savoir plus" }