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 40df81961c Cover service operation trace drilldown
40df81961c is described below

commit 40df81961c541a7b897cf796e7ed571debd61934
Author: Logic <[email protected]>
AuthorDate: Mon Jun 8 09:52:15 2026 +0800

    Cover service operation trace drilldown
---
 web-next/lib/signal-dashboards.test.ts | 102 +++++++++++++++++++++++++++++++--
 1 file changed, 98 insertions(+), 4 deletions(-)

diff --git a/web-next/lib/signal-dashboards.test.ts 
b/web-next/lib/signal-dashboards.test.ts
index 760595e916..e32a095f60 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,
+  buildSignalOperationDrilldownDashboard,
   buildSignalServiceOverviewDashboard,
   createSignalDashboardPanelDraftFromFilterSelection,
   createSignalDashboardPanelDraftsFromFilterSelection,
@@ -239,6 +240,71 @@ describe('signal dashboards API client', () => {
     expect(plans.every(plan => plan.state === 'ready')).toBe(true);
   });
 
+  it('builds an operation drilldown dashboard without promoting operation to 
an entity', () => {
+    const dashboard = buildSignalOperationDrilldownDashboard({
+      serviceName: 'checkout',
+      serviceNamespace: 'payments',
+      environment: 'prod',
+      operationName: 'POST /checkout',
+      entityId: '4200',
+      entityType: 'service',
+      entityName: 'Checkout API',
+      source: 'otlp',
+      collector: 'collector-a',
+      template: 'spring-boot',
+      timeRange: 'last-1h'
+    });
+    const panels = parseSignalDashboardPreviewPanels(dashboard);
+    const variables = parseSignalDashboardVariables(dashboard);
+    const plans = buildSignalDashboardExecutionPlans(dashboard);
+
+    expect(dashboard).toEqual(expect.objectContaining({
+      dashboardKey: 'service-checkout-operation-post-checkout-drilldown',
+      title: 'Checkout API POST /checkout operation drilldown',
+      tags: 'service,operation,apm,metrics,logs,traces'
+    }));
+    expect(panels).toHaveLength(8);
+    expect(panels.map(panel => panel.widget.title)).toEqual([
+      'Operation drilldown latency p95: operation.name=POST /checkout',
+      'Operation drilldown request rate: operation.name=POST /checkout',
+      'Operation drilldown error rate: operation.name=POST /checkout',
+      'Operation drilldown logs: operation.name=POST /checkout',
+      'Operation drilldown log errors: operation.name=POST /checkout',
+      'Operation drilldown traces: operation.name=POST /checkout',
+      'Operation drilldown trace errors: operation.name=POST /checkout',
+      'Operation drilldown exceptions: operation.name=POST /checkout'
+    ]);
+    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: 'operation.name', type: 'query', value: 
'POST /checkout' }),
+      expect.objectContaining({ name: 'hertzbeat.entity_id', type: 'dynamic', 
value: '4200' }),
+      expect.objectContaining({ name: 'hertzbeat.entity_type', type: 
'dynamic', value: 'service' })
+    ]));
+    expect(variables.find(variable => variable.name === 
'operation.name')?.type).toBe('query');
+    expect(variables.find(variable => variable.name === 
'hertzbeat.entity_type')?.value).toBe('service');
+    expect(variables.some(variable => variable.name === 
'hertzbeat.operation_entity')).toBe(false);
+
+    expect(plans[0]).toEqual(expect.objectContaining({
+      signal: 'metrics',
+      primaryUrl: expect.stringContaining('/ingestion/otlp/metrics/console?')
+    }));
+    expect(plans[0]?.primaryUrl).toContain('query=http.server.duration');
+    
expect(plans[0]?.primaryUrl).toContain('operation%3D%22POST+%2Fcheckout%22');
+    expect(plans[1]?.primaryUrl).toContain('temporalAggregation=rate');
+    
expect(plans[2]?.primaryUrl).toContain('status_code%3D%22STATUS_CODE_ERROR%22');
+    expect(plans[3]?.primaryUrl).toContain('/logs/list?');
+    
expect(plans[3]?.primaryUrl).toContain('attributeFilter=http.route%3APOST+%2Fcheckout');
+    expect(plans[4]?.primaryUrl).toContain('severityText=ERROR');
+    expect(plans[5]?.primaryUrl).toContain('/traces/list?');
+    expect(plans[5]?.primaryUrl).toContain('operationName=POST+%2Fcheckout');
+    expect(plans[6]?.primaryUrl).toContain('errorOnly=true');
+    expect(plans[7]?.primaryUrl).toContain('/traces/stats/group-by?');
+    expect(plans[7]?.primaryUrl).toContain('groupBy=exception.type');
+    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',
@@ -1750,7 +1816,7 @@ describe('signal dashboards API client', () => {
   });
 
   it('builds a synchronized dashboard tooltip from metrics, table, and trace 
rows', () => {
-    const [logsPlan, tracePlan, metricsPlan, dbMetricsPlan, 
externalMetricsPlan] = buildSignalDashboardExecutionPlans({
+    const [logsPlan, tracePlan, metricsPlan, dbMetricsPlan, 
externalMetricsPlan, operationMetricsPlan] = 
buildSignalDashboardExecutionPlans({
       dashboardKey: 'sync',
       title: 'Sync dashboard',
       description: 'Runtime sync',
@@ -1761,7 +1827,8 @@ describe('signal dashboards API client', () => {
         { id: 'trace-panel', signal: 'traces', title: 'Trace', visualization: 
'table', route: '/trace/manage?view=table' },
         { id: 'metrics-panel', signal: 'metrics', title: 'Metrics', 
visualization: 'time-series', route: '/ingestion/otlp/metrics?query=cpu' },
         { id: 'db-metrics-panel', signal: 'metrics', title: 'DB Metrics', 
visualization: 'time-series', route: 
'/ingestion/otlp/metrics?query=signoz_db_latency_count&serviceName=checkout&groupBy=db.system'
 },
-        { id: 'external-metrics-panel', signal: 'metrics', title: 'External 
Metrics', visualization: 'time-series', route: 
'/ingestion/otlp/metrics?query=signoz_external_call_latency_count&serviceName=checkout&groupBy=external.service.address'
 }
+        { id: 'external-metrics-panel', signal: 'metrics', title: 'External 
Metrics', visualization: 'time-series', route: 
'/ingestion/otlp/metrics?query=signoz_external_call_latency_count&serviceName=checkout&groupBy=external.service.address'
 },
+        { id: 'operation-metrics-panel', signal: 'metrics', title: 'Service 
key operations', visualization: 'time-series', route: 
'/ingestion/otlp/metrics?query=http.server.duration&serviceName=checkout&serviceNamespace=payments&groupBy=operation'
 }
       ])
     });
     const logsDescriptor = 
buildSignalDashboardPanelRuntimeRenderDescriptor(logsPlan, {
@@ -1868,18 +1935,33 @@ describe('signal dashboards API client', () => {
         }
       }
     });
+    const operationMetricsDescriptor = 
buildSignalDashboardPanelRuntimeRenderDescriptor(operationMetricsPlan, {
+      panelId: 'operation-metrics-panel',
+      state: 'ready',
+      primaryUrl: operationMetricsPlan.primaryUrl,
+      data: {
+        stats: { totalSeries: 1, nonEmptySeries: 1 },
+        results: {
+          frames: [{
+            schema: { labels: { __name__: 'http.server.duration', 
'service.name': 'checkout', 'service.namespace': 'payments', operation: 'POST 
/checkout' } },
+            data: [[1000, 180], [2000, 240]]
+          }]
+        }
+      }
+    });
 
     const syncTooltip = buildSignalDashboardRuntimeSyncTooltip([
       logsDescriptor,
       traceDescriptor,
       metricsDescriptor,
       dbMetricsDescriptor,
-      externalMetricsDescriptor
+      externalMetricsDescriptor,
+      operationMetricsDescriptor
     ], '2000', { timeRange: { start: '1000', end: '3000' }, returnTo: 
'/dashboard?start=1000&end=3000' });
     expect(syncTooltip).toEqual({
       timestamp: '2000',
       state: 'active',
-      rowCount: 6,
+      rowCount: 7,
       rows: [
         expect.objectContaining({
           panelId: 'logs-panel',
@@ -1962,6 +2044,18 @@ describe('signal dashboards API client', () => {
           resourceFilter: 'external.service.address=payments.internal',
           relatedSignal: 'traces',
           relatedHandoffHref: 
'/trace/manage?view=list&spanScope=all&serviceName=checkout&serviceNamespace=payments&resourceFilter=external.service.address%3Dpayments.internal&returnTo=%2Fdashboard%3Fstart%3D1000%26end%3D3000&start=1000&end=3000'
+        }),
+        expect.objectContaining({
+          panelId: 'operation-metrics-panel',
+          signal: 'metrics',
+          source: 'metrics-point',
+          label: 'http.server.duration',
+          value: '240',
+          serviceName: 'checkout',
+          serviceNamespace: 'payments',
+          operationName: 'POST /checkout',
+          relatedSignal: 'traces',
+          relatedHandoffHref: 
'/trace/manage?view=list&spanScope=all&serviceName=checkout&serviceNamespace=payments&operationName=POST+%2Fcheckout&returnTo=%2Fdashboard%3Fstart%3D1000%26end%3D3000&start=1000&end=3000'
         })
       ]
     });


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

Reply via email to