Skip to content

Commit 0e79816

Browse files
committed
feat(observability): add tenants config page
resolves: #MAOBS-120 Signed-off-by: David Arsène <[email protected]>
1 parent 393db0c commit 0e79816

File tree

72 files changed

+4988
-217
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+4988
-217
lines changed

packages/manager/apps/observability/eslint.config.mjs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
1-
import { prettierEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/prettier';
1+
import { tailwindSyntax } from '@eslint/css/syntax';
22

3-
import { javascriptEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/javascript';
4-
import { typescriptEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/typescript';
5-
import { reactEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/react';
63
import { a11yEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/a11y';
74
import {
85
complexityJsxTsxConfig,
96
complexityTsJsConfig,
107
} from '@ovh-ux/manager-static-analysis-kit/eslint/complexity';
11-
import { htmlEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/html';
128
import { cssEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/css';
9+
import { htmlEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/html';
10+
import { javascriptEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/javascript';
11+
import { prettierEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/prettier';
12+
import { reactEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/react';
1313
import { tailwindJsxConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/tailwind-jsx';
1414
import { tanStackQueryEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/tanstack';
1515
import { vitestEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/tests';
16-
import { tailwindSyntax } from '@eslint/css/syntax';
16+
import { typescriptEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/typescript';
1717

1818
export default [
1919
javascriptEslintConfig,
2020
typescriptEslintConfig,
21-
reactEslintConfig,
21+
{ ...reactEslintConfig, ignores: [...(reactEslintConfig.ignores || []), '**/__tests__/**'] },
2222
a11yEslintConfig,
2323
htmlEslintConfig,
2424
tailwindJsxConfig,
2525
tanStackQueryEslintConfig,
2626
vitestEslintConfig,
2727
prettierEslintConfig,
28-
complexityJsxTsxConfig,
29-
complexityTsJsConfig,
28+
{
29+
...complexityJsxTsxConfig,
30+
ignores: [...(complexityJsxTsxConfig.ignores || []), '**/__tests__/**'],
31+
},
32+
{
33+
...complexityTsJsConfig,
34+
ignores: [...(complexityTsJsConfig.ignores || []), '**/__tests__/**'],
35+
},
3036
{
3137
...cssEslintConfig,
3238
files: ['**/*.css', '**/*.scss'],

packages/manager/apps/observability/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"test:coverage": "manager-test run --coverage"
2020
},
2121
"dependencies": {
22+
"@hookform/resolvers": "5.2.1",
2223
"@ovh-ux/manager-common-translations": "*",
2324
"@ovh-ux/manager-config": "*",
2425
"@ovh-ux/manager-core-api": "*",
@@ -33,14 +34,18 @@
3334
"@tanstack/react-query": "5.51.21",
3435
"axios": "^1.6.0",
3536
"clsx": "^2.1.1",
37+
"date-fns": "^3.6.0",
38+
"duration-fns": "^3.0.2",
3639
"element-internals-polyfill": "^3.0.2",
3740
"i18next": "23.8.2",
3841
"i18next-http-backend": "2.4.3",
3942
"react": "18.2.0",
4043
"react-dom": "18.2.0",
44+
"react-hook-form": "^7.56.1",
4145
"react-i18next": "14.1.3",
4246
"react-router-dom": "6.16.0",
43-
"tailwindcss": "^3.4.4"
47+
"tailwindcss": "^3.4.4",
48+
"zod": "^4.0.17"
4449
},
4550
"devDependencies": {
4651
"@ovh-ux/manager-static-analysis-kit": "*",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"infrastructure":{
3+
"region":{
4+
"all": "Toutes les régions",
5+
"title": "Sélectionnez la région"
6+
}
7+
}
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"description":"Description (optionnelle)"
3+
}

packages/manager/apps/observability/public/translations/tenants/Messages_fr_FR.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,21 @@
55
"onboarding": {
66
"description": "Créez votre nouveau tenant pour continuer à explorer tout le potentiel des métriques d'observabilité. Vous pourrez envoyer des données, créer des tableaux de bord et configurer des alertes adaptées à vos besoins spécifiques.",
77
"orderButtonLabel": "Créez votre tenant"
8+
},
9+
"creation": {
10+
"title": "Créer un tenant",
11+
"tenantInformation": "Saisir les informations du tenant",
12+
"namePlaceholder": "Nom du tenant",
13+
"description":"Description (optionnelle)",
14+
"descriptionPlaceholder": "Ajoutez une description des metrics que vous allez écrire dans ce tenant"
15+
},
16+
"configuration": {
17+
"title": "Configurez votre tenant",
18+
"retention": "Rétention",
19+
"retentionPlaceholder": "Sélectionnez la rétention des données",
20+
"limit": {
21+
"title": "Limite d'ingestion (métriques actives)",
22+
"description": "Définissez la limite maximale du nombre de métriques actives pour ce tenant."
23+
}
824
}
9-
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
getInfrastructures as getInfrastructuresFromMock,
3+
getRetentions as getRetentionsFromMock,
4+
} from '@/__mocks__/infrastructures/infrastructures.mock';
5+
import { apiConfig } from '@/__mocks__/mock.config';
6+
import {
7+
getInfrastructures as getInfrastructuresFromApi,
8+
getRetentions as getRetentionsFromApi,
9+
} from '@/data/api/infrastructures.api';
10+
import { InfrastructuresParams, RetentionParams } from '@/data/api/infrastructures.props';
11+
import { Infrastructure, Retention } from '@/types/infrastructures.type';
12+
13+
export const getInfrastructures = async (
14+
params: InfrastructuresParams,
15+
): Promise<Infrastructure[]> => {
16+
const isMockEnabled = apiConfig.mode === 'mock';
17+
console.info('[MOCK-ADAPTER][getInfrastructures] Mock enabled -> ', isMockEnabled);
18+
return isMockEnabled ? getInfrastructuresFromMock(params) : getInfrastructuresFromApi(params);
19+
};
20+
21+
export const getRetentions = async (params: RetentionParams): Promise<Retention[]> => {
22+
const isMockEnabled = apiConfig.mode === 'mock';
23+
console.info('[MOCK-ADAPTER][getRetentions] Mock enabled -> ', isMockEnabled);
24+
return isMockEnabled ? getRetentionsFromMock(params) : getRetentionsFromApi(params);
25+
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { InfrastructuresParams, RetentionParams } from '@/data/api/infrastructures.props';
2+
import { Infrastructure, Retention } from '@/types/infrastructures.type';
3+
4+
const infrastructures: Infrastructure[] = [
5+
{
6+
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
7+
currentState: {
8+
location: 'eu-west-sbg',
9+
type: 'SHARED',
10+
usage: 'METRICS',
11+
},
12+
},
13+
{
14+
id: '6ee8fb35-2621-4530-a288-84fc0e85dac1',
15+
currentState: {
16+
location: 'eu-west-gra',
17+
type: 'SHARED',
18+
usage: 'METRICS',
19+
},
20+
},
21+
{
22+
id: '6ee8fb35-2621-4530-a288-84fc0e85bb1',
23+
currentState: {
24+
location: 'ca-east-bhs',
25+
type: 'SHARED',
26+
usage: 'METRICS',
27+
},
28+
},
29+
{
30+
id: '6ee8fb35-2621-2345-a288-84fc0e85bb1',
31+
currentState: {
32+
location: 'us-east-vin',
33+
type: 'SHARED',
34+
usage: 'METRICS',
35+
},
36+
},
37+
{
38+
id: '6ee8fb35-1111-2345-z288-84fc0e85bb1',
39+
currentState: {
40+
location: 'ap-southeast-sgp',
41+
type: 'SHARED',
42+
usage: 'METRICS',
43+
},
44+
},
45+
];
46+
47+
export const getInfrastructures = async ({
48+
resourceName,
49+
usages,
50+
types,
51+
}: InfrastructuresParams): Promise<Infrastructure[]> => {
52+
console.info(`[MOCK-ADAPTER][getInfrastructures] infrastructures mock for ${resourceName}`, {
53+
usages,
54+
types,
55+
infrastructures,
56+
});
57+
return Promise.resolve(infrastructures);
58+
};
59+
60+
const retentions: Retention[] = [
61+
{
62+
id: '1',
63+
duration: 'P1M',
64+
default: true,
65+
supported: true,
66+
},
67+
{
68+
id: '2',
69+
duration: 'P3M',
70+
default: false,
71+
supported: true,
72+
},
73+
{
74+
id: '3',
75+
duration: 'P6M',
76+
default: false,
77+
supported: true,
78+
},
79+
{
80+
id: '4',
81+
duration: 'P1Y',
82+
default: false,
83+
supported: true,
84+
},
85+
];
86+
87+
export const getRetentions = async ({
88+
resourceName,
89+
infrastructureId,
90+
}: RetentionParams): Promise<Retention[]> => {
91+
console.info(`[MOCK-ADAPTER][getRetentions] retentions mock for ${resourceName}`, {
92+
infrastructureId,
93+
retentions,
94+
});
95+
return Promise.resolve(retentions);
96+
};
Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import { apiConfig } from '@/__mocks__/mock.config';
2-
import { getTenants as getTenantsFromMock } from '@/__mocks__/tenants/tenant.mock';
3-
import { getTenants as getTenantsFromApi } from '@/data/api/tenant';
4-
import { ObservabilityServiceParams } from '@/types/ClientApi.type';
5-
import { Tenant } from '@/types/observability.type';
2+
import {
3+
createTenant as createTenantsFromMock,
4+
getTenants as getTenantsFromMock,
5+
} from '@/__mocks__/tenants/tenant.mock';
6+
import { ObservabilityServiceParams } from '@/data/api/observability.props';
7+
import {
8+
createTenants as createTenantsFromApi,
9+
getTenants as getTenantsFromApi,
10+
} from '@/data/api/tenants.api';
11+
import { CreateTenantsPayload } from '@/data/api/tenants.props';
12+
import { Tenant } from '@/types/tenants.type';
613

714
export const getTenants = async (params: ObservabilityServiceParams): Promise<Tenant[]> => {
815
const isMockEnabled = apiConfig.mode === 'mock';
916
console.info('[MOCK-ADAPTER][getTenants] Mock enabled -> ', isMockEnabled);
1017
return isMockEnabled ? getTenantsFromMock(params) : getTenantsFromApi(params);
1118
};
19+
20+
export const createTenants = async (payload: CreateTenantsPayload): Promise<Tenant> => {
21+
const isMockEnabled = apiConfig.mode === 'mock';
22+
console.info('[MOCK-ADAPTER][createTenant] Mock enabled -> ', isMockEnabled);
23+
return isMockEnabled ? createTenantsFromMock(payload) : createTenantsFromApi(payload);
24+
};

packages/manager/apps/observability/src/__mocks__/tenants/tenant.mock.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { ObservabilityServiceParams } from '@/types/ClientApi.type';
2-
import { Tenant } from '@/types/observability.type';
1+
import { ObservabilityServiceParams } from '@/data/api/observability.props';
2+
import { CreateTenantsPayload } from '@/data/api/tenants.props';
3+
import { Tenant } from '@/types/tenants.type';
34

45
export const getTenants = async ({
5-
serviceName,
6+
resourceName,
67
}: ObservabilityServiceParams): Promise<Tenant[]> => {
7-
const isOnboarding = serviceName !== 'ldp-rg-93836'; // '[DO NOT TOUCH] Monito service'
8+
const isOnboarding = resourceName !== 'ldp-rg-93836'; // '[DO NOT TOUCH] Monito service'
89
console.info(
9-
`[MOCK-ADAPTER][getTenants] is onboarding mock for ${serviceName} -> `,
10+
`[MOCK-ADAPTER][getTenants] is onboarding mock for ${resourceName} -> `,
1011
isOnboarding,
1112
);
1213
return Promise.resolve(
@@ -28,3 +29,31 @@ export const getTenants = async ({
2829
: [],
2930
);
3031
};
32+
33+
export const createTenant = async function ({
34+
resourceName,
35+
targetSpec,
36+
}: CreateTenantsPayload): Promise<Tenant> {
37+
console.info(`[MOCK-ADAPTER][createTenant] mock creation of tenant for ${resourceName}`);
38+
console.info(`[MOCK-ADAPTER][createTenant] targetSpec -> `, targetSpec);
39+
return Promise.resolve({
40+
id: '1',
41+
currentState: {
42+
title: 'Tenant 1',
43+
},
44+
targetSpec: {
45+
...targetSpec,
46+
limits: {
47+
...targetSpec.limits,
48+
numberOfSeries: {
49+
...targetSpec.limits.numberOfSeries,
50+
current: targetSpec.limits.numberOfSeries.maximum,
51+
},
52+
retention: {
53+
...targetSpec.limits.retention,
54+
duration: '30d', // Add required duration property
55+
},
56+
},
57+
},
58+
});
59+
};

packages/manager/apps/observability/src/__tests__/components/ServicesDropDown.component.spec.tsx

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
44
import { fireEvent, render, screen } from '@testing-library/react';
55
import { vi } from 'vitest';
66

7-
import ServicesDropDown from '../../components/ServicesDropDown.component';
8-
import { useObservabilityServiceContext } from '../../contexts/ObservabilityService.context';
9-
import { ObservabilityService } from '../../types/observability.type';
7+
import ServicesDropDown from '@/components/services/ServicesDropDown.component';
8+
import { useObservabilityServiceContext } from '@/contexts/ObservabilityService.context';
9+
import { ObservabilityService } from '@/types/observability.type';
1010

1111
// Mock the context
1212
vi.mock('@/contexts/ObservabilityService.context', () => ({
@@ -243,7 +243,7 @@ describe('ServicesDropDown', () => {
243243
// Arrange
244244
mockUseObservabilityServiceContext.mockReturnValue({
245245
setSelectedService: mockSetSelectedService,
246-
selectedService: 'service-1',
246+
selectedService: mockServices[0],
247247
services: mockServices,
248248
isLoading: false,
249249
isSuccess: true,
@@ -277,7 +277,7 @@ describe('ServicesDropDown', () => {
277277

278278
if (select) fireEvent(select, odsChangeEvent);
279279

280-
expect(mockSetSelectedService).toHaveBeenCalledWith('service-2');
280+
expect(mockSetSelectedService).toHaveBeenCalledWith(mockServices[1]);
281281
});
282282

283283
it('should handle undefined value in onOdsChange', () => {
@@ -295,28 +295,8 @@ describe('ServicesDropDown', () => {
295295
});
296296

297297
if (select) fireEvent(select, odsChangeEvent);
298-
299298
expect(mockSetSelectedService).toHaveBeenCalledWith(undefined);
300299
});
301-
302-
it('should convert value to string when calling setSelectedService', () => {
303-
// Act
304-
const { container } = render(<ServicesDropDown />, {
305-
wrapper: createWrapper(),
306-
});
307-
308-
// Assert
309-
const select = getOdsElement(container, 'ods-select');
310-
311-
// Simulate change event with number value (should be converted to string)
312-
const odsChangeEvent = new CustomEvent('odsChange', {
313-
detail: { value: 123 },
314-
});
315-
316-
if (select) fireEvent(select, odsChangeEvent);
317-
318-
expect(mockSetSelectedService).toHaveBeenCalledWith('123');
319-
});
320300
});
321301

322302
describe('CSS Classes and Styling', () => {
@@ -350,7 +330,7 @@ describe('ServicesDropDown', () => {
350330

351331
// Assert
352332
const select = getOdsElement(container, 'ods-select');
353-
expect(select).toHaveClass('max-w-[15rem]');
333+
expect(select).toHaveClass('max-w-[20rem]');
354334
});
355335

356336
it('should apply correct CSS classes to skeleton when loading', () => {
@@ -371,7 +351,7 @@ describe('ServicesDropDown', () => {
371351

372352
// Assert
373353
const skeleton = getOdsElement(container, 'ods-skeleton');
374-
expect(skeleton).toHaveClass('max-w-[15rem]');
354+
expect(skeleton).toHaveClass('max-w-[20rem]');
375355
});
376356
});
377357

0 commit comments

Comments
 (0)