Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/manager/apps/ips/mocks/dedicated-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,9 @@ export const getDedicatedServerMocks = ({
response: [],
api: 'v6',
},
{
url: '/dedicated/server.json',
response: {},
api: 'v6',
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,11 @@ export const getOrganisationMocks = ({
},
api: 'v6',
},
{
url: '/me.json',
response: {
ovhSubsidiary: 'FR',
},
api: 'v6',
},
];
11 changes: 11 additions & 0 deletions packages/manager/apps/ips/mocks/vrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ export const getVrackMocks = ({
getVrackKo,
isVrackExpired,
}: GetVrackMocksParams): Handler[] => [
{
url: '/vrack/:serviceName/task/:taskId',
response: {},
api: 'v6',
status: 404,
},
{
url: '/vrack/:serviceName/task',
response: [],
api: 'v6',
},
{
url: '/vrack/:serviceName/serviceInfos',
response: isVrackExpired ? expiredService : availableService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"step1EmptyCurrentServiceValue": "Parking des IP",
"step1DestinationServiceLabel": "Vers",
"step1NextHopLabel": "Sur",
"step1VrackMessage": "Veuillez d'abord détacher votre bloc IP de vRack <Link>(ici)</Link> pour pouvoir le connecter ailleurs.",
"step2DescriptionWithNextHop": "Voulez vous vraiment déplacer l'IP <b>{{ip}}</b> vers <b>{{destinationService}}</b> sur <b>{{nextHop}}</b> ?",
"step2DescriptionWithoutNextHop": "Voulez vous vraiment déplacer l'IP <b>{{ip}}</b> vers <b>{{destinationService}}</b> ?",
"moveIpOnGoingTaskMessage": "Cette IP est déjà en cours de déplacement.",
Expand Down
1 change: 1 addition & 0 deletions packages/manager/apps/ips/src/data/api/get/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export * from './productServices';
export * from './ipRipeInformation';
export * from './ipTask';
export * from './moveIp';
export * from './vrackTask';
31 changes: 31 additions & 0 deletions packages/manager/apps/ips/src/data/api/get/vrackTask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { v6, ApiResponse } from '@ovh-ux/manager-core-api';
import { VrackTask } from '@/types';

export type GetVrackTaskParams = {
serviceName: string;
};

export const getVrackTaskQueryKey = (params: GetVrackTaskParams) => [
'vRackTask',
params.serviceName,
];

export const getVrackTaskList = ({
serviceName,
}: GetVrackTaskParams): Promise<ApiResponse<number[]>> =>
v6.get(`/vrack/${serviceName}/task`);

export type GetVrackTaskDetailsParams = {
serviceName: string;
taskId: number;
};

export const getVrackTaskDetailsQueryKey = (
params: GetVrackTaskDetailsParams,
) => ['vRackTaskDetails', params.serviceName, params.taskId];

export const getVrackTaskDetails = ({
serviceName,
taskId,
}: GetVrackTaskDetailsParams): Promise<ApiResponse<VrackTask>> =>
v6.get(`/vrack/${serviceName}/task/${taskId}`);
17 changes: 17 additions & 0 deletions packages/manager/apps/ips/src/data/api/postorput/postMoveIp.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import { ApiResponse, apiClient } from '@ovh-ux/manager-core-api';
import ipaddr from 'ipaddr.js';
import { ipParkingOptionValue, IpTask } from '@/types';
import { getTypeByServiceName } from '@/utils';
import { IpTypeEnum } from '@/data/constants';

export type PostMoveIpParams = {
ip: string;
to: string | typeof ipParkingOptionValue;
serviceName: string;
nexthop?: string;
};

export const postMoveIp = async ({
ip,
to,
nexthop,
serviceName,
}: PostMoveIpParams): Promise<ApiResponse<IpTask>> => {
const isDetachedToParking = to === ipParkingOptionValue;
const isAttachedToSomeVrack =
getTypeByServiceName(serviceName) === IpTypeEnum.VRACK;

if (isDetachedToParking && isAttachedToSomeVrack) {
return apiClient.v6.delete(
`/vrack/${serviceName}/${
ipaddr.IPv6.isIPv6(ip) ? 'ipv6' : 'ip'
}/${encodeURIComponent(ip)}`,
{},
);
}

return apiClient.v6.post(
`/ip/${encodeURIComponent(ip)}/${isDetachedToParking ? 'park' : 'move'}`,
isDetachedToParking
Expand Down
149 changes: 145 additions & 4 deletions packages/manager/apps/ips/src/data/hooks/ip/useMoveIpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,135 @@ import {
getIpTaskDetailsQueryKey,
getIpTaskDetails,
MoveIpAvailableDestinationsResponse,
getVrackTaskList,
getVrackTaskQueryKey,
getVrackTaskDetailsQueryKey,
getVrackTaskDetails,
} from '@/data/api';
import { IpTaskStatus, IpTaskFunction, IpTask } from '@/types';
import { INVALIDATED_REFRESH_PERIOD, TRANSLATION_NAMESPACES } from '@/utils';
import {
IpTaskStatus,
IpTaskFunction,
IpTask,
VrackTask,
VrackTaskStatus,
VrackTaskFunction,
} from '@/types';
import {
getTypeByServiceName,
INVALIDATED_REFRESH_PERIOD,
TRANSLATION_NAMESPACES,
} from '@/utils';
import { IpTypeEnum } from '@/data/constants';

const getMoveIpOngoingTasksQueryKey = (ip: string) => [
'ipMoveOngoingTasks',
ip.includes('/') ? ip : `${ip}/32`,
];

export function useVrackMoveTasks({
ip,
serviceName,
enabled = true,
}: {
ip: string;
serviceName?: string;
enabled?: boolean;
}) {
const queryClient = useQueryClient();
const { t } = useTranslation([
TRANSLATION_NAMESPACES.ips,
TRANSLATION_NAMESPACES.moveIp,
]);
const { clearNotifications, addSuccess } = useNotifications();
const { trackPage } = useOvhTracking();
const { data, isLoading } = useQuery({
queryKey: getVrackTaskQueryKey({ serviceName }),
queryFn: () => getVrackTaskList({ serviceName }),
enabled:
!!serviceName &&
getTypeByServiceName(serviceName) === IpTypeEnum.VRACK &&
enabled,
});

const queries = useQueries({
queries: (data?.data ?? []).map((taskId) => ({
queryKey: getVrackTaskDetailsQueryKey({ serviceName, taskId }),
queryFn: async () => {
try {
return await getVrackTaskDetails({ serviceName, taskId });
} catch (err) {
if ((err as ApiError).status === 404) {
clearNotifications();
addSuccess(
t('moveIpDoneMessage', {
ip,
ns: TRANSLATION_NAMESPACES.moveIp,
}),
);
trackPage({
pageType: PageType.bannerSuccess,
pageName: 'move-ip_success',
});
queryClient.invalidateQueries({
queryKey: getIpDetailsQueryKey({ ip }),
});
return {} as ApiResponse<VrackTask>;
}
throw err;
}
},
refetchInterval: (query: Query<ApiResponse<VrackTask>, ApiError>) => {
if (
!query.state.error &&
[
VrackTaskFunction.addBlockToBridgeDomain,
VrackTaskFunction.removeBlockFromBridgeDomain,
].includes(query.state.data?.data?.function)
) {
if (
[
VrackTaskStatus.init,
VrackTaskStatus.todo,
VrackTaskStatus.doing,
].includes(query.state.data?.data?.status)
) {
return INVALIDATED_REFRESH_PERIOD;
}

if (query.state.data?.data?.status === VrackTaskStatus.done) {
clearNotifications();
addSuccess(
t('moveIpDoneMessage', {
ip,
ns: TRANSLATION_NAMESPACES.moveIp,
}),
);
trackPage({
pageType: PageType.bannerSuccess,
pageName: 'move-ip_success',
});
queryClient.invalidateQueries({
queryKey: getIpDetailsQueryKey({ ip }),
});
}

return undefined;
}

return undefined;
},
})),
});

return {
isVrackTasksLoading: isLoading || queries.some((q) => q.isLoading),
vrackTasksError: queries.find((q) => q.error)?.error as ApiError,
hasOnGoingVrackMoveTasks:
queries.map((q) => q.data).filter((d): d is ApiResponse<VrackTask> => !!d)
.length > 0,
};
}

export function useMoveIpTasks({
ip,
enabled,
Expand Down Expand Up @@ -140,12 +260,24 @@ export function useMoveIpTasks({

export function useMoveIpService({
ip,
serviceName,
onMoveIpSuccess,
}: {
ip: string;
serviceName?: string;
onMoveIpSuccess?: () => void;
}) {
const queryClient = useQueryClient();
const {
hasOnGoingVrackMoveTasks,
isVrackTasksLoading,
vrackTasksError,
} = useVrackMoveTasks({
ip,
serviceName,
enabled:
!!serviceName && getTypeByServiceName(serviceName) === IpTypeEnum.VRACK,
});
const { hasOnGoingMoveIpTask, isTasksLoading, taskError } = useMoveIpTasks({
ip,
});
Expand All @@ -172,7 +304,13 @@ export function useMoveIpService({
};
}
},
enabled: !isTasksLoading && !taskError && !hasOnGoingMoveIpTask,
enabled:
!isTasksLoading &&
!taskError &&
!hasOnGoingMoveIpTask &&
!hasOnGoingVrackMoveTasks &&
!isVrackTasksLoading &&
!vrackTasksError,
});

const isDedicatedCloudService = useCallback(
Expand Down Expand Up @@ -210,7 +348,10 @@ export function useMoveIpService({
mutationFn: apiPostMoveIp,
onSuccess: async () => {
queryClient.invalidateQueries({
queryKey: getMoveIpOngoingTasksQueryKey(ip),
queryKey:
serviceName && getTypeByServiceName(serviceName) === IpTypeEnum.VRACK
? getVrackTaskQueryKey({ serviceName })
: getMoveIpOngoingTasksQueryKey(ip),
});
onMoveIpSuccess?.();
},
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { waitFor, screen, fireEvent } from '@testing-library/react';
import { within, waitFor, screen, fireEvent } from '@testing-library/react';
import { describe } from 'vitest';
import { ODS_ICON_NAME } from '@ovhcloud/ods-components';
import {
Expand Down Expand Up @@ -82,7 +82,10 @@ describe('Move IP modal', () => {
);
const confirmNode = await screen.getByText(confirmText, { exact: false });
await waitFor(
() => expect(confirmNode.parentElement).toMatchSnapshot(),
() =>
expect(
within(confirmNode.parentElement).getByText(service),
).toBeInTheDocument(),
WAIT_FOR_DEFAULT_OPTIONS,
);
});
Expand All @@ -108,7 +111,13 @@ describe('Move IP modal', () => {
'',
);
const confirmNode = await screen.getByText(confirmText, { exact: false });
expect(confirmNode.parentElement.innerHTML).toMatchSnapshot();
await waitFor(
() =>
expect(
within(confirmNode.parentElement).getByText(service),
).toBeInTheDocument(),
WAIT_FOR_DEFAULT_OPTIONS,
);

const confirmButton = await getButtonByLabel({
container,
Expand Down
Loading
Loading