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 a7b4cb4aca Preserve RPC service in metric drilldowns
a7b4cb4aca is described below

commit a7b4cb4aca384d389daee3ee446704075540c30a
Author: Logic <[email protected]>
AuthorDate: Tue Jun 9 18:48:57 2026 +0800

    Preserve RPC service in metric drilldowns
---
 web-next/lib/signal-dashboards.test.ts | 58 ++++++++++++++++++++++++++++++++++
 web-next/lib/signal-dashboards.ts      | 11 ++++++-
 2 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/web-next/lib/signal-dashboards.test.ts 
b/web-next/lib/signal-dashboards.test.ts
index 9f9d507166..eca9f67102 100644
--- a/web-next/lib/signal-dashboards.test.ts
+++ b/web-next/lib/signal-dashboards.test.ts
@@ -2271,6 +2271,64 @@ describe('signal dashboards API client', () => {
     }));
   });
 
+  it('builds RPC metric point handoffs with service and method operation 
context', () => {
+    const [rpcMetricsPlan] = buildSignalDashboardExecutionPlans({
+      dashboardKey: 'rpc-sync',
+      title: 'RPC sync dashboard',
+      description: 'Runtime sync',
+      tags: 'metrics',
+      layout: '[]',
+      widgets: JSON.stringify([
+        {
+          id: 'rpc-metrics-panel',
+          signal: 'metrics',
+          title: 'RPC latency',
+          visualization: 'time-series',
+          route: 
'/ingestion/otlp/metrics?query=rpc.server.duration&serviceName=checkout&groupBy=rpc.service,rpc.method'
+        }
+      ])
+    });
+    const rpcMetricsDescriptor = 
buildSignalDashboardPanelRuntimeRenderDescriptor(rpcMetricsPlan, {
+      panelId: 'rpc-metrics-panel',
+      state: 'ready',
+      primaryUrl: rpcMetricsPlan.primaryUrl,
+      data: {
+        stats: { totalSeries: 1, nonEmptySeries: 1 },
+        results: {
+          frames: [{
+            schema: {
+              labels: {
+                __name__: 'rpc.server.duration',
+                'service.name': 'checkout',
+                'service.namespace': 'payments',
+                'rpc.service': 'payments.CheckoutService',
+                'rpc.method': 'Authorize'
+              }
+            },
+            data: [[1000, 32], [2000, 45]]
+          }]
+        }
+      }
+    });
+
+    const syncTooltip = buildSignalDashboardRuntimeSyncTooltip(
+      [rpcMetricsDescriptor],
+      '2000',
+      { timeRange: { start: '1000', end: '3000' }, returnTo: 
'/dashboard?start=1000&end=3000' }
+    );
+
+    expect(syncTooltip.rows[0]).toEqual(expect.objectContaining({
+      panelId: 'rpc-metrics-panel',
+      signal: 'metrics',
+      source: 'metrics-point',
+      serviceName: 'checkout',
+      serviceNamespace: 'payments',
+      operationName: 'payments.CheckoutService/Authorize',
+      relatedSignal: 'traces',
+      relatedHandoffHref: 
'/trace/manage?view=list&spanScope=all&serviceName=checkout&serviceNamespace=payments&operationName=payments.CheckoutService%2FAuthorize&returnTo=%2Fdashboard%3Fstart%3D1000%26end%3D3000&start=1000&end=3000'
+    }));
+  });
+
   it('creates panel drafts from runtime evidence rows with source context', () 
=> {
     const draft = createSignalDashboardPanelDraftFromRuntimeEvidence({
       row: {
diff --git a/web-next/lib/signal-dashboards.ts 
b/web-next/lib/signal-dashboards.ts
index 6afe43bdc1..0c8d8090fb 100644
--- a/web-next/lib/signal-dashboards.ts
+++ b/web-next/lib/signal-dashboards.ts
@@ -3394,6 +3394,15 @@ function metricLabelValue(labels: Record<string, 
string>, names: string[]) {
   return '';
 }
 
+function metricOperationName(labels: Record<string, string>) {
+  const explicitOperationName = metricLabelValue(labels, ['operation', 
'operationName', 'operation_name', 'http.route']);
+  if (explicitOperationName) return explicitOperationName;
+  const rpcMethod = metricLabelValue(labels, ['rpc.method', 'rpc_method']);
+  if (!rpcMethod) return '';
+  const rpcService = metricLabelValue(labels, ['rpc.service', 'rpc_service']);
+  return rpcService ? `${rpcService}/${rpcMethod}` : rpcMethod;
+}
+
 function metricResourceFilter(key: string, value: string) {
   if (!key || !value) return '';
   return `${key}=${value}`;
@@ -3448,7 +3457,7 @@ function syncTooltipMetricRelatedHandoff(
   const serviceNamespace = metricLabelValue(labels, ['service.namespace', 
'serviceNamespace', 'service_namespace']);
   const dbSystem = metricLabelValue(labels, ['db.system', 'dbSystem', 
'db_system']);
   const externalAddress = metricLabelValue(labels, 
['external.service.address', 'externalServiceAddress', 
'external_service_address', 'server.address', 'net.peer.name']);
-  const operationName = metricLabelValue(labels, ['operation', 
'operationName', 'operation_name', 'http.route', 'rpc.method']);
+  const operationName = metricOperationName(labels);
   const resourceFilter = dbSystem
     ? metricResourceFilter('db.system', dbSystem)
     : externalAddress ? metricResourceFilter('external.service.address', 
externalAddress) : '';


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

Reply via email to