mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 08:09:27 +00:00
Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com>
This commit is contained in:
co-authored by
Shrinath-O2
parent
2df66c4494
commit
cd0eff459d
@@ -310,6 +310,19 @@ export class AlertsPage {
|
||||
alertListTabRealtime: '[data-test="alert-list-tab-realTime"]',
|
||||
alertListFilterChip: '[data-test="alert-list-filter-chip"]',
|
||||
|
||||
// Frequency (cadence) cell — auto-generated by OTable as o2-table-cell-{columnId}
|
||||
frequencyCell: '[data-test="o2-table-cell-frequency"]',
|
||||
|
||||
// Evaluation-history retries cell (AlertEvaluationHistory.vue)
|
||||
alertDetailsHistoryRetriesCell: '[data-test="alerts-alertevaluationhistory-retries"]',
|
||||
|
||||
// Section tab strip (AlertSectionTabs.vue) — header tab switcher
|
||||
alertSectionTabs: '[data-test="alert-section-tabs"]',
|
||||
alertSectionTabAlertList: '[data-test="alert-section-tab-alertList"]',
|
||||
alertSectionTabAlertDestinations: '[data-test="alert-section-tab-alertDestinations"]',
|
||||
alertSectionTabAlertTemplates: '[data-test="alert-section-tab-alertTemplates"]',
|
||||
alertSectionTabAlertLibrary: '[data-test="alert-section-tab-alertLibrary"]',
|
||||
|
||||
// v3 AddAlert wizard tabs (OToggleGroupItem with data-test="add-alert-tab-{key}")
|
||||
addAlertTabCondition: '[data-test="add-alert-tab-condition"]',
|
||||
addAlertTabAdvanced: '[data-test="add-alert-tab-advanced"]',
|
||||
@@ -1351,6 +1364,105 @@ export class AlertsPage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click an alert-type filter tab (all / scheduled / realTime) and wait until it is
|
||||
* actually active (data-state="on") before returning, so callers never assert a
|
||||
* downstream column against a tab whose click silently failed.
|
||||
* @param {'all' | 'scheduled' | 'realTime'} value
|
||||
*/
|
||||
async clickAlertTypeTab(value) {
|
||||
const tabs = {
|
||||
all: this.locators.alertListTabAll,
|
||||
scheduled: this.locators.alertListTabScheduled,
|
||||
realTime: this.locators.alertListTabRealtime,
|
||||
};
|
||||
const tab = this.page.locator(tabs[value]);
|
||||
await expect(tab).toBeVisible({ timeout: 10000 });
|
||||
await tab.click();
|
||||
await expect(tab).toHaveAttribute('data-state', 'on', { timeout: 10000 });
|
||||
testLogger.info('Switched alert type tab', { value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the "Check every" (frequency) cell for the given alert row renders `expected`
|
||||
* (raw cron string, "N Mins", or "--"). Narrows the list to the alert first so the
|
||||
* row is present even when the list paginates past it.
|
||||
* @param {string} name
|
||||
* @param {string} expected
|
||||
*/
|
||||
async expectFrequencyCell(name, expected) {
|
||||
await this.searchAlert(name);
|
||||
const row = this.page.locator('tbody tr').filter({ hasText: name }).first();
|
||||
await expect(row).toBeVisible({ timeout: 15000 });
|
||||
const cell = row.locator(this.locators.frequencyCell);
|
||||
await expect(cell).toBeVisible({ timeout: 10000 });
|
||||
await expect(cell).toHaveText(expected, { timeout: 10000 });
|
||||
testLogger.info('Frequency cell asserted', { name, expected });
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the evaluation-history table renders a "Retries" column with at least one
|
||||
* numeric retries cell (the column lives between Condition and Evaluation Time).
|
||||
*/
|
||||
async expectEvaluationHistoryRetriesColumn() {
|
||||
const headerText = await this.page
|
||||
.locator(this.locators.alertDetailsHistoryTable)
|
||||
.locator('thead')
|
||||
.innerText();
|
||||
expect(headerText, 'evaluation history must include a Retries column').toContain('Retries');
|
||||
const retriesCell = this.page.locator(this.locators.alertDetailsHistoryRetriesCell).first();
|
||||
await expect(retriesCell).toBeVisible({ timeout: 15000 });
|
||||
testLogger.info('Evaluation history Retries column and cells present');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the newest evaluation's retries cell renders the given integer (0 for a clean
|
||||
* self-delivery). Newest-first row ordering means `.first()` is the latest run.
|
||||
* @param {number} value
|
||||
*/
|
||||
async expectEvaluationHistoryRetriesValue(value) {
|
||||
const cell = this.page.locator(this.locators.alertDetailsHistoryRetriesCell).first();
|
||||
await expect(cell).toBeVisible({ timeout: 15000 });
|
||||
await expect(cell).toHaveText(String(value), { timeout: 10000 });
|
||||
testLogger.info('Evaluation history retries value verified', { value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the section tab strip renders the given labels left-to-right in order.
|
||||
* @param {string[]} labels
|
||||
*/
|
||||
async expectSectionTabLabels(labels) {
|
||||
const strip = this.page.locator(this.locators.alertSectionTabs);
|
||||
await expect(strip).toBeVisible({ timeout: 10000 });
|
||||
const text = await strip.innerText();
|
||||
let lastIndex = -1;
|
||||
for (const label of labels) {
|
||||
const index = text.indexOf(label);
|
||||
expect(index, `section tab strip must show "${label}" in order`).toBeGreaterThan(lastIndex);
|
||||
lastIndex = index;
|
||||
}
|
||||
testLogger.info('Section tab labels verified in order', { labels });
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert a specific section tab renders the exact label text (proves the rename landed:
|
||||
* "Notification Destinations" / "Alert Library" rather than the deleted short forms).
|
||||
* @param {'alertList' | 'alertDestinations' | 'alertTemplates' | 'alertLibrary'} key
|
||||
* @param {string} text
|
||||
*/
|
||||
async expectSectionTabLabel(key, text) {
|
||||
const tabs = {
|
||||
alertList: this.locators.alertSectionTabAlertList,
|
||||
alertDestinations: this.locators.alertSectionTabAlertDestinations,
|
||||
alertTemplates: this.locators.alertSectionTabAlertTemplates,
|
||||
alertLibrary: this.locators.alertSectionTabAlertLibrary,
|
||||
};
|
||||
const tab = this.page.locator(tabs[key]);
|
||||
await expect(tab).toBeVisible({ timeout: 10000 });
|
||||
await expect(tab).toHaveText(text, { timeout: 10000 });
|
||||
testLogger.info('Section tab label verified', { key, text });
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Advanced-tab template override OSelect dropdown.
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,10 @@ const testLogger = require('../utils/test-logger.js');
|
||||
const PageManager = require('../../pages/page-manager.js');
|
||||
const logData = require('../../fixtures/log.json');
|
||||
const { getOrgIdentifier } = require('../utils/cloud-auth.js');
|
||||
const { findAlertId, getAlert, deleteAlerts, seedAlertFixtures, DEST } = require('../utils/alerts-api-helpers.js');
|
||||
const {
|
||||
findAlertId, getAlert, deleteAlerts, seedAlertFixtures, DEST,
|
||||
simpleAlert, realtimeAlert, cronAlert, createAlert, waitForAlertOutcome,
|
||||
} = require('../utils/alerts-api-helpers.js');
|
||||
|
||||
const NETWORK_IDLE_TIMEOUT_MS = 30000;
|
||||
const TEST_STREAM = 'e2e_automate';
|
||||
@@ -176,4 +179,104 @@ test.describe('Alerts — priority, tags & additional variables', {
|
||||
});
|
||||
testLogger.info('Alert list column set verified');
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Frequency ("Check every") cadence cell + column conditional (issue #14038)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
test('frequency cell renders the cadence for cron, minute-interval and real-time alerts', {
|
||||
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P0'],
|
||||
}, async ({ page }) => {
|
||||
const suffix = Math.random().toString(36).substring(2, 8);
|
||||
const cronName = 'auto_freq_cron_' + suffix;
|
||||
const minsName = 'auto_freq_mins_' + suffix;
|
||||
const rtName = 'auto_freq_rt_' + suffix;
|
||||
createdAlertNames.push(cronName, minsName, rtName);
|
||||
|
||||
const cronResp = await createAlert(page, cronAlert(cronName));
|
||||
const minsResp = await createAlert(page, simpleAlert(minsName));
|
||||
const rtResp = await createAlert(page, realtimeAlert(rtName));
|
||||
expect(cronResp.ok(), `cron alert create failed: ${cronResp.status()}`).toBeTruthy();
|
||||
expect(minsResp.ok(), `minutes alert create failed: ${minsResp.status()}`).toBeTruthy();
|
||||
expect(rtResp.ok(), `realtime alert create failed: ${rtResp.status()}`).toBeTruthy();
|
||||
|
||||
await page.goto(`${logData.alertUrl}?org_identifier=${getOrgIdentifier()}`);
|
||||
await page.waitForLoadState('networkidle', { timeout: NETWORK_IDLE_TIMEOUT_MS }).catch(() => {});
|
||||
|
||||
await pm.alertsPage.expectFrequencyCell(cronName, '0 */10 * * * *');
|
||||
await pm.alertsPage.expectFrequencyCell(minsName, '10 Mins');
|
||||
await pm.alertsPage.expectFrequencyCell(rtName, '--');
|
||||
testLogger.info('Frequency cells render cron, minutes and realtime cadence correctly');
|
||||
});
|
||||
|
||||
test('Check every column is present on scheduled and absent on the realTime tab', {
|
||||
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P0'],
|
||||
}, async ({ page }) => {
|
||||
const suffix = Math.random().toString(36).substring(2, 8);
|
||||
const schedName = 'auto_tab_sched_' + suffix;
|
||||
const rtName = 'auto_tab_rt_' + suffix;
|
||||
createdAlertNames.push(schedName, rtName);
|
||||
|
||||
// One scheduled + one real-time alert so both tabs render a table header to assert against.
|
||||
await createAlert(page, simpleAlert(schedName));
|
||||
await createAlert(page, realtimeAlert(rtName));
|
||||
|
||||
await page.goto(`${logData.alertUrl}?org_identifier=${getOrgIdentifier()}`);
|
||||
await page.waitForLoadState('networkidle', { timeout: NETWORK_IDLE_TIMEOUT_MS }).catch(() => {});
|
||||
|
||||
await pm.alertsPage.clickAlertTypeTab('scheduled');
|
||||
await pm.alertsPage.expectListColumns({ present: ['Check every'], absent: [] });
|
||||
|
||||
await pm.alertsPage.clickAlertTypeTab('realTime');
|
||||
await pm.alertsPage.expectListColumns({ present: [], absent: ['Check every'] });
|
||||
testLogger.info('Check every column present on scheduled, absent on realTime');
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Retries column in the per-alert evaluation history
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
test('evaluation history shows a Retries column with a numeric value', {
|
||||
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P1'],
|
||||
}, async ({ page }) => {
|
||||
const suffix = Math.random().toString(36).substring(2, 8);
|
||||
const retryName = 'auto_retry_' + suffix;
|
||||
createdAlertNames.push(retryName);
|
||||
|
||||
// Fast cadence so the scheduler evaluates it within the waitForAlertOutcome window.
|
||||
const retryAlert = simpleAlert(retryName);
|
||||
retryAlert.trigger_condition.frequency = 1;
|
||||
const createResp = await createAlert(page, retryAlert);
|
||||
expect(createResp.ok(), `alert create failed: ${createResp.status()}`).toBeTruthy();
|
||||
|
||||
// Guarantee a recent evaluation so the history endpoint returns a hit.
|
||||
const evaluated = await waitForAlertOutcome(page, retryName);
|
||||
expect(evaluated, `scheduled alert ${retryName} must be evaluated within the timeout`).toBeTruthy();
|
||||
expect(evaluated.last_outcome, 'the alert must have a run outcome').toBeTruthy();
|
||||
|
||||
const id = await findAlertId(page, retryName);
|
||||
expect(id, 'the evaluated alert must be findable by name').toBeTruthy();
|
||||
|
||||
await pm.alertDetailPage.open(id);
|
||||
await pm.alertsPage.expectAlertDetailsHistorySectionVisible();
|
||||
|
||||
await pm.alertsPage.expectEvaluationHistoryRetriesColumn();
|
||||
await pm.alertsPage.expectEvaluationHistoryRetriesValue(0);
|
||||
testLogger.info('Retries column renders a numeric value in evaluation history');
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Section tab strip agrees with the nav rail
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
test('the alert section tab strip shows the renamed labels', {
|
||||
tag: ['@alert-list-frequency-retries', '@all', '@alerts', '@P1'],
|
||||
}, async ({ page }) => {
|
||||
await page.goto(`${logData.alertUrl}?org_identifier=${getOrgIdentifier()}`);
|
||||
await page.waitForLoadState('networkidle', { timeout: NETWORK_IDLE_TIMEOUT_MS }).catch(() => {});
|
||||
|
||||
await pm.alertsPage.expectSectionTabLabels([
|
||||
'All Alerts', 'Notification Destinations', 'Destination Templates', 'Alert Library',
|
||||
]);
|
||||
await pm.alertsPage.expectSectionTabLabel('alertDestinations', 'Notification Destinations');
|
||||
await pm.alertsPage.expectSectionTabLabel('alertLibrary', 'Alert Library');
|
||||
testLogger.info('Section tab strip shows the renamed labels');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,6 +81,18 @@ function realtimeAlert(name) {
|
||||
return a;
|
||||
}
|
||||
|
||||
/** A scheduled cron alert whose cadence cell renders the raw cron string verbatim. */
|
||||
function cronAlert(name) {
|
||||
const a = simpleAlert(name);
|
||||
a.trigger_condition = {
|
||||
...a.trigger_condition,
|
||||
frequency_type: 'cron',
|
||||
frequency: 1,
|
||||
cron: '0 */10 * * * *',
|
||||
};
|
||||
return a;
|
||||
}
|
||||
|
||||
/** V1 composite payload. Operands are stable alert IDs, never display names. */
|
||||
function compositeAlert(name, childIds, overrides = {}) {
|
||||
return {
|
||||
@@ -228,7 +240,7 @@ function isFiringOutcome(outcome) {
|
||||
module.exports = {
|
||||
BASE, STREAM, SINK, TMPL, DEST,
|
||||
uniq, urls, api,
|
||||
simpleAlert, multiAlert, groupedSimpleAlert, realtimeAlert,
|
||||
simpleAlert, multiAlert, groupedSimpleAlert, realtimeAlert, cronAlert,
|
||||
compositeAlert, validateComposite, getCompositeReferences,
|
||||
createAlert, listAlerts, findAlertId, getAlert, deleteAlerts, seedAlertFixtures,
|
||||
ingest, getAlertGroups, getAlertTransitions,
|
||||
|
||||
Reference in New Issue
Block a user