From cb178787f2a13cc110bcbd8c91c1d54a9040bcbd Mon Sep 17 00:00:00 2001
From: Ashish Kolhe <35160958+oasisk@users.noreply.github.com>
Date: Fri, 2 Jan 2026 14:11:31 +0530
Subject: [PATCH] chore: incidents as separate tabs (#9819)
### **PR Type**
Enhancement, Tests
___
### **Description**
- Replace icon tabs with reusable app-tabs component
- Introduce Alerts/Incidents viewMode switching logic
- Add extensive UI tests for new tab functionality
- Update getPath logic and dev script configuration
___
### Diagram Walkthrough
```mermaid
flowchart LR
TabsComp["app-tabs component"] --> ViewState["viewMode state"]
ViewState --> AlertsView["Alerts view UI"]
ViewState --> IncidentsView["Incidents view UI"]
Tests["UI tests"] --> TabsComp
```
File Walkthrough
| Relevant
files |
|---|
| Bug
fix |
zincutils.tsRefine getPath base URL
logic
web/src/utils/zincutils.ts
- refine getPath logic for web base path
- conditionally
prepend "/web/" on localhost
|
+1/-1
|
|
| Enhancement |
AlertList.vueReplace custom tabs
with app-tabs
web/src/components/alerts/AlertList.vue
- remove custom icon-only view tabs
- integrate
app-tabs for view switching - add
viewTabs computed property - update related SCSS
styling
|
+35/-92
|
|
| Tests |
alertsPage.jsAdd view mode tab
methods and locators
tests/ui-testing/pages/alertsPages/alertsPage.js
- add locators for alert and incident tabs
- implement
click and expect methods for tabs
- add search methods for both
views
|
+113/-0
|
alertsIncidentsTabs.spec.jsAdd E2E
tests for alerts/incidents tabs
tests/ui-testing/playwright-tests/Alerts/alertsIncidentsTabs.spec.js
- add E2E tests for tabs visibility and switching
- cover
functional and edge-case scenarios
|
+347/-0
|
|
| Configuration
changes |
package.jsonAdd base path to dev
script
web/package.json
- update `dev` script to include base `/web/`
|
+1/-1
|
|
___
---
.../pages/alertsPages/alertsPage.js | 113 ++++++
tests/ui-testing/pages/commonActions.js | 3 +-
.../Alerts/alertsIncidentsTabs.spec.js | 347 ++++++++++++++++++
web/package.json | 2 +-
web/src/components/alerts/AlertList.spec.ts | 53 ++-
web/src/components/alerts/AlertList.vue | 127 ++-----
web/src/utils/zincutils.ts | 2 +-
7 files changed, 541 insertions(+), 106 deletions(-)
create mode 100644 tests/ui-testing/playwright-tests/Alerts/alertsIncidentsTabs.spec.js
diff --git a/tests/ui-testing/pages/alertsPages/alertsPage.js b/tests/ui-testing/pages/alertsPages/alertsPage.js
index 2e5b9a2722..9e0ce063ca 100644
--- a/tests/ui-testing/pages/alertsPages/alertsPage.js
+++ b/tests/ui-testing/pages/alertsPages/alertsPage.js
@@ -142,6 +142,18 @@ export class AlertsPage {
searchAcrossFoldersToggle: '[data-test="alert-list-search-across-folders-toggle"]',
alertDeleteOption: 'Delete',
+ // View Mode Tabs (Alerts / Incidents) - VERIFIED from source code
+ alertIncidentViewTabs: '[data-test="alert-incident-view-tabs"]',
+ alertsViewTab: '[data-test="tab-alerts"]',
+ incidentsViewTab: '[data-test="tab-incidents"]',
+
+ // Incidents view locators
+ incidentListTable: '[data-test="incident-list-table"]',
+ incidentSearchInput: '[data-test="incident-search-input"]',
+
+ // Import button
+ alertImportButton: '[data-test="alert-import"]',
+
// Table locators
tableBodyRowWithIndex: 'tbody tr[data-index]',
tableLocator: 'table',
@@ -630,6 +642,107 @@ export class AlertsPage {
await expect(this.page.locator('[data-test="dashboard-folder-tab-default"]').getByText('default')).toBeVisible();
}
+ // ==================== VIEW MODE TABS (ALERTS / INCIDENTS) ====================
+
+ /**
+ * Click the Alerts view tab
+ */
+ async clickAlertsTab() {
+ testLogger.info('Clicking Alerts tab');
+ await this.page.locator(this.locators.alertsViewTab).click();
+ await this.page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
+ testLogger.info('Switched to Alerts view');
+ }
+
+ /**
+ * Click the Incidents view tab
+ */
+ async clickIncidentsTab() {
+ testLogger.info('Clicking Incidents tab');
+ await this.page.locator(this.locators.incidentsViewTab).click();
+ await this.page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
+ testLogger.info('Switched to Incidents view');
+ }
+
+ /**
+ * Verify the view mode tabs are visible on page load
+ */
+ async expectViewModeTabsVisible() {
+ testLogger.info('Verifying view mode tabs are visible');
+ await expect(this.page.locator(this.locators.alertIncidentViewTabs)).toBeVisible({ timeout: 10000 });
+ await expect(this.page.locator(this.locators.alertsViewTab)).toBeVisible();
+ await expect(this.page.locator(this.locators.incidentsViewTab)).toBeVisible();
+ testLogger.info('View mode tabs verified');
+ }
+
+ /**
+ * Verify Alerts view UI elements are visible
+ */
+ async expectAlertsViewElementsVisible() {
+ testLogger.info('Verifying Alerts view elements');
+ await expect(this.page.locator('[data-test="alert-list-table"]')).toBeVisible({ timeout: 10000 });
+ await expect(this.page.locator(this.locators.alertSearchInput)).toBeVisible();
+ await expect(this.page.locator(this.locators.searchAcrossFoldersToggle)).toBeVisible();
+ await expect(this.page.locator(this.locators.alertImportButton)).toBeVisible();
+ await expect(this.page.locator(this.locators.addAlertButton)).toBeVisible();
+ testLogger.info('Alerts view elements verified');
+ }
+
+ /**
+ * Verify Incidents view UI elements are visible
+ */
+ async expectIncidentsViewElementsVisible() {
+ testLogger.info('Verifying Incidents view elements');
+ await expect(this.page.locator(this.locators.incidentListTable)).toBeVisible({ timeout: 10000 });
+ await expect(this.page.locator(this.locators.incidentSearchInput)).toBeVisible();
+ testLogger.info('Incidents view elements verified');
+ }
+
+ /**
+ * Verify Alerts-only elements are hidden in Incidents view
+ */
+ async expectAlertsOnlyElementsHidden() {
+ testLogger.info('Verifying Alerts-only elements are hidden');
+ await expect(this.page.locator('[data-test="alert-list-table"]')).not.toBeVisible();
+ await expect(this.page.locator(this.locators.searchAcrossFoldersToggle)).not.toBeVisible();
+ await expect(this.page.locator(this.locators.alertImportButton)).not.toBeVisible();
+ await expect(this.page.locator(this.locators.addAlertButton)).not.toBeVisible();
+ testLogger.info('Alerts-only elements confirmed hidden');
+ }
+
+ /**
+ * Verify Incidents-only elements are hidden in Alerts view
+ */
+ async expectIncidentsOnlyElementsHidden() {
+ testLogger.info('Verifying Incidents-only elements are hidden');
+ await expect(this.page.locator(this.locators.incidentListTable)).not.toBeVisible();
+ await expect(this.page.locator(this.locators.incidentSearchInput)).not.toBeVisible();
+ testLogger.info('Incidents-only elements confirmed hidden');
+ }
+
+ /**
+ * Search in Alerts view
+ */
+ async searchInAlertsView(query) {
+ testLogger.info(`Searching in Alerts view: ${query}`);
+ const searchInput = this.page.locator(this.locators.alertSearchInput);
+ await searchInput.click();
+ await searchInput.clear();
+ await searchInput.type(query);
+ await this.page.waitForTimeout(500); // Wait for debounce
+ testLogger.info('Search applied in Alerts view');
+ }
+
+ /**
+ * Search in Incidents view
+ */
+ async searchInIncidentsView(query) {
+ testLogger.info(`Searching in Incidents view: ${query}`);
+ await this.page.locator(this.locators.incidentSearchInput).fill(query);
+ await this.page.waitForTimeout(500); // Wait for debounce
+ testLogger.info('Search applied in Incidents view');
+ }
+
// ==================== IMPORT/EXPORT OPERATIONS ====================
async exportAlerts() {
diff --git a/tests/ui-testing/pages/commonActions.js b/tests/ui-testing/pages/commonActions.js
index 1e0f021570..7cea774784 100644
--- a/tests/ui-testing/pages/commonActions.js
+++ b/tests/ui-testing/pages/commonActions.js
@@ -25,7 +25,8 @@ export class CommonActions {
async navigateToAlerts() {
await this.page.locator(this.alertsMenuItem).click();
await this.page.waitForTimeout(2000);
- await expect(this.page.locator('[data-test="alerts-list-title"]')).toContainText('Alerts');
+ // Updated to check for view mode tabs instead of old title element
+ await expect(this.page.locator('[data-test="alert-incident-view-tabs"]')).toBeVisible();
}
async navigateToSettings() {
diff --git a/tests/ui-testing/playwright-tests/Alerts/alertsIncidentsTabs.spec.js b/tests/ui-testing/playwright-tests/Alerts/alertsIncidentsTabs.spec.js
new file mode 100644
index 0000000000..674094304c
--- /dev/null
+++ b/tests/ui-testing/playwright-tests/Alerts/alertsIncidentsTabs.spec.js
@@ -0,0 +1,347 @@
+/**
+ * Alerts & Incidents Tab Separation - E2E Tests
+ *
+ * Test Suite: Validates the new tab-based UI for separating Alerts and Incidents views
+ * Module: Alerts
+ * Priority: High (Core UX Enhancement)
+ *
+ * Feature: Users can switch between Alerts (configuration) and Incidents (events) using
+ * distinct tabs with proper conditional rendering of view-specific UI elements.
+ *
+ * Test Coverage:
+ * - P0 (Smoke): Tabs visible, default view, tab switching
+ * - P1 (Functional): View-specific elements, search functionality
+ * - P2 (Edge Cases): Empty states, page refresh
+ */
+
+const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
+const testLogger = require('../utils/test-logger.js');
+const PageManager = require('../../pages/page-manager.js');
+const logData = require('../../fixtures/log.json');
+
+test.describe("Alerts & Incidents Tab Separation", () => {
+ test.describe.configure({ mode: 'parallel' });
+ let pm;
+
+ test.beforeEach(async ({ page }, testInfo) => {
+ testLogger.testStart(testInfo.title, testInfo.file);
+ await navigateToBase(page);
+ pm = new PageManager(page);
+
+ // Navigate to alerts page
+ await page.goto(`${logData.logsUrl}?org_identifier=${process.env["ORGNAME"]}`);
+ await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
+ testLogger.info('Navigated to homepage');
+
+ // Wait for any loading overlays to disappear (wait up to 30 seconds)
+ const loadingOverlay = page.locator('.fullscreen.bg-blue');
+ if (await loadingOverlay.isVisible().catch(() => false)) {
+ await loadingOverlay.waitFor({ state: 'hidden', timeout: 30000 }).catch(() => {});
+ }
+
+ // Additional wait to ensure page is ready
+ await page.waitForTimeout(2000);
+
+ // Click alerts menu item
+ await page.locator(pm.alertsPage.locators.alertMenuItem).click();
+ await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
+ testLogger.info('Navigated to alerts page');
+ });
+
+ // ========================================
+ // P0 - SMOKE TESTS (Critical Path)
+ // ========================================
+
+ test("TC-01: Tabs visible on page load", {
+ tag: ['@alertsIncidentsTabs', '@smoke', '@P0', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P0] Testing tabs visibility on page load');
+
+ // Verify tabs container and individual tabs are visible
+ await pm.alertsPage.expectViewModeTabsVisible();
+
+ testLogger.info('[P0] ✓ Tabs verified visible on page load');
+ });
+
+ test("TC-02: Default view is Alerts", {
+ tag: ['@alertsIncidentsTabs', '@smoke', '@P0', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P0] Testing default view is Alerts');
+
+ // Verify Alerts view UI elements are visible
+ await pm.alertsPage.expectAlertsViewElementsVisible();
+
+ // Verify Incidents-only elements are hidden
+ await pm.alertsPage.expectIncidentsOnlyElementsHidden();
+
+ testLogger.info('[P0] ✓ Default view confirmed as Alerts');
+ });
+
+ test("TC-03: Tab switching works (Alerts → Incidents)", {
+ tag: ['@alertsIncidentsTabs', '@smoke', '@P0', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P0] Testing tab switch from Alerts to Incidents');
+
+ // Click Incidents tab
+ await pm.alertsPage.clickIncidentsTab();
+
+ // Verify Incidents view elements are visible
+ await pm.alertsPage.expectIncidentsViewElementsVisible();
+
+ // Verify Alerts-only elements are hidden
+ await pm.alertsPage.expectAlertsOnlyElementsHidden();
+
+ testLogger.info('[P0] ✓ Tab switch to Incidents successful');
+ });
+
+ test("TC-04: Tab switching works (Incidents → Alerts)", {
+ tag: ['@alertsIncidentsTabs', '@smoke', '@P0', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P0] Testing tab switch from Incidents back to Alerts');
+
+ // First switch to Incidents
+ await pm.alertsPage.clickIncidentsTab();
+ testLogger.info('Switched to Incidents view');
+
+ // Then switch back to Alerts
+ await pm.alertsPage.clickAlertsTab();
+
+ // Verify Alerts view elements are visible again
+ await pm.alertsPage.expectAlertsViewElementsVisible();
+
+ // Verify Incidents-only elements are hidden again
+ await pm.alertsPage.expectIncidentsOnlyElementsHidden();
+
+ testLogger.info('[P0] ✓ Tab switch back to Alerts successful');
+ });
+
+ // ========================================
+ // P1 - FUNCTIONAL TESTS
+ // ========================================
+
+ test("TC-05: Alerts view shows all required UI elements", {
+ tag: ['@alertsIncidentsTabs', '@functional', '@P1', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P1] Testing Alerts view UI elements');
+
+ // Verify all Alerts view elements
+ await expect(page.locator('[data-test="alert-list-table"]')).toBeVisible({ timeout: 10000 });
+ await expect(page.locator(pm.alertsPage.locators.alertSearchInput)).toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.searchAcrossFoldersToggle)).toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.alertImportButton)).toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.addAlertButton)).toBeVisible();
+
+ // Verify folders panel (splitter) is visible
+ await expect(page.locator('[data-test="alert-list-splitter"]')).toBeVisible();
+
+ testLogger.info('[P1] ✓ All Alerts view UI elements verified');
+ });
+
+ test("TC-06: Incidents view shows only incident UI elements", {
+ tag: ['@alertsIncidentsTabs', '@functional', '@P1', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P1] Testing Incidents view UI elements');
+
+ // Switch to Incidents view
+ await pm.alertsPage.clickIncidentsTab();
+
+ // Verify Incidents view elements visible
+ await pm.alertsPage.expectIncidentsViewElementsVisible();
+
+ // Verify Alerts-only elements are NOT visible
+ await expect(page.locator('[data-test="alert-list-table"]')).not.toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.searchAcrossFoldersToggle)).not.toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.alertImportButton)).not.toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.addAlertButton)).not.toBeVisible();
+
+ testLogger.info('[P1] ✓ Incidents view UI elements verified');
+ });
+
+ test("TC-07: Search works in Alerts view", {
+ tag: ['@alertsIncidentsTabs', '@functional', '@P1', '@all', '@search']
+ }, async ({ page }) => {
+ testLogger.info('[P1] Testing search in Alerts view');
+
+ // Type in alert search input
+ const searchQuery = 'test';
+ const searchInput = page.locator(pm.alertsPage.locators.alertSearchInput);
+
+ // Click and focus the input
+ await searchInput.click();
+ await page.waitForTimeout(500);
+
+ // Type the search query
+ await searchInput.pressSequentially(searchQuery, { delay: 100 });
+ await page.waitForTimeout(1000);
+
+ // Verify search functionality works (input accepts text and table is visible)
+ await expect(searchInput).toBeFocused();
+ await expect(page.locator('[data-test="alert-list-table"]')).toBeVisible();
+
+ testLogger.info('[P1] ✓ Search in Alerts view verified');
+ });
+
+ test("TC-08: Search works in Incidents view", {
+ tag: ['@alertsIncidentsTabs', '@functional', '@P1', '@all', '@search']
+ }, async ({ page }) => {
+ testLogger.info('[P1] Testing search in Incidents view');
+
+ // Switch to Incidents view
+ await pm.alertsPage.clickIncidentsTab();
+
+ // Type in incident search input
+ const searchQuery = 'test';
+ await pm.alertsPage.searchInIncidentsView(searchQuery);
+
+ // Verify search input has the value
+ await expect(page.locator(pm.alertsPage.locators.incidentSearchInput)).toHaveValue(searchQuery);
+
+ // Verify incident table still visible (search results)
+ await expect(page.locator(pm.alertsPage.locators.incidentListTable)).toBeVisible();
+
+ testLogger.info('[P1] ✓ Search in Incidents view verified');
+ });
+
+ test("TC-09: 'All Folders' toggle only in Alerts view", {
+ tag: ['@alertsIncidentsTabs', '@functional', '@P1', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P1] Testing All Folders toggle visibility');
+
+ // Verify toggle visible in Alerts view
+ await expect(page.locator(pm.alertsPage.locators.searchAcrossFoldersToggle)).toBeVisible();
+ testLogger.info('Toggle visible in Alerts view');
+
+ // Switch to Incidents view
+ await pm.alertsPage.clickIncidentsTab();
+
+ // Verify toggle hidden in Incidents view
+ await expect(page.locator(pm.alertsPage.locators.searchAcrossFoldersToggle)).not.toBeVisible();
+ testLogger.info('Toggle hidden in Incidents view');
+
+ // Switch back to Alerts view
+ await pm.alertsPage.clickAlertsTab();
+
+ // Verify toggle visible again
+ await expect(page.locator(pm.alertsPage.locators.searchAcrossFoldersToggle)).toBeVisible();
+ testLogger.info('Toggle visible again in Alerts view');
+
+ testLogger.info('[P1] ✓ All Folders toggle visibility verified');
+ });
+
+ test("TC-10: Import and Add Alert buttons only in Alerts view", {
+ tag: ['@alertsIncidentsTabs', '@functional', '@P1', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P1] Testing Import and Add Alert buttons visibility');
+
+ // Verify buttons visible in Alerts view
+ await expect(page.locator(pm.alertsPage.locators.alertImportButton)).toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.addAlertButton)).toBeVisible();
+ testLogger.info('Buttons visible in Alerts view');
+
+ // Switch to Incidents view
+ await pm.alertsPage.clickIncidentsTab();
+
+ // Verify buttons hidden in Incidents view
+ await expect(page.locator(pm.alertsPage.locators.alertImportButton)).not.toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.addAlertButton)).not.toBeVisible();
+ testLogger.info('Buttons hidden in Incidents view');
+
+ // Switch back to Alerts view
+ await pm.alertsPage.clickAlertsTab();
+
+ // Verify buttons visible again
+ await expect(page.locator(pm.alertsPage.locators.alertImportButton)).toBeVisible();
+ await expect(page.locator(pm.alertsPage.locators.addAlertButton)).toBeVisible();
+ testLogger.info('Buttons visible again in Alerts view');
+
+ testLogger.info('[P1] ✓ Import and Add Alert buttons visibility verified');
+ });
+
+ // ========================================
+ // P2 - EDGE CASE TESTS
+ // ========================================
+
+ test("TC-11: Empty alerts list shows empty state", {
+ tag: ['@alertsIncidentsTabs', '@edgeCase', '@P2', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P2] Testing empty alerts list handling');
+
+ // Even if no alerts, table container should be present
+ // Note: This test assumes there might be alerts. If no alerts exist,
+ // the empty state will be visible within the table container.
+ await expect(page.locator('[data-test="alert-list-table"]')).toBeVisible({ timeout: 10000 });
+
+ // Verify tabs still functional
+ await pm.alertsPage.expectViewModeTabsVisible();
+
+ // Verify Add Alert button still visible (key action for empty state)
+ await expect(page.locator(pm.alertsPage.locators.addAlertButton)).toBeVisible();
+
+ testLogger.info('[P2] ✓ Empty alerts list handled correctly');
+ });
+
+ test("TC-12: Empty incidents list shows empty state", {
+ tag: ['@alertsIncidentsTabs', '@edgeCase', '@P2', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P2] Testing empty incidents list handling');
+
+ // Switch to Incidents view
+ await pm.alertsPage.clickIncidentsTab();
+
+ // Even if no incidents, table container should be present
+ await expect(page.locator(pm.alertsPage.locators.incidentListTable)).toBeVisible({ timeout: 10000 });
+
+ // Verify tabs still functional - can switch back
+ await pm.alertsPage.clickAlertsTab();
+ await pm.alertsPage.expectAlertsViewElementsVisible();
+
+ testLogger.info('[P2] ✓ Empty incidents list handled correctly');
+ });
+
+ test("TC-13: Page refresh preserves current view", {
+ tag: ['@alertsIncidentsTabs', '@edgeCase', '@P2', '@all']
+ }, async ({ page }) => {
+ testLogger.info('[P2] Testing page refresh preserves view state');
+
+ // Get current URL for alerts page
+ const alertsUrl = page.url();
+ testLogger.info(`Current alerts URL: ${alertsUrl}`);
+
+ // Test 1: Switch to Incidents view
+ await pm.alertsPage.clickIncidentsTab();
+ await pm.alertsPage.expectIncidentsViewElementsVisible();
+ testLogger.info('Switched to Incidents view');
+
+ // Get URL with view=incidents parameter
+ const incidentsUrl = page.url();
+ testLogger.info(`Incidents URL: ${incidentsUrl}`);
+
+ // Refresh page - this should preserve the view parameter
+ await page.reload({ waitUntil: 'networkidle', timeout: 30000 });
+ testLogger.info('Page refreshed from Incidents view');
+
+ // Wait for any loading overlays
+ const loadingOverlay = page.locator('.fullscreen.bg-blue');
+ if (await loadingOverlay.isVisible().catch(() => false)) {
+ await loadingOverlay.waitFor({ state: 'hidden', timeout: 30000 }).catch(() => {});
+ }
+
+ // Wait for page to stabilize
+ await page.waitForTimeout(3000);
+
+ // Verify tabs are visible
+ await pm.alertsPage.expectViewModeTabsVisible();
+ testLogger.info('Tabs visible after refresh');
+
+ // Verify still in Incidents view (URL state preserved)
+ await pm.alertsPage.expectIncidentsViewElementsVisible();
+ await pm.alertsPage.expectAlertsOnlyElementsHidden();
+ testLogger.info('✓ Incidents view preserved after refresh');
+
+ // Test 2: Switch back to Alerts and verify
+ await pm.alertsPage.clickAlertsTab();
+ await pm.alertsPage.expectAlertsViewElementsVisible();
+
+ testLogger.info('[P2] ✓ Page refresh preserves view state correctly');
+ });
+});
diff --git a/web/package.json b/web/package.json
index d8e60d9d6c..5668a3867b 100644
--- a/web/package.json
+++ b/web/package.json
@@ -5,7 +5,7 @@
"private": true,
"type": "module",
"scripts": {
- "dev": "vite",
+ "dev": "vite --base /web/",
"devcloud": "NODE_ENV=devcloud vite --mode=devcloud",
"build-alpha1": "run-p type-check build-only-alpha1",
"build-cloud-prod": "run-p type-check build-only-cloud-prod",
diff --git a/web/src/components/alerts/AlertList.spec.ts b/web/src/components/alerts/AlertList.spec.ts
index 2b8d99549c..d0b53c3acc 100644
--- a/web/src/components/alerts/AlertList.spec.ts
+++ b/web/src/components/alerts/AlertList.spec.ts
@@ -134,9 +134,7 @@ async function mountAlertList() {
emits: ["update:active-tab"],
template:
'
' +
- '' +
- '' +
- '' +
+ '' +
'
',
},
SelectFolderDropDown: true,
@@ -295,11 +293,12 @@ const waitData = async (wrapper: any) => {
// 1. Basic rendering and structure
describe("AlertList - basic rendering", () => {
- it("renders the page container and title", async () => {
+ it("renders the page container and view mode tabs", async () => {
const wrapper = await mountAlertList();
await waitData(wrapper);
expect(wrapper.find('[data-test="alert-list-page"]').exists()).toBe(true);
- expect(wrapper.get('[data-test="alerts-list-title"]').text()).toBeTruthy();
+ // Verify view mode tabs exist (Alerts/Incidents tabs)
+ expect(wrapper.find('[data-test="alert-incident-view-tabs"]').exists()).toBe(true);
});
it("renders search input and toggle", async () => {
@@ -336,20 +335,26 @@ describe("AlertList - data fetching and columns", () => {
const wrapper: any = await mountAlertList();
await waitData(wrapper);
+ // Ensure we're in alerts view mode first
+ wrapper.vm.viewMode = 'alerts';
+ await flushPromises();
+
// default tab: all
+ wrapper.vm.activeTab = 'all';
+ await flushPromises();
let names = wrapper.vm.columns.map((c: any) => c.name);
expect(names).toContain("period");
expect(names).toContain("frequency");
// switch to realTime
- await wrapper.find(".tab-realtime").trigger("click");
+ wrapper.vm.activeTab = 'realTime';
await flushPromises();
names = wrapper.vm.columns.map((c: any) => c.name);
expect(names).not.toContain("period");
expect(names).not.toContain("frequency");
// switch to scheduled
- await wrapper.find(".tab-scheduled").trigger("click");
+ wrapper.vm.activeTab = 'scheduled';
await flushPromises();
names = wrapper.vm.columns.map((c: any) => c.name);
expect(names).toContain("period");
@@ -381,7 +386,13 @@ describe("AlertList - filtering behaviors", () => {
const wrapper: any = await mountAlertList();
await waitData(wrapper);
- await wrapper.find(".tab-scheduled").trigger("click");
+ // Ensure we're in alerts view mode
+ wrapper.vm.viewMode = 'alerts';
+ await flushPromises();
+
+ // Set activeTab and trigger filtering
+ wrapper.vm.activeTab = 'scheduled';
+ wrapper.vm.filterAlertsByTab(true);
await flushPromises();
expect(wrapper.vm.filteredResults.every((r: any) => !r.is_real_time)).toBe(true);
@@ -391,7 +402,13 @@ describe("AlertList - filtering behaviors", () => {
const wrapper: any = await mountAlertList();
await waitData(wrapper);
- await wrapper.find(".tab-realtime").trigger("click");
+ // Ensure we're in alerts view mode
+ wrapper.vm.viewMode = 'alerts';
+ await flushPromises();
+
+ // Set activeTab and trigger filtering
+ wrapper.vm.activeTab = 'realTime';
+ wrapper.vm.filterAlertsByTab(true);
await flushPromises();
expect(wrapper.vm.filteredResults.every((r: any) => r.is_real_time)).toBe(true);
@@ -415,7 +432,13 @@ describe("AlertList - filtering behaviors", () => {
const wrapper: any = await mountAlertList();
await waitData(wrapper);
- await wrapper.find(".tab-scheduled").trigger("click");
+ // Ensure we're in alerts view mode
+ wrapper.vm.viewMode = 'alerts';
+ await flushPromises();
+
+ // Set activeTab and trigger filtering
+ wrapper.vm.activeTab = 'scheduled';
+ wrapper.vm.filterAlertsByTab(true);
await flushPromises();
wrapper.vm.filterAlertsByQuery("Scheduled");
@@ -662,8 +685,16 @@ describe("AlertList - additional validations", () => {
it(`tab ${tab} shows only matching rows (${idx})`, async () => {
const wrapper: any = await mountAlertList();
await waitData(wrapper);
- await wrapper.find(`.tab-${tab === "all" ? "all" : tab.toLowerCase()}`).trigger("click");
+
+ // Ensure we're in alerts view mode
+ wrapper.vm.viewMode = 'alerts';
await flushPromises();
+
+ // Set activeTab and trigger filtering
+ wrapper.vm.activeTab = tab;
+ wrapper.vm.filterAlertsByTab(true);
+ await flushPromises();
+
expect(wrapper.vm.filteredResults.every((r: any) => predicate(r))).toBe(true);
});
});
diff --git a/web/src/components/alerts/AlertList.vue b/web/src/components/alerts/AlertList.vue
index b550b97ed7..e4a2917c18 100644
--- a/web/src/components/alerts/AlertList.vue
+++ b/web/src/components/alerts/AlertList.vue
@@ -27,38 +27,14 @@ along with this program. If not, see .
class="flex justify-between full-width tw:py-3 tw:px-4 items-center"
>
-
-
-
-
- {{ t('alerts.header') }}
-
-
-
- {{ t('alerts.incidents.title') }}
-
-
-
-
-
- {{ viewMode === 'alerts' ? t('alerts.header') : t('alerts.incidents.title') }}
+
+
@@ -1174,6 +1150,18 @@ export default defineComponent({
// Incident search query
const incidentSearchQuery = ref("");
+ // View mode tabs (Alerts / Incidents)
+ const viewTabs = computed(() => [
+ {
+ label: t('alerts.header'),
+ value: 'alerts',
+ },
+ {
+ label: t('alerts.incidents.title'),
+ value: 'incidents',
+ }
+ ]);
+
// Tabs for alerts view only (removed incidents tab)
const alertTabs = reactive([
{
@@ -2788,6 +2776,7 @@ export default defineComponent({
tabs,
alertTabs,
viewMode,
+ viewTabs,
onViewModeChange,
incidentSearchQuery,
filterAlertsByTab,
@@ -2812,75 +2801,29 @@ export default defineComponent({