diff --git a/web/cypress.config.ts b/web/cypress.config.ts index 47aad4b4..172007a4 100644 --- a/web/cypress.config.ts +++ b/web/cypress.config.ts @@ -22,6 +22,7 @@ export default defineConfig({ LOGIN_USERNAME: process.env.CYPRESS_LOGIN_USERS.split(',')[0].split(':')[0], LOGIN_PASSWORD: process.env.CYPRESS_LOGIN_USERS.split(',')[0].split(':')[1], TIMEZONE: process.env.CYPRESS_TIMEZONE || 'UTC', + MOCK_NEW_METRICS: process.env.CYPRESS_MOCK_NEW_METRICS || 'false', typeDelay: 200, }, fixturesFolder: 'cypress/fixtures', diff --git a/web/cypress/README.md b/web/cypress/README.md index 2b06d5d2..37366440 100644 --- a/web/cypress/README.md +++ b/web/cypress/README.md @@ -67,6 +67,11 @@ Set the following var to specify the cluster timezone for incident timeline calc export CYPRESS_TIMEZONE= ``` +Set the following var to transform old metric names to new format in mocks (temporary workaround for testing against locally built instances). +```bash +export CYPRESS_MOCK_NEW_METRICS=false +``` + Set the following var to enable Cypress session management for faster test execution. ```bash export CYPRESS_SESSION=true diff --git a/web/cypress/configure-env.sh b/web/cypress/configure-env.sh index 5db5df4a..96cf978f 100755 --- a/web/cypress/configure-env.sh +++ b/web/cypress/configure-env.sh @@ -173,6 +173,7 @@ print_current_config() { print_var "CYPRESS_CUSTOM_COO_BUNDLE_IMAGE" "${CYPRESS_CUSTOM_COO_BUNDLE_IMAGE-}" print_var "CYPRESS_MCP_CONSOLE_IMAGE" "${CYPRESS_MCP_CONSOLE_IMAGE-}" print_var "CYPRESS_TIMEZONE" "${CYPRESS_TIMEZONE-}" + print_var "CYPRESS_MOCK_NEW_METRICS" "${CYPRESS_MOCK_NEW_METRICS-}" print_var "CYPRESS_SESSION" "${CYPRESS_SESSION-}" print_var "CYPRESS_SKIP_KBV_INSTALL" "${CYPRESS_SKIP_KBV_INSTALL-}" print_var "CYPRESS_KBV_UI_INSTALL" "${CYPRESS_KBV_UI_INSTALL-}" @@ -220,6 +221,7 @@ main() { local def_custom_coo_bundle=${CYPRESS_CUSTOM_COO_BUNDLE_IMAGE-} local def_mcp_console_image=${CYPRESS_MCP_CONSOLE_IMAGE-} local def_timezone=${CYPRESS_TIMEZONE-} + local def_mock_new_metrics=${CYPRESS_MOCK_NEW_METRICS-} local def_session=${CYPRESS_SESSION-} local def_skip_kbv=${CYPRESS_SKIP_KBV_INSTALL-} local def_kbv_ui_install=${CYPRESS_KBV_UI_INSTALL-} @@ -416,6 +418,11 @@ main() { local timezone timezone=$(ask "Cluster timezone (CYPRESS_TIMEZONE)" "${def_timezone:-UTC}") + local mock_new_metrics_ans + mock_new_metrics_ans=$(ask_yes_no "Transform old metric names to new format in mocks? (sets CYPRESS_MOCK_NEW_METRICS)" "$(bool_to_default_yn "$def_mock_new_metrics")") + local mock_new_metrics="false" + [[ "$mock_new_metrics_ans" == "y" ]] && mock_new_metrics="true" + local session_ans session_ans=$(ask_yes_no "Enable Cypress session management for faster test execution? (sets CYPRESS_SESSION)" "$(bool_to_default_yn "$def_session")") local session="false" @@ -464,6 +471,7 @@ main() { if [[ -n "$timezone" ]]; then export_lines+=("export CYPRESS_TIMEZONE='$(printf %s "$timezone" | escape_for_single_quotes)'" ) fi + export_lines+=("export CYPRESS_MOCK_NEW_METRICS='$(printf %s "$mock_new_metrics" | escape_for_single_quotes)'" ) export_lines+=("export CYPRESS_SESSION='$(printf %s "$session" | escape_for_single_quotes)'" ) if [[ -n "$skip_kbv_install" ]]; then @@ -511,6 +519,7 @@ main() { [[ -n "${CYPRESS_CUSTOM_COO_BUNDLE_IMAGE-}$custom_coo_bundle" ]] && echo " CYPRESS_CUSTOM_COO_BUNDLE_IMAGE=${CYPRESS_CUSTOM_COO_BUNDLE_IMAGE:-$custom_coo_bundle}" [[ -n "${CYPRESS_MCP_CONSOLE_IMAGE-}$mcp_console_image" ]] && echo " CYPRESS_MCP_CONSOLE_IMAGE=${CYPRESS_MCP_CONSOLE_IMAGE:-$mcp_console_image}" [[ -n "${CYPRESS_TIMEZONE-}$timezone" ]] && echo " CYPRESS_TIMEZONE=${CYPRESS_TIMEZONE:-$timezone}" + echo " CYPRESS_MOCK_NEW_METRICS=${CYPRESS_MOCK_NEW_METRICS:-$mock_new_metrics}" echo " CYPRESS_SESSION=${CYPRESS_SESSION:-$session}" echo " CYPRESS_SKIP_KBV_INSTALL=${CYPRESS_SKIP_KBV_INSTALL:-$skip_kbv_install}" echo " CYPRESS_KBV_UI_INSTALL=${CYPRESS_KBV_UI_INSTALL:-$kbv_ui_install}" diff --git a/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts b/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts index 7cbdc3c1..7ae7ae5b 100644 --- a/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts +++ b/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts @@ -37,6 +37,7 @@ describe('BVT: Incidents - e2e', () => { }); it('1. Admin perspective - Incidents page - Incident with custom alert lifecycle', () => { + cy.transformMetrics(); cy.log('1.1 Navigate to Incidents page and clear filters'); incidentsPage.goTo(); incidentsPage.clearAllFilters(); diff --git a/web/cypress/e2e/incidents/01.incidents.cy.ts b/web/cypress/e2e/incidents/01.incidents.cy.ts index 4ba586c4..36718ccb 100644 --- a/web/cypress/e2e/incidents/01.incidents.cy.ts +++ b/web/cypress/e2e/incidents/01.incidents.cy.ts @@ -40,6 +40,8 @@ describe('BVT: Incidents - UI', () => { beforeEach(() => { cy.log('Navigate to Observe → Incidents'); incidentsPage.goTo(); + // Temporary workaround for testing against locally built instances. + cy.transformMetrics(); }); it('1. Admin perspective - Incidents page - Toolbar and charts toggle functionality', () => { diff --git a/web/cypress/fixtures/export.sh b/web/cypress/fixtures/export.sh index bc8dd511..13671dcc 100755 --- a/web/cypress/fixtures/export.sh +++ b/web/cypress/fixtures/export.sh @@ -36,5 +36,8 @@ export CYPRESS_MCP_CONSOLE_IMAGE= # Set the following var to specify the cluster timezone for incident timeline calculations. Defaults to UTC if not specified. export CYPRESS_TIMEZONE= +# Set the following var to transform old metric names to new format in mocks (for testing against locally built instances) +export CYPRESS_MOCK_NEW_METRICS=false + # Set the following var to enable Cypress session management for faster test execution. export CYPRESS_SESSION=true diff --git a/web/cypress/support/incidents_prometheus_query_mocks/README.md b/web/cypress/support/incidents_prometheus_query_mocks/README.md index c6bcc5cc..2020f2f6 100644 --- a/web/cypress/support/incidents_prometheus_query_mocks/README.md +++ b/web/cypress/support/incidents_prometheus_query_mocks/README.md @@ -41,6 +41,16 @@ const incidents: IncidentDefinition[] = [ cy.mockIncidents(incidents); ``` +### Metric Transformation for Locally Built Instances + +```typescript +// Transform old metric names to new format (for testing against locally built instances) +cy.transformMetrics(); + +// Then visit the page or perform actions that trigger Prometheus queries +cy.visit('/monitoring/incidents'); +``` + ## Key Features - **Schema Validation**: All YAML fixtures are validated against JSON Schema @@ -48,6 +58,7 @@ cy.mockIncidents(incidents); - **Timeline Support**: Define incident start/end times and severity changes - **Timezone Configuration**: Set timezone via `CYPRESS_TIMEZONE` environment variable - **Multiple Query Types**: Supports both `cluster:health:components:map` and `ALERTS` queries +- **Metric Transformation**: Transform old metric names to new format via `cy.transformMetrics()` ## File Structure @@ -104,6 +115,31 @@ export CYPRESS_TIMEZONE="Asia/Tokyo" Default: UTC (if `CYPRESS_TIMEZONE` is not set) +### Metric Transformation + +Enable transformation of old metric names to new format for testing against locally built instances: + +```bash +export CYPRESS_MOCK_NEW_METRICS=true +``` + +When enabled, `cy.transformMetrics()` will intercept Prometheus queries and transform both request and response: +- **Request**: `cluster:health:components:map` → `cluster_health_components_map` +- **Response**: `cluster:health:components:map` → `cluster_health_components_map` + +**Usage:** +```typescript +// Call before visiting pages that make Prometheus queries +cy.transformMetrics(); +cy.visit('/monitoring/incidents'); +``` + +**Use Cases:** +- **`CYPRESS_MOCK_NEW_METRICS=false`** (default): Test against current release/backend +- **`CYPRESS_MOCK_NEW_METRICS=true`**: Test against locally built instances with new metric format + +Default: `false` (if `CYPRESS_MOCK_NEW_METRICS` is not set) + ## YAML Fixture Format ```yaml diff --git a/web/cypress/support/incidents_prometheus_query_mocks/mock-generators.ts b/web/cypress/support/incidents_prometheus_query_mocks/mock-generators.ts index 635cf6f6..76a3ce46 100644 --- a/web/cypress/support/incidents_prometheus_query_mocks/mock-generators.ts +++ b/web/cypress/support/incidents_prometheus_query_mocks/mock-generators.ts @@ -2,6 +2,7 @@ import { IncidentDefinition, PrometheusResult } from './types'; import { severityToValue, parseQueryLabels } from './utils'; import { nowInClusterTimezone } from './utils'; +import { NEW_METRIC_NAME, OLD_METRIC_NAME } from './prometheus-mocks'; /** * Generates 5-minute interval timestamps between start and end time @@ -98,6 +99,8 @@ export function createIncidentMock(incidents: IncidentDefinition[], query?: stri // Parse query to extract label selectors if provided const queryLabels = query ? parseQueryLabels(query) : {}; + const versioned_metric = query?.includes(NEW_METRIC_NAME) ? NEW_METRIC_NAME : OLD_METRIC_NAME; + incidents.forEach(incident => { // Filter incidents based on query parameters if (queryLabels.group_id) { @@ -113,7 +116,7 @@ export function createIncidentMock(incidents: IncidentDefinition[], query?: stri incident.alerts.forEach(alert => { const metric: Record = { - __name__: 'cluster:health:components:map', + __name__: versioned_metric, component: incident.component, layer: incident.layer, group_id: incident.id, diff --git a/web/cypress/support/incidents_prometheus_query_mocks/prometheus-mocks.ts b/web/cypress/support/incidents_prometheus_query_mocks/prometheus-mocks.ts index a6f79ed2..4df5c8a8 100644 --- a/web/cypress/support/incidents_prometheus_query_mocks/prometheus-mocks.ts +++ b/web/cypress/support/incidents_prometheus_query_mocks/prometheus-mocks.ts @@ -7,17 +7,22 @@ declare global { interface Chainable { mockIncidents(incidents: IncidentDefinition[]): Chainable; mockIncidentFixture(fixturePath: string): Chainable; + transformMetrics(): Chainable; } } } +export const NEW_METRIC_NAME = 'cluster_health_components_map'; +export const OLD_METRIC_NAME = 'cluster:health:components:map'; +const MOCK_QUERY = '/api/prometheus/api/v1/query_range*'; + /** * Main mocking function - sets up cy.intercept for Prometheus query_range API * Intercepts the query_range API and returns the mock data for the incidents * @param incidents */ export function mockPrometheusQueryRange(incidents: IncidentDefinition[]): void { - cy.intercept('GET', '/api/prometheus/api/v1/query_range*', (req) => { + cy.intercept('GET', MOCK_QUERY, (req) => { const url = new URL(req.url, window.location.origin); const query = url.searchParams.get('query') || ''; @@ -25,14 +30,17 @@ export function mockPrometheusQueryRange(incidents: IncidentDefinition[]): void console.log(`Query: ${query}`); let results: any[]; + + const versioned_metric = query.includes(NEW_METRIC_NAME) + ? NEW_METRIC_NAME: OLD_METRIC_NAME; - if (!(query.includes('cluster:health:components:map') || query.includes('ALERTS{'))) { + if (!(query.includes(versioned_metric) || query.includes('ALERTS{'))) { console.log(`Passing through non-mocked query`); req.continue(); return; } - results = query.includes('cluster:health:components:map') ? createIncidentMock(incidents, query) : createAlertDetailsMock(incidents, query); + results = query.includes(versioned_metric) ? createIncidentMock(incidents, query) : createAlertDetailsMock(incidents, query); const response: PrometheusResponse = { status: 'success', data: { @@ -85,4 +93,46 @@ Cypress.Commands.add('mockIncidentFixture', (fixturePath: string) => { // The mocking is not applied until the page is reloaded and the components fetch the new data cy.reload(); +}); + +Cypress.Commands.add('transformMetrics', () => { + cy.log('=== SETTING UP METRIC TRANSFORMATION ==='); + const mockNewMetrics = Cypress.env('MOCK_NEW_METRICS') === true; + + if (!mockNewMetrics) { + cy.log('CYPRESS_MOCK_NEW_METRICS is disabled, skipping transformation'); + return; + } + + cy.log('Transforming old metric queries to new format'); + + cy.intercept('GET', MOCK_QUERY, (req) => { + const url = new URL(req.url, window.location.origin); + const query = url.searchParams.get('query') || ''; + const hasNewMetric = query.includes(NEW_METRIC_NAME); + + if (hasNewMetric) { + const transformedQuery = query.replace(new RegExp(NEW_METRIC_NAME, 'g'), OLD_METRIC_NAME); + console.log(`Transforming metric query: ${query} -> ${transformedQuery}`); + + // Update the URL with the transformed query + url.searchParams.set('query', transformedQuery); + req.url = url.toString(); + + // Also transform the response to use new metric names + req.continue((res) => { + if (res.body?.data?.result) { + res.body.data.result.forEach((result: any) => { + if (result?.metric?.__name__ === OLD_METRIC_NAME) { + console.log(`Transforming response metric name: ${OLD_METRIC_NAME} -> ${NEW_METRIC_NAME}`); + result.metric.__name__ = NEW_METRIC_NAME; + } + }); + } + res.send(); + }); + } else { + req.continue(); + } + }); }); \ No newline at end of file