Skip to content

Commit 8588d14

Browse files
committed
feat(pci-block-storage): block retyping for classic 3az volumes
ref: #TAPC-4567 Signed-off-by: Adrien Turmo <[email protected]>
1 parent 95dcb85 commit 8588d14

File tree

12 files changed

+382
-209
lines changed

12 files changed

+382
-209
lines changed

packages/manager/apps/pci-block-storage/public/translations/retype/Messages_fr_FR.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"pci_projects_project_storages_blocks_retype_cant_retype": "Nous sommes désolé mais vous ne pouvez pas changer votre type d'offre pour le moment",
2+
"pci_projects_project_storages_blocks_retype_cant_retype": "Les fonctionnalités de modification du type et de chiffrement ne sont pas disponibiles pour les volumes Classic 3AZ",
33
"pci_projects_project_storages_blocks_retype_title": "Modifier votre type d'offre",
44
"pci_projects_project_storages_blocks_retype_change_type_title": "Sélectionner le type",
55
"pci_projects_project_storages_blocks_retype_change_encryption_title": "Sélectionner le chiffrement",

packages/manager/apps/pci-block-storage/src/api/hooks/useCatalogWithPreselection.spec.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import {
77
TVolumeRetypeModel,
88
useCatalogWithPreselection,
99
} from '@/api/hooks/useCatalogWithPreselection';
10-
import { mapRetypingVolumeCatalog } from '@/api/select/catalog';
11-
import { EncryptionType, getEncryption } from '@/api/select/volume';
10+
import { is3az, mapRetypingVolumeCatalog } from '@/api/select/catalog';
11+
import {
12+
EncryptionType,
13+
getEncryption,
14+
isClassicMultiAttach,
15+
} from '@/api/select/volume';
1216
import { TRegion } from '@/api/data/regions';
1317

1418
vi.mock('@tanstack/react-query', () => ({
@@ -21,11 +25,13 @@ vi.mock('@/api/select/volume', async (importActual) => {
2125
return {
2226
...actual,
2327
getEncryption: vi.fn(),
28+
isClassicMultiAttach: vi.fn(),
2429
};
2530
});
2631

2732
vi.mock('@/api/select/catalog', () => ({
2833
mapRetypingVolumeCatalog: vi.fn(),
34+
is3az: vi.fn(),
2935
}));
3036

3137
vi.mock('@/api/hooks/useVolume', () => ({
@@ -134,6 +140,8 @@ describe('useCatalogWithPreselection', () => {
134140
{ data: volumeMock, isPending: false },
135141
{ data: mockCatalog, isPending: false },
136142
]);
143+
vi.mocked(is3az).mockReturnValue(true);
144+
vi.mocked(isClassicMultiAttach).mockReturnValue(true);
137145

138146
const { result } = renderHook(() =>
139147
useCatalogWithPreselection(PROJECT_ID, VOLUME_ID),

packages/manager/apps/pci-block-storage/src/api/hooks/useCatalogWithPreselection.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import { NAMESPACES } from '@ovh-ux/manager-common-translations';
66
import { getVolumeCatalogQuery } from '@/api/hooks/useCatalog';
77
import { getVolumeQuery } from '@/api/hooks/useVolume';
88
import {
9+
is3az,
910
mapRetypingVolumeCatalog,
1011
TModelAttach,
1112
TModelAvailabilityZones,
1213
TModelName,
1314
TModelPreselection,
1415
TModelPrice,
1516
} from '@/api/select/catalog';
16-
import { getEncryption } from '@/api/select/volume';
17+
import { getEncryption, isClassicMultiAttach } from '@/api/select/volume';
1718

1819
export type TVolumeRetypeModel = TModelPrice &
1920
TModelAvailabilityZones &
@@ -42,13 +43,9 @@ export const useCatalogWithPreselection = (
4243
const [data, preselectedEncryptionType] = useMemo(() => {
4344
if (!catalogData || !volumeData) return [null, null];
4445

45-
const regionType = catalogData?.regions.find(
46-
(region) => region.name === volumeData?.region,
47-
)?.type;
48-
4946
if (
50-
regionType === 'region-3-az' &&
51-
volumeData.type === 'classic-multiattach'
47+
is3az(catalogData.regions, volumeData.region) &&
48+
isClassicMultiAttach(volumeData)
5249
) {
5350
return [[], null];
5451
}

packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.spec.tsx

Lines changed: 83 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,31 @@ import { act, renderHook, waitFor } from '@testing-library/react';
22
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
33
import { describe, it, vi } from 'vitest';
44
import {
5+
useAllVolumes,
56
useAttachVolume,
67
useDeleteVolume,
78
useDetachVolume,
89
useVolume,
910
useVolumes,
1011
} from '@/api/hooks/useVolume';
1112
import {
12-
getVolume,
13-
getAllVolumes,
14-
deleteVolume,
1513
attachVolume,
14+
deleteVolume,
1615
detachVolume,
16+
getAllVolumes,
17+
getVolume,
1718
TAPIVolume,
1819
} from '@/api/data/volume';
20+
import {
21+
mapVolumeAttach,
22+
mapVolumeEncryption,
23+
mapVolumePricing,
24+
mapVolumeRegion,
25+
mapVolumeStatus,
26+
mapVolumeType,
27+
paginateResults,
28+
sortResults,
29+
} from '@/api/select/volume';
1930

2031
vi.mock('@/api/data/volume', async (importOriginal) => {
2132
const actual: any = await importOriginal();
@@ -29,6 +40,23 @@ vi.mock('@/api/data/volume', async (importOriginal) => {
2940
};
3041
});
3142

43+
vi.mock('@/api/select/volume', async (importOriginal) => {
44+
const actual: any = await importOriginal();
45+
return {
46+
...actual,
47+
mapVolumeAttach: vi.fn().mockReturnValue(vi.fn()),
48+
mapVolumeEncryption: vi.fn().mockReturnValue(vi.fn()),
49+
mapVolumePricing: vi.fn().mockReturnValue(vi.fn()),
50+
mapVolumeRegion: vi.fn().mockReturnValue(vi.fn()),
51+
mapVolumeStatus: vi.fn().mockReturnValue(vi.fn()),
52+
mapVolumeToAdd: vi.fn().mockReturnValue(vi.fn()),
53+
mapVolumeToEdit: vi.fn().mockReturnValue(vi.fn()),
54+
mapVolumeType: vi.fn().mockReturnValue(vi.fn()),
55+
paginateResults: vi.fn(),
56+
sortResults: vi.fn(),
57+
};
58+
});
59+
3260
vi.mock('@/api/data/catalog');
3361

3462
const queryClient = new QueryClient();
@@ -49,21 +77,8 @@ const onSuccess = vi.fn();
4977
const onError = vi.fn();
5078

5179
describe('useVolume', () => {
52-
it('returns volume data when volumeId is provided', async () => {
53-
const volumeMock = {
54-
id: '1',
55-
name: 'Volume 1',
56-
attachedTo: [],
57-
creationDate: '',
58-
description: '',
59-
size: 0,
60-
status: '',
61-
region: 'region1',
62-
bootable: false,
63-
planCode: '',
64-
type: 'model1',
65-
availabilityZone: 'any',
66-
} as TAPIVolume;
80+
it('returns mapped volume data when volumeId is provided', async () => {
81+
const volumeMock = { id: '1' } as TAPIVolume;
6782

6883
vi.mocked(getVolume).mockResolvedValue(volumeMock);
6984

@@ -73,7 +88,12 @@ describe('useVolume', () => {
7388

7489
await waitFor(() => {
7590
expect(getVolume).toHaveBeenCalledWith('123', '1');
76-
expect(result.current.data).toEqual(expect.objectContaining(volumeMock));
91+
expect(mapVolumeAttach).toHaveBeenCalled();
92+
expect(mapVolumeEncryption).toHaveBeenCalled();
93+
expect(mapVolumeStatus).toHaveBeenCalled();
94+
expect(mapVolumeRegion).toHaveBeenCalled();
95+
expect(mapVolumePricing).toHaveBeenCalled();
96+
expect(result.current.isPending).toBe(false);
7797
});
7898
});
7999

@@ -86,36 +106,10 @@ describe('useVolume', () => {
86106
});
87107

88108
describe('useVolumes', () => {
89-
it('returns volumes data when projectId is provided', async () => {
109+
it('returns volumes data mapped, paginated and sorted when projectId is provided', async () => {
90110
const volumesMock: TAPIVolume[] = [
91-
{
92-
id: '1',
93-
name: 'Volume 1',
94-
attachedTo: ['inst1'],
95-
creationDate: '',
96-
description: '',
97-
size: 0,
98-
status: 'available',
99-
region: 'region1',
100-
bootable: false,
101-
planCode: '',
102-
type: 'model1',
103-
availabilityZone: 'any',
104-
},
105-
{
106-
id: '2',
107-
name: 'Volume 2',
108-
attachedTo: [],
109-
creationDate: '',
110-
description: '',
111-
size: 0,
112-
status: 'available',
113-
region: 'region2',
114-
bootable: false,
115-
planCode: '',
116-
type: 'model1',
117-
availabilityZone: 'any',
118-
},
111+
{ id: '1' },
112+
{ id: '2' },
119113
] as TAPIVolume[];
120114

121115
vi.mocked(getAllVolumes).mockResolvedValue(volumesMock);
@@ -133,48 +127,14 @@ describe('useVolumes', () => {
133127

134128
await waitFor(() => {
135129
expect(getAllVolumes).toHaveBeenCalledWith('123');
136-
expect(result.current.data).toEqual({
137-
pageCount: 1,
138-
rows: [
139-
expect.objectContaining({
140-
attachedTo: ['inst1'],
141-
bootable: false,
142-
creationDate: '',
143-
description: '',
144-
id: '1',
145-
name: 'Volume 1',
146-
planCode: '',
147-
region: 'region1',
148-
regionName: 'region:manager_components_region_region_micro',
149-
size: 0,
150-
status: 'available',
151-
statusGroup: 'ACTIVE',
152-
type: 'model1',
153-
availabilityZone: 'any',
154-
canAttachInstance: false,
155-
canDetachInstance: true,
156-
}),
157-
expect.objectContaining({
158-
attachedTo: [],
159-
bootable: false,
160-
creationDate: '',
161-
description: '',
162-
id: '2',
163-
name: 'Volume 2',
164-
planCode: '',
165-
region: 'region2',
166-
regionName: 'region:manager_components_region_region_micro',
167-
size: 0,
168-
status: 'available',
169-
statusGroup: 'ACTIVE',
170-
type: 'model1',
171-
availabilityZone: 'any',
172-
canAttachInstance: true,
173-
canDetachInstance: false,
174-
}),
175-
],
176-
totalRows: 2,
177-
});
130+
expect(mapVolumeStatus).toHaveBeenCalled();
131+
expect(mapVolumeRegion).toHaveBeenCalled();
132+
expect(mapVolumeAttach).toHaveBeenCalled();
133+
expect(mapVolumeEncryption).toHaveBeenCalled();
134+
expect(mapVolumeType).toHaveBeenCalled();
135+
expect(paginateResults).toHaveBeenCalled();
136+
expect(sortResults).toHaveBeenCalled();
137+
expect(result.current.isPending).toBe(false);
178138
});
179139
});
180140

@@ -193,6 +153,39 @@ describe('useVolumes', () => {
193153
});
194154
});
195155

156+
describe('useAllVolumes', () => {
157+
it('returns volumes data when projectId is provided', async () => {
158+
const volumesMock: TAPIVolume[] = [
159+
{ id: '1' },
160+
{ id: '2' },
161+
] as TAPIVolume[];
162+
163+
vi.mocked(getAllVolumes).mockResolvedValue(volumesMock);
164+
165+
const { result } = renderHook(() => useAllVolumes('123'), {
166+
wrapper,
167+
});
168+
169+
await waitFor(() => {
170+
expect(getAllVolumes).toHaveBeenCalledWith('123');
171+
expect(getAllVolumes).toHaveBeenCalledWith('123');
172+
expect(mapVolumeStatus).toHaveBeenCalled();
173+
expect(mapVolumeRegion).toHaveBeenCalled();
174+
expect(mapVolumeAttach).toHaveBeenCalled();
175+
expect(mapVolumeEncryption).toHaveBeenCalled();
176+
expect(mapVolumeType).toHaveBeenCalled();
177+
expect(result.current.isPending).toBe(false);
178+
});
179+
});
180+
181+
it('does not fetch data when projectId is not provided', () => {
182+
const { result } = renderHook(() => useAllVolumes(null), { wrapper });
183+
184+
expect(getAllVolumes).not.toHaveBeenCalled();
185+
expect(result.current.isPending).toBe(true);
186+
});
187+
});
188+
196189
describe('useDeleteVolume', () => {
197190
it('deletes volume when volumeId is provided', async () => {
198191
vi.mocked(deleteVolume).mockResolvedValue(null);

packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
mapVolumeStatus,
3636
mapVolumeToAdd,
3737
mapVolumeToEdit,
38+
mapVolumeType,
3839
paginateResults,
3940
sortResults,
4041
TVolumeAttach,
@@ -43,12 +44,14 @@ import {
4344
TVolumeRegion,
4445
TVolumeStatus,
4546
TVolumeToAdd,
47+
TVolumeType,
4648
} from '@/api/select/volume';
4749

4850
export type TVolume = TAPIVolume &
4951
TVolumeAttach &
5052
TVolumeEncryption &
51-
TVolumeStatus & {
53+
TVolumeStatus &
54+
TVolumeType & {
5255
regionName: string;
5356
};
5457

@@ -79,6 +82,7 @@ export const useAllVolumes = (projectId: string | null) => {
7982
mapVolumeRegion(t),
8083
mapVolumeAttach(catalogData),
8184
mapVolumeEncryption(t, catalogData),
85+
mapVolumeType(catalogData),
8286
),
8387
[t, catalogData],
8488
);
@@ -305,7 +309,7 @@ export const convertUcentsToCurrency = (value: number, interval = 1) =>
305309
interface UpdateVolumeProps {
306310
projectId: string;
307311
volumeToEdit: Pick<TVolume, 'name' | 'size' | 'bootable'>;
308-
originalVolume: TVolume;
312+
originalVolume: UseVolumeResult;
309313
onError: (cause: Error) => void;
310314
onSuccess: () => void;
311315
}

packages/manager/apps/pci-block-storage/src/api/select/catalog.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TFunction } from 'i18next';
33
import { TVolumeAddon, TVolumeCatalog } from '@/api/data/catalog';
44
import { TRegion } from '@/api/data/regions';
55
import {
6+
is3az,
67
mapRetypingVolumeCatalog,
78
mapVolumeCatalog,
89
} from '@/api/select/catalog';
@@ -321,3 +322,29 @@ describe('mapRetypingVolumeCatalog', () => {
321322
expect(result[1].isPreselected).toBe(false);
322323
});
323324
});
325+
326+
describe('is3az', () => {
327+
it('should return false if the catalog region mathing the input region have a type different that "region-3-az"', () => {
328+
const matchingRegionName = 'matching region';
329+
const mockRegions = [
330+
{ name: matchingRegionName, type: 'region' },
331+
{ name: 'otherRegion', type: 'region-3-az' },
332+
] as TRegion[];
333+
334+
const result = is3az(mockRegions, matchingRegionName);
335+
336+
expect(result).toBe(false);
337+
});
338+
339+
it('should return true if the catalog region mathing the input region have a type equal to "region-3-az"', () => {
340+
const matchingRegionName = 'matching region';
341+
const mockRegions = [
342+
{ name: matchingRegionName, type: 'region-3-az' },
343+
{ name: 'otherRegion', type: 'region' },
344+
] as TRegion[];
345+
346+
const result = is3az(mockRegions, matchingRegionName);
347+
348+
expect(result).toBe(true);
349+
});
350+
});

0 commit comments

Comments
 (0)