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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"edge_add": "Ajouter une Edge Gateway",
"edge_add_tooltip": "Vous pouvez créer 5 Edge Gateways maximum.",
"edge_connectivity_type": "Type de connectivité",
"edge_ip_block": "Bloc IP",
"edge_edit_config": "Modifier la configuration"
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import { BreadcrumbItem } from '@/hooks/breadcrumb/useBreadcrumb';
import VcdDashboardLayout, {
DashboardTab,
} from '@/components/dashboard/layout/VcdDashboardLayout.component';
import { COMPUTE_LABEL, STORAGE_LABEL } from './datacentreDashboard.constants';
import {
COMPUTE_LABEL,
EDGE_GATEWAY_LABEL,
STORAGE_LABEL,
} from './datacentreDashboard.constants';
import { subRoutes, urls } from '@/routes/routes.constant';
import { useAutoRefetch } from '@/data/hooks/useAutoRefetch';
import { isUpdatingTargetSpec } from '@/utils/refetchConditions';
Expand All @@ -21,6 +25,7 @@ import { VIRTUAL_DATACENTERS_LABEL } from '../organization/organizationDashboard
import { VRACK_LABEL } from '../dashboard.constants';
import { FEATURE_FLAGS } from '@/app.constants';
import MessageSuspendedService from '@/components/message/MessageSuspendedService.component';
import { isEdgeCompatibleVDC } from '@/utils/edgeGatewayCompatibility';

function DatacentreDashboardPage() {
const { id, vdcId } = useParams();
Expand Down Expand Up @@ -66,6 +71,13 @@ function DatacentreDashboardPage() {
disabled:
!isVrackFeatureAvailable || !vcdDatacentre?.data?.currentState?.vrack,
},
{
name: 'edge-gateway',
title: EDGE_GATEWAY_LABEL,
to: useResolvedPath(subRoutes.edgeGateway).pathname,
trackingActions: TRACKING_TABS_ACTIONS.edgeGateway,
disabled: isEdgeCompatibleVDC(vcdDatacentre?.data), // TODO: [EDGE] inverse condition when unmocking (testing only)
},
].filter(({ disabled }) => !disabled);

const serviceName = vcdDatacentre?.data?.currentState?.description ?? vdcId;
Expand Down Expand Up @@ -95,6 +107,10 @@ function DatacentreDashboardPage() {
id: vdcId,
label: serviceName,
},
{
id: subRoutes.edgeGateway,
label: EDGE_GATEWAY_LABEL,
},
{
id: subRoutes.vrackSegments,
label: VRACK_LABEL,
Expand Down Expand Up @@ -123,12 +139,6 @@ function DatacentreDashboardPage() {
'datacentres/vrack-segment:managed_vcd_dashboard_vrack_segment_add_title',
),
},
{
id: subRoutes.edit,
label: t(
'datacentres/vrack-segment:managed_vcd_dashboard_vrack_edit_vlan',
),
},
{
id: subRoutes.deleteSegment,
label: t(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const COMPUTE_LABEL = 'Compute';
export const STORAGE_LABEL = 'Storage';
export const EDGE_GATEWAY_LABEL = 'Edge Gateway';
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
useVcdDatacentre,
useVcdEdgeGatewaysMocks,
} from '@ovh-ux/manager-module-vcd-api';
import {
Datagrid,
ErrorBanner,
RedirectionGuard,
} from '@ovh-ux/manager-react-components';
import { OdsText } from '@ovhcloud/ods-components/react';
import { useParams } from 'react-router-dom';
import { Suspense } from 'react';
import { urls } from '@/routes/routes.constant';
import { EDGE_GATEWAY_LABEL } from '../datacentreDashboard.constants';
import Loading from '@/components/loading/Loading.component';
import { useEdgeGatewayListingColumns } from './hooks/useEdgeGatewayListingColumns';
import { EdgeGatewayOrderButton } from './components/EdgeGatewayOrderButton.component';
import { isEdgeCompatibleVDC } from '@/utils/edgeGatewayCompatibility';

export default function EdgeGatewayListingPage() {
const { id, vdcId } = useParams();
const columns = useEdgeGatewayListingColumns();
const vdcQuery = useVcdDatacentre(id, vdcId);
const edgeQuery = useVcdEdgeGatewaysMocks(id, vdcId);

const queryList = [vdcQuery, edgeQuery];
const queries = {
isLoading: queryList.some((q) => q.isLoading),
isError: queryList.some((q) => q.isError),
error: queryList.find((q) => q.isError)?.error?.message ?? null,
data: { vdc: vdcQuery?.data?.data, edge: edgeQuery?.data },
};

if (queries.isLoading) return <Loading />;
if (queries.isError) {
return (
<ErrorBanner error={{ status: 404, data: { message: queries.error } }} />
);
}

return (
<RedirectionGuard
isLoading={queries.isLoading}
route={urls.listing}
condition={isEdgeCompatibleVDC(queries.data.vdc)}
>
<Suspense fallback={<Loading />}>
<section className="px-10 flex flex-col">
<OdsText preset="heading-3">{EDGE_GATEWAY_LABEL}</OdsText>
<EdgeGatewayOrderButton className="mt-4 mb-8" />
<Datagrid
columns={columns}
items={queries.data.edge}
totalItems={queries.data.edge?.length}
/>
</section>
</Suspense>
</RedirectionGuard>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect, it, describe } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import {
organizationList,
datacentreList,
EDGE_GATEWAY_MOCKS,
} from '@ovh-ux/manager-module-vcd-api';
import { assertTextVisibility } from '@ovh-ux/manager-core-test-utils';
import { labels, renderTest } from '../../../../test-utils';
import { EDGE_GATEWAY_LABEL } from '../datacentreDashboard.constants';
import { urls } from '../../../../routes/routes.constant';
import TEST_IDS from '../../../../utils/testIds.constants';

const config = {
org: organizationList[0],
vdc: datacentreList[0],
edge: EDGE_GATEWAY_MOCKS[0],
labels: { ...labels.datacentresEdgeGateway, ...labels.commonDashboard },
waitOptions: { timeout: 10_000 },
};

describe('Datacentre Edge Gateway Listing Page', () => {
const initialRoute = urls.edgeGateway
.replace(':id', config.org.id)
.replace(':vdcId', config.vdc.id);

it('access and display Edge Gateway listing page', async () => {
await renderTest({ initialRoute });

// check page title
await assertTextVisibility(EDGE_GATEWAY_LABEL);

// wait for data to be loaded
await waitFor(() => {
expect(
screen.getByText(config.edge.currentState.edgeGatewayName),
).toBeVisible();
}, config.waitOptions);

// check order button
const orderCta = screen.getByTestId(TEST_IDS.edgeGatewayOrderCta);
expect(orderCta).toBeVisible();
expect(orderCta).toBeEnabled();
expect(orderCta).toHaveAttribute('label', config.labels.edge_add);

// check datagrid columns
const elements = [
labels.commonDashboard.name,
config.labels.edge_ip_block,
config.labels.edge_connectivity_type,
config.edge.currentState.edgeGatewayName,
config.edge.currentState.ipBlock,
];
elements.forEach((el) => expect(screen.getByText(el)).toBeVisible());
});

// TODO: [EDGE] remove when unmocking
it.skip('display an error', async () => {
await renderTest({ initialRoute, isEdgeGatewayKO: true });

await assertTextVisibility(labels.error.manager_error_page_default);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
import { VCDEdgeGateway } from '@ovh-ux/manager-module-vcd-api';
import {
ActionMenu,
ActionMenuItem,
DataGridTextCell,
} from '@ovh-ux/manager-react-components';
import { ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components';
import { useId } from 'react';
import { useTranslation } from 'react-i18next';

export const EdgeGatewayNameCell = (edge: VCDEdgeGateway) => (
<DataGridTextCell>{edge.currentState.edgeGatewayName}</DataGridTextCell>
);

export const EdgeGatewayConnectivityCell = () => (
// TODO: [EDGE] see what content goes here
<DataGridTextCell>N/A</DataGridTextCell>
);

export const EdgeGatewayIPBlockCell = (edge: VCDEdgeGateway) => (
<DataGridTextCell>{edge.currentState.ipBlock}</DataGridTextCell>
);

export const EdgeGatewayActionCell = () => {
const { t } = useTranslation([
'datacentres/edge-gateway',
NAMESPACES.ACTIONS,
]);
const id = useId();

const actionMenuItems: ActionMenuItem[] = [
{
id: 1,
label: t('datacentres/edge-gateway:edge_edit_config'),
isDisabled: false,
onClick: () => {},
},
{
id: 2,
label: t(`${NAMESPACES.ACTIONS}:delete`),
isDisabled: false,
onClick: () => {},
},
];

return (
<ActionMenu
id={`edgeActionMenu-${id}`}
items={actionMenuItems}
isCompact
variant={ODS_BUTTON_VARIANT.ghost}
isDisabled={false}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useId } from 'react';
import { useTranslation } from 'react-i18next';
import clsx from 'clsx';
import { ODS_BUTTON_COLOR } from '@ovhcloud/ods-components';
import {
OdsButton,
OdsIcon,
OdsText,
OdsTooltip,
} from '@ovhcloud/ods-components/react';
import TEST_IDS from '@/utils/testIds.constants';

export const EdgeGatewayOrderButton = (
props: React.HTMLAttributes<HTMLDivElement>,
) => {
const { t } = useTranslation('datacentres/edge-gateway');
const tooltipId = useId();

return (
<div
{...props}
className={clsx('flex gap-x-2 items-center w-fit', props.className)}
>
<OdsButton
label={t('edge_add')}
variant="outline"
onClick={() => {}}
data-testid={TEST_IDS.edgeGatewayOrderCta}
/>
<OdsIcon
id={tooltipId}
name="circle-info"
className="cursor-help"
color={ODS_BUTTON_COLOR.primary}
/>
<OdsTooltip triggerId={tooltipId}>
<OdsText>{t('edge_add_tooltip')}</OdsText>
</OdsTooltip>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { VCDEdgeGateway } from '@ovh-ux/manager-module-vcd-api';
import { DatagridColumn } from '@ovh-ux/manager-react-components';
import { useTranslation } from 'react-i18next';
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
import {
EdgeGatewayNameCell,
EdgeGatewayConnectivityCell,
EdgeGatewayIPBlockCell,
EdgeGatewayActionCell,
} from '../components/EdgeGatewayCells.component';

export const useEdgeGatewayListingColumns = () => {
const { t } = useTranslation('datacentres/edge-gateway');
const { t: tCommonDashboard } = useTranslation(NAMESPACES.DASHBOARD);

const columns: Array<DatagridColumn<VCDEdgeGateway>> = [
{
id: 'name',
cell: EdgeGatewayNameCell,
label: tCommonDashboard('name'),
isSortable: false,
},
{
id: 'connectivity_type',
cell: EdgeGatewayConnectivityCell,
label: t('edge_connectivity_type'),
isSortable: false,
},
{
id: 'ip_block',
cell: EdgeGatewayIPBlockCell,
label: t('edge_ip_block'),
isSortable: false,
},
{
id: 'actions',
cell: EdgeGatewayActionCell,
label: '',
isSortable: false,
},
];

return columns;
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const subRoutes = {
addNetwork: 'add-network',
deleteSegment: 'delete-segment',
deleteNetwork: 'delete-network',
edgeGateway: 'edge-gateway',
terminate: 'terminate',
} as const;

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

export const veeamBackupAppName = 'veeam-backup';
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ const DeleteVrackSegmentPage = React.lazy(() =>
),
);

const EdgeGatewayListingPage = React.lazy(() =>
import(
'@/pages/dashboard/datacentre/edge-gateway/DatacentreEdgeGateway.page'
),
);

const TerminateOrganizationPage = React.lazy(() =>
import('@/pages/terminate/TerminateOrganization.page'),
);
Expand Down Expand Up @@ -310,6 +316,11 @@ export default (
},
}}
/>
<Route
id={'edge-gateway'}
path={urls.edgeGateway}
Component={EdgeGatewayListingPage}
/>
</Route>
<Route
id={'onboarding'}
Expand Down
Loading
Loading