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
The following commit(s) were added to refs/heads/2.0.0 by this push:
new b7a61f1471 Add service overview dashboard browser smoke
b7a61f1471 is described below
commit b7a61f1471756eb776d89b36d4f11fd5a9f7e137
Author: Logic <[email protected]>
AuthorDate: Mon Jun 8 09:25:29 2026 +0800
Add service overview dashboard browser smoke
---
.../dashboard-source-edit-browser-smoke.spec.ts | 245 +++++++++++++++++++++
.../dashboard-source-edit-browser-smoke.test.ts | 14 ++
2 files changed, 259 insertions(+)
diff --git a/web-next/scripts/dashboard-source-edit-browser-smoke.spec.ts
b/web-next/scripts/dashboard-source-edit-browser-smoke.spec.ts
index 8588ad5f28..b3988366b1 100644
--- a/web-next/scripts/dashboard-source-edit-browser-smoke.spec.ts
+++ b/web-next/scripts/dashboard-source-edit-browser-smoke.spec.ts
@@ -649,6 +649,162 @@ async function installDashboardVariableMocks(page: Page) {
};
}
+async function installDashboardServiceOverviewMocks(page: Page) {
+ let dashboard: Record<string, unknown> | null = null;
+ const savedDashboards: Array<Record<string, unknown>> = [];
+ const executedUrls: string[] = [];
+
+ await page.addInitScript(() => {
+ window.localStorage.setItem('Authorization',
'dashboard-service-overview-smoke-token');
+ window.localStorage.setItem('refresh-token',
'dashboard-service-overview-smoke-refresh');
+ window.localStorage.setItem('hb.lang', 'en-US');
+ window.localStorage.setItem('layout.lang', 'en-US');
+ });
+
+ await page.route('**/*', async route => {
+ const request = route.request();
+ const url = new URL(request.url());
+ const path = url.pathname;
+ const method = request.method().toUpperCase();
+
+ if (!path.startsWith('/api/')) {
+ await route.continue();
+ return;
+ }
+
+ if (path === '/api/account/session') {
+ await fulfillJson(route, {
+ authenticated: true,
+ user: { id: 1, name: 'admin' }
+ });
+ return;
+ }
+
+ if (path === '/api/account/auth/refresh') {
+ await fulfillJson(route, apiMessage({
+ token: 'dashboard-service-overview-smoke-token',
+ refreshToken: 'dashboard-service-overview-smoke-refresh'
+ }));
+ return;
+ }
+
+ if (path.startsWith('/api/signal/dashboard-panel-draft/') && method ===
'GET') {
+ await fulfillJson(route, apiMessage([]));
+ return;
+ }
+
+ if (path.startsWith('/api/signal/saved-view/') && method === 'GET') {
+ await fulfillJson(route, apiMessage([]));
+ return;
+ }
+
+ if (path === '/api/signal/dashboard' && method === 'GET') {
+ await fulfillJson(route, apiMessage(dashboard ? [dashboard] : []));
+ return;
+ }
+
+ if (path === '/api/signal/dashboard' && method === 'PUT') {
+ dashboard = JSON.parse(request.postData() || '{}') as Record<string,
unknown>;
+ savedDashboards.push(dashboard);
+ await fulfillJson(route, apiMessage(dashboard));
+ return;
+ }
+
+ if (path === '/api/ingestion/otlp/metrics/console') {
+ executedUrls.push(`${path}${url.search}`);
+ await fulfillJson(route,
apiMessage(metricsPayload(url.searchParams.get('query') ||
'http.server.duration')));
+ return;
+ }
+
+ if (path === '/api/logs/list') {
+ executedUrls.push(`${path}${url.search}`);
+ await fulfillJson(route, apiMessage(logListPayload()));
+ return;
+ }
+
+ if (path === '/api/logs/stats/overview') {
+ await fulfillJson(route, apiMessage(logOverviewPayload()));
+ return;
+ }
+
+ if (path === '/api/logs/stats/trend') {
+ await fulfillJson(route, apiMessage({ hourlyStats: { '10:00': 1 } }));
+ return;
+ }
+
+ if (path === '/api/logs/stats/trace-coverage') {
+ await fulfillJson(route, apiMessage({ traceCoverage: { withTrace: 1,
withSpan: 1, withBothTraceAndSpan: 1 } }));
+ return;
+ }
+
+ if (path === '/api/logs/stats/group-by') {
+ executedUrls.push(`${path}${url.search}`);
+ await fulfillJson(route, apiMessage(logGroupPayload()));
+ return;
+ }
+
+ if (path === '/api/traces/list') {
+ executedUrls.push(`${path}${url.search}`);
+ await fulfillJson(route, apiMessage(traceListPayload()));
+ return;
+ }
+
+ if (path === '/api/traces/stats/overview') {
+ await fulfillJson(route, apiMessage(traceOverviewPayload()));
+ return;
+ }
+
+ if (path === '/api/traces/stats/group-by') {
+ executedUrls.push(`${path}${url.search}`);
+ await fulfillJson(route, apiMessage(traceGroupPayload()));
+ return;
+ }
+
+ if (path === '/api/alerts/group') {
+ executedUrls.push(`${path}${url.search}`);
+ await fulfillJson(route, apiMessage({
+ content: [{
+ id: 7,
+ status: 'firing',
+ groupKey: 'checkout-alerts',
+ commonLabels: {
+ alertname: 'HighErrorRate',
+ 'service.name': 'checkout',
+ severity: 'critical'
+ },
+ commonAnnotations: {
+ summary: 'Checkout error rate is high'
+ },
+ gmtUpdate: 1713200000000
+ }],
+ totalElements: 1,
+ pageIndex: 0,
+ pageSize: 8
+ }));
+ return;
+ }
+
+ if (path === '/api/alerts/summary') {
+ await fulfillJson(route, apiMessage({
+ total: 4,
+ dealNum: 1,
+ rate: 25,
+ priorityWarningNum: 1,
+ priorityCriticalNum: 2,
+ priorityEmergencyNum: 1
+ }));
+ return;
+ }
+
+ await fulfillJson(route, apiMessage({}));
+ });
+
+ return {
+ executedUrls,
+ savedDashboards
+ };
+}
+
async function installDashboardRuntimeBreakoutMocks(page: Page) {
const dashboard = buildRuntimeBreakoutDashboard();
const runtimeBreakoutDrafts: Array<Record<string, unknown>> = [];
@@ -1469,6 +1625,95 @@ test.describe('dashboard source edit browser smoke', ()
=> {
}));
});
+ test('saves a service overview dashboard from URL service and entity
context', async ({ page }) => {
+ test.setTimeout(BROWSER_SMOKE_TIMEOUT);
+ const smokeState = await installDashboardServiceOverviewMocks(page);
+
+ await
page.goto(routeUrl('/dashboard?serviceName=checkout&serviceNamespace=payments&environment=prod&entityId=4200&entityType=service&entityName=Checkout%20API&source=otlp&collector=collector-a&template=spring-boot&timeRange=last-1h&refresh=30&live=true'),
{
+ timeout: BROWSER_SMOKE_TIMEOUT,
+ waitUntil: 'domcontentloaded'
+ });
+
+ const serviceOverviewAction =
page.locator('[data-dashboard-service-overview-action="save"]').first();
+ await
expect(page.locator('[data-dashboard-service-overview-context="ready"]')).toHaveAttribute(
+ 'data-dashboard-service-overview-service',
+ 'checkout',
+ { timeout: WORKBENCH_READY_TIMEOUT }
+ );
+ await
expect(serviceOverviewAction).toHaveAttribute('data-dashboard-service-overview-action-state',
'ready');
+ await
expect(serviceOverviewAction).toHaveAttribute('data-dashboard-service-overview-action-service',
'checkout');
+ await serviceOverviewAction.click();
+
+ await expect.poll(() => smokeState.savedDashboards, {
+ timeout: WORKBENCH_READY_TIMEOUT
+ }).toHaveLength(1);
+ const [savedDashboard] = smokeState.savedDashboards;
+ const savedWidgets = JSON.parse(String(savedDashboard.widgets || '[]')) as
Array<Record<string, unknown>>;
+ const savedVariables = JSON.parse(String(savedDashboard.variables ||
'[]')) as Array<Record<string, unknown>>;
+ expect(savedDashboard).toEqual(expect.objectContaining({
+ dashboardKey: 'service-checkout-overview',
+ title: 'Checkout API service overview',
+ tags: 'service,apm,metrics,logs,traces,alerts'
+ }));
+ expect(savedWidgets).toHaveLength(18);
+ expect(savedWidgets.map(widget =>
widget.title)).toEqual(expect.arrayContaining([
+ 'Service overview request rate: service.name=checkout',
+ 'Service overview error rate: service.name=checkout',
+ 'Service overview apdex: service.name=checkout',
+ 'Service overview log errors: service.name=checkout',
+ 'Service overview exceptions: service.name=checkout',
+ 'Service overview firing alerts: service.name=checkout'
+ ]));
+ expect(savedVariables).toEqual(expect.arrayContaining([
+ expect.objectContaining({ name: 'service.name', type: 'query', value:
'checkout' }),
+ expect.objectContaining({ name: 'service.namespace', type: 'query',
value: 'payments' }),
+ expect.objectContaining({ name: 'hertzbeat.entity_id', type: 'dynamic',
value: '4200' }),
+ expect.objectContaining({ name: 'hertzbeat.entity_type', type:
'dynamic', value: 'service' }),
+ expect.objectContaining({ name: 'hertzbeat.template', type: 'dynamic',
value: 'spring-boot' })
+ ]));
+
+ await expect(page).toHaveURL(/dashboard=service-checkout-overview/, {
+ timeout: WORKBENCH_READY_TIMEOUT
+ });
+ await
expect(page.locator('[data-dashboard-composition-preview-panel]')).toHaveCount(18,
{
+ timeout: WORKBENCH_READY_TIMEOUT
+ });
+
+ const requestRatePanel =
page.locator('[data-dashboard-composition-preview-panel]').filter({ hasText:
'Service overview request rate' }).first();
+ await
expect(requestRatePanel).toHaveAttribute('data-dashboard-composition-preview-signal',
'metrics');
+ await
expect(requestRatePanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/query=http_server_duration_milliseconds_count/);
+ await
expect(requestRatePanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/temporalAggregation=rate/);
+ await
expect(requestRatePanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/entityType=service/);
+
+ const apdexPanel =
page.locator('[data-dashboard-composition-preview-panel]').filter({ hasText:
'Service overview apdex' }).first();
+ await
expect(apdexPanel).toHaveAttribute('data-dashboard-composition-execution-endpoints',
'3');
+ await
expect(apdexPanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/template=service-apdex/);
+
+ const logErrorsPanel =
page.locator('[data-dashboard-composition-preview-panel]').filter({ hasText:
'Service overview log errors' }).first();
+ await
expect(logErrorsPanel).toHaveAttribute('data-dashboard-composition-preview-signal',
'logs');
+ await
expect(logErrorsPanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/\/logs\/list\?/);
+ await
expect(logErrorsPanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/severityText=ERROR/);
+
+ const exceptionPanel =
page.locator('[data-dashboard-composition-preview-panel]').filter({ hasText:
'Service overview exceptions' }).first();
+ await
expect(exceptionPanel).toHaveAttribute('data-dashboard-composition-preview-signal',
'traces');
+ await
expect(exceptionPanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/\/traces\/stats\/group-by\?/);
+ await
expect(exceptionPanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/groupBy=exception.type/);
+
+ const alertPanel =
page.locator('[data-dashboard-composition-preview-panel]').filter({ hasText:
'Service overview firing alerts' }).first();
+ await
expect(alertPanel).toHaveAttribute('data-dashboard-composition-preview-signal',
'alerts');
+ await
expect(alertPanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/\/alerts\/group\?/);
+ await
expect(alertPanel).toHaveAttribute('data-dashboard-composition-execution-primary-url',
/status=firing/);
+
+ await expect.poll(() => smokeState.executedUrls.some(url =>
url.includes('/api/ingestion/otlp/metrics/console') &&
url.includes('entityType=service')), {
+ timeout: WORKBENCH_READY_TIMEOUT
+ }).toBe(true);
+ expect(smokeState.executedUrls).toEqual(expect.arrayContaining([
+ expect.stringContaining('/api/logs/list'),
+ expect.stringContaining('/api/traces/stats/group-by'),
+ expect.stringContaining('/api/alerts/group')
+ ]));
+ });
+
for (const signalCase of SIGNAL_CASES) {
test(`edits a ${signalCase.signal} dashboard panel from the source
workbench and returns to the updated dashboard preview`, async ({ page }) => {
test.setTimeout(BROWSER_SMOKE_TIMEOUT);
diff --git a/web-next/scripts/dashboard-source-edit-browser-smoke.test.ts
b/web-next/scripts/dashboard-source-edit-browser-smoke.test.ts
index e4b6b0747f..11ab5aaadc 100644
--- a/web-next/scripts/dashboard-source-edit-browser-smoke.test.ts
+++ b/web-next/scripts/dashboard-source-edit-browser-smoke.test.ts
@@ -117,6 +117,20 @@ describe('dashboard source edit browser smoke coverage',
() => {
expect(source).toContain('data-dashboard-composition-runtime-preview-row-related-signal');
expect(source).toContain('data-dashboard-composition-runtime-preview-row-related-handoff');
expect(source).toContain('data-dashboard-composition-runtime-preview-row-action="open-related"');
+ expect(source).toContain('saves a service overview dashboard from URL
service and entity context');
+ expect(source).toContain('installDashboardServiceOverviewMocks');
+ expect(source).toContain('data-dashboard-service-overview-action="save"');
+
expect(source).toContain('data-dashboard-service-overview-context="ready"');
+ expect(source).toContain('service-checkout-overview');
+ expect(source).toContain('Checkout API service overview');
+ expect(source).toContain('Service overview request rate:
service.name=checkout');
+ expect(source).toContain('Service overview error rate:
service.name=checkout');
+ expect(source).toContain('Service overview apdex: service.name=checkout');
+ expect(source).toContain('Service overview log errors:
service.name=checkout');
+ expect(source).toContain('Service overview exceptions:
service.name=checkout');
+ expect(source).toContain('Service overview firing alerts:
service.name=checkout');
+ expect(source).toContain('hertzbeat.entity_type');
+ expect(source).toContain('entityType=service');
expect(source).toContain('data-dashboard-composition-runtime-sync-tooltip-row-action="add-panel-draft"');
expect(source).toContain('spanScope=all');
expect(source).toContain('runtimeEvidenceDrafts');
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]