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]