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 3d2b941baa Add service overview dashboard contract
3d2b941baa is described below

commit 3d2b941baa98b83649499f5399e2f70fde4b4b47
Author: Logic <[email protected]>
AuthorDate: Mon Jun 8 09:12:28 2026 +0800

    Add service overview dashboard contract
---
 web-next/lib/signal-dashboards.test.ts | 74 ++++++++++++++++++++++++++++++
 web-next/lib/signal-dashboards.ts      | 83 ++++++++++++++++++++++++++++++++++
 2 files changed, 157 insertions(+)

diff --git a/web-next/lib/signal-dashboards.test.ts 
b/web-next/lib/signal-dashboards.test.ts
index b97a2ad162..760595e916 100644
--- a/web-next/lib/signal-dashboards.test.ts
+++ b/web-next/lib/signal-dashboards.test.ts
@@ -25,6 +25,7 @@ import {
   saveSignalDashboard,
   selectSignalDashboardVariableOption,
   buildSignalDashboardPanelRuntimeRenderDescriptor,
+  buildSignalServiceOverviewDashboard,
   createSignalDashboardPanelDraftFromFilterSelection,
   createSignalDashboardPanelDraftsFromFilterSelection,
   createSignalDashboardPanelDraftFromRuntimeBreakout,
@@ -165,6 +166,79 @@ describe('signal dashboards API client', () => {
     });
   });
 
+  it('builds a service overview dashboard from HertzBeat entity context', () 
=> {
+    const dashboard = buildSignalServiceOverviewDashboard({
+      serviceName: 'checkout',
+      serviceNamespace: 'payments',
+      environment: 'prod',
+      entityId: '4200',
+      entityType: 'service',
+      entityName: 'Checkout API',
+      source: 'otlp',
+      collector: 'collector-a',
+      template: 'spring-boot',
+      timeRange: 'last-1h',
+      refresh: '30',
+      live: 'true'
+    });
+    const panels = parseSignalDashboardPreviewPanels(dashboard);
+    const variables = parseSignalDashboardVariables(dashboard);
+    const plans = buildSignalDashboardExecutionPlans(dashboard);
+
+    expect(dashboard).toEqual(expect.objectContaining({
+      dashboardKey: 'service-checkout-overview',
+      title: 'Checkout API service overview',
+      tags: 'service,apm,metrics,logs,traces,alerts'
+    }));
+    expect(panels).toHaveLength(18);
+    expect(variables).toEqual(expect.arrayContaining([
+      expect.objectContaining({ name: 'service.name', type: 'query', value: 
'checkout' }),
+      expect.objectContaining({ name: 'service.namespace', type: 'query', 
value: 'payments' }),
+      expect.objectContaining({ name: 'deployment.environment.name', type: 
'query', value: 'prod' }),
+      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' })
+    ]));
+    expect(panels.map(panel => panel.widget.title)).toEqual([
+      'Service overview: service.name=checkout',
+      'Service overview table: service.name=checkout',
+      'Service overview latency p95: service.name=checkout',
+      'Service overview request rate: service.name=checkout',
+      'Service overview error rate: service.name=checkout',
+      'Service overview apdex: service.name=checkout',
+      'Service overview db calls rate: service.name=checkout',
+      'Service overview db call duration: service.name=checkout',
+      'Service overview external calls rate: service.name=checkout',
+      'Service overview external call duration: service.name=checkout',
+      'Service overview key operations: service.name=checkout',
+      'Service overview logs: service.name=checkout',
+      'Service overview log errors: service.name=checkout',
+      'Service overview traces: service.name=checkout',
+      'Service overview trace errors: service.name=checkout',
+      'Service overview exceptions: service.name=checkout',
+      'Service overview exception messages: service.name=checkout',
+      'Service overview firing alerts: service.name=checkout'
+    ]);
+    expect(plans[2]).toEqual(expect.objectContaining({
+      signal: 'metrics',
+      primaryUrl: expect.stringContaining('/ingestion/otlp/metrics/console?')
+    }));
+    expect(plans[2]?.primaryUrl).toContain('query=http.server.duration');
+    expect(plans[2]?.primaryUrl).toContain('entityType=service');
+    
expect(plans[3]?.primaryUrl).toContain('query=http_server_duration_milliseconds_count');
+    expect(plans[3]?.primaryUrl).toContain('temporalAggregation=rate');
+    expect(plans[5]?.apiUrls).toEqual(expect.objectContaining({
+      apdexTolerating: expect.stringContaining('le%3D%222.0%22'),
+      apdexTotal: expect.stringContaining('query=http.server.duration.count')
+    }));
+    expect(plans[12]?.primaryUrl).toContain('/logs/list?');
+    expect(plans[12]?.primaryUrl).toContain('severityText=ERROR');
+    expect(plans[15]?.primaryUrl).toContain('/traces/stats/group-by?');
+    expect(plans[15]?.primaryUrl).toContain('groupBy=exception.type');
+    expect(plans[17]?.primaryUrl).toContain('/alerts/group?');
+    expect(plans.every(plan => plan.state === 'ready')).toBe(true);
+  });
+
   it('merges suggested panel drafts into an existing dashboard composition', 
() => {
     const dashboard = buildSignalDashboardCompositionFromDrafts({
       dashboardKey: 'signals-overview',
diff --git a/web-next/lib/signal-dashboards.ts 
b/web-next/lib/signal-dashboards.ts
index 57cc49f4d5..47345bf63e 100644
--- a/web-next/lib/signal-dashboards.ts
+++ b/web-next/lib/signal-dashboards.ts
@@ -382,6 +382,23 @@ type BuildSignalDashboardCompositionInput = {
   drafts: SignalDashboardPanelDraft[];
 };
 
+type BuildSignalServiceOverviewDashboardInput = {
+  serviceName: string;
+  serviceNamespace?: string;
+  environment?: string;
+  entityId?: string;
+  entityType?: string;
+  entityName?: string;
+  source?: string;
+  collector?: string;
+  template?: string;
+  timeRange?: string;
+  start?: string;
+  end?: string;
+  refresh?: string;
+  live?: string;
+};
+
 type ResolveSignalDashboardPreviewOptions = {
   timeRange?: SignalDashboardTimeRange;
   now?: number;
@@ -1861,6 +1878,72 @@ export function 
createSignalDashboardPanelDraftsFromFilterSelection(input: {
   });
 }
 
+function serviceOverviewRoute(input: BuildSignalServiceOverviewDashboardInput) 
{
+  const url = new URL('/ingestion/otlp/metrics', 'http://hertzbeat.local');
+  url.searchParams.set('query', 'http.server.duration');
+  url.searchParams.set('serviceName', input.serviceName);
+  [
+    ['serviceNamespace', input.serviceNamespace],
+    ['environment', input.environment],
+    ['entityId', input.entityId],
+    ['entityType', input.entityType],
+    ['entityName', input.entityName],
+    ['source', input.source],
+    ['collector', input.collector],
+    ['template', input.template],
+    ['timeRange', input.timeRange],
+    ['start', input.start],
+    ['end', input.end],
+    ['refresh', input.refresh],
+    ['live', input.live]
+  ].forEach(([key, value]) => {
+    if (value) url.searchParams.set(key, value);
+  });
+  return `${url.pathname}${url.search}${url.hash}`;
+}
+
+function serviceOverviewVariableType(name: string): 
SignalDashboardVariableType {
+  return name === 'service.name' || name === 'service.namespace' || name === 
'deployment.environment.name'
+    ? 'query'
+    : name.startsWith('hertzbeat.') ? 'dynamic' : 'textbox';
+}
+
+export function buildSignalServiceOverviewDashboard(input: 
BuildSignalServiceOverviewDashboardInput): SignalDashboard {
+  const serviceName = input.serviceName.trim();
+  if (!serviceName) {
+    throw new Error('Service overview dashboard requires a service name');
+  }
+  const serviceLabel = input.entityName?.trim() || serviceName;
+  const sourceRoute = serviceOverviewRoute({ ...input, serviceName });
+  const drafts = createSignalDashboardPanelDraftsFromFilterSelection({
+    variable: {
+      name: 'service.name',
+      type: 'query',
+      value: serviceName,
+      options: [serviceName],
+      multi: false
+    },
+    signal: 'metrics',
+    route: sourceRoute,
+    titlePrefix: 'Service overview'
+  });
+  const dashboard = buildSignalDashboardCompositionFromDrafts({
+    dashboardKey: 
normalizeSignalDashboardKey(`service-${serviceName}-overview`),
+    title: `${serviceLabel} service overview`,
+    description: `Service-level RED, Apdex, logs, traces, exceptions, and 
alerts for ${serviceLabel}.`,
+    tags: ['service', 'apm', 'metrics', 'logs', 'traces', 'alerts'],
+    drafts
+  });
+  return updateSignalDashboardVariables(
+    dashboard,
+    parseSignalDashboardVariables(dashboard).map(variable => ({
+      ...variable,
+      type: serviceOverviewVariableType(variable.name),
+      options: variable.value ? [variable.value] : variable.options
+    }))
+  );
+}
+
 function errorMessage(error: unknown) {
   return error instanceof Error ? error.message : 'Signal dashboard panel 
execution failed';
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to