Skip to content

Commit d87b729

Browse files
committed
feat(sap): add installation dashboard page
ref: #MANAGER-17205 Signed-off-by: Thibault Barske <[email protected]>
1 parent 8c949dd commit d87b729

File tree

17 files changed

+796
-15
lines changed

17 files changed

+796
-15
lines changed

packages/manager-react-components/src/components/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export * from './datagrid/useDatagrid';
1616
export * from './datagrid/useDatagridSearchParams';
1717
export * from './datagrid/clipboard-cell.component';
1818

19+
export * from './formatted-date/FormattedDate';
20+
1921
export * from './guides-header';
2022

2123
export * from './notifications/notifications.component';
@@ -25,7 +27,6 @@ export * from './filters';
2527

2628
export * from './ManagerButton/ManagerButton';
2729
export * from './ManagerText/ManagerText';
28-
2930
export * from './pci-maintenance-banner';
3031
export * from './region/region.component';
3132
export * from './order';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"dashboard_installation_title": "Assistant de pré-installation SAP",
3+
"dashboard_installation_description": "Rapport d'installation",
4+
"dashboard_installation_generals_informations": "Informations générales",
5+
"dashboard_installation_item_start_date": "Date de lancement",
6+
"dashboard_installation_item_end_date": "Date de fin",
7+
"dashboard_installation_item_application_version": "Version d'application",
8+
"dashboard_installation_item_application_type": "Type d'application",
9+
"dashboard_installation_item_deploiement_type": "Type de déploiement",
10+
"dashboard_installation_progress_step_initialisation": "Initialisation des ressources temporaires dans votre service VMware on OVHcloud",
11+
"dashboard_installation_progress_step_create_virtuals_machines": "Création des machines virtuelles",
12+
"dashboard_installation_progress_step_sap_hana_install": "Installation de SAP HANA et configuration des options demandées",
13+
"dashboard_installation_progress_step_sap_install": "Installation du système SAP et configuration des options demandées",
14+
"dashboard_installation_progress_step_clean_resources": "Suppression des ressources temporaires dans votre service VMware on OVHcloud",
15+
"dashboard_installation_progress_step_success": "Étape terminée avec succès",
16+
"dashboard_installation_progress_step_failure": "Étape échouée",
17+
"dashboard_installation_progress_step_pending": "Étape en cours d'éxécution",
18+
"dashboard_installation_progress_step_waiting": "Étape en attente",
19+
"dashboard_installation_progress_status_label": "Statut de l'installation",
20+
"dashboard_installation_error_message": "Erreur à l'étape \"{{stepName}}\". Plus de détails ci-dessous."
21+
}

packages/manager/apps/sap-features-hub/public/translations/installation/Messages_fr_FR.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,10 @@
7474
"summary_title": "Validez la configuration de l'installation",
7575
"summary_subtitle": "Vérifiez les informations saisies avant de lancer l'installation. Vous pouvez également télécharger le fichier de la configuration de votre installation <DownloadLink label=\"ici\">{{label}}</DownloadLink>. Vous pourrez le réutiliser ultérieurement pour initialiser une nouvelle installation.",
7676
"summary_cta_submit": "Lancer l'installation",
77-
"summary_api_error": "Le lancement de l'installation a échoué : {{ error }}"
77+
"summary_api_error": "Le lancement de l'installation a échoué : {{ error }}",
78+
"status_SUCCESS": "Succès",
79+
"status_PENDING": "En attente",
80+
"status_REVOKED": "Révoqué",
81+
"status_FAILURE": "Erreur",
82+
"status_STARTED": "En cours"
7883
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import React from 'react';
2-
import { OdsText } from '@ovhcloud/ods-components/react';
2+
import { OdsText, OdsSkeleton } from '@ovhcloud/ods-components/react';
33
import { StepFieldData } from '@/types/formStep.type';
44
import { testIds } from '@/utils/testIds.constants';
55
import { FORM_LABELS } from '@/constants/form.constants';
66

77
type FormFieldSummaryProps = React.HTMLAttributes<HTMLDivElement> & {
88
field: StepFieldData;
9+
isLoading?: boolean;
910
};
1011

1112
export const FormFieldSummary = ({
1213
field,
14+
isLoading,
1315
...props
1416
}: FormFieldSummaryProps) => {
1517
const valueText = field.isSecretValue ? FORM_LABELS.secretText : field.value;
@@ -20,9 +22,12 @@ export const FormFieldSummary = ({
2022
{...props}
2123
>
2224
<OdsText className="max-w-60">{field.label}</OdsText>
23-
<OdsText className="max-w-60" data-testid={testIds.summaryFieldValue}>
24-
{field.value ? valueText : FORM_LABELS.unknownText}
25-
</OdsText>
25+
{isLoading && <OdsSkeleton className="w-20" />}
26+
{!isLoading && (
27+
<OdsText className="max-w-60" data-testid={testIds.summaryFieldValue}>
28+
{field.value ? valueText : FORM_LABELS.unknownText}
29+
</OdsText>
30+
)}
2631
</div>
2732
);
2833
};

packages/manager/apps/sap-features-hub/src/components/InstallationStatus/InstallationStatus.component.spec.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,7 @@ describe('SAPInstallationStatus component tests suite', () => {
4242

4343
const badge = container.querySelector('ods-badge');
4444
expect(badge).toHaveAttribute('color', color);
45-
expect(badge).toHaveAttribute(
46-
'label',
47-
`sap_hub_history_installation_${status}`,
48-
);
45+
expect(badge).toHaveAttribute('label', `status_${status}`);
4946
},
5047
);
5148
});

packages/manager/apps/sap-features-hub/src/components/InstallationStatus/InstallationStatus.component.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ export const InstallationStatus = ({
2222
size = ODS_BADGE_SIZE.md,
2323
...rest
2424
}: InstallationStatusProps) => {
25-
const { t } = useTranslation('listing');
25+
const { t } = useTranslation('installation');
2626
return (
2727
<React.Suspense>
2828
<OdsBadge
29-
label={t(`sap_hub_history_installation_${status}`)}
29+
label={t(`status_${status}`)}
3030
size={size}
3131
color={BADGE_COLOR_STATUS[status] ?? ODS_BADGE_COLOR.neutral}
3232
{...rest}

packages/manager/apps/sap-features-hub/src/data/api/installationDeployment.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ApiResponse, v6 } from '@ovh-ux/manager-core-api';
2+
import { InstallationDetails } from '@/types/installation.type';
23

34
const getApplicationVersionRoute = (serviceName: string) =>
45
`/dedicatedCloud/${serviceName}/sap/capabilities`;
@@ -7,3 +8,12 @@ export const getApplicationVersions = async (
78
serviceName: string,
89
): Promise<ApiResponse<unknown>> =>
910
v6.get(getApplicationVersionRoute(serviceName));
11+
12+
const getInstallationTaskDetailsRoute = (serviceName: string, taskId: string) =>
13+
`/dedicatedCloud/${serviceName}/sap/${taskId}`;
14+
15+
export const getInstallationTaskDetails = async (
16+
serviceName: string,
17+
taskId: string,
18+
): Promise<ApiResponse<InstallationDetails>> =>
19+
v6.get(getInstallationTaskDetailsRoute(serviceName, taskId));

packages/manager/apps/sap-features-hub/src/hooks/installationDeployment/useApplicationVersions.ts

+80-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { useQuery } from '@tanstack/react-query';
2-
import { getApplicationVersions } from '@/data/api/installationDeployment';
2+
import {
3+
getApplicationVersions,
4+
getInstallationTaskDetails,
5+
} from '@/data/api/installationDeployment';
6+
import {
7+
InstallationDetails,
8+
SAPInstallationStatus,
9+
} from '@/types/installation.type';
310

411
// TODO: implement API calls when developed
512
export const useApplicationVersions = (serviceName: string) =>
@@ -9,3 +16,75 @@ export const useApplicationVersions = (serviceName: string) =>
916
select: (res) => res.data,
1017
enabled: !!serviceName,
1118
});
19+
20+
// TODO: implement API calls when developed
21+
export const useInstallationTaskDetails = (
22+
serviceName?: string,
23+
taskId?: string,
24+
) =>
25+
useQuery({
26+
queryKey: ['sap', serviceName, 'taskId'],
27+
queryFn: () => getInstallationTaskDetails(serviceName, taskId),
28+
select: (res) => res.data,
29+
enabled: !!serviceName && !!taskId,
30+
});
31+
32+
export const useMockInstallationTaskDetails = (
33+
serviceName?: string,
34+
taskId?: string,
35+
) => {
36+
return useQuery({
37+
queryKey: ['sap', serviceName, 'taskId'],
38+
queryFn: () => {
39+
return new Promise((resolve) => {
40+
// Generate fake data for InstallationDetails
41+
const mockData: InstallationDetails = {
42+
ansibleSapHanaStatus: SAPInstallationStatus.failure,
43+
ansibleSapSystemStatus: SAPInstallationStatus.failure,
44+
applicationType: 'ABAP',
45+
applicationVersion: '1.0.0',
46+
cleanStatus: SAPInstallationStatus.pending,
47+
deploymentType: 'Standard',
48+
endTime: '2022-01-01T12:00:00',
49+
errorMessage: `1. [ERROR] DependencyError: Failed to install package "libwebsocket-3.2.1"
50+
2. [WARN] VersionMismatch: Requested Node >=16.x, found Node v14.15.0
51+
3. [CRITICAL] SegmentationFault: Installation aborted at step "configure_network_adapter"
52+
4. [ERROR] ChecksumFailed: File integrity compromised on "module-crypto-4.0.2.tar.gz"
53+
5. [FATAL] PermissionDenied: Unable to access "/usr/local/bin/" directory
54+
6. [ERROR] CompilationError: Rust crate "tokio-async-1.6.3" failed to build
55+
7. [WARN] TimeoutWarning: Network timeout while downloading dependency "[email protected]"
56+
8. [ERROR] SyntaxError: Unexpected token 'import' during parsing config.js
57+
9. [CRITICAL] KernelIncompatibility: Module "fs-extended" incompatible with Linux Kernel 5.8.0
58+
10. [ERROR] PortUnavailable: Default port 5432 already in use by another service
59+
11. [WARN] EnvironmentVariableMissing: Variable DATABASE_URL not found
60+
12. [ERROR] ConflictDetected: Conflicting installations detected for package "express-router"
61+
13. [FATAL] DiskSpaceInsufficient: Less than 200MB remaining on partition "/var"
62+
14. [ERROR] AuthenticationFailed: Access denied to private repository "[email protected]:user/private-lib.git"
63+
15. [CRITICAL] DockerDaemonOffline: Docker service unavailable or stopped
64+
16. [ERROR] SSLHandshakeFailed: Unable to verify certificate for domain "api.example.com"
65+
17. [WARN] DeprecationNotice: Package "[email protected]" deprecated, installation skipped
66+
18. [ERROR] DatabaseMigrationFailed: PostgreSQL migration error at script "20240401_init.sql"
67+
19. [CRITICAL] MissingSystemLibrary: Required system library "libssl-dev" not found
68+
20. [ERROR] InvalidConfiguration: Unrecognized parameter "max_memory_allocation" in config.yml`,
69+
gatewayStatus: SAPInstallationStatus.success,
70+
iam: {
71+
displayName: 'John Doe',
72+
id: '123456',
73+
urn: 'urn:iam:123456',
74+
},
75+
sapHanaSid: 'HANA01',
76+
sapSid: 'SAP01',
77+
startTime: '2022-01-01T10:00:00',
78+
status: SAPInstallationStatus.failure,
79+
taskId: '789012',
80+
terraformStatus: SAPInstallationStatus.success,
81+
};
82+
setTimeout(() => {
83+
resolve({ data: mockData });
84+
}, 500);
85+
});
86+
},
87+
select: (res) => res.data,
88+
enabled: () => !!serviceName && !!taskId,
89+
});
90+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useParams } from 'react-router-dom';
2+
import { BaseLayout } from '@ovh-ux/manager-react-components';
3+
import React, { Suspense } from 'react';
4+
5+
import { useTranslation } from 'react-i18next';
6+
import Breadcrumb from '@/components/Breadcrumb/Breadcrumb';
7+
import InstallationDashboardInstallationSummary from './installationSummary';
8+
import InstallationDashboardInstallationProgress from './installationProgress';
9+
import InstallationDashboardInstallationError from './installationError';
10+
11+
export default function InstallationDashboardPage() {
12+
const { serviceName, taskId } = useParams();
13+
const { t } = useTranslation('dashboard/installation');
14+
const header = {
15+
title: t('dashboard_installation_title'),
16+
description: t('dashboard_installation_description'),
17+
};
18+
19+
return (
20+
<Suspense>
21+
<BaseLayout breadcrumb={<Breadcrumb />} header={header}>
22+
<div className="flex flex-col gap-8">
23+
<InstallationDashboardInstallationError
24+
serviceName={serviceName}
25+
taskId={taskId}
26+
/>
27+
<InstallationDashboardInstallationSummary
28+
serviceName={serviceName}
29+
taskId={taskId}
30+
/>
31+
<InstallationDashboardInstallationProgress
32+
serviceName={serviceName}
33+
taskId={taskId}
34+
/>
35+
</div>
36+
</BaseLayout>
37+
</Suspense>
38+
);
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import '@testing-library/jest-dom';
2+
import { vi } from 'vitest';
3+
import React from 'react';
4+
import { render, screen } from '@testing-library/react';
5+
import installationTranslation from '../../../../../public/translations/dashboard/installation/Messages_fr_FR.json';
6+
import { useMockInstallationTaskDetails } from '@/hooks/installationDeployment/useApplicationVersions';
7+
import {
8+
InstallationDetails,
9+
SAPInstallationStatus,
10+
} from '@/types/installation.type';
11+
import InstallationDashboardInstallationError, {
12+
getTranslationKeyOfStepInError,
13+
} from '.';
14+
15+
const baseInstallation: InstallationDetails = {
16+
ansibleSapHanaStatus: SAPInstallationStatus.pending,
17+
ansibleSapSystemStatus: SAPInstallationStatus.pending,
18+
applicationType: 'ABAP',
19+
applicationVersion: '1.0.0',
20+
cleanStatus: SAPInstallationStatus.pending,
21+
deploymentType: 'Standard',
22+
endTime: null,
23+
errorMessage: null,
24+
gatewayStatus: SAPInstallationStatus.pending,
25+
sapHanaSid: 'SID',
26+
sapSid: 'SID',
27+
startTime: '2020-01-01T00:00:00Z',
28+
status: SAPInstallationStatus.pending,
29+
taskId: 'task-1',
30+
iam: {
31+
id: '',
32+
urn: '',
33+
},
34+
terraformStatus: SAPInstallationStatus.pending,
35+
};
36+
37+
const testCases = [
38+
{
39+
step: 'all steps',
40+
installation: {
41+
...baseInstallation,
42+
ansibleSapHanaStatus: SAPInstallationStatus.failure,
43+
ansibleSapSystemStatus: SAPInstallationStatus.failure,
44+
cleanStatus: SAPInstallationStatus.failure,
45+
gatewayStatus: SAPInstallationStatus.failure,
46+
terraformStatus: SAPInstallationStatus.failure,
47+
},
48+
expected: 'dashboard_installation_progress_step_initialisation',
49+
},
50+
{
51+
step: 'gatewayStatus',
52+
installation: {
53+
...baseInstallation,
54+
gatewayStatus: SAPInstallationStatus.failure,
55+
},
56+
expected: 'dashboard_installation_progress_step_initialisation',
57+
},
58+
{
59+
step: 'cleanStatus',
60+
installation: {
61+
...baseInstallation,
62+
cleanStatus: SAPInstallationStatus.failure,
63+
},
64+
expected: 'dashboard_installation_progress_step_clean_resources',
65+
},
66+
{
67+
step: 'no step',
68+
installation: {
69+
...baseInstallation,
70+
},
71+
expected: '',
72+
},
73+
];
74+
75+
describe('computeProgressPercentage', () => {
76+
it.each(testCases)(
77+
'Should return $expected if $step is in failure',
78+
({ installation, expected }) => {
79+
expect(getTranslationKeyOfStepInError(installation)).toBe(expected);
80+
},
81+
);
82+
});
83+
84+
vi.mock('@/hooks/installationDeployment/useApplicationVersions', () => ({
85+
useMockInstallationTaskDetails: vi.fn(),
86+
}));
87+
88+
describe('InstallationDashboardInstallationError', () => {
89+
const serviceName = 'test-service';
90+
const taskId = '123';
91+
92+
it('renders nothing if status is not failure', () => {
93+
(useMockInstallationTaskDetails as jest.Mock).mockReturnValue({
94+
data: baseInstallation,
95+
isLoading: false,
96+
isError: false,
97+
});
98+
99+
const { container } = render(
100+
<InstallationDashboardInstallationError
101+
serviceName={serviceName}
102+
taskId={taskId}
103+
/>,
104+
);
105+
106+
expect(container).toBeEmptyDOMElement();
107+
});
108+
109+
it('renders OdsMessage and error message if status is failure', () => {
110+
const installationInError = {
111+
...baseInstallation,
112+
status: SAPInstallationStatus.failure,
113+
gatewayStatus: SAPInstallationStatus.failure,
114+
errorMessage: 'Test error',
115+
currentStep: 'stepKey',
116+
};
117+
(useMockInstallationTaskDetails as jest.Mock).mockReturnValue({
118+
data: installationInError,
119+
isLoading: false,
120+
});
121+
122+
render(
123+
<InstallationDashboardInstallationError
124+
serviceName={serviceName}
125+
taskId={taskId}
126+
/>,
127+
);
128+
129+
expect(screen.getByText(/Test error/)).toBeVisible();
130+
expect(
131+
screen.getByText(
132+
'dashboard_installation_error_message' as keyof typeof installationTranslation,
133+
),
134+
).toBeVisible();
135+
});
136+
});

0 commit comments

Comments
 (0)