Skip to content

Commit fd16103

Browse files
committed
feat(public-vcf-aas): add edge gateway tab
ref: #MANAGER-18386 Signed-off-by: Paul Dickerson <[email protected]>
1 parent b03ad50 commit fd16103

28 files changed

+489
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"edge_add": "Ajouter une Edge Gateway",
3+
"edge_add_tooltip": "Vous pouvez créer 5 Edge Gateways maximum.",
4+
"edge_connectivity_type": "Type de connectivité",
5+
"edge_ip_block": "Bloc IP",
6+
"edge_edit_config": "Modifier la configuration"
7+
}

packages/manager/apps/hpc-vmware-public-vcf-aas/src/pages/dashboard/datacentre/DatacentreDashboard.page.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import { BreadcrumbItem } from '@/hooks/breadcrumb/useBreadcrumb';
1111
import VcdDashboardLayout, {
1212
DashboardTab,
1313
} from '@/components/dashboard/layout/VcdDashboardLayout.component';
14-
import { COMPUTE_LABEL, STORAGE_LABEL } from './datacentreDashboard.constants';
14+
import {
15+
COMPUTE_LABEL,
16+
EDGE_GATEWAY_LABEL,
17+
STORAGE_LABEL,
18+
} from './datacentreDashboard.constants';
1519
import { subRoutes, urls } from '@/routes/routes.constant';
1620
import { useAutoRefetch } from '@/data/hooks/useAutoRefetch';
1721
import { isUpdatingTargetSpec } from '@/utils/refetchConditions';
@@ -21,6 +25,7 @@ import { VIRTUAL_DATACENTERS_LABEL } from '../organization/organizationDashboard
2125
import { VRACK_LABEL } from '../dashboard.constants';
2226
import { FEATURE_FLAGS } from '@/app.constants';
2327
import MessageSuspendedService from '@/components/message/MessageSuspendedService.component';
28+
import { isEdgeCompatibleVDC } from '@/utils/edgeGatewayCompatibility';
2429

2530
function DatacentreDashboardPage() {
2631
const { id, vdcId } = useParams();
@@ -66,6 +71,13 @@ function DatacentreDashboardPage() {
6671
disabled:
6772
!isVrackFeatureAvailable || !vcdDatacentre?.data?.currentState?.vrack,
6873
},
74+
{
75+
name: 'edge-gateway',
76+
title: EDGE_GATEWAY_LABEL,
77+
to: useResolvedPath(subRoutes.edgeGateway).pathname,
78+
trackingActions: TRACKING_TABS_ACTIONS.edgeGateway,
79+
disabled: isEdgeCompatibleVDC(vcdDatacentre?.data), // TODO: [EDGE] inverse condition when unmocking (testing only)
80+
},
6981
].filter(({ disabled }) => !disabled);
7082

7183
const serviceName = vcdDatacentre?.data?.currentState?.description ?? vdcId;
@@ -95,6 +107,10 @@ function DatacentreDashboardPage() {
95107
id: vdcId,
96108
label: serviceName,
97109
},
110+
{
111+
id: subRoutes.edgeGateway,
112+
label: EDGE_GATEWAY_LABEL,
113+
},
98114
{
99115
id: subRoutes.vrackSegments,
100116
label: VRACK_LABEL,
@@ -123,12 +139,6 @@ function DatacentreDashboardPage() {
123139
'datacentres/vrack-segment:managed_vcd_dashboard_vrack_segment_add_title',
124140
),
125141
},
126-
{
127-
id: subRoutes.edit,
128-
label: t(
129-
'datacentres/vrack-segment:managed_vcd_dashboard_vrack_edit_vlan',
130-
),
131-
},
132142
{
133143
id: subRoutes.deleteSegment,
134144
label: t(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export const COMPUTE_LABEL = 'Compute';
22
export const STORAGE_LABEL = 'Storage';
3+
export const EDGE_GATEWAY_LABEL = 'Edge Gateway';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
useVcdDatacentre,
3+
useVcdEdgeGatewaysMocks,
4+
} from '@ovh-ux/manager-module-vcd-api';
5+
import {
6+
Datagrid,
7+
ErrorBanner,
8+
RedirectionGuard,
9+
} from '@ovh-ux/manager-react-components';
10+
import { OdsText } from '@ovhcloud/ods-components/react';
11+
import { useParams } from 'react-router-dom';
12+
import { Suspense } from 'react';
13+
import { urls } from '@/routes/routes.constant';
14+
import { EDGE_GATEWAY_LABEL } from '../datacentreDashboard.constants';
15+
import Loading from '@/components/loading/Loading.component';
16+
import { useEdgeGatewayListingColumns } from './hooks/useEdgeGatewayListingColumns';
17+
import { EdgeGatewayOrderButton } from './components/EdgeGatewayOrderButton.component';
18+
import { isEdgeCompatibleVDC } from '@/utils/edgeGatewayCompatibility';
19+
20+
export default function EdgeGatewayListingPage() {
21+
const { id, vdcId } = useParams();
22+
const columns = useEdgeGatewayListingColumns();
23+
const vdcQuery = useVcdDatacentre(id, vdcId);
24+
const edgeQuery = useVcdEdgeGatewaysMocks(id, vdcId);
25+
26+
const queryList = [vdcQuery, edgeQuery];
27+
const queries = {
28+
isLoading: queryList.some((q) => q.isLoading),
29+
isError: queryList.some((q) => q.isError),
30+
error: queryList.find((q) => q.isError)?.error?.message ?? null,
31+
data: { vdc: vdcQuery?.data?.data, edge: edgeQuery?.data },
32+
};
33+
34+
if (queries.isLoading) return <Loading />;
35+
if (queries.isError) {
36+
return (
37+
<ErrorBanner error={{ status: 404, data: { message: queries.error } }} />
38+
);
39+
}
40+
41+
return (
42+
<RedirectionGuard
43+
isLoading={queries.isLoading}
44+
route={urls.listing}
45+
condition={isEdgeCompatibleVDC(queries.data.vdc)}
46+
>
47+
<Suspense fallback={<Loading />}>
48+
<section className="px-10 flex flex-col">
49+
<OdsText preset="heading-3">{EDGE_GATEWAY_LABEL}</OdsText>
50+
<EdgeGatewayOrderButton className="mt-4 mb-8" />
51+
<Datagrid
52+
columns={columns}
53+
items={queries.data.edge}
54+
totalItems={queries.data.edge?.length}
55+
/>
56+
</section>
57+
</Suspense>
58+
</RedirectionGuard>
59+
);
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect, it, describe } from 'vitest';
2+
import { screen, waitFor } from '@testing-library/react';
3+
import {
4+
organizationList,
5+
datacentreList,
6+
EDGE_GATEWAY_MOCKS,
7+
} from '@ovh-ux/manager-module-vcd-api';
8+
import { assertTextVisibility } from '@ovh-ux/manager-core-test-utils';
9+
import { labels, renderTest } from '../../../../test-utils';
10+
import { EDGE_GATEWAY_LABEL } from '../datacentreDashboard.constants';
11+
import { urls } from '../../../../routes/routes.constant';
12+
import TEST_IDS from '../../../../utils/testIds.constants';
13+
14+
const config = {
15+
org: organizationList[0],
16+
vdc: datacentreList[0],
17+
edge: EDGE_GATEWAY_MOCKS[0],
18+
labels: { ...labels.datacentresEdgeGateway, ...labels.commonDashboard },
19+
waitOptions: { timeout: 10_000 },
20+
};
21+
22+
describe('Datacentre Edge Gateway Listing Page', () => {
23+
const initialRoute = urls.edgeGateway
24+
.replace(':id', config.org.id)
25+
.replace(':vdcId', config.vdc.id);
26+
27+
it('access and display Edge Gateway listing page', async () => {
28+
await renderTest({ initialRoute });
29+
30+
// check page title
31+
await assertTextVisibility(EDGE_GATEWAY_LABEL);
32+
33+
// wait for data to be loaded
34+
await waitFor(() => {
35+
expect(
36+
screen.getByText(config.edge.currentState.edgeGatewayName),
37+
).toBeVisible();
38+
}, config.waitOptions);
39+
40+
// check order button
41+
const orderCta = screen.getByTestId(TEST_IDS.edgeGatewayOrderCta);
42+
expect(orderCta).toBeVisible();
43+
expect(orderCta).toBeEnabled();
44+
expect(orderCta).toHaveAttribute('label', config.labels.edge_add);
45+
46+
// check datagrid columns
47+
const elements = [
48+
labels.commonDashboard.name,
49+
config.labels.edge_ip_block,
50+
config.labels.edge_connectivity_type,
51+
config.edge.currentState.edgeGatewayName,
52+
config.edge.currentState.ipBlock,
53+
];
54+
elements.forEach((el) => expect(screen.getByText(el)).toBeVisible());
55+
});
56+
57+
// TODO: [EDGE] remove when unmocking
58+
it.skip('display an error', async () => {
59+
await renderTest({ initialRoute, isEdgeGatewayKO: true });
60+
61+
await assertTextVisibility(labels.error.manager_error_page_default);
62+
});
63+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
2+
import { VCDEdgeGateway } from '@ovh-ux/manager-module-vcd-api';
3+
import {
4+
ActionMenu,
5+
ActionMenuItem,
6+
DataGridTextCell,
7+
} from '@ovh-ux/manager-react-components';
8+
import { ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components';
9+
import { useId } from 'react';
10+
import { useTranslation } from 'react-i18next';
11+
12+
export const EdgeGatewayNameCell = (edge: VCDEdgeGateway) => (
13+
<DataGridTextCell>{edge.currentState.edgeGatewayName}</DataGridTextCell>
14+
);
15+
16+
export const EdgeGatewayConnectivityCell = () => (
17+
// TODO: [EDGE] see what content goes here
18+
<DataGridTextCell>N/A</DataGridTextCell>
19+
);
20+
21+
export const EdgeGatewayIPBlockCell = (edge: VCDEdgeGateway) => (
22+
<DataGridTextCell>{edge.currentState.ipBlock}</DataGridTextCell>
23+
);
24+
25+
export const EdgeGatewayActionCell = () => {
26+
const { t } = useTranslation([
27+
'datacentres/edge-gateway',
28+
NAMESPACES.ACTIONS,
29+
]);
30+
const id = useId();
31+
32+
const actionMenuItems: ActionMenuItem[] = [
33+
{
34+
id: 1,
35+
label: t('datacentres/edge-gateway:edge_edit_config'),
36+
isDisabled: false,
37+
onClick: () => {},
38+
},
39+
{
40+
id: 2,
41+
label: t(`${NAMESPACES.ACTIONS}:delete`),
42+
isDisabled: false,
43+
onClick: () => {},
44+
},
45+
];
46+
47+
return (
48+
<ActionMenu
49+
id={`edgeActionMenu-${id}`}
50+
items={actionMenuItems}
51+
isCompact
52+
variant={ODS_BUTTON_VARIANT.ghost}
53+
isDisabled={false}
54+
/>
55+
);
56+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useId } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import clsx from 'clsx';
4+
import { ODS_BUTTON_COLOR } from '@ovhcloud/ods-components';
5+
import {
6+
OdsButton,
7+
OdsIcon,
8+
OdsText,
9+
OdsTooltip,
10+
} from '@ovhcloud/ods-components/react';
11+
import TEST_IDS from '@/utils/testIds.constants';
12+
13+
export const EdgeGatewayOrderButton = (
14+
props: React.HTMLAttributes<HTMLDivElement>,
15+
) => {
16+
const { t } = useTranslation('datacentres/edge-gateway');
17+
const tooltipId = useId();
18+
19+
return (
20+
<div
21+
{...props}
22+
className={clsx('flex gap-x-2 items-center w-fit', props.className)}
23+
>
24+
<OdsButton
25+
label={t('edge_add')}
26+
variant="outline"
27+
onClick={() => {}}
28+
data-testid={TEST_IDS.edgeGatewayOrderCta}
29+
/>
30+
<OdsIcon
31+
id={tooltipId}
32+
name="circle-info"
33+
className="cursor-help"
34+
color={ODS_BUTTON_COLOR.primary}
35+
/>
36+
<OdsTooltip triggerId={tooltipId}>
37+
<OdsText>{t('edge_add_tooltip')}</OdsText>
38+
</OdsTooltip>
39+
</div>
40+
);
41+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { VCDEdgeGateway } from '@ovh-ux/manager-module-vcd-api';
2+
import { DatagridColumn } from '@ovh-ux/manager-react-components';
3+
import { useTranslation } from 'react-i18next';
4+
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
5+
import {
6+
EdgeGatewayNameCell,
7+
EdgeGatewayConnectivityCell,
8+
EdgeGatewayIPBlockCell,
9+
EdgeGatewayActionCell,
10+
} from '../components/EdgeGatewayCells.component';
11+
12+
export const useEdgeGatewayListingColumns = () => {
13+
const { t } = useTranslation('datacentres/edge-gateway');
14+
const { t: tCommonDashboard } = useTranslation(NAMESPACES.DASHBOARD);
15+
16+
const columns: Array<DatagridColumn<VCDEdgeGateway>> = [
17+
{
18+
id: 'name',
19+
cell: EdgeGatewayNameCell,
20+
label: tCommonDashboard('name'),
21+
isSortable: false,
22+
},
23+
{
24+
id: 'connectivity_type',
25+
cell: EdgeGatewayConnectivityCell,
26+
label: t('edge_connectivity_type'),
27+
isSortable: false,
28+
},
29+
{
30+
id: 'ip_block',
31+
cell: EdgeGatewayIPBlockCell,
32+
label: t('edge_ip_block'),
33+
isSortable: false,
34+
},
35+
{
36+
id: 'actions',
37+
cell: EdgeGatewayActionCell,
38+
label: '',
39+
isSortable: false,
40+
},
41+
];
42+
43+
return columns;
44+
};

packages/manager/apps/hpc-vmware-public-vcf-aas/src/routes/routes.constant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const subRoutes = {
1818
addNetwork: 'add-network',
1919
deleteSegment: 'delete-segment',
2020
deleteNetwork: 'delete-network',
21+
edgeGateway: 'edge-gateway',
2122
terminate: 'terminate',
2223
} as const;
2324

@@ -42,6 +43,7 @@ export const urls = {
4243
vrackSegmentEditVlanId: `/${subRoutes.root}/${subRoutes.dashboard}/${subRoutes.virtualDatacenters}/${subRoutes.vdcId}/${subRoutes.vrackSegments}/${subRoutes.vrackSegmentId}/${subRoutes.edit}`,
4344
vrackSegmentAddNetwork: `/${subRoutes.root}/${subRoutes.dashboard}/${subRoutes.virtualDatacenters}/${subRoutes.vdcId}/${subRoutes.vrackSegments}/${subRoutes.vrackSegmentId}/${subRoutes.addNetwork}`,
4445
vrackSegmentDeleteNetwork: `/${subRoutes.root}/${subRoutes.dashboard}/${subRoutes.virtualDatacenters}/${subRoutes.vdcId}/${subRoutes.vrackSegments}/${subRoutes.vrackSegmentId}/${subRoutes.vrackNetwork}/${subRoutes.vrackNetworkId}/${subRoutes.deleteNetwork}`,
46+
edgeGateway: `/${subRoutes.root}/${subRoutes.dashboard}/${subRoutes.virtualDatacenters}/${subRoutes.vdcId}/${subRoutes.edgeGateway}`,
4547
} as const;
4648

4749
export const veeamBackupAppName = 'veeam-backup';

packages/manager/apps/hpc-vmware-public-vcf-aas/src/routes/routes.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ const DeleteVrackSegmentPage = React.lazy(() =>
9696
),
9797
);
9898

99+
const EdgeGatewayListingPage = React.lazy(() =>
100+
import(
101+
'@/pages/dashboard/datacentre/edge-gateway/DatacentreEdgeGateway.page'
102+
),
103+
);
104+
99105
const TerminateOrganizationPage = React.lazy(() =>
100106
import('@/pages/terminate/TerminateOrganization.page'),
101107
);
@@ -310,6 +316,11 @@ export default (
310316
},
311317
}}
312318
/>
319+
<Route
320+
id={'edge-gateway'}
321+
path={urls.edgeGateway}
322+
Component={EdgeGatewayListingPage}
323+
/>
313324
</Route>
314325
<Route
315326
id={'onboarding'}

0 commit comments

Comments
 (0)