diff --git a/client/src/locales/en-US.json b/client/src/locales/en-US.json index bc5b1a6bfe1..0f583c7cacd 100644 --- a/client/src/locales/en-US.json +++ b/client/src/locales/en-US.json @@ -1232,6 +1232,28 @@ }, "billingDetails": { "retrieveFailed": "Failed to retrieve billing details." + }, + "statistics": { + "titles": { + "stats": "Statistics", + "users": "Users", + "active": "active", + "revoked": "revoked", + "storage": "Used storage" + }, + "admin": "Administrator", + "standard": "Standard", + "outsider": "Outsider", + "admins": "Administrators", + "standards": "Standards", + "outsiders": "Outsiders", + "consumptionDetail": "Consumption detail", + "nonPaying": "Non paying", + "paying": "charged", + "free": "free", + "ofWhich": "of which", + "data": "data", + "metadata": "metadata" } } } diff --git a/client/src/locales/fr-FR.json b/client/src/locales/fr-FR.json index 523dffb9a92..bf0f4ba1b6f 100644 --- a/client/src/locales/fr-FR.json +++ b/client/src/locales/fr-FR.json @@ -1232,6 +1232,28 @@ }, "billingDetails": { "retrieveFailed": "Impossible de récupérer les informations de paiement." + }, + "statistics": { + "titles": { + "stats": "Statistiques", + "users": "Utilisateurs", + "active": "actifs", + "revoked": "révoqués", + "storage": "Stockage utilisé" + }, + "admin": "Administrateur", + "standard": "Standard", + "outsider": "Externe", + "admins": "Administrateurs", + "standards": "Standards", + "outsiders": "Externes", + "consumptionDetail": "Détail des consommations", + "nonPaying": "Non payant", + "paying": "payant", + "free": "gratuit", + "ofWhich": "dont", + "data": "de données", + "metadata": "métadonnées" } } } diff --git a/client/src/services/bms/api.ts b/client/src/services/bms/api.ts index bae4b4be2c0..916ca559d95 100644 --- a/client/src/services/bms/api.ts +++ b/client/src/services/bms/api.ts @@ -184,8 +184,15 @@ async function getOrganizationStats(token: AuthenticationToken, query: Organizat data: { type: DataType.OrganizationStats, dataSize: axiosResponse.data.data_size, + metadataSize: axiosResponse.data.metadata_size, + users: axiosResponse.data.users ?? 0, + activeUsers: axiosResponse.data.active_users ?? 0, + adminUsersDetail: axiosResponse.data.users_per_profile_detail.ADMIN, + standardUsersDetail: axiosResponse.data.users_per_profile_detail.STANDARD, + outsiderUsersDetail: axiosResponse.data.users_per_profile_detail.OUTSIDER, + freeSliceSize: axiosResponse.data.free_slice_size ?? 1024 * 1024 * 1024 * 200, // arbitrary value + payingSliceSize: axiosResponse.data.paying_slice_size ?? 1024 * 1024 * 1024 * 100, // arbitrary value status: axiosResponse.data.status, - users: axiosResponse.data.users, }, }; }); diff --git a/client/src/services/bms/types.ts b/client/src/services/bms/types.ts index 080552279f5..3be0baed057 100644 --- a/client/src/services/bms/types.ts +++ b/client/src/services/bms/types.ts @@ -67,12 +67,25 @@ interface ListOrganizationsResultData { organizations: Array; } +interface UserPerProfileDetails { + active: number; + revoked: number; +} + interface OrganizationStatsResultData { type: DataType.OrganizationStats; + realms?: number; dataSize: number; + metadataSize: number; + users: number; + activeUsers: number; + adminUsersDetail: UserPerProfileDetails; + standardUsersDetail: UserPerProfileDetails; + outsiderUsersDetail: UserPerProfileDetails; + freeSliceSize: number; + payingSliceSize: number; // Add more status status: 'ok'; - users: number; } interface OrganizationStatusResultData { diff --git a/client/src/views/client-area/StatisticsPage.vue b/client/src/views/client-area/StatisticsPage.vue index 1c9cc6d0dbd..60538d251a1 100644 --- a/client/src/views/client-area/StatisticsPage.vue +++ b/client/src/views/client-area/StatisticsPage.vue @@ -2,9 +2,87 @@ @@ -12,20 +90,129 @@ - + diff --git a/client/tests/pw/e2e/client_area_stats_page.spec.ts b/client/tests/pw/e2e/client_area_stats_page.spec.ts new file mode 100644 index 00000000000..d58cc186b6e --- /dev/null +++ b/client/tests/pw/e2e/client_area_stats_page.spec.ts @@ -0,0 +1,77 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +import { expect } from '@tests/pw/helpers/assertions'; +import { MockBms } from '@tests/pw/helpers/bms'; +import { DEFAULT_ORGANIZATION_DATA_SLICE } from '@tests/pw/helpers/data'; +import { msTest } from '@tests/pw/helpers/fixtures'; + +[ + { + data: 0, + bar1: { progress: 0 }, + bar2: { visible: false }, + bar3: { visible: false }, + }, + { + data: DEFAULT_ORGANIZATION_DATA_SLICE.free * 0.74, + bar1: { progress: 74 }, + bar2: { visible: false }, + bar3: { visible: false }, + }, + { + data: DEFAULT_ORGANIZATION_DATA_SLICE.free + DEFAULT_ORGANIZATION_DATA_SLICE.paying * 0.7, + bar1: { progress: 100 }, + bar2: { visible: true, progress: 70, count: 0 }, + bar3: { visible: false }, + }, + { + data: DEFAULT_ORGANIZATION_DATA_SLICE.free + DEFAULT_ORGANIZATION_DATA_SLICE.paying * 1.51, + bar1: { progress: 100 }, + bar2: { visible: true, progress: 100, count: 1 }, + bar3: { visible: true, progress: 51 }, + }, + { + data: DEFAULT_ORGANIZATION_DATA_SLICE.free + DEFAULT_ORGANIZATION_DATA_SLICE.paying * 2.654, + bar1: { progress: 100 }, + bar2: { visible: true, progress: 100, count: 2 }, + bar3: { visible: true, progress: 65 }, + }, + { + data: DEFAULT_ORGANIZATION_DATA_SLICE.free + DEFAULT_ORGANIZATION_DATA_SLICE.paying * 3, + bar1: { progress: 100 }, + bar2: { visible: true, progress: 100, count: 3 }, + bar3: { visible: true, progress: 0 }, + }, + { + data: DEFAULT_ORGANIZATION_DATA_SLICE.free + DEFAULT_ORGANIZATION_DATA_SLICE.paying * 29.1, + bar1: { progress: 100 }, + bar2: { visible: true, progress: 100, count: 29 }, + bar3: { visible: true, progress: 10 }, + }, +].forEach(({ data, bar1, bar2, bar3 }) => { + msTest( + `Test stats progress bars Data(${data / (DEFAULT_ORGANIZATION_DATA_SLICE.free + DEFAULT_ORGANIZATION_DATA_SLICE.paying)})`, + async ({ clientArea }) => { + await MockBms.mockOrganizationStats(clientArea, data); + + await clientArea.locator('.menu-client').locator('.menu-client-list').getByRole('listitem').nth(1).click(); + + await expect(clientArea.locator('#firstBar')).toContainText(`${bar1.progress}%`); + + if (bar2.visible) { + await expect(clientArea.locator('#secondBar')).toBeVisible(); + await expect(clientArea.locator('#secondBar')).toContainText(`${bar2.progress}%`); + if (bar2.count === undefined || bar2.count === 0) { + await expect(clientArea.locator('#secondBar')).not.toContainText('x'); + } else { + await expect(clientArea.locator('#secondBar')).toContainText(`x${bar2.count}`); + } + } + + if (bar3.visible) { + await expect(clientArea.locator('#thirdBar')).toBeVisible(); + await expect(clientArea.locator('#thirdBar')).toContainText(`${bar3.progress}%`); + } + }, + ); +}); diff --git a/client/tests/pw/helpers/bms.ts b/client/tests/pw/helpers/bms.ts index a13d34123d5..6f60748f5eb 100644 --- a/client/tests/pw/helpers/bms.ts +++ b/client/tests/pw/helpers/bms.ts @@ -1,7 +1,7 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS import { Page } from '@playwright/test'; -import { DEFAULT_ORGANIZATION_INFORMATION, DEFAULT_USER_INFORMATION } from '@tests/pw/helpers/data'; +import { DEFAULT_ORGANIZATION_DATA_SLICE, DEFAULT_ORGANIZATION_INFORMATION, DEFAULT_USER_INFORMATION } from '@tests/pw/helpers/data'; import { DateTime } from 'luxon'; async function mockLogin(page: Page, success: boolean, timeout?: boolean): Promise { @@ -114,7 +114,15 @@ async function mockListOrganizations(page: Page): Promise { }); } -async function mockOrganizationStats(page: Page): Promise { +async function mockOrganizationStats(page: Page, data?: number): Promise { + const usersPerProfileDetail: { [profile: string]: { active: number; revoked: number } } = {}; + usersPerProfileDetail.ADMIN = { active: 4, revoked: 1 }; + usersPerProfileDetail.STANDARD = { active: 54, revoked: 1 }; + usersPerProfileDetail.OUTSIDER = { active: 1, revoked: 142 }; + + const dataSize = data ? data * 0.999 : 400000000000; + const metadataSize = data ? data - dataSize : 400000000; + await page.route( // eslint-disable-next-line max-len `**/users/${DEFAULT_USER_INFORMATION.id}/clients/${DEFAULT_USER_INFORMATION.clientId}/organizations/${DEFAULT_ORGANIZATION_INFORMATION.bmsId}/stats`, @@ -123,9 +131,19 @@ async function mockOrganizationStats(page: Page): Promise { status: 200, json: { // eslint-disable-next-line camelcase - data_size: 13374242, + users_per_profile_detail: usersPerProfileDetail, + // eslint-disable-next-line camelcase + data_size: dataSize, + // eslint-disable-next-line camelcase + metadata_size: metadataSize, + // eslint-disable-next-line camelcase + free_slice_size: DEFAULT_ORGANIZATION_DATA_SLICE.free, + // eslint-disable-next-line camelcase + paying_slice_size: DEFAULT_ORGANIZATION_DATA_SLICE.paying, + users: 1564, + // eslint-disable-next-line camelcase + active_users: 159, status: 'ok', - users: 5, }, }); }, diff --git a/client/tests/pw/helpers/data.ts b/client/tests/pw/helpers/data.ts index 9e8f21e51f3..c1bd84627ea 100644 --- a/client/tests/pw/helpers/data.ts +++ b/client/tests/pw/helpers/data.ts @@ -26,3 +26,8 @@ export const DEFAULT_ORGANIZATION_INFORMATION = { serverAddr: 'parsec3://blackmesa.com', bmsId: '42', }; + +export const DEFAULT_ORGANIZATION_DATA_SLICE = { + free: 1024 * 1024 * 1024 * 200, // 200 Gb + paying: 1024 * 1024 * 1024 * 100, // 100 Gb +};