diff --git a/ui/e2e/tests/superuser/billing-metrics-dashboard.spec.ts b/ui/e2e/tests/superuser/billing-metrics-dashboard.spec.ts new file mode 100644 index 0000000000..edbdc58e55 --- /dev/null +++ b/ui/e2e/tests/superuser/billing-metrics-dashboard.spec.ts @@ -0,0 +1,68 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { test, expect } from '@playwright/test'; +import { METRICS_DATA_RESPONSE } from '../../../tests/helpers/billing/stubs'; + +test('billing metrics dashboard workflow', async ({ page }) => { + await test.step('navigate to billing metrics page and mock data', async () => { + await page.route('**/sys/license/features', async (route) => + route.fulfill({ json: { features: ['Consumption Billing'] } }) + ); + await page.route('**/sys/billing/overview', async (route) => + route.fulfill({ json: METRICS_DATA_RESPONSE }) + ); + + await page.goto('dashboard'); + await page.getByRole('link', { name: 'Billing metrics' }).click(); + await page.waitForResponse('**/sys/billing/overview'); + }); + + await test.step('display billing metrics summary panel', async () => { + await expect(page.getByRole('button', { name: 'From start of January' })).toContainText( + 'From start of January 2026' + ); + await expect(page.getByRole('heading', { name: 'Billing metrics' })).toBeVisible(); + await expect(page.getByText('Data reflects usage across')).toContainText( + 'Data reflects usage across this Vault cluster. Billing metrics are used in license utilization.' + ); + await expect(page.getByRole('heading', { name: 'Summary' })).toBeVisible(); + + await expect(page.locator('section')).toContainText( + 'Summary Secrets 10 PKI units 100.1234 Data protection calls 420 Managed keys 430 KMIP Enabled Plugins 100' + ); + }); + + await test.step('display billing metrics details by metric panel', async () => { + await expect(page.getByRole('heading', { name: 'Details by metric' })).toBeVisible(); + + await expect(page.locator('section')).toContainText( + 'Secrets Highest number of static secrets, static roles, and dynamic roles managed on the cluster during the month. Secrets replicated to this cluster are not counted. Total 210 KV Secrets 10 Dynamic roles 130 Static roles 70' + ); + await expect(page.locator('section')).toContainText( + 'Credential units Certificates, tokens, and other credentials issued during the month, adjusted by their duration. Total 200.3702 PKI units 100.1234 SSH OTP units 50.1234 SSH certificate units 50.1234' + ); + await expect(page.locator('section')).toContainText( + 'Data protection calls Total number of data elements processed. Total 420 Transform 220 Transit 200' + ); + await expect(page.locator('section')).toContainText( + 'Managed keys Highest number of cryptographic keys managed on the cluster during the month. Keys replicated to this cluster are not counted. Total 430 TOTP 220 KMSE 210' + ); + }); + + await test.step('change the billing period date', async () => { + await page.getByRole('button', { name: 'From start of January' }).click(); + await page.getByRole('option', { name: 'From start of Dec' }).click(); + await expect(page.locator('section')).toContainText( + 'Summary Secrets 2 PKI units 100.1234 Data protection calls 220 Managed keys 220 KMIP Not enabled Plugins 100' + ); + await expect(page.locator('section')).toContainText( + 'Secrets Highest number of static secrets, static roles, and dynamic roles managed on the cluster during the month. Secrets replicated to this cluster are not counted. Total 192 KV Secrets 2 Dynamic roles 125 Static roles 65' + ); + await expect(page.getByRole('button', { name: 'From start of December' })).toContainText( + 'From start of December 2025' + ); + }); +}); diff --git a/ui/tests/helpers/billing/stubs.ts b/ui/tests/helpers/billing/stubs.ts new file mode 100644 index 0000000000..8503151efc --- /dev/null +++ b/ui/tests/helpers/billing/stubs.ts @@ -0,0 +1,201 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +export const METRICS_DATA_RESPONSE = { + request_id: 'c16f7715-e534-7ebe-439f-7bc6005a26da', + lease_id: '', + renewable: false, + lease_duration: 0, + data: { + months: [ + { + month: '2026-01', + updated_at: '2026-01-14T10:49:00Z', + usage_metrics: [ + { + metric_name: 'static_secrets', + metric_data: { + total: 10, + metric_details: [{ type: 'kv', count: 10 }], + }, + }, + { + metric_name: 'dynamic_roles', + metric_data: { + total: 130, + metric_details: [ + { type: 'aws_dynamic', count: 10 }, + { type: 'azure_dynamic', count: 10 }, + { type: 'database_dynamic', count: 10 }, + { type: 'gcp_dynamic', count: 10 }, + { type: 'ldap_dynamic', count: 10 }, + { type: 'openldap_dynamic', count: 10 }, + { type: 'alicloud_dynamic', count: 10 }, + { type: 'rabbitmq_dynamic', count: 10 }, + { type: 'consul_dynamic', count: 10 }, + { type: 'nomad_dynamic', count: 10 }, + { type: 'kubernetes_dynamic', count: 10 }, + { type: 'mongodbatlas_dynamic', count: 10 }, + { type: 'terraform_dynamic', count: 10 }, + ], + }, + }, + { + metric_name: 'auto_rotated_roles', + metric_data: { + total: 70, + metric_details: [ + { type: 'aws_static', count: 10 }, + { type: 'azure_static', count: 10 }, + { type: 'database_static', count: 10 }, + { type: 'gcp_static', count: 10 }, + { type: 'gcp_impersonated', count: 10 }, + { type: 'ldap_static', count: 10 }, + { type: 'openldap_static', count: 10 }, + ], + }, + }, + { + metric_name: 'kmip', + metric_data: { + used_in_month: true, + }, + }, + { + metric_name: 'pki_units', + metric_data: { total: 100.1234 }, + }, + { + metric_name: 'ssh_units', + metric_data: { + total: 100.2468, + metric_details: [ + { type: 'otp_units', count: 50.1234 }, + { type: 'certificate_units', count: 50.1234 }, + ], + }, + }, + { + metric_name: 'external_plugins', + metric_data: { total: 100 }, + }, + { + metric_name: 'data_protection_calls', + metric_data: { + total: 420, + metric_details: [ + { type: 'transit', count: 200 }, + { type: 'transform', count: 220 }, + ], + }, + }, + { + metric_name: 'managed_keys', + metric_data: { + total: 430, + metric_details: [ + { type: 'kmse', count: 210 }, + { type: 'totp', count: 220 }, + ], + }, + }, + ], + }, + { + month: '2025-12', + updated_at: '2026-01-14T10:49:00Z', + usage_metrics: [ + { + metric_name: 'static_secrets', + metric_data: { + total: 2, + metric_details: [{ type: 'kv', count: 2 }], + }, + }, + { + metric_name: 'dynamic_roles', + metric_data: { + total: 125, + metric_details: [ + { type: 'aws_dynamic', count: 5 }, + { type: 'azure_dynamic', count: 10 }, + { type: 'database_dynamic', count: 10 }, + { type: 'gcp_dynamic', count: 10 }, + { type: 'ldap_dynamic', count: 10 }, + { type: 'openldap_dynamic', count: 10 }, + { type: 'alicloud_dynamic', count: 10 }, + { type: 'rabbitmq_dynamic', count: 10 }, + { type: 'consul_dynamic', count: 10 }, + { type: 'nomad_dynamic', count: 10 }, + { type: 'kubernetes_dynamic', count: 10 }, + { type: 'mongodbatlas_dynamic', count: 10 }, + { type: 'terraform_dynamic', count: 10 }, + ], + }, + }, + { + metric_name: 'auto_rotated_roles', + metric_data: { + total: 65, + metric_details: [ + { type: 'aws_static', count: 5 }, + { type: 'azure_static', count: 10 }, + { type: 'database_static', count: 10 }, + { type: 'gcp_static', count: 10 }, + { type: 'gcp_impersonated', count: 10 }, + { type: 'ldap_static', count: 10 }, + { type: 'openldap_static', count: 10 }, + ], + }, + }, + { + metric_name: 'kmip', + metric_data: { + used_in_month: false, + }, + }, + { + metric_name: 'pki_units', + metric_data: { total: 100.1234 }, + }, + { + metric_name: 'ssh_units', + metric_data: { + total: 100.2468, + metric_details: [ + { type: 'otp_units', count: 50.1234 }, + { type: 'certificate_units', count: 50.1234 }, + ], + }, + }, + { + metric_name: 'external_plugins', + metric_data: { total: 100 }, + }, + { + metric_name: 'data_protection_calls', + metric_data: { + total: 220, + metric_details: [ + { type: 'transit', count: 200 }, + { type: 'transform', count: 220 }, + ], + }, + }, + { + metric_name: 'managed_keys', + metric_data: { + total: 220, + metric_details: [ + { type: 'kmse', count: 200 }, + { type: 'totp', count: 220 }, + ], + }, + }, + ], + }, + ], + }, +};