This is an automated email from the ASF dual-hosted git repository. zqr10159 pushed a commit to branch 2.0.0 in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
commit cddb70aa561ceffca58652b8cb467a43a49d6235 Author: Logic <[email protected]> AuthorDate: Fri May 29 01:51:04 2026 +0800 feat(web-next): close dashboard route catalog parity --- .../app/compatibility-entrypoints.chrome.test.ts | 48 ++++++--- web-next/app/dashboard/page.test.ts | 66 +++++++----- web-next/app/dashboard/page.tsx | 13 ++- web-next/app/entity-detail-family.chrome.test.ts | 4 +- web-next/app/entity-editor-family.chrome.test.ts | 36 +++++-- .../app/milestone4-auth-settings.chrome.test.ts | 38 ++++--- .../milestone5-shared-route-owners.chrome.test.ts | 45 ++++---- web-next/app/monitor-family.chrome.test.ts | 63 ++++++----- web-next/app/monitor-route-ownership.test.ts | 37 ++++--- web-next/app/operator-family.chrome.test.ts | 34 +++--- web-next/app/page.test.ts | 40 ++++++- web-next/app/page.tsx | 12 ++- web-next/app/settings-family.chrome.test.ts | 11 +- web-next/app/three-signal-family.chrome.test.ts | 21 ++-- web-next/lib/dashboard/navigation.test.ts | 6 +- web-next/lib/dashboard/navigation.ts | 6 +- web-next/lib/nav.test.ts | 4 +- web-next/lib/nav.ts | 32 +++--- web-next/lib/parity/route-manifest.json | 115 +++++++++++--------- web-next/lib/parity/route-manifest.test.ts | 116 +++++++++++++-------- web-next/lib/workspace-navigation.test.ts | 6 +- 21 files changed, 465 insertions(+), 288 deletions(-) diff --git a/web-next/app/compatibility-entrypoints.chrome.test.ts b/web-next/app/compatibility-entrypoints.chrome.test.ts index f0c73988a3..f8bfc7a0ad 100644 --- a/web-next/app/compatibility-entrypoints.chrome.test.ts +++ b/web-next/app/compatibility-entrypoints.chrome.test.ts @@ -16,37 +16,51 @@ describe('compatibility entrypoint posture', () => { const logIntegrationSource = readFileSync(resolve(process.cwd(), 'app/log/integration/page.tsx'), 'utf8'); const logIntegrationSourceAlias = readFileSync(resolve(process.cwd(), 'app/log/integration/[source]/page.tsx'), 'utf8'); - expect(dashboardSource).toContain("from '../overview/page'"); - expect(dashboardSource).not.toContain('buildDashboardCompatRouteUrl'); - expect(dashboardSource).not.toContain('redirect('); - expect(alertsSource).toContain('AlertCenterPage'); - expect(alertCenterSource).toContain("from '../../../lib/compat/search-params'"); - expect(eventsSource).toContain("from '../log/manage/log-manage-page'"); - expect(eventsSource).toContain('forcedView="explorer"'); - expect(loginSource).toContain("from '../../lib/compat/search-params'"); - expect(statusPublicSource).toContain("from '../../../lib/compat/search-params'"); - expect(settingSource).toContain("from '../../lib/compat/search-params'"); - expect(settingSettingsSource).toContain("from '../../../lib/compat/search-params'"); + expect(dashboardSource).toContain('buildDashboardCompatRouteUrl'); + expect(dashboardSource).toContain('redirect('); + expect(dashboardSource).not.toContain("from '../overview/page'"); + expect(alertsSource).toContain('buildAlertCompatRouteUrlFromSearchParams'); + expect(alertsSource).not.toContain('createCompatSearchParamReader'); + expect(alertsSource).toContain('redirect('); + expect(alertCenterSource).toContain('buildAlertCompatRouteUrlFromSearchParams'); + expect(alertCenterSource).not.toContain("from '../../../lib/compat/search-params'"); + expect(eventsSource).toContain('buildLogCompatRouteUrlFromSearchParams'); + expect(eventsSource).not.toContain('createCompatSearchParamReader'); + expect(eventsSource).toContain("view: 'list'"); + expect(eventsSource).toContain('redirect('); + expect(loginSource).toContain('buildLoginCompatRouteUrl'); + expect(loginSource).not.toContain("from '../../lib/compat/search-params'"); + expect(statusPublicSource).toContain('buildPublicStatusCompatRouteUrl'); + expect(statusPublicSource).not.toContain("from '../../../lib/compat/search-params'"); + expect(settingSource).toContain('buildSettingsCompatRouteUrl'); + expect(settingSource).not.toContain("from '../../lib/compat/search-params'"); + expect(settingSettingsSource).toContain('buildSettingsCompatRouteUrl'); + expect(settingSettingsSource).not.toContain("from '../../../lib/compat/search-params'"); expect(alertsSource).not.toContain('function createSearchParamReader'); - expect(alertsSource).not.toContain('redirect('); + expect(alertsSource).not.toContain("from '../alert/page'"); expect(eventsSource).not.toContain('function createSearchParamReader'); - expect(eventsSource).not.toContain('redirect('); + expect(eventsSource).not.toContain("from '../log/manage/log-manage-page'"); + expect(eventsSource).not.toContain('forcedView="explorer"'); expect(loginSource).not.toContain('function buildSearchParams'); expect(alertCenterSource).not.toContain("redirect('/alert')"); expect(statusPublicSource).not.toContain("redirect('/status')"); expect(settingSource).not.toContain("redirect('/setting/settings')"); expect(settingSettingsSource).not.toContain("redirect('/setting/settings/config')"); - expect(logStreamSource).toContain('data-log-stream-surface="angular-log-stream"'); - expect(logStreamSource).toContain('data-log-stream-toolbar="angular-actions"'); + expect(logStreamSource).toContain('data-log-stream-canonical-live-route="log-manage-stream"'); + expect(logStreamSource).toContain('forcedView="stream"'); + expect(logStreamSource).toContain('showViewToggle={false}'); + expect(logStreamSource).not.toContain('data-log-stream-surface="angular-log-stream"'); + expect(logStreamSource).not.toContain('data-log-stream-toolbar="angular-actions"'); expect(logStreamSource).not.toContain('buildLogCompatRouteUrl'); expect(logStreamSource).not.toContain('redirect('); - expect(logStreamSource).not.toContain('LogManagePage'); + expect(logStreamSource).toContain('LogManagePage'); expect(logIntegrationSource).toContain('buildLogIntegrationIngestionHref'); expect(logIntegrationSource).toContain('createSearchParamReader'); expect(logIntegrationSource).toContain('redirect('); expect(logIntegrationSource).not.toContain('LogIntegrationRedirectShell'); expect(logIntegrationSourceAlias).toContain('buildLogIntegrationIngestionHref'); - expect(logIntegrationSourceAlias).toContain('createSearchParamReader(resolvedSearchParams, resolved.source)'); + expect(logIntegrationSourceAlias).toContain('createSearchParamReader(resolvedSearchParams)'); + expect(logIntegrationSourceAlias).not.toContain('resolved.source'); expect(logIntegrationSourceAlias).toContain('redirect('); }); }); diff --git a/web-next/app/dashboard/page.test.ts b/web-next/app/dashboard/page.test.ts index 0b6750d3b9..5953ebb831 100644 --- a/web-next/app/dashboard/page.test.ts +++ b/web-next/app/dashboard/page.test.ts @@ -1,34 +1,48 @@ -import React from 'react'; -import { readFileSync } from 'node:fs'; -import { resolve } from 'node:path'; -import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it, vi } from 'vitest'; -vi.mock('../overview/page', () => ({ - default: () => - React.createElement( - 'main', - { 'data-workspace-shell': 'true' }, - React.createElement('aside', null, '总览 rail'), - React.createElement('section', { 'data-overview-status-grid': 'true' }, '工作区状态'), - React.createElement('section', { 'data-overview-guidance': 'true' }, '下一步:先接入一条可用信号链路'), - React.createElement('button', null, '刷新') - ) +const redirect = vi.fn(); + +vi.mock('next/navigation', () => ({ + redirect })); describe('dashboard alias route', () => { - it('renders the overview workspace directly instead of a redirect-only shell', async () => { - const source = readFileSync(resolve(process.cwd(), 'app/dashboard/page.tsx'), 'utf8'); + it('redirects dashboard compatibility traffic to the overview workbench', async () => { + redirect.mockImplementation((target: string) => { + throw new Error(`redirect:${target}`); + }); + + const { default: DashboardAliasPage } = await import('./page'); + + await expect(DashboardAliasPage({ searchParams: Promise.resolve({}) })).rejects.toThrow('redirect:/overview'); + expect(redirect).toHaveBeenCalledWith('/overview'); + }); + + it('preserves machine route context and strips display labels when redirecting dashboard aliases', async () => { + redirect.mockImplementation((target: string) => { + throw new Error(`redirect:${target}`); + }); + const { default: DashboardAliasPage } = await import('./page'); - const html = renderToStaticMarkup(React.createElement(DashboardAliasPage)); - - expect(html).toContain('data-workspace-shell="true"'); - expect(html).toContain('data-overview-status-grid="true"'); - expect(html).toContain('data-overview-guidance="true"'); - expect(html).toContain('下一步:先接入一条可用信号链路'); - expect(html).toContain('刷新'); - expect(source).toContain("from '../overview/page'"); - expect(source).not.toContain('redirect('); - expect(source).not.toContain('buildDashboardCompatRouteUrl'); + + await expect( + DashboardAliasPage({ + searchParams: Promise.resolve({ + start: '10', + end: '20', + entityId: '7', + entityName: 'checkout', + returnTo: '/monitors?returnLabel=Monitors', + returnLabel: 'Monitors', + serviceName: 'checkout', + environment: ['prod', 'staging'] + }) + }) + ).rejects.toThrow( + 'redirect:/overview?start=10&end=20&entityId=7&entityName=checkout&returnTo=%2Fmonitors&serviceName=checkout&environment=prod' + ); + expect(redirect).toHaveBeenLastCalledWith( + '/overview?start=10&end=20&entityId=7&entityName=checkout&returnTo=%2Fmonitors&serviceName=checkout&environment=prod' + ); }); }); diff --git a/web-next/app/dashboard/page.tsx b/web-next/app/dashboard/page.tsx index 9ac6b44075..52b18c7da2 100644 --- a/web-next/app/dashboard/page.tsx +++ b/web-next/app/dashboard/page.tsx @@ -1,6 +1,11 @@ -import React from 'react'; -import OverviewPage from '../overview/page'; +import { redirect } from 'next/navigation'; +import { buildDashboardCompatRouteUrl, type SearchParamsRecord } from '../../lib/dashboard/navigation'; -export default function DashboardAliasPage() { - return <OverviewPage />; +export default async function DashboardAliasPage({ + searchParams +}: { + searchParams?: Promise<SearchParamsRecord>; +}) { + const resolvedSearchParams = await searchParams; + redirect(buildDashboardCompatRouteUrl(resolvedSearchParams)); } diff --git a/web-next/app/entity-detail-family.chrome.test.ts b/web-next/app/entity-detail-family.chrome.test.ts index b81c4ed180..f0de789536 100644 --- a/web-next/app/entity-detail-family.chrome.test.ts +++ b/web-next/app/entity-detail-family.chrome.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest'; describe('entity detail family cold-workbench chrome', () => { it('removes the remaining legacy white-on-black chrome from the current entity slice', () => { - const detailSource = readFileSync(resolve(process.cwd(), 'app/entities/[entityId]/page.tsx'), 'utf8'); + const detailSource = readFileSync(resolve(process.cwd(), 'app/entities/[entityId]/entity-detail-page.tsx'), 'utf8'); const definitionSource = readFileSync(resolve(process.cwd(), 'components/pages/entity-definition-workspace-surface.tsx'), 'utf8'); expect(detailSource).not.toContain('text-white/55'); @@ -21,7 +21,7 @@ describe('entity detail family cold-workbench chrome', () => { }); it('adopts shared ops tokens across the current entity slice', () => { - const detailSource = readFileSync(resolve(process.cwd(), 'app/entities/[entityId]/page.tsx'), 'utf8'); + const detailSource = readFileSync(resolve(process.cwd(), 'app/entities/[entityId]/entity-detail-page.tsx'), 'utf8'); const detailSurfaceSource = readFileSync(resolve(process.cwd(), 'components/pages/entity-detail-surface.tsx'), 'utf8'); const definitionSource = readFileSync(resolve(process.cwd(), 'components/pages/entity-definition-workspace-surface.tsx'), 'utf8'); diff --git a/web-next/app/entity-editor-family.chrome.test.ts b/web-next/app/entity-editor-family.chrome.test.ts index 8dd0167710..1bb39b245c 100644 --- a/web-next/app/entity-editor-family.chrome.test.ts +++ b/web-next/app/entity-editor-family.chrome.test.ts @@ -7,7 +7,7 @@ describe('entity editor family cold-workbench chrome', () => { const editorRowsSource = readFileSync(resolve(process.cwd(), 'components/observability/editor-rows.tsx'), 'utf8'); const monitorEditorSource = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8'); const entityEditorSource = readFileSync(resolve(process.cwd(), 'components/pages/entity-editor-surface.tsx'), 'utf8'); - const entitiesSource = readFileSync(resolve(process.cwd(), 'app/entities/page.tsx'), 'utf8'); + const entitiesSource = readFileSync(resolve(process.cwd(), 'app/entities/entity-list-page.tsx'), 'utf8'); expect(editorRowsSource).not.toContain('border-white/10'); expect(editorRowsSource).not.toContain('bg-black/20'); @@ -56,9 +56,11 @@ describe('entity editor family cold-workbench chrome', () => { it('adopts shared ops tokens across the shared editor slice', () => { const editorRowsSource = readFileSync(resolve(process.cwd(), 'components/observability/editor-rows.tsx'), 'utf8'); + const workbenchPrimitivesSource = readFileSync(resolve(process.cwd(), 'components/workbench/primitives.tsx'), 'utf8'); + const workbenchPageSource = readFileSync(resolve(process.cwd(), 'components/workbench/workbench-page.tsx'), 'utf8'); const monitorEditorSource = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8'); const entityEditorSource = readFileSync(resolve(process.cwd(), 'components/pages/entity-editor-surface.tsx'), 'utf8'); - const entitiesSource = readFileSync(resolve(process.cwd(), 'app/entities/page.tsx'), 'utf8'); + const entitiesSource = readFileSync(resolve(process.cwd(), 'app/entities/entity-list-page.tsx'), 'utf8'); expect(editorRowsSource).toContain('border-[var(--ops-border-color)]'); expect(editorRowsSource).toContain('bg-[var(--ops-surface-panel)]'); @@ -66,11 +68,19 @@ describe('entity editor family cold-workbench chrome', () => { expect(editorRowsSource).toContain('text-[var(--ops-text-secondary)]'); expect(editorRowsSource).toContain('hover:bg-[var(--ops-surface-hover)]'); - expect(monitorEditorSource).toContain('border-[var(--ops-border-color)]'); - expect(monitorEditorSource).toContain('bg-[var(--ops-surface-panel)]'); - expect(monitorEditorSource).toContain('text-[var(--ops-text-primary)]'); - expect(monitorEditorSource).toContain('text-[var(--ops-text-secondary)]'); - expect(monitorEditorSource).toContain('text-[var(--ops-text-tertiary)]'); + expect(monitorEditorSource).toContain('HzMonitorEditorForm'); + expect(monitorEditorSource).toContain('HzMonitorEditorHeader'); + expect(monitorEditorSource).toContain('HzMonitorEditorSection'); + expect(monitorEditorSource).toContain('data-monitor-editor-form-owner="hertzbeat-ui-monitor-editor-form"'); + expect(monitorEditorSource).not.toContain('WorkbenchPage'); + expect(monitorEditorSource).not.toContain('SurfaceSection'); + expect(workbenchPrimitivesSource).toContain('border-[var(--ops-border-color)]'); + expect(workbenchPrimitivesSource).toContain('bg-[var(--ops-surface-panel)]'); + expect(workbenchPrimitivesSource).toContain('text-[var(--ops-text-primary)]'); + expect(workbenchPrimitivesSource).toContain('text-[var(--ops-text-secondary)]'); + expect(workbenchPageSource).toContain('divide-[var(--ops-border-color)]'); + expect(workbenchPageSource).toContain('text-[var(--ops-text-primary)]'); + expect(workbenchPageSource).toContain('text-[var(--ops-text-secondary)]'); expect(entityEditorSource).toContain('data-entity-editor-shell="otlp-cold-entity-composer"'); expect(entityEditorSource).toContain('data-entity-editor-style-baseline="hertzbeat-cold-matte"'); @@ -87,12 +97,18 @@ describe('entity editor family cold-workbench chrome', () => { }); it('keeps the active monitor/entity edit routes composed from the shared editor owners', () => { - const monitorEditSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/page.tsx'), 'utf8'); - const entityNewSource = readFileSync(resolve(process.cwd(), 'app/entities/new/page.tsx'), 'utf8'); - const entityEditSource = readFileSync(resolve(process.cwd(), 'app/entities/[entityId]/edit/page.tsx'), 'utf8'); + const monitorEditRouteSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/page.tsx'), 'utf8'); + const monitorEditSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/monitor-edit-page.tsx'), 'utf8'); + const entityNewRouteSource = readFileSync(resolve(process.cwd(), 'app/entities/new/page.tsx'), 'utf8'); + const entityNewSource = readFileSync(resolve(process.cwd(), 'app/entities/new/entity-new-page.tsx'), 'utf8'); + const entityEditRouteSource = readFileSync(resolve(process.cwd(), 'app/entities/[entityId]/edit/page.tsx'), 'utf8'); + const entityEditSource = readFileSync(resolve(process.cwd(), 'app/entities/[entityId]/edit/entity-edit-page.tsx'), 'utf8'); + expect(monitorEditRouteSource).toContain("import MonitorEditPage from './monitor-edit-page'"); expect(monitorEditSource).toContain('MonitorEditorSurface'); + expect(entityNewRouteSource).toContain("import EntityNewPage from './entity-new-page'"); expect(entityNewSource).toContain('EntityEditorSurface'); + expect(entityEditRouteSource).toContain("import EntityEditPage from './entity-edit-page'"); expect(entityEditSource).toContain('EntityEditorSurface'); }); }); diff --git a/web-next/app/milestone4-auth-settings.chrome.test.ts b/web-next/app/milestone4-auth-settings.chrome.test.ts index b28798181d..2d709b6a75 100644 --- a/web-next/app/milestone4-auth-settings.chrome.test.ts +++ b/web-next/app/milestone4-auth-settings.chrome.test.ts @@ -4,8 +4,8 @@ import { describe, expect, it } from 'vitest'; describe('milestone 4 auth/public and settings cold-workbench chrome', () => { it('removes the remaining bright-shell residue from the current auth/settings slice', () => { - const passportLockSource = readFileSync(resolve(process.cwd(), 'app/passport/lock/page.tsx'), 'utf8'); - const settingTokenSource = readFileSync(resolve(process.cwd(), 'app/setting/settings/token/page.tsx'), 'utf8'); + const passportLockSource = readFileSync(resolve(process.cwd(), 'app/passport/lock/passport-lock-page.tsx'), 'utf8'); + const settingTokenSource = readFileSync(resolve(process.cwd(), 'app/setting/settings/token/setting-token-page.tsx'), 'utf8'); const combinedSource = [passportLockSource, settingTokenSource].join('\n'); expect(combinedSource).not.toContain('border-white/35'); @@ -23,24 +23,30 @@ describe('milestone 4 auth/public and settings cold-workbench chrome', () => { }); it('adopts shared ops owners and danger states across the current auth/settings slice', () => { - const passportLockSource = readFileSync(resolve(process.cwd(), 'app/passport/lock/page.tsx'), 'utf8'); - const settingTokenSource = readFileSync(resolve(process.cwd(), 'app/setting/settings/token/page.tsx'), 'utf8'); + const passportLockRouteSource = readFileSync(resolve(process.cwd(), 'app/passport/lock/page.tsx'), 'utf8'); + const passportLockSource = readFileSync(resolve(process.cwd(), 'app/passport/lock/passport-lock-page.tsx'), 'utf8'); + const settingTokenSource = readFileSync(resolve(process.cwd(), 'app/setting/settings/token/setting-token-page.tsx'), 'utf8'); - expect(passportLockSource).toContain('components/observability'); - expect(passportLockSource).toContain('ObservabilityStatusState'); + expect(passportLockRouteSource).not.toMatch(/^['"]use client['"]/); + expect(passportLockRouteSource).toContain("import PassportLockPage from './passport-lock-page'"); + expect(passportLockSource).toContain('HzPassportLockSurface'); + expect(passportLockSource).toContain('PassportPanel'); expect(passportLockSource).not.toContain('components/workbench/primitives'); expect(passportLockSource).toContain('border-[var(--ops-border-color)]'); - expect(passportLockSource).toContain('bg-[var(--ops-surface-panel)]'); - expect(passportLockSource).toContain('bg-[var(--ops-surface-raised)]'); - expect(passportLockSource).toContain('text-[var(--ops-text-primary)]'); - expect(passportLockSource).toContain('text-[var(--ops-text-tertiary)]'); - expect(passportLockSource).toContain('text-[var(--ops-primary)]'); + expect(passportLockSource).toContain('bg-[#101217]'); + expect(passportLockSource).toContain('data-passport-lock-panel="angular-wide"'); + expect(passportLockSource).toContain('data-passport-lock-panel-owner="hertzbeat-ui-passport-lock"'); - expect(settingTokenSource).toContain('ObservabilityStatusState'); - expect(settingTokenSource).toContain('StageSection'); - expect(settingTokenSource).toContain('tone="danger"'); - expect(settingTokenSource).toContain('DrawerCodePreview'); - expect(settingTokenSource).toContain('SummaryMetricGrid'); + expect(settingTokenSource).toContain('SettingsConsoleTitle'); + expect(settingTokenSource).toContain('coldOpsCatalogVisual'); + expect(settingTokenSource).toContain('data-setting-token-table-panel="cold-dense-table"'); + expect(settingTokenSource).toContain('data-setting-token-strip-style="cold-inline-counts"'); + expect(settingTokenSource).toContain('data-setting-token-row-action="cold-row-action"'); + expect(settingTokenSource).toContain('text-[#fca5a5]'); + expect(settingTokenSource).not.toContain('ObservabilityStatusState'); + expect(settingTokenSource).not.toContain('StageSection'); + expect(settingTokenSource).not.toContain('DrawerCodePreview'); + expect(settingTokenSource).not.toContain('SummaryMetricGrid'); expect(settingTokenSource).not.toContain('PayloadPreview density="compact" className="mt-3"'); }); }); diff --git a/web-next/app/milestone5-shared-route-owners.chrome.test.ts b/web-next/app/milestone5-shared-route-owners.chrome.test.ts index 4499341847..ffe0d47d20 100644 --- a/web-next/app/milestone5-shared-route-owners.chrome.test.ts +++ b/web-next/app/milestone5-shared-route-owners.chrome.test.ts @@ -38,42 +38,47 @@ function collectNonTestAppFiles(dir: string): string[] { describe('Milestone 5 shared route owners', () => { it('keeps representative route entrypoints pinned to shared observability owners', () => { - const overviewSource = readFileSync(resolve(process.cwd(), 'app/overview/page.tsx'), 'utf8'); - const monitorsSource = readFileSync(resolve(process.cwd(), 'app/monitors/page.tsx'), 'utf8'); + const overviewSource = readFileSync(resolve(process.cwd(), 'app/overview/overview-page.tsx'), 'utf8'); + const monitorsSource = readFileSync(resolve(process.cwd(), 'app/monitors/monitor-manage-page.tsx'), 'utf8'); const logSource = readFileSync(resolve(process.cwd(), 'app/log/manage/log-manage-page.tsx'), 'utf8'); - const traceSource = readFileSync(resolve(process.cwd(), 'app/trace/manage/page.tsx'), 'utf8'); + const traceSource = readFileSync(resolve(process.cwd(), 'app/trace/manage/trace-manage-page.tsx'), 'utf8'); const alertIntegrationSource = readFileSync(resolve(process.cwd(), 'app/alert/integration/[source]/page.tsx'), 'utf8'); - const alertNoticeSource = readFileSync(resolve(process.cwd(), 'app/alert/notice/page.tsx'), 'utf8'); - const statusSource = readFileSync(resolve(process.cwd(), 'app/status/page.tsx'), 'utf8'); + const alertNoticeSource = readFileSync(resolve(process.cwd(), 'app/alert/notice/alert-notice-page.tsx'), 'utf8'); + const statusSource = readFileSync(resolve(process.cwd(), 'app/status/status-page.tsx'), 'utf8'); const passportSource = readFileSync(resolve(process.cwd(), 'app/passport/login/page.tsx'), 'utf8'); expect(overviewSource).toContain('StageSection'); expect(overviewSource).toContain('SupportPanel'); expect(overviewSource).not.toContain('components/workbench/primitives'); - expect(monitorsSource).toContain('StageSection'); - expect(monitorsSource).toContain('SummaryMetricGrid'); - expect(monitorsSource).toContain('DrawerSection'); + expect(monitorsSource).toContain('HzExplorerFrame'); + expect(monitorsSource).toContain('HzDataTable'); + expect(monitorsSource).toContain('data-monitor-manage-shell-owner="hertzbeat-ui-explorer-frame"'); + expect(monitorsSource).not.toContain('StageSection'); + expect(monitorsSource).not.toContain('SummaryMetricGrid'); + expect(monitorsSource).not.toContain('DrawerSection'); expect(monitorsSource).not.toContain('components/workbench/primitives'); - expect(logSource).toContain('FactsStrip'); - expect(logSource).toContain('StageSection'); - expect(logSource).toContain('DrawerSection'); - expect(logSource).toContain('DrawerCodePreview'); + expect(logSource).toContain('ClientWorkbench'); + expect(logSource).toContain('TimeRangeControl'); + expect(logSource).toContain('data-log-manage-time-control="shared-time-context-control"'); expect(logSource).not.toContain('components/workbench/primitives'); - expect(traceSource).toContain('FactsStrip'); - expect(traceSource).toContain('StageSection'); - expect(traceSource).toContain('DrawerSection'); - expect(traceSource).toContain('DrawerCodePreview'); + expect(traceSource).toContain('ClientWorkbench'); + expect(traceSource).toContain('TimeRangeControl'); + expect(traceSource).toContain('ObservabilityWaterfall'); + expect(traceSource).toContain('data-trace-manage-time-control="shared-time-context-control"'); expect(traceSource).not.toContain('components/workbench/primitives'); - expect(alertIntegrationSource).toContain('StageSection'); - expect(alertIntegrationSource).toContain('DrawerSection'); - expect(alertIntegrationSource).toContain('DrawerCodePreview'); + expect(alertIntegrationSource).toContain('AlertIntegrationMarkdown'); + expect(alertIntegrationSource).toContain('coldOpsCatalogVisual'); + expect(alertIntegrationSource).toContain('data-alert-integration-surface="otlp-cold-source-doc"'); expect(alertIntegrationSource).not.toContain('components/workbench/primitives'); - expect(alertNoticeSource).toContain('StageSection'); + expect(alertNoticeSource).toContain('AlertNoticeConsoleShell'); + expect(alertNoticeSource).toContain('ClientWorkbench'); + expect(alertNoticeSource).toContain('HzConfirmDialog'); + expect(alertNoticeSource).toContain('data-alert-notice-surface="otlp-cold-notice-console"'); expect(alertNoticeSource).not.toContain('components/workbench/primitives'); expect(statusSource).toContain('PublicStatusShell'); diff --git a/web-next/app/monitor-family.chrome.test.ts b/web-next/app/monitor-family.chrome.test.ts index 6274be92f6..78ab561df1 100644 --- a/web-next/app/monitor-family.chrome.test.ts +++ b/web-next/app/monitor-family.chrome.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest'; describe('monitor family cold-workbench chrome', () => { it('removes the remaining legacy white-on-black chrome from the current monitor slice', () => { - const monitorsSource = readFileSync(resolve(process.cwd(), 'app/monitors/page.tsx'), 'utf8'); + const monitorsSource = readFileSync(resolve(process.cwd(), 'app/monitors/monitor-manage-page.tsx'), 'utf8'); const consoleSource = readFileSync(resolve(process.cwd(), 'components/monitor-detail/monitor-detail-console.tsx'), 'utf8'); const sectionsSource = readFileSync(resolve(process.cwd(), 'components/monitor-detail/monitor-detail-sections.tsx'), 'utf8'); const realtimeSource = readFileSync(resolve(process.cwd(), 'components/monitor-detail/monitor-realtime-panel.tsx'), 'utf8'); @@ -40,24 +40,26 @@ describe('monitor family cold-workbench chrome', () => { }); it('adopts shared ops tokens across the current monitor slice', () => { - const monitorsSource = readFileSync(resolve(process.cwd(), 'app/monitors/page.tsx'), 'utf8'); - const detailRouteSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/page.tsx'), 'utf8'); + const monitorsSource = readFileSync(resolve(process.cwd(), 'app/monitors/monitor-manage-page.tsx'), 'utf8'); + const detailRouteSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/monitor-detail-page.tsx'), 'utf8'); const consoleSource = readFileSync(resolve(process.cwd(), 'components/monitor-detail/monitor-detail-console.tsx'), 'utf8'); const sectionsSource = readFileSync(resolve(process.cwd(), 'components/monitor-detail/monitor-detail-sections.tsx'), 'utf8'); const realtimeSource = readFileSync(resolve(process.cwd(), 'components/monitor-detail/monitor-realtime-panel.tsx'), 'utf8'); const historySource = readFileSync(resolve(process.cwd(), 'components/monitor-detail/monitor-history-panel.tsx'), 'utf8'); - expect(monitorsSource).toContain('border-[var(--ops-border-color)]'); - expect(monitorsSource).toContain('text-[var(--ops-text-secondary)]'); - expect(monitorsSource).toContain('text-[var(--ops-text-tertiary)]'); - expect(monitorsSource).toContain('SummaryMetricGrid'); - expect(monitorsSource).toContain('StageSection'); - expect(monitorsSource).toContain("title={t('monitors.section.list.title')}"); - expect(monitorsSource).toContain("DrawerSection title={t('monitors.rail.selected')}"); - expect(monitorsSource).toContain("DrawerSection title={t('monitors.rail.labels')}"); - expect(monitorsSource).toContain("DrawerSection title={t('monitors.rail.controls')}"); - expect(monitorsSource).toContain('DrawerCodePreview'); - expect(monitorsSource).toContain('ObservabilityStatusState'); + expect(monitorsSource).toContain('HzExplorerFrame'); + expect(monitorsSource).toContain('HzDataTable'); + expect(monitorsSource).toContain('HzBatchToolbar'); + expect(monitorsSource).toContain('HzMonitorFilterBar'); + expect(monitorsSource).toContain('data-monitor-manage-filter-owner="hertzbeat-ui-monitor-filter-bar"'); + expect(monitorsSource).toContain('data-monitor-manage-shell-owner="hertzbeat-ui-explorer-frame"'); + expect(monitorsSource).toContain('HzStatusBadge'); + expect(monitorsSource).not.toContain('HzWorkbenchSurface'); + expect(monitorsSource).not.toContain('data-monitor-manage-detail-rail'); + expect(monitorsSource).not.toContain('SummaryMetricGrid'); + expect(monitorsSource).not.toContain('StageSection'); + expect(monitorsSource).not.toContain('DrawerSection'); + expect(monitorsSource).not.toContain('DrawerCodePreview'); expect(monitorsSource).not.toContain('PayloadPreview density="compact" className="mt-2"'); expect(monitorsSource).not.toContain('components/workbench/primitives'); expect(monitorsSource).not.toContain('components/workbench/toolbar'); @@ -65,29 +67,26 @@ describe('monitor family cold-workbench chrome', () => { expect(detailRouteSource).toContain('MonitorDetailConsole'); expect(detailRouteSource).toContain('MonitorDetailSections'); - expect(consoleSource).toContain('border-[var(--ops-border-color)]'); - expect(consoleSource).toContain('text-[var(--ops-text-primary)]'); - expect(consoleSource).toContain('text-[var(--ops-text-secondary)]'); - expect(consoleSource).toContain('text-[var(--ops-text-tertiary)]'); - expect(consoleSource).toContain('ObservabilityControlChip'); - expect(consoleSource).toContain('ObservabilityPillButton'); - expect(consoleSource).toContain('ObservabilityBadge'); + expect(consoleSource).toContain('HzMonitorDetailConsoleShell'); + expect(consoleSource).toContain('HzMonitorDetailWorkbenchFrame'); + expect(consoleSource).toContain('data-monitor-detail-console-shell-owner="hertzbeat-ui-detail-console-shell"'); + expect(consoleSource).toContain('data-monitor-workbench-stage-owner="hertzbeat-ui-detail-workbench-frame"'); expect(consoleSource).not.toContain("from '../workbench/primitives'"); - expect(sectionsSource).toContain('border-[var(--ops-border-color)]'); - expect(sectionsSource).toContain('bg-[var(--ops-surface-panel)]'); - expect(sectionsSource).toContain('text-[var(--ops-text-secondary)]'); - expect(sectionsSource).toContain('ObservabilityControlChip'); - expect(sectionsSource).toContain('ObservabilityBadge'); + expect(sectionsSource).toContain('HzMonitorDetailStage'); + expect(sectionsSource).toContain('HzMonitorDetailSignalList'); + expect(sectionsSource).toContain('data-monitor-detail-stage-owner="hertzbeat-ui-detail-stage"'); + expect(sectionsSource).toContain('data-monitor-detail-signal-list-owner="hertzbeat-ui-signal-list"'); expect(sectionsSource).not.toContain('ObservabilityInsetPanel'); expect(sectionsSource).not.toContain("from '../workbench/primitives'"); - expect(realtimeSource).toContain('border-[var(--ops-border-color)]'); - expect(realtimeSource).toContain('bg-[var(--ops-surface-panel)]'); - expect(realtimeSource).toContain('text-[var(--ops-text-secondary)]'); + expect(realtimeSource).toContain('HzMonitorDetailStage'); + expect(realtimeSource).toContain('HzMonitorRealtimeToolbar'); + expect(realtimeSource).toContain('data-monitor-realtime-toolbar-owner="hertzbeat-ui-realtime-toolbar"'); - expect(historySource).toContain('border-[var(--ops-border-color)]'); - expect(historySource).toContain('bg-[var(--ops-surface-panel)]'); - expect(historySource).toContain('text-[var(--ops-text-secondary)]'); + expect(historySource).toContain('HzMonitorDetailStage'); + expect(historySource).toContain('HzMonitorEvidenceFrame'); + expect(historySource).toContain('data-monitor-history-stage-owner="hertzbeat-ui-detail-stage"'); + expect(historySource).toContain('data-monitor-history-chart-owner="hertzbeat-ui-echarts-panel"'); }); }); diff --git a/web-next/app/monitor-route-ownership.test.ts b/web-next/app/monitor-route-ownership.test.ts index 3528100aaf..84909ed0ba 100644 --- a/web-next/app/monitor-route-ownership.test.ts +++ b/web-next/app/monitor-route-ownership.test.ts @@ -4,12 +4,16 @@ import { describe, expect, it } from 'vitest'; describe('monitor route ownership posture', () => { it('keeps monitor list/detail/create/edit handoffs on shared navigation owners', () => { - const listSource = readFileSync(resolve(process.cwd(), 'app/monitors/page.tsx'), 'utf8'); - const detailSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/page.tsx'), 'utf8'); - const newSource = readFileSync(resolve(process.cwd(), 'app/monitors/new/page.tsx'), 'utf8'); - const editSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/page.tsx'), 'utf8'); + const listRouteSource = readFileSync(resolve(process.cwd(), 'app/monitors/page.tsx'), 'utf8'); + const listSource = readFileSync(resolve(process.cwd(), 'app/monitors/monitor-manage-page.tsx'), 'utf8'); + const detailSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/monitor-detail-page.tsx'), 'utf8'); + const newRouteSource = readFileSync(resolve(process.cwd(), 'app/monitors/new/page.tsx'), 'utf8'); + const newSource = readFileSync(resolve(process.cwd(), 'app/monitors/new/monitor-new-page.tsx'), 'utf8'); + const editRouteSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/page.tsx'), 'utf8'); + const editSource = readFileSync(resolve(process.cwd(), 'app/monitors/[monitorId]/edit/monitor-edit-page.tsx'), 'utf8'); const editorSurfaceSource = readFileSync(resolve(process.cwd(), 'components/pages/monitor-editor-surface.tsx'), 'utf8'); + expect(listRouteSource).toContain("import MonitorManagePage from './monitor-manage-page'"); expect(listSource).toContain('buildMonitorDetailHref'); expect(listSource).toContain('buildMonitorEditHref'); expect(listSource).toContain('buildMonitorNewHref'); @@ -20,20 +24,21 @@ describe('monitor route ownership posture', () => { expect(detailSource).not.toContain("from '@/lib/monitor-editor/navigation'"); expect(detailSource).not.toContain('buildMonitorEditorReturnUrl'); - expect(newSource).toContain('returnContext={{'); - expect(newSource).toContain("labels: searchParams.get('labels')"); - expect(newSource).toContain("pageIndex: searchParams.get('pageIndex')"); - expect(newSource).toContain("pageSize: searchParams.get('pageSize')"); - expect(newSource).toContain("entityId: searchParams.get('entityId')"); - expect(newSource).toContain("entityName: searchParams.get('entityName')"); + expect(newRouteSource).toContain("import MonitorNewPage from './monitor-new-page'"); + expect(newSource).toContain('returnContext={returnContext}'); + expect(newRouteSource).toContain('readMonitorNewRouteState(resolvedSearchParams)'); expect(newSource).not.toContain("returnLabel: searchParams.get('returnLabel')"); - expect(editSource).toContain('returnContext={{'); - expect(editSource).toContain("labels: searchParams.get('labels')"); - expect(editSource).toContain("pageIndex: searchParams.get('pageIndex')"); - expect(editSource).toContain("pageSize: searchParams.get('pageSize')"); - expect(editSource).toContain("entityId: searchParams.get('entityId')"); - expect(editSource).toContain("entityName: searchParams.get('entityName')"); + expect(editRouteSource).toContain("import MonitorEditPage from './monitor-edit-page'"); + expect(editSource).toContain('returnContext={returnContext}'); + expect(editRouteSource).toContain('readMonitorEditRouteState(resolvedSearchParams)'); expect(editSource).not.toContain("returnLabel: searchParams.get('returnLabel')"); + const queryStateSource = readFileSync(resolve(process.cwd(), 'lib/monitor-editor/query-state.ts'), 'utf8'); + expect(queryStateSource).toContain("labels: reader.get('labels')"); + expect(queryStateSource).toContain("pageIndex: reader.get('pageIndex')"); + expect(queryStateSource).toContain("pageSize: reader.get('pageSize')"); + expect(queryStateSource).toContain("entityId: reader.get('entityId')"); + expect(queryStateSource).toContain("entityName: reader.get('entityName')"); + expect(queryStateSource).not.toContain("returnLabel: reader.get('returnLabel')"); expect(editorSurfaceSource).toContain('returnContext?:'); expect(editorSurfaceSource).toContain('...returnContext'); diff --git a/web-next/app/operator-family.chrome.test.ts b/web-next/app/operator-family.chrome.test.ts index 1f272df894..6fbe9765f5 100644 --- a/web-next/app/operator-family.chrome.test.ts +++ b/web-next/app/operator-family.chrome.test.ts @@ -4,8 +4,8 @@ import { describe, expect, it } from 'vitest'; describe('operator family cold-workbench chrome', () => { it('removes the remaining route-local rounded panel recipes from the OTLP operator slice', () => { - const otlpSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/page.tsx'), 'utf8'); - const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/page.tsx'), 'utf8'); + const otlpSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/otlp-page.tsx'), 'utf8'); + const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/otlp-metrics-page.tsx'), 'utf8'); const combinedSource = [otlpSource, metricsSource].join('\n'); expect(combinedSource).not.toContain('rounded-[10px] border-[var(--ops-border-color)] bg-[var(--ops-surface-panel)] px-4 py-3 shadow-none'); @@ -13,24 +13,26 @@ describe('operator family cold-workbench chrome', () => { expect(combinedSource).not.toContain('rounded-[10px] border border-[var(--ops-border-color)] bg-[var(--ops-surface-panel)]'); }); - it('moves the OTLP pilot route onto the new observability owner layer', () => { - const otlpSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/page.tsx'), 'utf8'); - const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/page.tsx'), 'utf8'); + it('keeps the OTLP pilot route on the cold-ops catalog owner layer', () => { + const otlpSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/otlp-page.tsx'), 'utf8'); + const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/otlp-metrics-page.tsx'), 'utf8'); - expect(otlpSource).toContain('components/observability'); - expect(otlpSource).toContain('FactsStrip'); - expect(otlpSource).toContain('StageSection'); - expect(otlpSource).toContain('SummaryMetricGrid'); - expect(otlpSource).toContain('DrawerSection'); - expect(otlpSource).toContain('SupportActionBar'); + expect(otlpSource).toContain('coldOpsCatalogVisual'); + expect(otlpSource).toContain('SearchRow'); + expect(otlpSource).toContain('ClientWorkbench'); + expect(otlpSource).toContain('buildReadinessRows'); + expect(otlpSource).toContain('buildSelfCheckRows'); + expect(otlpSource).toContain('data-otlp-center-source-grid="hertzbeat-source-catalog"'); + expect(otlpSource).toContain('data-otlp-center-filter-rail="hertzbeat-prism-filters"'); expect(otlpSource).not.toContain('components/workbench/primitives'); expect(metricsSource).toContain('components/observability'); - expect(metricsSource).toContain('FactsStrip'); - expect(metricsSource).toContain('StageSection'); - expect(metricsSource).toContain('SummaryMetricGrid'); - expect(metricsSource).toContain('DrawerSection'); - expect(metricsSource).toContain('DrawerCodePreview'); + expect(metricsSource).toContain('components/observability/time-range-control'); + expect(metricsSource).toContain('EChartsPanel'); + expect(metricsSource).toContain('ClientWorkbench'); + expect(metricsSource).toContain('buildConsoleFacts'); + expect(metricsSource).toContain('buildMetricsHandoffLinks'); + expect(metricsSource).toContain('data-otlp-metrics-route="otlp-cold-metrics-workbench"'); expect(metricsSource).not.toContain('components/workbench/primitives'); }); }); diff --git a/web-next/app/page.test.ts b/web-next/app/page.test.ts index 00fb5ab761..25f41bb891 100644 --- a/web-next/app/page.test.ts +++ b/web-next/app/page.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; const redirect = vi.fn(); @@ -7,6 +7,10 @@ vi.mock('next/navigation', () => ({ })); describe('root alias route', () => { + beforeEach(() => { + redirect.mockReset(); + }); + it('redirects the shell entry point to the overview workbench', async () => { redirect.mockImplementation((target: string) => { throw new Error(`redirect:${target}`); @@ -14,7 +18,39 @@ describe('root alias route', () => { const { default: HomePage } = await import('./page'); - expect(() => HomePage()).toThrow('redirect:/overview'); + await expect(HomePage({})).rejects.toThrow('redirect:/overview'); expect(redirect).toHaveBeenCalledWith('/overview'); }); + + it('preserves machine query context when redirecting to overview', async () => { + redirect.mockImplementation((target: string) => { + throw new Error(`redirect:${target}`); + }); + + const { default: HomePage } = await import('./page'); + + await expect( + HomePage({ + searchParams: Promise.resolve({ + source: 'shell', + serviceName: 'checkout', + returnTo: '/monitors?returnLabel=Legacy', + returnLabel: 'Overview', + start: '1700000000000', + environment: ['prod', 'ignored'] + }) + }) + ).rejects.toThrow('redirect:/overview?'); + + const target = redirect.mock.calls[0]?.[0] as string; + const url = new URL(target, 'http://127.0.0.1'); + + expect(url.pathname).toBe('/overview'); + expect(url.searchParams.get('source')).toBe('shell'); + expect(url.searchParams.get('serviceName')).toBe('checkout'); + expect(url.searchParams.get('environment')).toBe('prod'); + expect(url.searchParams.get('returnTo')).toBe('/monitors'); + expect(url.searchParams.get('returnLabel')).toBeNull(); + expect(url.searchParams.get('start')).toBe('1700000000000'); + }); }); diff --git a/web-next/app/page.tsx b/web-next/app/page.tsx index db5e41d241..a868bb1d37 100644 --- a/web-next/app/page.tsx +++ b/web-next/app/page.tsx @@ -1,5 +1,13 @@ import { redirect } from 'next/navigation'; -export default function HomePage() { - redirect('/overview'); +import { buildOverviewCompatRouteUrl, type SearchParamsRecord } from '../lib/overview/navigation'; + +interface HomePageProps { + searchParams?: Promise<SearchParamsRecord>; +} + +export default async function HomePage({ searchParams }: HomePageProps) { + const resolvedSearchParams = await searchParams; + + redirect(buildOverviewCompatRouteUrl(resolvedSearchParams)); } diff --git a/web-next/app/settings-family.chrome.test.ts b/web-next/app/settings-family.chrome.test.ts index a55b707919..2e7372c2be 100644 --- a/web-next/app/settings-family.chrome.test.ts +++ b/web-next/app/settings-family.chrome.test.ts @@ -36,7 +36,7 @@ describe('settings family cold-matte chrome', () => { const labelSource = readFileSync(resolve(process.cwd(), 'components/pages/label-manage-surface.tsx'), 'utf8'); const defineSource = readFileSync(resolve(process.cwd(), 'components/pages/setting-define-surface.tsx'), 'utf8'); const statusSource = readFileSync(resolve(process.cwd(), 'components/pages/status-setting-surface.tsx'), 'utf8'); - const tokenSource = readFileSync(resolve(process.cwd(), 'app/setting/settings/token/page.tsx'), 'utf8'); + const tokenSource = readFileSync(resolve(process.cwd(), 'app/setting/settings/token/setting-token-page.tsx'), 'utf8'); expect(settingsSurfaceSource).toContain('variant="flat"'); @@ -53,11 +53,14 @@ describe('settings family cold-matte chrome', () => { expect(pluginSource).not.toContain('WorkbenchTableFrame'); expect(labelSource).toContain('data-label-manage-style-baseline={coldLabelVisual.canvasName}'); - expect(labelSource).toContain('data-label-table-shell="cold-dense-table"'); + expect(labelSource).toContain('data-label-card-grid-contract="angular-card-grid"'); + expect(labelSource).toContain('data-label-card-grid-owner="hertzbeat-ui-label-tag"'); + expect(labelSource).not.toContain('data-label-table-shell="cold-dense-table"'); expect(defineSource).toContain('data-setting-define-style-baseline={coldDefineVisual.canvasName}'); expect(defineSource).toContain('data-setting-define-workspace="cold-define-workspace"'); - expect(defineSource).toContain("from '../ui/search-row'"); - expect(defineSource).toContain('data-setting-define-search-owner="shared-search-row"'); + expect(defineSource).toContain('HzYamlWorkspace'); + expect(defineSource).not.toContain("from '../ui/search-row'"); + expect(defineSource).not.toContain('data-setting-define-search-owner="shared-search-row"'); expect(defineSource).not.toContain('coldDefineVisual.search.row'); expect(defineSource).not.toContain('coldDefineVisual.search.input'); expect(statusSource).toContain('data-status-setting-style-baseline={coldStatusVisual.canvasName}'); diff --git a/web-next/app/three-signal-family.chrome.test.ts b/web-next/app/three-signal-family.chrome.test.ts index 0238a3ab53..1504880614 100644 --- a/web-next/app/three-signal-family.chrome.test.ts +++ b/web-next/app/three-signal-family.chrome.test.ts @@ -6,10 +6,11 @@ const sourceFiles = [ 'components/pages/three-signal-desk-shell.tsx', 'components/overview/overview-console.tsx', 'components/workbench/primitives.tsx', + 'app/overview/overview-page.tsx', 'app/log/manage/log-manage-page.tsx', - 'app/trace/manage/page.tsx', - 'app/ingestion/otlp/page.tsx', - 'app/ingestion/otlp/metrics/page.tsx' + 'app/trace/manage/trace-manage-page.tsx', + 'app/ingestion/otlp/otlp-page.tsx', + 'app/ingestion/otlp/metrics/otlp-metrics-page.tsx' ]; describe('three-signal family cold-workbench chrome', () => { @@ -24,10 +25,10 @@ describe('three-signal family cold-workbench chrome', () => { it('adopts shared workbench owners across the exactness-sensitive three-signal family', () => { const shellSource = readFileSync(resolve(process.cwd(), 'components/pages/three-signal-desk-shell.tsx'), 'utf8'); - const overviewSource = readFileSync(resolve(process.cwd(), 'app/overview/page.tsx'), 'utf8'); + const overviewSource = readFileSync(resolve(process.cwd(), 'app/overview/overview-page.tsx'), 'utf8'); const logSource = readFileSync(resolve(process.cwd(), 'app/log/manage/log-manage-page.tsx'), 'utf8'); - const traceSource = readFileSync(resolve(process.cwd(), 'app/trace/manage/page.tsx'), 'utf8'); - const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/page.tsx'), 'utf8'); + const traceSource = readFileSync(resolve(process.cwd(), 'app/trace/manage/trace-manage-page.tsx'), 'utf8'); + const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/otlp-metrics-page.tsx'), 'utf8'); expect(shellSource).toContain("../observability/workspace-shell"); expect(shellSource).not.toContain("../workbench/workspace-shell"); @@ -74,11 +75,11 @@ describe('three-signal family cold-workbench chrome', () => { }); it('removes the remaining legacy white-on-black chrome from the three-signal family', () => { - const overviewSource = readFileSync(resolve(process.cwd(), 'app/overview/page.tsx'), 'utf8'); + const overviewSource = readFileSync(resolve(process.cwd(), 'app/overview/overview-page.tsx'), 'utf8'); const logSource = readFileSync(resolve(process.cwd(), 'app/log/manage/log-manage-page.tsx'), 'utf8'); - const traceSource = readFileSync(resolve(process.cwd(), 'app/trace/manage/page.tsx'), 'utf8'); - const otlpSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/page.tsx'), 'utf8'); - const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/page.tsx'), 'utf8'); + const traceSource = readFileSync(resolve(process.cwd(), 'app/trace/manage/trace-manage-page.tsx'), 'utf8'); + const otlpSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/otlp-page.tsx'), 'utf8'); + const metricsSource = readFileSync(resolve(process.cwd(), 'app/ingestion/otlp/metrics/otlp-metrics-page.tsx'), 'utf8'); expect(overviewSource).not.toContain('text-white/44'); expect(overviewSource).not.toContain('#f3eee6'); diff --git a/web-next/lib/dashboard/navigation.test.ts b/web-next/lib/dashboard/navigation.test.ts index 6692b59427..d650d9df0c 100644 --- a/web-next/lib/dashboard/navigation.test.ts +++ b/web-next/lib/dashboard/navigation.test.ts @@ -31,6 +31,10 @@ describe('dashboard navigation', () => { expect(source).not.toContain('sanitizeDashboardSearchParams'); expect(source).not.toContain('delete next.returnLabel'); expect(source).not.toContain('stripReturnLabelFromHref'); - expect(source).toContain("buildCompatRedirectTarget('/overview', searchParams)"); + expect(source).toContain("import { buildOverviewCompatRouteUrl, type SearchParamsRecord } from '../overview/navigation'"); + expect(source).toContain('export type { SearchParamsRecord }'); + expect(source).toContain('return buildOverviewCompatRouteUrl(searchParams)'); + expect(source).not.toContain("buildCompatRedirectTarget('/overview'"); + expect(source).not.toContain("'/overview'"); }); }); diff --git a/web-next/lib/dashboard/navigation.ts b/web-next/lib/dashboard/navigation.ts index a443d93859..a227a352f4 100644 --- a/web-next/lib/dashboard/navigation.ts +++ b/web-next/lib/dashboard/navigation.ts @@ -1,5 +1,7 @@ -import { buildCompatRedirectTarget, type SearchParamsRecord } from '../compat/search-params'; +import { buildOverviewCompatRouteUrl, type SearchParamsRecord } from '../overview/navigation'; + +export type { SearchParamsRecord }; export function buildDashboardCompatRouteUrl(searchParams?: SearchParamsRecord) { - return buildCompatRedirectTarget('/overview', searchParams); + return buildOverviewCompatRouteUrl(searchParams); } diff --git a/web-next/lib/nav.test.ts b/web-next/lib/nav.test.ts index 175e952ba9..3944b47f7b 100644 --- a/web-next/lib/nav.test.ts +++ b/web-next/lib/nav.test.ts @@ -89,7 +89,9 @@ describe('navigation information architecture', () => { expect(cutoverCandidateRoutes.some(route => route.href === '/topology')).toBe(true); expect(cutoverHoldRoutes.some(route => route.href === '/log/manage')).toBe(true); expect(cutoverHoldRoutes.some(route => route.href === '/trace/manage')).toBe(true); - expect(placeholderRoutes.map(route => route.href)).toEqual(['/incidents', '/actions', '/explorer']); + expect(cutoverCandidateRoutes.some(route => route.href === '/incidents')).toBe(true); + expect(cutoverCandidateRoutes.some(route => route.href === '/explorer')).toBe(true); + expect(placeholderRoutes.map(route => route.href)).toEqual(['/actions']); }); it('keeps legacy aliases and route-matrix targets in the route contract', () => { diff --git a/web-next/lib/nav.ts b/web-next/lib/nav.ts index 63315350e4..eaac230991 100644 --- a/web-next/lib/nav.ts +++ b/web-next/lib/nav.ts @@ -74,7 +74,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.entity.discovery', label: 'Discovery', href: '/entities/discovery', - icon: 'entities', + icon: 'entity-discovery', navSectionKey: 'objects', routeKind: 'primary', cutoverStatus: 'candidate', @@ -86,7 +86,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.entity.definition', label: 'Definitions', href: '/entities/import', - icon: 'entities', + icon: 'entity-definition', navSectionKey: 'objects', routeKind: 'primary', cutoverStatus: 'candidate', @@ -163,7 +163,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.alert.integration', label: 'Integrations', href: '/alert/integration/[source]', - icon: 'alert', + icon: 'alert-integration', navSectionKey: 'alerting', routeKind: 'primary', cutoverStatus: 'candidate', @@ -175,7 +175,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.alert.group', label: 'Grouping', href: '/alert/group', - icon: 'alert', + icon: 'alert-group', navSectionKey: 'alerting', routeKind: 'primary', cutoverStatus: 'candidate', @@ -187,7 +187,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.alert.inhibit', label: 'Inhibit rules', href: '/alert/inhibit', - icon: 'alert', + icon: 'alert-inhibit', navSectionKey: 'alerting', routeKind: 'primary', cutoverStatus: 'candidate', @@ -199,7 +199,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.alert.silence', label: 'Silence rules', href: '/alert/silence', - icon: 'alert', + icon: 'alert-silence', navSectionKey: 'alerting', routeKind: 'primary', cutoverStatus: 'candidate', @@ -213,7 +213,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ href: '/incidents', icon: 'incidents', routeKind: 'primary', - cutoverStatus: 'placeholder', + cutoverStatus: 'candidate', smokePath: '/incidents', includeInRouteMatrix: false }, @@ -233,7 +233,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.alert.dispatch', label: 'Notifications', href: '/alert/notice', - icon: 'alert', + icon: 'alert-notice', navSectionKey: 'alerting', routeKind: 'primary', cutoverStatus: 'candidate', @@ -319,7 +319,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ label: 'Explorer', href: '/explorer', routeKind: 'primary', - cutoverStatus: 'placeholder', + cutoverStatus: 'candidate', smokePath: '/explorer', includeInRouteMatrix: false }, @@ -381,7 +381,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.monitor.collector', label: 'Collector', href: '/setting/collector', - icon: 'monitor', + icon: 'collector', navSectionKey: 'ingestion', routeKind: 'primary', cutoverStatus: 'candidate', @@ -393,7 +393,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.monitor.template', label: 'Definitions', href: '/setting/define', - icon: 'monitor', + icon: 'monitor-template', navSectionKey: 'ingestion', routeKind: 'primary', cutoverStatus: 'candidate', @@ -405,7 +405,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.monitor.bulletin', label: 'Bulletin', href: '/bulletin', - icon: 'bulletin', + icon: 'alert-bulletin', navSectionKey: 'alerting', routeKind: 'primary', cutoverStatus: 'candidate', @@ -431,7 +431,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.advanced.mcp-server', label: 'MCP Server', href: '/setting/settings/mcp-server', - icon: 'settings', + icon: 'mcp-server', navSectionKey: 'settings', includeInNavigation: true, routeKind: 'legacy-alias', @@ -468,7 +468,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.advanced.labels', label: 'Labels', href: '/setting/labels', - icon: 'settings', + icon: 'token', navSectionKey: 'settings', routeKind: 'primary', cutoverStatus: 'candidate', @@ -480,7 +480,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.advanced.plugins', label: 'Plugins', href: '/setting/plugins', - icon: 'settings', + icon: 'plugins', navSectionKey: 'settings', routeKind: 'primary', cutoverStatus: 'candidate', @@ -492,7 +492,7 @@ export const routeCatalog: RouteCatalogEntry[] = [ labelKey: 'menu.extras.help', label: 'Help center', href: 'https://hertzbeat.apache.org/docs/', - icon: 'settings', + icon: 'help', navSectionKey: 'settings', includeInNavigation: true, routeKind: 'primary', diff --git a/web-next/lib/parity/route-manifest.json b/web-next/lib/parity/route-manifest.json index e89f3bf5bf..87baa4905d 100644 --- a/web-next/lib/parity/route-manifest.json +++ b/web-next/lib/parity/route-manifest.json @@ -853,7 +853,7 @@ "[data-entity-list-table-shell=\"cold-dense-table\"]", "[data-entity-list-table=\"cold-entity-table\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input", @@ -897,25 +897,25 @@ "[data-entity-detail-drilldown-panel=\"cold-drilldown-panel\"]", "[data-entity-detail-error=\"cold-inline-error\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "a", "button" ], "textSnippets": [ - "实体详情", - "对象优先调查", - "上下文", - "相关信号", - "下一步", - "高级入口" + "Entity detail", + "Entity-first investigation", + "Context", + "Related signals", + "Next step", + "Advanced entries" ], "actionLabels": [ - "全部实体", - "刷新", - "编辑定义", - "删除", - "编辑" + "All entities", + "Refresh", + "Edit definition", + "Delete", + "Edit" ], "minimumVerificationCommand": "npm exec vitest run 'app/entities/[entityId]/page.test.tsx' components/pages/entity-detail-surface.test.tsx app/entity-detail-family.chrome.test.ts lib/entity-detail/view-model.test.ts lib/parity/route-manifest.test.ts" }, @@ -948,7 +948,7 @@ "[data-entity-type-icon=\"service\"]", "[data-entity-type-icon=\"database\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "form", "input", "button" @@ -1000,7 +1000,7 @@ "[data-entity-type-icon=\"service\"]", "[data-entity-type-icon=\"database\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "form", "input", "button" @@ -1042,7 +1042,7 @@ "[data-entity-definition-template-panel=\"true\"]", "[data-entity-definition-batch-panel=\"true\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "textarea", "button" @@ -1084,7 +1084,7 @@ "[data-entity-discovery-table=\"cold-discovery-table\"]", "[data-entity-discovery-row-actions=\"cold-inline-actions\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input", @@ -1133,7 +1133,7 @@ "[data-entity-definition-load-error=\"cold-inline\"]", "[data-entity-definition-error-placement=\"cold-context-panel\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "textarea", "button", @@ -1185,7 +1185,7 @@ "[data-alert-center-empty-state=\"cold-table-empty\"]", "[data-alert-center-empty-icon=\"cold-empty-box\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "input", "select", @@ -1287,7 +1287,7 @@ "[data-alert-group-empty-state=\"cold-table-empty\"]", "[data-alert-group-empty-icon=\"cold-empty-box\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input" @@ -1325,7 +1325,7 @@ "[data-alert-silence-empty-state=\"cold-table-empty\"]", "[data-alert-silence-empty-icon=\"cold-empty-box\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input" @@ -1364,7 +1364,7 @@ "[data-alert-inhibit-empty-state=\"cold-table-empty\"]", "[data-alert-inhibit-empty-icon=\"cold-empty-box\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input" @@ -1403,7 +1403,7 @@ "[data-alert-notice-receiver-toolbar=\"cold-query-toolbar\"]", "[data-alert-notice-receiver-table-shell=\"cold-dense-table\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "[data-tab=\"receiver\"]", "main", "button", @@ -1456,7 +1456,7 @@ "[data-alert-notice-rule-toolbar=\"cold-query-toolbar\"]", "[data-alert-notice-rule-table-shell=\"cold-dense-table\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input" @@ -1510,7 +1510,7 @@ "[data-alert-notice-template-table-shell=\"cold-dense-table\"]", "[data-alert-notice-pagination=\"cold-dense-pagination\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input", @@ -1553,7 +1553,7 @@ "[data-alert-setting-empty-state=\"cold-table-empty\"]", "[data-alert-setting-empty-icon=\"cold-empty-box\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input" @@ -1594,7 +1594,7 @@ "[data-alert-integration-code-block=\"json\"]", "[data-alert-integration-mermaid]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button" ], @@ -1642,7 +1642,7 @@ "[data-setting-config-form=\"cold-settings-form\"]", "[data-setting-config-actions=\"standard-equal-buttons\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "form", "select", "button" @@ -1748,7 +1748,7 @@ "[data-setting-config-form=\"cold-settings-form\"]", "[data-setting-config-actions=\"standard-equal-buttons\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "form", "select", "button" @@ -1787,7 +1787,7 @@ "[data-setting-object-store-provider=\"cold-provider-select\"]", "[data-setting-object-store-actions=\"standard-equal-buttons\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "form", "select", "button" @@ -1830,7 +1830,7 @@ "[data-settings-summary-action=\"email\"]", "[data-settings-summary-action=\"sms\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button" ], @@ -1874,7 +1874,7 @@ "[data-setting-token-table-panel=\"cold-dense-table\"]", "[data-setting-token-table=\"cold-token-table\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "table" @@ -1958,15 +1958,14 @@ "[data-setting-define-menu-shell=\"cold-dense-list\"]", "[data-setting-define-editor=\"cold-settings-form\"]", "[data-setting-define-editor-shell=\"cold-yaml-editor\"]", - "[data-setting-define-editor-field=\"cold-code-textarea\"]", + "[data-setting-define-editor-field=\"cold-code-editor\"]", + "[data-setting-define-code-editor=\"monitor-template-yaml\"]", "button", - "input", - "textarea" + "input" ], "textSnippets": [ "定义", - "管理监控类型定义", - "数据源状态" + "管理监控模板 YAML" ], "actionLabels": [ "新增类型", @@ -1974,7 +1973,8 @@ "取消", "保存并应用", "搜索", - "预览查询" + "隐藏", + "显示" ], "minimumVerificationCommand": "npm exec vitest run app/setting/define/page.test.tsx components/pages/setting-define-surface.test.tsx lib/setting-define/controller.test.ts lib/setting-define/query-state.test.ts lib/setting-define/view-model.test.ts lib/parity/route-manifest.test.ts" }, @@ -1997,7 +1997,7 @@ "[data-label-table=\"cold-label-table\"]", "[data-label-empty-state=\"cold-table-empty\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input", @@ -2037,7 +2037,7 @@ "[data-plugin-manage-table=\"cold-plugin-table\"]", "[data-plugin-manage-empty-state=\"cold-table-empty\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input", @@ -2077,7 +2077,7 @@ "[data-status-component-table-shell=\"cold-dense-table\"]", "[data-status-component-table=\"cold-component-table\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "button", "input", @@ -2294,8 +2294,8 @@ "key": "compatibility-placeholder-family", "milestone": 5, "legacyArea": "compatibility-and-placeholder", - "summary": "Close compatibility aliases plus placeholder twins in the final parity sweep.", - "familyVerificationCommand": "npm exec vitest run app/dashboard/page.test.ts app/overview/page.test.tsx lib/overview/navigation.test.ts lib/overview/view-model.test.ts app/events/page.test.ts app/log/manage/page.test.tsx lib/log-manage/query-state.test.ts lib/log-manage/view-model.test.ts app/alerts/page.test.ts app/alert/page.test.tsx components/pages/alert-center-surface.test.tsx lib/alert-manage/query-state.test.ts lib/alert-manage/controller.test.ts lib/alert-manage/view-model.te [...] + "summary": "Close compatibility aliases plus placeholder twins and the unified Explorer workbench in the final parity sweep.", + "familyVerificationCommand": "npm exec vitest run app/dashboard/page.test.ts app/overview/page.test.tsx lib/overview/navigation.test.ts lib/overview/view-model.test.ts app/events/page.test.ts app/log/manage/page.test.tsx lib/log-manage/query-state.test.ts lib/log-manage/view-model.test.ts app/alerts/page.test.ts app/alert/page.test.tsx components/pages/alert-center-surface.test.tsx lib/alert-manage/query-state.test.ts lib/alert-manage/controller.test.ts lib/alert-manage/view-model.te [...] "referenceBaseUrl": "http://127.0.0.1:4301", "nextBaseUrl": "http://127.0.0.1:4200", "routePairs": [ @@ -2383,7 +2383,7 @@ "[data-alert-center-empty-state=\"cold-table-empty\"]", "[data-alert-center-empty-icon=\"cold-empty-box\"]", "[data-platform-footer=\"angular-footer\"]", - "[data-shell-help-launcher=\"angular-help\"]", + "[data-shell-ai-chat-launcher=\"angular-ai-chat\"]", "main", "input", "select", @@ -2446,8 +2446,22 @@ "primarySelectors": [ "[data-actions-route=\"otlp-cold-ops-entry\"]", "[data-actions-style-baseline=\"hertzbeat-cold-matte\"]", + "[data-actions-shared-workbench=\"hertzbeat-ui\"]", + "[data-hz-action-workbench-owner=\"hertzbeat-ui-action-workbench\"]", "[data-actions-shell-panel=\"cold-ops-shell-panel\"]", "[data-actions-launch-checklist=\"cold-ops-static-rail\"]", + "[data-actions-catalog=\"manual-action-catalog-api\"]", + "[data-actions-catalog-owner=\"next-actions-catalog-bff\"]", + "[data-actions-catalog-execution-allowed=\"false\"]", + "[data-actions-approval-draft=\"manual-approval-draft-api\"]", + "[data-actions-approval-draft-owner=\"next-actions-approval-draft-bff\"]", + "[data-actions-approval-draft-execution-allowed=\"false\"]", + "[data-actions-approval-draft-queue=\"manual-approval-draft-read-api\"]", + "[data-actions-approval-draft-queue-owner=\"next-actions-approval-draft-bff\"]", + "[data-actions-approval-draft-queue-execution-allowed=\"false\"]", + "[data-actions-approval-decision=\"manual-approval-decision-api\"]", + "[data-actions-approval-decision-owner=\"next-actions-approval-decision-bff\"]", + "[data-actions-approval-decision-execution-allowed=\"false\"]", "[data-actions-empty-state=\"cold-ops-domain-adapter\"]", "main", "section", @@ -2465,7 +2479,7 @@ "打开概览", "查看对象" ], - "minimumVerificationCommand": "npm exec vitest run app/actions/page.test.tsx lib/actions-surface/view-model.test.ts lib/actions-surface/model.test.ts lib/parity/route-manifest.test.ts" + "minimumVerificationCommand": "npm exec vitest run app/actions/page.test.tsx app/api/actions/approval-drafts/route.test.ts app/api/actions/approval-drafts/[draftId]/decision/route.test.ts app/api/actions/catalog/route.test.ts lib/actions-surface/view-model.test.ts lib/actions-surface/model.test.ts lib/parity/route-manifest.test.ts" }, { "key": "topology-placeholder", @@ -2500,7 +2514,7 @@ "minimumVerificationCommand": "npm exec vitest run app/topology/page.test.tsx lib/topology-surface/view-model.test.ts lib/parity/route-manifest.test.ts" }, { - "key": "explorer-placeholder", + "key": "explorer-workbench", "nextRoute": "/explorer", "referenceRoute": "/explorer", "nextPagePath": "web-next/app/explorer/page.tsx", @@ -2511,9 +2525,16 @@ "primarySelectors": [ "[data-explorer-route=\"otlp-cold-workbench\"]", "[data-explorer-style-baseline=\"hertzbeat-cold-matte\"]", + "[data-explorer-api-owner=\"trace-log-bff-query-api\"]", + "[data-explorer-query-state]", + "[data-explorer-signal-filter]", + "[data-explorer-api-source]", + "[data-explorer-shared-frame=\"hertzbeat-ui\"]", + "[data-hz-ui=\"explorer-frame\"]", "[data-explorer-query-bar=\"cold-query-row\"]", "[data-explorer-chart-band=\"cold-chart-band\"]", "[data-explorer-result-table=\"cold-dense-table\"]", + "[data-explorer-result-table-owner=\"hertzbeat-ui-data-table\"]", "[data-explorer-detail-panel=\"cold-detail-panel\"]", "main", "button", @@ -2532,7 +2553,7 @@ "创建告警", "加入仪表盘" ], - "minimumVerificationCommand": "npm exec vitest run app/explorer/page.test.tsx lib/explorer-surface/view-model.test.ts lib/parity/route-manifest.test.ts" + "minimumVerificationCommand": "npm exec vitest run app/explorer/page.test.tsx lib/explorer-surface/controller.test.ts lib/explorer-surface/view-model.test.ts lib/otlp-metrics/controller.test.ts lib/otlp-metrics/view-model.test.ts lib/parity/route-manifest.test.ts" } ] } diff --git a/web-next/lib/parity/route-manifest.test.ts b/web-next/lib/parity/route-manifest.test.ts index 92a2fee26a..a8909d7f32 100644 --- a/web-next/lib/parity/route-manifest.test.ts +++ b/web-next/lib/parity/route-manifest.test.ts @@ -139,7 +139,7 @@ describe('parity route manifest', () => { '[data-alert-center-empty-state="cold-table-empty"]', '[data-alert-center-empty-icon="cold-empty-box"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'input', 'select', @@ -187,8 +187,22 @@ describe('parity route manifest', () => { primarySelectors: expect.arrayContaining([ '[data-actions-route="otlp-cold-ops-entry"]', '[data-actions-style-baseline="hertzbeat-cold-matte"]', + '[data-actions-shared-workbench="hertzbeat-ui"]', + '[data-hz-action-workbench-owner="hertzbeat-ui-action-workbench"]', '[data-actions-shell-panel="cold-ops-shell-panel"]', '[data-actions-launch-checklist="cold-ops-static-rail"]', + '[data-actions-catalog="manual-action-catalog-api"]', + '[data-actions-catalog-owner="next-actions-catalog-bff"]', + '[data-actions-catalog-execution-allowed="false"]', + '[data-actions-approval-draft="manual-approval-draft-api"]', + '[data-actions-approval-draft-owner="next-actions-approval-draft-bff"]', + '[data-actions-approval-draft-execution-allowed="false"]', + '[data-actions-approval-draft-queue="manual-approval-draft-read-api"]', + '[data-actions-approval-draft-queue-owner="next-actions-approval-draft-bff"]', + '[data-actions-approval-draft-queue-execution-allowed="false"]', + '[data-actions-approval-decision="manual-approval-decision-api"]', + '[data-actions-approval-decision-owner="next-actions-approval-decision-bff"]', + '[data-actions-approval-decision-execution-allowed="false"]', '[data-actions-empty-state="cold-ops-domain-adapter"]', 'main', 'section', @@ -198,7 +212,7 @@ describe('parity route manifest', () => { textSnippets: expect.arrayContaining(['自动化处置', '按 OTLP 工作台的冷色基线', '冷色入口已接入', '等待接入执行适配器', '你已完成 83% 的平台配置']), actionLabels: ['打开概览', '查看对象'], minimumVerificationCommand: - 'npm exec vitest run app/actions/page.test.tsx lib/actions-surface/view-model.test.ts lib/actions-surface/model.test.ts lib/parity/route-manifest.test.ts' + 'npm exec vitest run app/actions/page.test.tsx app/api/actions/approval-drafts/route.test.ts app/api/actions/approval-drafts/[draftId]/decision/route.test.ts app/api/actions/catalog/route.test.ts lib/actions-surface/view-model.test.ts lib/actions-surface/model.test.ts lib/parity/route-manifest.test.ts' }); }); @@ -223,15 +237,22 @@ describe('parity route manifest', () => { }); }); - it('tracks the OTLP cold unified explorer instead of the old external-product-first surface', () => { - expect(getParityRoutePair('compatibility-placeholder-family', 'explorer-placeholder')).toMatchObject({ + it('tracks the OTLP cold unified explorer as an API/query-backed workbench instead of the old external-product-first surface', () => { + expect(getParityRoutePair('compatibility-placeholder-family', 'explorer-workbench')).toMatchObject({ screenshotMode: 'viewport', primarySelectors: expect.arrayContaining([ '[data-explorer-route="otlp-cold-workbench"]', '[data-explorer-style-baseline="hertzbeat-cold-matte"]', + '[data-explorer-api-owner="trace-log-bff-query-api"]', + '[data-explorer-query-state]', + '[data-explorer-signal-filter]', + '[data-explorer-api-source]', + '[data-explorer-shared-frame="hertzbeat-ui"]', + '[data-hz-ui="explorer-frame"]', '[data-explorer-query-bar="cold-query-row"]', '[data-explorer-chart-band="cold-chart-band"]', '[data-explorer-result-table="cold-dense-table"]', + '[data-explorer-result-table-owner="hertzbeat-ui-data-table"]', '[data-explorer-detail-panel="cold-detail-panel"]', 'main', 'button', @@ -241,9 +262,9 @@ describe('parity route manifest', () => { textSnippets: expect.arrayContaining(['查询工作台', '信号类型', '运行查询', 'checkout']), actionLabels: ['运行查询', '保存视图', '创建告警', '加入仪表盘'], minimumVerificationCommand: - 'npm exec vitest run app/explorer/page.test.tsx lib/explorer-surface/view-model.test.ts lib/parity/route-manifest.test.ts' + 'npm exec vitest run app/explorer/page.test.tsx lib/explorer-surface/controller.test.ts lib/explorer-surface/view-model.test.ts lib/otlp-metrics/controller.test.ts lib/otlp-metrics/view-model.test.ts lib/parity/route-manifest.test.ts' }); - expect(getParityRoutePair('compatibility-placeholder-family', 'explorer-placeholder').primarySelectors.join(' ')).not.toContain('signoz-'); + expect(getParityRoutePair('compatibility-placeholder-family', 'explorer-workbench').primarySelectors.join(' ')).not.toContain('signoz-'); }); it('declares explicit parity owners for the milestone 2 families', () => { @@ -290,9 +311,9 @@ describe('parity route manifest', () => { ); }); - it('declares a family-closeout verification command for the compatibility aliases and placeholder twins', () => { + it('declares a family-closeout verification command for compatibility aliases, placeholder twins, and Explorer', () => { expect((getParityFamily('compatibility-placeholder-family') as Record<string, unknown>).familyVerificationCommand).toBe( - 'npm exec vitest run app/dashboard/page.test.ts app/overview/page.test.tsx lib/overview/navigation.test.ts lib/overview/view-model.test.ts app/events/page.test.ts app/log/manage/page.test.tsx lib/log-manage/query-state.test.ts lib/log-manage/view-model.test.ts app/alerts/page.test.ts app/alert/page.test.tsx components/pages/alert-center-surface.test.tsx lib/alert-manage/query-state.test.ts lib/alert-manage/controller.test.ts lib/alert-manage/view-model.test.ts app/incidents/page.te [...] + 'npm exec vitest run app/dashboard/page.test.ts app/overview/page.test.tsx lib/overview/navigation.test.ts lib/overview/view-model.test.ts app/events/page.test.ts app/log/manage/page.test.tsx lib/log-manage/query-state.test.ts lib/log-manage/view-model.test.ts app/alerts/page.test.ts app/alert/page.test.tsx components/pages/alert-center-surface.test.tsx lib/alert-manage/query-state.test.ts lib/alert-manage/controller.test.ts lib/alert-manage/view-model.test.ts app/incidents/page.te [...] ); }); @@ -741,7 +762,7 @@ describe('parity route manifest', () => { '[data-entity-list-table-shell="cold-dense-table"]', '[data-entity-list-table="cold-entity-table"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input', @@ -788,7 +809,7 @@ describe('parity route manifest', () => { '[data-entity-type-icon="service"]', '[data-entity-type-icon="database"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]' + '[data-shell-ai-chat-launcher="angular-ai-chat"]' ]), textSnippets: expect.arrayContaining(['新建实体', '全部实体', '实体元数据', '页面录入', '基本信息', '你已完成 83% 的平台配置']), actionLabels: ['创建实体'] @@ -826,7 +847,7 @@ describe('parity route manifest', () => { '[data-entity-type-icon="service"]', '[data-entity-type-icon="database"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]' + '[data-shell-ai-chat-launcher="angular-ai-chat"]' ]), textSnippets: expect.arrayContaining(['编辑实体', '全部实体', '实体元数据', '基本信息', '你已完成 0% 的平台配置']), actionLabels: ['保存'] @@ -859,7 +880,7 @@ describe('parity route manifest', () => { '[data-entity-definition-template-panel="true"]', '[data-entity-definition-batch-panel="true"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'textarea', 'button' @@ -898,7 +919,7 @@ describe('parity route manifest', () => { '[data-entity-definition-load-error="cold-inline"]', '[data-entity-definition-error-placement="cold-context-panel"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'textarea', 'button', @@ -945,7 +966,7 @@ describe('parity route manifest', () => { '[data-entity-discovery-table="cold-discovery-table"]', '[data-entity-discovery-row-actions="cold-inline-actions"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input', @@ -987,21 +1008,28 @@ describe('parity route manifest', () => { '[data-entity-detail-drilldown-panel="cold-drilldown-panel"]', '[data-entity-detail-error="cold-inline-error"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'a', 'button' ]), - textSnippets: expect.arrayContaining(['实体详情', '对象优先调查', '上下文', '相关信号', '下一步', '高级入口']), - actionLabels: ['全部实体', '刷新', '编辑定义', '删除', '编辑'], + textSnippets: expect.arrayContaining([ + 'Entity detail', + 'Entity-first investigation', + 'Context', + 'Related signals', + 'Next step', + 'Advanced entries' + ]), + actionLabels: ['All entities', 'Refresh', 'Edit definition', 'Delete', 'Edit'], minimumVerificationCommand: "npm exec vitest run 'app/entities/[entityId]/page.test.tsx' components/pages/entity-detail-surface.test.tsx app/entity-detail-family.chrome.test.ts lib/entity-detail/view-model.test.ts lib/parity/route-manifest.test.ts" }); expect(getParityRoutePair('entity-family', 'entity-detail').primarySelectors).not.toContain('aside'); - expect(getParityRoutePair('entity-family', 'entity-detail').textSnippets).not.toContain('Entity'); - expect(getParityRoutePair('entity-family', 'entity-detail').textSnippets).not.toContain('Next steps'); - expect(getParityRoutePair('entity-family', 'entity-detail').actionLabels).not.toContain('Refresh'); - expect(getParityRoutePair('entity-family', 'entity-detail').actionLabels).not.toContain('Edit definition'); + expect(getParityRoutePair('entity-family', 'entity-detail').textSnippets).not.toContain('实体详情'); + expect(getParityRoutePair('entity-family', 'entity-detail').textSnippets).not.toContain('对象优先调查'); + expect(getParityRoutePair('entity-family', 'entity-detail').actionLabels).not.toContain('刷新'); + expect(getParityRoutePair('entity-family', 'entity-detail').actionLabels).not.toContain('编辑定义'); }); it('tracks the alert center through targeted route, shared-surface, and filter contracts', () => { @@ -1019,7 +1047,7 @@ describe('parity route manifest', () => { '[data-alert-center-empty-state="cold-table-empty"]', '[data-alert-center-empty-icon="cold-empty-box"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'input', 'select', @@ -1053,7 +1081,7 @@ describe('parity route manifest', () => { '[data-alert-group-empty-state="cold-table-empty"]', '[data-alert-group-empty-icon="cold-empty-box"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input' @@ -1115,7 +1143,7 @@ describe('parity route manifest', () => { '[data-alert-notice-receiver-toolbar="cold-query-toolbar"]', '[data-alert-notice-receiver-table-shell="cold-dense-table"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', '[data-tab="receiver"]', 'main', 'button', @@ -1148,7 +1176,7 @@ describe('parity route manifest', () => { '[data-alert-notice-rule-toolbar="cold-query-toolbar"]', '[data-alert-notice-rule-table-shell="cold-dense-table"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input' @@ -1179,7 +1207,7 @@ describe('parity route manifest', () => { '[data-alert-notice-template-table-shell="cold-dense-table"]', '[data-alert-notice-pagination="cold-dense-pagination"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input', @@ -1217,7 +1245,7 @@ describe('parity route manifest', () => { '[data-alert-setting-empty-state="cold-table-empty"]', '[data-alert-setting-empty-icon="cold-empty-box"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input' @@ -1252,7 +1280,7 @@ describe('parity route manifest', () => { '[data-alert-integration-code-block="json"]', '[data-alert-integration-mermaid]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button' ]), @@ -1282,7 +1310,7 @@ describe('parity route manifest', () => { '[data-alert-silence-empty-state="cold-table-empty"]', '[data-alert-silence-empty-icon="cold-empty-box"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input' @@ -1314,7 +1342,7 @@ describe('parity route manifest', () => { '[data-alert-inhibit-empty-state="cold-table-empty"]', '[data-alert-inhibit-empty-icon="cold-empty-box"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input' @@ -1346,7 +1374,7 @@ describe('parity route manifest', () => { '[data-setting-config-form="cold-settings-form"]', '[data-setting-config-actions="standard-equal-buttons"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'form', 'select', 'button' @@ -1378,7 +1406,7 @@ describe('parity route manifest', () => { '[data-setting-config-form="cold-settings-form"]', '[data-setting-config-actions="standard-equal-buttons"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'form', 'select', 'button' @@ -1418,7 +1446,7 @@ describe('parity route manifest', () => { '[data-setting-object-store-provider="cold-provider-select"]', '[data-setting-object-store-actions="standard-equal-buttons"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'form', 'select', 'button' @@ -1464,7 +1492,7 @@ describe('parity route manifest', () => { '[data-settings-summary-action="email"]', '[data-settings-summary-action="sms"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button' ]), @@ -1510,7 +1538,7 @@ describe('parity route manifest', () => { '[data-setting-token-table-panel="cold-dense-table"]', '[data-setting-token-table="cold-token-table"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'table' @@ -1586,14 +1614,14 @@ describe('parity route manifest', () => { '[data-setting-define-menu-shell="cold-dense-list"]', '[data-setting-define-editor="cold-settings-form"]', '[data-setting-define-editor-shell="cold-yaml-editor"]', - '[data-setting-define-editor-field="cold-code-textarea"]', + '[data-setting-define-editor-field="cold-code-editor"]', + '[data-setting-define-code-editor="monitor-template-yaml"]', 'main', 'button', - 'input', - 'textarea' + 'input' ]), - textSnippets: expect.arrayContaining(['定义', '管理监控类型定义', '数据源状态']), - actionLabels: ['新增类型', '编辑', '取消', '保存并应用', '搜索', '预览查询'], + textSnippets: expect.arrayContaining(['定义', '管理监控模板 YAML']), + actionLabels: ['新增类型', '编辑', '取消', '保存并应用', '搜索', '隐藏', '显示'], minimumVerificationCommand: 'npm exec vitest run app/setting/define/page.test.tsx components/pages/setting-define-surface.test.tsx lib/setting-define/controller.test.ts lib/setting-define/query-state.test.ts lib/setting-define/view-model.test.ts lib/parity/route-manifest.test.ts' }); @@ -1601,6 +1629,8 @@ describe('parity route manifest', () => { expect(getParityRoutePair('setting-family', 'setting-define').primarySelectors).not.toContain('[data-setting-define-summary-rail="cold-static-rail"]'); expect(getParityRoutePair('setting-family', 'setting-define').textSnippets).not.toContain('Define'); expect(getParityRoutePair('setting-family', 'setting-define').actionLabels).not.toContain('New Monitor Type'); + expect(getParityRoutePair('setting-family', 'setting-define').textSnippets).not.toContain('数据源状态'); + expect(getParityRoutePair('setting-family', 'setting-define').actionLabels).not.toContain('预览查询'); }); it('pins label management to the OTLP cold-matte dense admin/list contract', () => { @@ -1618,7 +1648,7 @@ describe('parity route manifest', () => { '[data-label-table="cold-label-table"]', '[data-label-empty-state="cold-table-empty"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input', @@ -1654,7 +1684,7 @@ describe('parity route manifest', () => { '[data-plugin-manage-table="cold-plugin-table"]', '[data-plugin-manage-empty-state="cold-table-empty"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input', @@ -1689,7 +1719,7 @@ describe('parity route manifest', () => { '[data-status-component-table-shell="cold-dense-table"]', '[data-status-component-table="cold-component-table"]', '[data-platform-footer="angular-footer"]', - '[data-shell-help-launcher="angular-help"]', + '[data-shell-ai-chat-launcher="angular-ai-chat"]', 'main', 'button', 'input', diff --git a/web-next/lib/workspace-navigation.test.ts b/web-next/lib/workspace-navigation.test.ts index 6832203d50..3932cbd8ab 100644 --- a/web-next/lib/workspace-navigation.test.ts +++ b/web-next/lib/workspace-navigation.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; +import { createTranslatorMock } from '../test/i18n-test-helper'; import { buildEntityEditorWorkspaceTabs, buildEntitySignalRouteContext, @@ -7,7 +8,7 @@ import { buildSignalWorkspaceTabs } from './workspace-navigation'; -const t = (key: string) => key; +const t = createTranslatorMock({ locale: 'zh-CN' }); describe('workspace navigation', () => { it('prefers the explicit return path for entity workspace navigation', () => { @@ -61,6 +62,7 @@ describe('workspace navigation', () => { const ingestionTab = tabs.find(tab => tab.key === 'ingestion'); const monitorsTab = tabs.find(tab => tab.key === 'monitors'); + expect(tabs.map(tab => tab.label)).toEqual(['实体详情', '可观测接入', '指标工作台', '日志工作台', '链路工作台', '监控中心']); expect(logsTab?.href).toContain('/log/manage?'); expect(logsTab?.href).toContain('traceId=trace-123'); expect(logsTab?.href).toContain('severityText=ERROR'); @@ -139,6 +141,7 @@ describe('workspace navigation', () => { }); expect(tabs.map(tab => tab.key)).toEqual(['entity', 'metrics', 'monitors', 'logs', 'traces']); + expect(tabs.map(tab => tab.label)).toEqual(['实体详情', '指标工作台', '监控中心', '日志工作台', '链路工作台']); expect(tabs[2]?.href).toContain('/monitors?'); expect(tabs[2]?.href).toContain('entityId=7'); expect(tabs[3]?.href).toContain('/log/manage?'); @@ -153,6 +156,7 @@ describe('workspace navigation', () => { }); expect(tabs.map(tab => tab.key)).toEqual(['entity', 'monitors', 'logs', 'traces']); + expect(tabs.map(tab => tab.label)).toEqual(['实体详情', '监控中心', '日志工作台', '链路工作台']); expect(tabs.find(tab => tab.key === 'entity')?.active).toBe(true); expect(tabs.find(tab => tab.key === 'monitors')?.disabled).toBe(true); expect(tabs.find(tab => tab.key === 'logs')?.disabled).toBe(true); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
