Skip to content

Commit 04b59d6

Browse files
committed
feat(pci-instances): add instance flex bloc
ref: #TAPC-5558 Signed-off-by: Yann Allançon <yann.allancon.ext@corp.ovh.com>
1 parent ce556a6 commit 04b59d6

21 files changed

+672
-57
lines changed

packages/manager/apps/pci-instances/public/translations/creation/Messages_de_DE.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Darf nur Zahlen, Buchstaben, Unterstriche, Bindestriche oder Punkte enthalten.",
33
"pci_instances_creation_advanced_parameters": "Erweiterte Einstellungen",
4+
"pci_instance_creation_instance_flex_title": "Flexible Instanz",
5+
"pci_instance_creation_instance_flex_description": "Die Auswahl der Flex-Option erzwingt einen Storage mit 50 GB Speicherplatz, was schnellere Snapshots und ein Downgrade zu einem späteren Zeitpunkt ermöglicht.",
46
"pci_instances_creation_quantity_title": "Wählen Sie eine Menge",
57
"pci_instances_creation_quantity_label": "Anzahl der zu erstellenden Instanzen",
68
"pci_instance_creation_quantity_rule": "Ihr aktuelles Kontingent erlaubt es Ihnen, gleichzeitig bis zu {{ quota }} Instanz(en) vom Typ {{ type }} für die Region {{ region }} zu erstellen. <Link>Überprüfen Sie Ihr Kontingent.</Link>",

packages/manager/apps/pci-instances/public/translations/creation/Messages_en_GB.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Must only contain numbers, letters, underscores, dashes, or dots.",
33
"pci_instances_creation_advanced_parameters": "Advanced settings",
4+
"pci_instance_creation_instance_flex_title": "Flexible instance",
5+
"pci_instance_creation_instance_flex_description": "The Flex option opts for 50GB storage, providing faster snapshots and the ability to downgrade later on.",
46
"pci_instances_creation_quantity_title": "Choose a quantity",
57
"pci_instances_creation_quantity_label": "Number of instances to create",
68
"pci_instance_creation_quantity_rule": "Your current quota allows you to create up to {{ quota }} instance(s) of type {{ type }} for the {{ region }} region simultaneously. <Link>Check your quota.</Link>",

packages/manager/apps/pci-instances/public/translations/creation/Messages_es_ES.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Solo debe contener números, letras, guiones bajos, guiones o puntos.",
33
"pci_instances_creation_advanced_parameters": "Parámetros avanzados",
4+
"pci_instance_creation_instance_flex_title": "Instancia flexible",
5+
"pci_instance_creation_instance_flex_description": "Las instancias flexibles fuerzan un almacenamiento de 50 GB, ofreciendo snapshots más rápidos y la posibilidad de reducir el tamaño posteriormente.",
46
"pci_instances_creation_quantity_title": "Elegir una cantidad",
57
"pci_instances_creation_quantity_label": "Número de instancias a crear",
68
"pci_instance_creation_quantity_rule": "Su cuota actual le permite crear simultáneamente hasta {{ quota }} instancia(s) del tipo {{ type }} para la región de {{ region }}. <Link>Consulte su cuota.</Link>",

packages/manager/apps/pci-instances/public/translations/creation/Messages_fr_CA.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Doit uniquement contenir des nombres, lettres, underscores, tirets ou points.",
33
"pci_instances_creation_advanced_parameters": "Paramètres avancés",
4+
"pci_instance_creation_instance_flex_title": "Instance flexible",
5+
"pci_instance_creation_instance_flex_description": "Cette option Flex force un stockage de 50 Go, offrant des snapshots plus rapides et la possibilité de downgrader ultérieurement.",
46
"pci_instances_creation_quantity_title": "Choisir une quantité",
57
"pci_instances_creation_quantity_label": "Nombre d'instances à créer",
68
"pci_instance_creation_quantity_rule": "Votre quota actuel vous permet de créer simultanément jusqu'à {{ quota }} instance(s) de type {{ type }} pour la région de {{ region }}. <Link>Consultez votre quota.</Link>",

packages/manager/apps/pci-instances/public/translations/creation/Messages_fr_FR.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Doit uniquement contenir des nombres, lettres, underscores, tirets ou points.",
33
"pci_instances_creation_advanced_parameters": "Paramètres avancés",
4+
"pci_instance_creation_instance_flex_title": "Instance flexible",
5+
"pci_instance_creation_instance_flex_description": "Cette option Flex force un stockage de 50 Go, offrant des snapshots plus rapides et la possibilité de downgrader ultérieurement.",
46
"pci_instances_creation_quantity_title": "Choisir une quantité",
57
"pci_instances_creation_quantity_label": "Nombre d'instances à créer",
68
"pci_instance_creation_quantity_rule": "Votre quota actuel vous permet de créer simultanément jusqu'à {{ quota }} instance(s) de type {{ type }} pour la région de {{ region }}. <Link>Consultez votre quota.</Link>",

packages/manager/apps/pci-instances/public/translations/creation/Messages_it_IT.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Deve contenere solo numeri, lettere, underscore, trattini o punti.",
33
"pci_instances_creation_advanced_parameters": "Impostazioni avanzate",
4+
"pci_instance_creation_instance_flex_title": "Istanza flessibile",
5+
"pci_instance_creation_instance_flex_description": "L'opzione Flex forza uno storage di 50 GB, offrendo snapshot più rapidi e la possibilità di effettuare un downgrade successivamente.",
46
"pci_instances_creation_quantity_title": "Scegli una quantità",
57
"pci_instances_creation_quantity_label": "Numero di istanze da creare",
68
"pci_instance_creation_quantity_rule": "Il tuo attuale quota ti consente di creare simultaneamente fino a {{ quota }} istanza(e) di tipo {{ type }} per la regione di {{ region }}. <Link>Controlla la tua quota.</Link>",

packages/manager/apps/pci-instances/public/translations/creation/Messages_pl_PL.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Musi zawierać tylko liczby, litery, podkreślenia, myślniki lub kropki.",
33
"pci_instances_creation_advanced_parameters": "Ustawienia zaawansowane",
4+
"pci_instance_creation_instance_flex_title": "Elastyczna instancja",
5+
"pci_instance_creation_instance_flex_description": "Zaznaczenie tej opcji wymusza przestrzeń dyskową 50 GB, oferującą szybsze snapshoty i możliwość późniejszej zmiany na niższą ofertę.",
46
"pci_instances_creation_quantity_title": "Wybierz ilość",
57
"pci_instances_creation_quantity_label": "Liczba instancji do utworzenia",
68
"pci_instance_creation_quantity_rule": "Twój aktualny limit pozwala na jednoczesne utworzenie do {{ quota }} instancji typu {{ type }} w regionie {{ region }}. <Link>Sprawdź swój limit.</Link>",

packages/manager/apps/pci-instances/public/translations/creation/Messages_pt_PT.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"pci_instance_creation_name_rule": "Deve conter apenas números, letras, sublinhados, hífens ou pontos.",
33
"pci_instances_creation_advanced_parameters": "Parâmetros avançados",
4+
"pci_instance_creation_instance_flex_title": "Instância flexível",
5+
"pci_instance_creation_instance_flex_description": "A opção Flex força um armazenamento de 50 GB, oferecendo snapshots mais rápidas e a possibilidade de efetuar um downgrade posteriormente.",
46
"pci_instances_creation_quantity_title": "Escolher uma quantidade",
57
"pci_instances_creation_quantity_label": "Número de instâncias a criar",
68
"pci_instance_creation_quantity_rule": "O seu quota atual permite criar simultaneamente até {{ quota }} instância(s) do tipo {{ type }} para a região de {{ region }}. <Link>Consulte o seu quota.</Link>",
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import {
3+
isFlexFlavorName,
4+
getFlexFlavorName,
5+
getBaseFlavorName,
6+
getFlexRegionalizedFlavorId,
7+
getBaseRegionalizedFlavorId,
8+
hasFlexVariant,
9+
} from '../flavor.service';
10+
import {
11+
TInstancesCatalog,
12+
TRegionalizedFlavor,
13+
} from '@/domain/entities/instancesCatalog';
14+
import { mockedInstancesCatalogEntity } from '@/__mocks__/instance/constants';
15+
16+
vi.mock('@/utils', () => ({
17+
getRegionalizedFlavorId: (flavorName: string, regionName: string) =>
18+
`${flavorName}_${regionName}`,
19+
}));
20+
21+
function createMinimalCatalog(
22+
regionalizedFlavors: Array<{
23+
id: string;
24+
flavorId: string;
25+
regionId: string;
26+
}>,
27+
): TInstancesCatalog {
28+
const byId = new Map<string, TRegionalizedFlavor>(
29+
regionalizedFlavors.map((rf) => [
30+
rf.id,
31+
{
32+
id: rf.id,
33+
flavorId: rf.flavorId,
34+
regionId: rf.regionId,
35+
hasStock: true,
36+
quota: 0,
37+
osTypes: ['linux'],
38+
tags: null,
39+
},
40+
]),
41+
);
42+
return {
43+
...mockedInstancesCatalogEntity,
44+
entities: {
45+
...mockedInstancesCatalogEntity.entities,
46+
regionalizedFlavors: {
47+
byId,
48+
allIds: regionalizedFlavors.map((rf) => rf.id),
49+
},
50+
},
51+
};
52+
}
53+
54+
describe('flavor.service – isFlexFlavorName', () => {
55+
it('returns true when name ends with -flex', () => {
56+
expect(isFlexFlavorName('b2-7-flex')).toBe(true);
57+
expect(isFlexFlavorName('d2-2-flex')).toBe(true);
58+
});
59+
it('returns false when name does not end with -flex', () => {
60+
expect(isFlexFlavorName('b2-7')).toBe(false);
61+
expect(isFlexFlavorName('d2-2')).toBe(false);
62+
expect(isFlexFlavorName('flex-b2')).toBe(false);
63+
});
64+
});
65+
66+
describe('flavor.service – getFlexFlavorName', () => {
67+
it('appends -flex to base flavor name', () => {
68+
expect(getFlexFlavorName('b2-7')).toBe('b2-7-flex');
69+
expect(getFlexFlavorName('d2-2')).toBe('d2-2-flex');
70+
});
71+
});
72+
73+
describe('flavor.service – getBaseFlavorName', () => {
74+
it('strips -flex suffix when present', () => {
75+
expect(getBaseFlavorName('b2-7-flex')).toBe('b2-7');
76+
expect(getBaseFlavorName('d2-2-flex')).toBe('d2-2');
77+
});
78+
it('leaves name unchanged when no -flex suffix', () => {
79+
expect(getBaseFlavorName('b2-7')).toBe('b2-7');
80+
});
81+
});
82+
83+
describe('flavor.service – getFlexRegionalizedFlavorId', () => {
84+
it('returns flex id when flex variant exists in catalog', () => {
85+
const catalog = createMinimalCatalog([
86+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
87+
{ id: 'b2-7-flex_GRA11', flavorId: 'b2-7-flex', regionId: 'GRA11' },
88+
]);
89+
expect(getFlexRegionalizedFlavorId(catalog, 'b2-7_GRA11')).toBe(
90+
'b2-7-flex_GRA11',
91+
);
92+
});
93+
it('returns same id when flavor is already flex', () => {
94+
const catalog = createMinimalCatalog([
95+
{ id: 'b2-7-flex_GRA11', flavorId: 'b2-7-flex', regionId: 'GRA11' },
96+
]);
97+
expect(getFlexRegionalizedFlavorId(catalog, 'b2-7-flex_GRA11')).toBe(
98+
'b2-7-flex_GRA11',
99+
);
100+
});
101+
it('returns null when regionalized flavor is not in catalog', () => {
102+
const catalog = createMinimalCatalog([
103+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
104+
]);
105+
expect(getFlexRegionalizedFlavorId(catalog, 'unknown-id')).toBeNull();
106+
});
107+
it('returns null when flex variant is not in catalog', () => {
108+
const catalog = createMinimalCatalog([
109+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
110+
]);
111+
expect(getFlexRegionalizedFlavorId(catalog, 'b2-7_GRA11')).toBeNull();
112+
});
113+
});
114+
115+
describe('flavor.service – getBaseRegionalizedFlavorId', () => {
116+
it('returns base id when flex variant exists in catalog', () => {
117+
const catalog = createMinimalCatalog([
118+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
119+
{ id: 'b2-7-flex_GRA11', flavorId: 'b2-7-flex', regionId: 'GRA11' },
120+
]);
121+
expect(getBaseRegionalizedFlavorId(catalog, 'b2-7-flex_GRA11')).toBe(
122+
'b2-7_GRA11',
123+
);
124+
});
125+
it('returns same id when flavor is not flex', () => {
126+
const catalog = createMinimalCatalog([
127+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
128+
]);
129+
expect(getBaseRegionalizedFlavorId(catalog, 'b2-7_GRA11')).toBe(
130+
'b2-7_GRA11',
131+
);
132+
});
133+
it('returns null when regionalized flavor is not in catalog', () => {
134+
const catalog = createMinimalCatalog([
135+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
136+
]);
137+
expect(getBaseRegionalizedFlavorId(catalog, 'unknown-id')).toBeNull();
138+
});
139+
it('returns null when base variant is not in catalog', () => {
140+
const catalog = createMinimalCatalog([
141+
{ id: 'b2-7-flex_GRA11', flavorId: 'b2-7-flex', regionId: 'GRA11' },
142+
]);
143+
expect(getBaseRegionalizedFlavorId(catalog, 'b2-7-flex_GRA11')).toBeNull();
144+
});
145+
});
146+
147+
describe('flavor.service – hasFlexVariant', () => {
148+
it('returns true when flex variant exists in catalog', () => {
149+
const catalog = createMinimalCatalog([
150+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
151+
{ id: 'b2-7-flex_GRA11', flavorId: 'b2-7-flex', regionId: 'GRA11' },
152+
]);
153+
expect(hasFlexVariant(catalog, 'b2-7_GRA11')).toBe(true);
154+
});
155+
it('returns true when regionalized flavor is already flex', () => {
156+
const catalog = createMinimalCatalog([
157+
{ id: 'b2-7-flex_GRA11', flavorId: 'b2-7-flex', regionId: 'GRA11' },
158+
]);
159+
expect(hasFlexVariant(catalog, 'b2-7-flex_GRA11')).toBe(true);
160+
});
161+
it('returns false when catalog is undefined', () => {
162+
expect(hasFlexVariant(undefined, 'b2-7_GRA11')).toBe(false);
163+
});
164+
it('returns false when regionalizedFlavorId is null', () => {
165+
const catalog = createMinimalCatalog([
166+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
167+
]);
168+
expect(hasFlexVariant(catalog, null)).toBe(false);
169+
});
170+
it('returns false when flex variant does not exist in catalog', () => {
171+
const catalog = createMinimalCatalog([
172+
{ id: 'b2-7_GRA11', flavorId: 'b2-7', regionId: 'GRA11' },
173+
]);
174+
expect(hasFlexVariant(catalog, 'b2-7_GRA11')).toBe(false);
175+
});
176+
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { TInstancesCatalog } from '../entities/instancesCatalog';
2+
import { getRegionalizedFlavorId } from '@/utils';
3+
4+
const FLEX_FLAVOR_SUFFIX = '-flex';
5+
6+
export const isFlexFlavorName = (flavorName: string): boolean =>
7+
flavorName.endsWith(FLEX_FLAVOR_SUFFIX);
8+
9+
export const getFlexFlavorName = (baseFlavorName: string): string =>
10+
`${baseFlavorName}${FLEX_FLAVOR_SUFFIX}`;
11+
12+
export const getBaseFlavorName = (flavorName: string): string =>
13+
flavorName.replace(new RegExp(`${FLEX_FLAVOR_SUFFIX}$`), '');
14+
15+
export const getFlexRegionalizedFlavorId = (
16+
catalog: TInstancesCatalog,
17+
regionalizedFlavorId: string,
18+
): string | null => {
19+
const regionalizedFlavor = catalog.entities.regionalizedFlavors.byId.get(
20+
regionalizedFlavorId,
21+
);
22+
if (!regionalizedFlavor) return null;
23+
24+
const alreadyFlex = isFlexFlavorName(regionalizedFlavor.flavorId);
25+
if (alreadyFlex) return regionalizedFlavorId;
26+
27+
const flexRegionalizedId = getRegionalizedFlavorId(
28+
getFlexFlavorName(regionalizedFlavor.flavorId),
29+
regionalizedFlavor.regionId,
30+
);
31+
const existsInCatalog = catalog.entities.regionalizedFlavors.byId.has(
32+
flexRegionalizedId,
33+
);
34+
return existsInCatalog ? flexRegionalizedId : null;
35+
};
36+
37+
export const getBaseRegionalizedFlavorId = (
38+
catalog: TInstancesCatalog,
39+
regionalizedFlavorId: string,
40+
): string | null => {
41+
const regionalizedFlavor = catalog.entities.regionalizedFlavors.byId.get(
42+
regionalizedFlavorId,
43+
);
44+
if (!regionalizedFlavor) {
45+
return null;
46+
}
47+
48+
const isFlex = isFlexFlavorName(regionalizedFlavor.flavorId);
49+
if (!isFlex) {
50+
return regionalizedFlavorId;
51+
}
52+
53+
const baseRegionalizedId = getRegionalizedFlavorId(
54+
getBaseFlavorName(regionalizedFlavor.flavorId),
55+
regionalizedFlavor.regionId,
56+
);
57+
const existsInCatalog = catalog.entities.regionalizedFlavors.byId.has(
58+
baseRegionalizedId,
59+
);
60+
return existsInCatalog ? baseRegionalizedId : null;
61+
};
62+
63+
export const hasFlexVariant = (
64+
catalog: TInstancesCatalog | undefined,
65+
regionalizedFlavorId: string | null,
66+
): boolean =>
67+
Boolean(
68+
regionalizedFlavorId &&
69+
catalog &&
70+
getFlexRegionalizedFlavorId(catalog, regionalizedFlavorId),
71+
);

0 commit comments

Comments
 (0)