Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 5 additions & 0 deletions web/cypress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ Set the following var to specify the cluster timezone for incident timeline calc
export CYPRESS_TIMEZONE=<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
Expand Down
9 changes: 9 additions & 0 deletions web/cypress/configure-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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-}"
Expand Down Expand Up @@ -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-}
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}"
Expand Down
1 change: 1 addition & 0 deletions web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions web/cypress/e2e/incidents/01.incidents.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -86,6 +88,8 @@ describe('BVT: Incidents - UI', () => {
it('5. Admin perspective - Incidents page - Traverse Incident Table', () => {
cy.log('5.1 Traverse incident table');
incidentsPage.clearAllFilters();
cy.mockIncidents([]);
incidentsPage.findIncidentWithAlert('TargetAlert').should('be.false');

cy.log('5.2 Verify traversing incident table works when the alert is not present');
cy.mockIncidentFixture('incident-scenarios/1-single-incident-firing-critical-and-warning-alerts.yaml');
Expand Down
3 changes: 3 additions & 0 deletions web/cypress/fixtures/export.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@ export CYPRESS_MCP_CONSOLE_IMAGE=<Monitoring Console Plugin image>
# Set the following var to specify the cluster timezone for incident timeline calculations. Defaults to UTC if not specified.
export CYPRESS_TIMEZONE=<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
36 changes: 36 additions & 0 deletions web/cypress/support/incidents_prometheus_query_mocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,24 @@ 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
- **Query Filtering**: Mock data is filtered based on Prometheus query parameters
- **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

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -113,7 +116,7 @@ export function createIncidentMock(incidents: IncidentDefinition[], query?: stri

incident.alerts.forEach(alert => {
const metric: Record<string, string> = {
__name__: 'cluster:health:components:map',
__name__: versioned_metric,
component: incident.component,
layer: incident.layer,
group_id: incident.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,40 @@ declare global {
interface Chainable {
mockIncidents(incidents: IncidentDefinition[]): Chainable<Element>;
mockIncidentFixture(fixturePath: string): Chainable<Element>;
transformMetrics(): Chainable<Element>;
}
}
}

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') || '';

console.log(`INTERCEPTED: ${req.method} ${req.url}`);
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: {
Expand Down Expand Up @@ -85,4 +93,47 @@ 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();
}
});
});
Loading