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 e50af063e5 Compact trace attribute operator actions
e50af063e5 is described below
commit e50af063e5b5ed514354af2139ace89c60ca4716
Author: Logic <[email protected]>
AuthorDate: Wed Jun 10 00:00:22 2026 +0800
Compact trace attribute operator actions
---
web-next/app/trace/manage/page.test.tsx | 73 +---
.../app/trace/manage/trace-manage-client.test.tsx | 35 +-
web-next/app/trace/manage/trace-manage-page.tsx | 480 +++++++++------------
web-next/lib/i18n-runtime-messages.ts | 24 ++
4 files changed, 268 insertions(+), 344 deletions(-)
diff --git a/web-next/app/trace/manage/page.test.tsx
b/web-next/app/trace/manage/page.test.tsx
index 3faab0651c..aa3bd8b21d 100644
--- a/web-next/app/trace/manage/page.test.tsx
+++ b/web-next/app/trace/manage/page.test.tsx
@@ -1839,62 +1839,35 @@ describe('trace manage page', () => {
expect(source).toContain('applyTraceResourceGroupBy');
expect(source).toContain('data-trace-manage-drawer-span-attributes="span-attributes"');
expect(source).toContain('data-trace-manage-drawer-span-attributes-owner="hertzbeat-ui-detail-rows"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-filter-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-filter-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-filter-out-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-filter-out-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-contains-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-contains-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-not-contains-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-not-contains-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-in-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-in-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-not-in-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-not-in-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-exists-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-exists-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-not-exists-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-not-exists-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-replace-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-replace-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-group-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-span-attribute-group-action-owner="hertzbeat-ui-button"');
+ expect(source).toContain('buildTraceAttributeOperatorOptions');
+ expect(source).toContain('traceDrawerAttributeOperatorDataAttributes');
+ expect(source).toContain('renderTraceDrawerAttributeOperator');
+ expect(source).toContain("const prefix = scope === 'span' ?
'data-trace-manage-drawer-span-attribute' :
'data-trace-manage-drawer-resource';");
+ expect(source).toContain('`${prefix}-operator-action`');
+ expect(source).toContain('`${prefix}-operator-owner`');
+
expect(source).toContain('traceDrawerAttributeOperatorDataAttributes(scope,
option.value as TraceAttributeOperator, name, value)');
+ expect(source).toContain('`${prefix}-filter-action`');
+ expect(source).toContain('`${prefix}-filter-action-owner`');
+ expect(source).toContain('`${prefix}-contains-action`');
+ expect(source).toContain('`${prefix}-not-contains-action`');
+ expect(source).toContain('`${prefix}-in-action`');
+ expect(source).toContain('`${prefix}-not-in-action`');
+ expect(source).toContain('`${prefix}-exists-action`');
+ expect(source).toContain('`${prefix}-not-exists-action`');
+ expect(source).toContain('`${prefix}-replace-action`');
+ expect(source).toContain('`${prefix}-group-action`');
+
expect(source).not.toContain('data-trace-manage-drawer-span-attribute-filter-action-owner="hertzbeat-ui-button"');
+
expect(source).not.toContain('data-trace-manage-drawer-span-attribute-contains-action-owner="hertzbeat-ui-button"');
+
expect(source).not.toContain('data-trace-manage-drawer-span-attribute-group-action-owner="hertzbeat-ui-button"');
expect(source).toContain('attributeFilter:
mergeTraceResourceFilterExpression(draft.attributeFilter, expression)');
expect(source).toContain('attributeFilter: expression');
expect(source).toContain("heading={t('trace.manage.drawer.attributes.span.title')}");
expect(source).toContain("aria-label={t('trace.manage.drawer.attributes.span.aria')}");
expect(source).toContain('data-trace-manage-drawer-resource-attributes="resource-attributes"');
expect(source).toContain('data-trace-manage-drawer-resource-attributes-owner="hertzbeat-ui-detail-rows"');
-
expect(source).toContain('data-trace-manage-drawer-resource-filter-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-filter-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.filter-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-filter-out-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-filter-out-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.filter-out-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-contains-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-contains-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.contains-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-not-contains-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-not-contains-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.not-contains-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-in-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-in-action-owner="hertzbeat-ui-button"');
- expect(source).toContain("t('trace.manage.drawer.attributes.in-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-not-in-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-not-in-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.not-in-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-exists-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-exists-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain('data-trace-manage-drawer-resource-not-exists-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-not-exists-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.exists-action')");
-
expect(source).toContain("t('trace.manage.drawer.attributes.not-exists-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-replace-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-replace-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.replace-action')");
-
expect(source).toContain('data-trace-manage-drawer-resource-group-action="true"');
-
expect(source).toContain('data-trace-manage-drawer-resource-group-action-owner="hertzbeat-ui-button"');
-
expect(source).toContain("t('trace.manage.drawer.attributes.group-action')");
+
expect(source).not.toContain('data-trace-manage-drawer-resource-filter-action-owner="hertzbeat-ui-button"');
+
expect(source).not.toContain('data-trace-manage-drawer-resource-contains-action-owner="hertzbeat-ui-button"');
+
expect(source).not.toContain('data-trace-manage-drawer-resource-group-action-owner="hertzbeat-ui-button"');
expect(source).toContain('type TraceRelatedLogsState = {');
expect(source).toContain('function buildTraceRelatedLogsApiUrl(');
expect(source).toContain("apiMessageGetWithTimeout<PageResult<LogEntry>>(relatedLogsUrl)");
diff --git a/web-next/app/trace/manage/trace-manage-client.test.tsx
b/web-next/app/trace/manage/trace-manage-client.test.tsx
index d741513c32..4b7aab6366 100644
--- a/web-next/app/trace/manage/trace-manage-client.test.tsx
+++ b/web-next/app/trace/manage/trace-manage-client.test.tsx
@@ -85,6 +85,29 @@ describe('TraceManagePage client loading', () => {
container = null;
});
+ async function applyDrawerResourceOperator(name: string, operator: string) {
+ const operatorRoot = document.body.querySelector(
+
`[data-trace-manage-drawer-resource-operator-action="true"][data-trace-manage-drawer-resource-filter-name="${name}"]`
+ ) as HTMLElement | null;
+ expect(operatorRoot).not.toBeNull();
+
expect(operatorRoot?.getAttribute('data-trace-manage-drawer-resource-operator-owner')).toBe('hertzbeat-ui-select');
+ expect(operatorRoot?.getAttribute('data-hz-ui')).toBe('select');
+
+ await act(async () => {
+ (operatorRoot?.querySelector('[data-hz-ui="select-trigger"]') as
HTMLButtonElement | null)?.click();
+ await Promise.resolve();
+ });
+
+ const option = operatorRoot?.querySelector(
+
`[data-trace-manage-drawer-resource-operator-option="${operator}"][data-trace-manage-drawer-resource-filter-name="${name}"]`
+ ) as HTMLButtonElement | null;
+ expect(option).not.toBeNull();
+ await act(async () => {
+ option?.click();
+ await Promise.resolve();
+ });
+ }
+
it('does not crash when monitor handoff trace APIs return empty or missing
payloads', async () => {
apiMessageGet
.mockResolvedValueOnce(null)
@@ -708,17 +731,7 @@ describe('TraceManagePage client loading', () => {
await Promise.resolve();
});
- const replaceAction = document.body.querySelector(
-
'[data-trace-manage-drawer-resource-replace-action="true"][data-trace-manage-drawer-resource-filter-name="service.namespace"]'
- ) as HTMLButtonElement | null;
- expect(replaceAction).not.toBeNull();
-
expect(replaceAction?.getAttribute('data-trace-manage-drawer-resource-replace-action-owner')).toBe('hertzbeat-ui-button');
-
expect(replaceAction?.getAttribute('aria-label')).toContain('service.namespace');
-
- await act(async () => {
- replaceAction?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
- await Promise.resolve();
- });
+ await applyDrawerResourceOperator('service.namespace', 'replace');
const href = String(replace.mock.calls.at(-1)?.[0]);
expect(href).toContain('/trace/manage?');
diff --git a/web-next/app/trace/manage/trace-manage-page.tsx
b/web-next/app/trace/manage/trace-manage-page.tsx
index 9a2d006b61..ad6edeb2fe 100644
--- a/web-next/app/trace/manage/trace-manage-page.tsx
+++ b/web-next/app/trace/manage/trace-manage-page.tsx
@@ -2,7 +2,7 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from
'react';
import Link from 'next/link';
-import { BarChart3, Ban, BellPlus, BellRing, Check, Copy, Download, Filter,
ListChecks, Pencil, Play, Replace, RotateCcw, Save, Search, ScrollText, Server,
Timer, Trash2, Workflow, X } from 'lucide-react';
+import { BarChart3, BellPlus, BellRing, Check, Copy, Download, Filter,
ListChecks, Pencil, Play, Replace, RotateCcw, Save, Search, ScrollText, Server,
Timer, Trash2, Workflow, X } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import { HzActionGroup, HzAttributeDiagnostics, HzButton, HzButtonIcon,
HzButtonLink, HzCheckbox, HzChipGroup, HzControlStack, HzDataCellText,
HzDataTable, HzDetailRows, HzDialogBodyLayout, HzDialogEventNotice,
HzDialogEventText, HzDialogMetaItem, HzDisabledActionShell, HzEmptyState,
HzInput, HzPanelHeader, HzPanelSurface, HzQueryActionGroup,
HzQueryStatusSelect, HzQueryTokenField, HzSearchFieldFrame, HzSearchFieldIcon,
HzSelect, HzSignalSummaryStrip, HzSignalTrendBars, HzSignalWorkbench [...]
import { buildTimeRangeControlLabels, TimeRangeControl } from
'@/components/observability/time-range-control';
@@ -75,6 +75,8 @@ type TraceSavedQueryView = SignalSavedQueryView;
type TraceExportRowLimit = 'current' | '10000' | '30000' | '50000';
type TraceDashboardPanelDraftState = 'idle' | 'saving' | 'saved' | 'failed';
+type TraceAttributeOperator = 'filter' | 'exclude' | 'contains' |
'not-contains' | 'in' | 'not-in' | 'exists' | 'not-exists' | 'replace' |
'group';
+type TraceAttributeScope = 'span' | 'resource';
const EMPTY_TRACE_OVERVIEW: TraceOverview = {
totalTraceCount: 0,
@@ -222,6 +224,58 @@ type TraceRelatedLogsState = {
};
type TraceTranslator = ReturnType<typeof useI18n>['t'];
+const TRACE_ATTRIBUTE_OPERATOR_LABEL_KEYS: Record<TraceAttributeOperator,
string> = {
+ filter: 'trace.manage.drawer.attributes.operator.filter',
+ exclude: 'trace.manage.drawer.attributes.operator.exclude',
+ contains: 'trace.manage.drawer.attributes.operator.contains',
+ 'not-contains': 'trace.manage.drawer.attributes.operator.not-contains',
+ in: 'trace.manage.drawer.attributes.operator.in',
+ 'not-in': 'trace.manage.drawer.attributes.operator.not-in',
+ exists: 'trace.manage.drawer.attributes.operator.exists',
+ 'not-exists': 'trace.manage.drawer.attributes.operator.not-exists',
+ replace: 'trace.manage.drawer.attributes.operator.replace',
+ group: 'trace.manage.drawer.attributes.operator.group'
+};
+
+function buildTraceAttributeOperatorOptions(t: TraceTranslator, operators:
TraceAttributeOperator[]) {
+ return operators.map(operator => ({
+ value: operator,
+ label: t(TRACE_ATTRIBUTE_OPERATOR_LABEL_KEYS[operator] as
Parameters<TraceTranslator>[0])
+ }));
+}
+
+function traceDrawerAttributeOperatorDataAttributes(scope:
TraceAttributeScope, operator: TraceAttributeOperator, name: string, value:
string) {
+ const prefix = scope === 'span' ? 'data-trace-manage-drawer-span-attribute'
: 'data-trace-manage-drawer-resource';
+ const base = {
+ [`${prefix}-operator-option`]: operator,
+ [`${prefix}-operator-scope`]: scope,
+ [`${prefix}-filter-name`]: name,
+ [`${prefix}-filter-value`]: value
+ };
+ switch (operator) {
+ case 'filter':
+ return { ...base, [`${prefix}-filter-action`]: 'true',
[`${prefix}-filter-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'exclude':
+ return { ...base, [`${prefix}-filter-out-action`]: 'true',
[`${prefix}-filter-out-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'contains':
+ return { ...base, [`${prefix}-contains-action`]: 'true',
[`${prefix}-contains-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'not-contains':
+ return { ...base, [`${prefix}-not-contains-action`]: 'true',
[`${prefix}-not-contains-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'in':
+ return { ...base, [`${prefix}-in-action`]: 'true',
[`${prefix}-in-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'not-in':
+ return { ...base, [`${prefix}-not-in-action`]: 'true',
[`${prefix}-not-in-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'exists':
+ return { ...base, [`${prefix}-exists-action`]: 'true',
[`${prefix}-exists-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'not-exists':
+ return { ...base, [`${prefix}-not-exists-action`]: 'true',
[`${prefix}-not-exists-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'replace':
+ return { ...base, [`${prefix}-replace-action`]: 'true',
[`${prefix}-replace-action-owner`]: 'hertzbeat-ui-select-menu-option' };
+ case 'group':
+ return { ...base, [`${prefix}-group-action`]: 'true',
[`${prefix}-group-action-owner`]: 'hertzbeat-ui-select-menu-option',
[`${prefix}-group-name`]: name };
+ }
+}
+
function buildTraceRelatedMetricsApiUrl(metricsHref: string | null |
undefined) {
if (!metricsHref) return null;
const href = new URL(metricsHref, 'http://localhost');
@@ -931,6 +985,146 @@ function TraceWaterfallDrawer({
t('trace.manage.drawer.attributes.resource.meta'),
t
);
+ const applyTraceAttributeOperator = useCallback((scope: TraceAttributeScope,
operator: TraceAttributeOperator, name: string, value: string) => {
+ if (scope === 'span') {
+ switch (operator) {
+ case 'filter':
+ onApplySpanAttributeFilter(name, value);
+ break;
+ case 'exclude':
+ onExcludeSpanAttributeFilter(name, value);
+ break;
+ case 'contains':
+ onApplySpanAttributeContainsFilter(name, value);
+ break;
+ case 'not-contains':
+ onApplySpanAttributeNotContainsFilter(name, value);
+ break;
+ case 'in':
+ onApplySpanAttributeInFilter(name, value);
+ break;
+ case 'not-in':
+ onApplySpanAttributeNotInFilter(name, value);
+ break;
+ case 'exists':
+ onApplySpanAttributeExistsFilter(name);
+ break;
+ case 'not-exists':
+ onApplySpanAttributeNotExistsFilter(name);
+ break;
+ case 'replace':
+ onReplaceSpanAttributeFilter(name, value);
+ break;
+ case 'group':
+ onApplySpanAttributeGroupBy(name);
+ break;
+ }
+ return;
+ }
+
+ switch (operator) {
+ case 'filter':
+ onApplyResourceFilter(name, value);
+ break;
+ case 'exclude':
+ onExcludeResourceFilter(name, value);
+ break;
+ case 'contains':
+ onApplyResourceContainsFilter(name, value);
+ break;
+ case 'not-contains':
+ onApplyResourceNotContainsFilter(name, value);
+ break;
+ case 'in':
+ onApplyResourceInFilter(name, value);
+ break;
+ case 'not-in':
+ onApplyResourceNotInFilter(name, value);
+ break;
+ case 'exists':
+ onApplyResourceExistsFilter(name);
+ break;
+ case 'not-exists':
+ onApplyResourceNotExistsFilter(name);
+ break;
+ case 'replace':
+ onReplaceResourceFilter(name, value);
+ break;
+ case 'group':
+ onApplyResourceGroupBy(name);
+ break;
+ }
+ }, [
+ onApplyResourceContainsFilter,
+ onApplyResourceExistsFilter,
+ onApplyResourceFilter,
+ onApplyResourceGroupBy,
+ onApplyResourceInFilter,
+ onApplyResourceNotContainsFilter,
+ onApplyResourceNotExistsFilter,
+ onApplyResourceNotInFilter,
+ onApplySpanAttributeContainsFilter,
+ onApplySpanAttributeExistsFilter,
+ onApplySpanAttributeFilter,
+ onApplySpanAttributeGroupBy,
+ onApplySpanAttributeInFilter,
+ onApplySpanAttributeNotContainsFilter,
+ onApplySpanAttributeNotExistsFilter,
+ onApplySpanAttributeNotInFilter,
+ onExcludeResourceFilter,
+ onExcludeSpanAttributeFilter,
+ onReplaceResourceFilter,
+ onReplaceSpanAttributeFilter
+ ]);
+ const renderTraceDrawerAttributeOperator = useCallback((scope:
TraceAttributeScope, title: React.ReactNode, copy: React.ReactNode) => {
+ const name = String(title ?? '').trim();
+ const value = String(copy ?? '').trim();
+ const availableOperators: TraceAttributeOperator[] = scope === 'span'
+ ? [
+ ...(buildTraceSpanAttributeFilterExpression(name, value) ? ['filter']
as const : []),
+ ...(buildTraceSpanAttributeExcludeFilterExpression(name, value) ?
['exclude'] as const : []),
+ ...(buildTraceSpanAttributeContainsFilterExpression(name, value) ?
['contains'] as const : []),
+ ...(buildTraceSpanAttributeNotContainsFilterExpression(name, value) ?
['not-contains'] as const : []),
+ ...(buildTraceSpanAttributeInFilterExpression(name, value) ? ['in'] as
const : []),
+ ...(buildTraceSpanAttributeNotInFilterExpression(name, value) ?
['not-in'] as const : []),
+ ...(buildTraceSpanAttributeExistsFilterExpression(name) ? ['exists']
as const : []),
+ ...(buildTraceSpanAttributeNotExistsFilterExpression(name) ?
['not-exists'] as const : []),
+ ...(buildTraceSpanAttributeFilterExpression(name, value) ? ['replace']
as const : []),
+ ...(buildTraceSpanAttributeGroupBy(name) ? ['group'] as const : [])
+ ]
+ : [
+ ...(buildTraceResourceFilterExpression(name, value) ? ['filter'] as
const : []),
+ ...(buildTraceResourceExcludeFilterExpression(name, value) ?
['exclude'] as const : []),
+ ...(buildTraceResourceContainsFilterExpression(name, value) ?
['contains'] as const : []),
+ ...(buildTraceResourceNotContainsFilterExpression(name, value) ?
['not-contains'] as const : []),
+ ...(buildTraceResourceInFilterExpression(name, value) ? ['in'] as
const : []),
+ ...(buildTraceResourceNotInFilterExpression(name, value) ? ['not-in']
as const : []),
+ ...(buildTraceResourceExistsFilterExpression(name) ? ['exists'] as
const : []),
+ ...(buildTraceResourceNotExistsFilterExpression(name) ? ['not-exists']
as const : []),
+ ...(buildTraceResourceFilterExpression(name, value) ? ['replace'] as
const : []),
+ ...(buildTraceResourceGroupBy(name) ? ['group'] as const : [])
+ ];
+ if (!availableOperators.length) return null;
+ const prefix = scope === 'span' ?
'data-trace-manage-drawer-span-attribute' : 'data-trace-manage-drawer-resource';
+ return (
+ <HzSelect
+ {...{
+ [`${prefix}-operator-action`]: 'true',
+ [`${prefix}-operator-owner`]: 'hertzbeat-ui-select',
+ [`${prefix}-filter-name`]: name,
+ [`${prefix}-filter-value`]: value
+ }}
+ size="sm"
+ width="log-severity"
+ value=""
+ placeholder={t('trace.manage.drawer.attributes.operator.placeholder')}
+ aria-label={t('trace.manage.drawer.attributes.operator-action.aria', {
name, value })}
+ options={buildTraceAttributeOperatorOptions(t, availableOperators)}
+ optionDataAttributes={option =>
traceDrawerAttributeOperatorDataAttributes(scope, option.value as
TraceAttributeOperator, name, value)}
+ onChange={event => applyTraceAttributeOperator(scope,
event.target.value as TraceAttributeOperator, name, value)}
+ />
+ );
+ }, [applyTraceAttributeOperator, t]);
const selectedTraceEvent = findTraceWaterfallEvent(waterfallRows,
state.selectedEventKey);
const handoffLinks = buildTraceHandoffLinks(detail, selectedSpan,
handoffRouteContext, {
intakeReturnTo: currentTraceReturnHref,
@@ -1338,147 +1532,7 @@ function TraceWaterfallDrawer({
title: row.title,
copy: row.copy,
meta: row.meta,
- action: buildTraceSpanAttributeFilterExpression(row.title,
row.copy) || buildTraceSpanAttributeExcludeFilterExpression(row.title,
row.copy) || buildTraceSpanAttributeContainsFilterExpression(row.title,
row.copy) || buildTraceSpanAttributeNotContainsFilterExpression(row.title,
row.copy) || buildTraceSpanAttributeInFilterExpression(row.title, row.copy) ||
buildTraceSpanAttributeNotInFilterExpression(row.title, row.copy) ||
buildTraceSpanAttributeExistsFilterExpression(r [...]
- <HzActionGroup
-
data-trace-manage-drawer-span-attribute-action-group="filter-group"
-
data-trace-manage-drawer-span-attribute-action-group-owner="hertzbeat-ui-action-group"
- layout="end-wrap"
- >
- {buildTraceSpanAttributeFilterExpression(row.title,
row.copy) ? (
- <>
- <HzButton
-
data-trace-manage-drawer-span-attribute-filter-action="true"
-
data-trace-manage-drawer-span-attribute-filter-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
-
data-trace-manage-drawer-span-attribute-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.filter-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Filter}
data-trace-manage-drawer-span-attribute-filter-action-icon="filter"
data-trace-manage-drawer-span-attribute-filter-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.filter-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-filter-out-action="true"
-
data-trace-manage-drawer-span-attribute-filter-out-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
-
data-trace-manage-drawer-span-attribute-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onExcludeSpanAttributeFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.filter-out-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={X}
data-trace-manage-drawer-span-attribute-filter-out-action-icon="exclude"
data-trace-manage-drawer-span-attribute-filter-out-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.filter-out-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-contains-action="true"
-
data-trace-manage-drawer-span-attribute-contains-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
-
data-trace-manage-drawer-span-attribute-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeContainsFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.contains-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Search}
data-trace-manage-drawer-span-attribute-contains-action-icon="contains"
data-trace-manage-drawer-span-attribute-contains-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.contains-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-not-contains-action="true"
-
data-trace-manage-drawer-span-attribute-not-contains-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
-
data-trace-manage-drawer-span-attribute-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeNotContainsFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.not-contains-action.aria', {
name: row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Ban}
data-trace-manage-drawer-span-attribute-not-contains-action-icon="not-contains"
data-trace-manage-drawer-span-attribute-not-contains-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.not-contains-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-in-action="true"
-
data-trace-manage-drawer-span-attribute-in-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
-
data-trace-manage-drawer-span-attribute-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeInFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.in-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={ListChecks}
data-trace-manage-drawer-span-attribute-in-action-icon="in"
data-trace-manage-drawer-span-attribute-in-action-icon-owner="hertzbeat-ui-button-icon"
/>
- {t('trace.manage.drawer.attributes.in-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-not-in-action="true"
-
data-trace-manage-drawer-span-attribute-not-in-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
-
data-trace-manage-drawer-span-attribute-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeNotInFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.not-in-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Ban}
data-trace-manage-drawer-span-attribute-not-in-action-icon="not-in"
data-trace-manage-drawer-span-attribute-not-in-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.not-in-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-exists-action="true"
-
data-trace-manage-drawer-span-attribute-exists-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeExistsFilter(row.title)}
-
aria-label={t('trace.manage.drawer.attributes.exists-action.aria', { name:
row.title })}
- >
- <HzButtonIcon icon={Check}
data-trace-manage-drawer-span-attribute-exists-action-icon="exists"
data-trace-manage-drawer-span-attribute-exists-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.exists-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-not-exists-action="true"
-
data-trace-manage-drawer-span-attribute-not-exists-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeNotExistsFilter(row.title)}
-
aria-label={t('trace.manage.drawer.attributes.not-exists-action.aria', { name:
row.title })}
- >
- <HzButtonIcon icon={Ban}
data-trace-manage-drawer-span-attribute-not-exists-action-icon="not-exists"
data-trace-manage-drawer-span-attribute-not-exists-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.not-exists-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-span-attribute-replace-action="true"
-
data-trace-manage-drawer-span-attribute-replace-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-filter-name={row.title}
-
data-trace-manage-drawer-span-attribute-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onReplaceSpanAttributeFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.replace-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Replace}
data-trace-manage-drawer-span-attribute-replace-action-icon="replace"
data-trace-manage-drawer-span-attribute-replace-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.replace-action')}
- </HzButton>
- </>
- ) : null}
- {buildTraceSpanAttributeGroupBy(row.title) ? (
- <HzButton
-
data-trace-manage-drawer-span-attribute-group-action="true"
-
data-trace-manage-drawer-span-attribute-group-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-span-attribute-group-name={row.title}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplySpanAttributeGroupBy(row.title)}
-
aria-label={t('trace.manage.drawer.attributes.group-action.aria', { name:
row.title })}
- >
- <HzButtonIcon icon={BarChart3}
data-trace-manage-drawer-span-attribute-group-action-icon="group"
data-trace-manage-drawer-span-attribute-group-action-icon-owner="hertzbeat-ui-button-icon"
/>
- {t('trace.manage.drawer.attributes.group-action')}
- </HzButton>
- ) : null}
- </HzActionGroup>
- ) : null
+ action: renderTraceDrawerAttributeOperator('span',
row.title, row.copy)
}))}
/>
<HzDetailRows
@@ -1491,147 +1545,7 @@ function TraceWaterfallDrawer({
title: row.title,
copy: row.copy,
meta: row.meta,
- action: buildTraceResourceFilterExpression(row.title,
row.copy) || buildTraceResourceExcludeFilterExpression(row.title, row.copy) ||
buildTraceResourceContainsFilterExpression(row.title, row.copy) ||
buildTraceResourceNotContainsFilterExpression(row.title, row.copy) ||
buildTraceResourceInFilterExpression(row.title, row.copy) ||
buildTraceResourceNotInFilterExpression(row.title, row.copy) ||
buildTraceResourceExistsFilterExpression(row.title) || buildTraceResourceNotE
[...]
- <HzActionGroup
-
data-trace-manage-drawer-resource-action-group="filter-group"
-
data-trace-manage-drawer-resource-action-group-owner="hertzbeat-ui-action-group"
- layout="end-wrap"
- >
- {buildTraceResourceFilterExpression(row.title,
row.copy) ? (
- <>
- <HzButton
-
data-trace-manage-drawer-resource-filter-action="true"
-
data-trace-manage-drawer-resource-filter-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
-
data-trace-manage-drawer-resource-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() => onApplyResourceFilter(row.title,
row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.filter-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Filter}
data-trace-manage-drawer-resource-filter-action-icon="filter"
data-trace-manage-drawer-resource-filter-action-icon-owner="hertzbeat-ui-button-icon"
/>
- {t('trace.manage.drawer.attributes.filter-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-resource-filter-out-action="true"
-
data-trace-manage-drawer-resource-filter-out-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
-
data-trace-manage-drawer-resource-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() => onExcludeResourceFilter(row.title,
row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.filter-out-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={X}
data-trace-manage-drawer-resource-filter-out-action-icon="exclude"
data-trace-manage-drawer-resource-filter-out-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.filter-out-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-resource-contains-action="true"
-
data-trace-manage-drawer-resource-contains-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
-
data-trace-manage-drawer-resource-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplyResourceContainsFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.contains-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Search}
data-trace-manage-drawer-resource-contains-action-icon="contains"
data-trace-manage-drawer-resource-contains-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.contains-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-resource-not-contains-action="true"
-
data-trace-manage-drawer-resource-not-contains-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
-
data-trace-manage-drawer-resource-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplyResourceNotContainsFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.not-contains-action.aria', {
name: row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Ban}
data-trace-manage-drawer-resource-not-contains-action-icon="not-contains"
data-trace-manage-drawer-resource-not-contains-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.not-contains-action')}
- </HzButton>
- <HzButton
- data-trace-manage-drawer-resource-in-action="true"
-
data-trace-manage-drawer-resource-in-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
-
data-trace-manage-drawer-resource-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() => onApplyResourceInFilter(row.title,
row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.in-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={ListChecks}
data-trace-manage-drawer-resource-in-action-icon="in"
data-trace-manage-drawer-resource-in-action-icon-owner="hertzbeat-ui-button-icon"
/>
- {t('trace.manage.drawer.attributes.in-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-resource-not-in-action="true"
-
data-trace-manage-drawer-resource-not-in-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
-
data-trace-manage-drawer-resource-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplyResourceNotInFilter(row.title, row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.not-in-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Ban}
data-trace-manage-drawer-resource-not-in-action-icon="not-in"
data-trace-manage-drawer-resource-not-in-action-icon-owner="hertzbeat-ui-button-icon"
/>
- {t('trace.manage.drawer.attributes.not-in-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-resource-exists-action="true"
-
data-trace-manage-drawer-resource-exists-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplyResourceExistsFilter(row.title)}
-
aria-label={t('trace.manage.drawer.attributes.exists-action.aria', { name:
row.title })}
- >
- <HzButtonIcon icon={Check}
data-trace-manage-drawer-resource-exists-action-icon="exists"
data-trace-manage-drawer-resource-exists-action-icon-owner="hertzbeat-ui-button-icon"
/>
- {t('trace.manage.drawer.attributes.exists-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-resource-not-exists-action="true"
-
data-trace-manage-drawer-resource-not-exists-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
- size="sm"
- intent="secondary"
- onClick={() =>
onApplyResourceNotExistsFilter(row.title)}
-
aria-label={t('trace.manage.drawer.attributes.not-exists-action.aria', { name:
row.title })}
- >
- <HzButtonIcon icon={Ban}
data-trace-manage-drawer-resource-not-exists-action-icon="not-exists"
data-trace-manage-drawer-resource-not-exists-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.not-exists-action')}
- </HzButton>
- <HzButton
-
data-trace-manage-drawer-resource-replace-action="true"
-
data-trace-manage-drawer-resource-replace-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-filter-name={row.title}
-
data-trace-manage-drawer-resource-filter-value={row.copy}
- size="sm"
- intent="secondary"
- onClick={() => onReplaceResourceFilter(row.title,
row.copy)}
-
aria-label={t('trace.manage.drawer.attributes.replace-action.aria', { name:
row.title, value: row.copy })}
- >
- <HzButtonIcon icon={Replace}
data-trace-manage-drawer-resource-replace-action-icon="replace"
data-trace-manage-drawer-resource-replace-action-icon-owner="hertzbeat-ui-button-icon"
/>
-
{t('trace.manage.drawer.attributes.replace-action')}
- </HzButton>
- </>
- ) : null}
- {buildTraceResourceGroupBy(row.title) ? (
- <HzButton
-
data-trace-manage-drawer-resource-group-action="true"
-
data-trace-manage-drawer-resource-group-action-owner="hertzbeat-ui-button"
-
data-trace-manage-drawer-resource-group-name={row.title}
- size="sm"
- intent="secondary"
- onClick={() => onApplyResourceGroupBy(row.title)}
-
aria-label={t('trace.manage.drawer.attributes.group-action.aria', { name:
row.title })}
- >
- <HzButtonIcon icon={BarChart3}
data-trace-manage-drawer-resource-group-action-icon="group"
data-trace-manage-drawer-resource-group-action-icon-owner="hertzbeat-ui-button-icon"
/>
- {t('trace.manage.drawer.attributes.group-action')}
- </HzButton>
- ) : null}
- </HzActionGroup>
- ) : null
+ action: renderTraceDrawerAttributeOperator('resource',
row.title, row.copy)
}))}
/>
{selectedTraceEvent ? (
diff --git a/web-next/lib/i18n-runtime-messages.ts
b/web-next/lib/i18n-runtime-messages.ts
index 3ac5d8cd11..ffb1b7602e 100644
--- a/web-next/lib/i18n-runtime-messages.ts
+++ b/web-next/lib/i18n-runtime-messages.ts
@@ -2759,6 +2759,18 @@ export const SUPPLEMENTAL_MESSAGES:
Partial<Record<LocaleCode, Messages>> = {
'trace.manage.drawer.attributes.empty.copy': 'No attributes are available
for the selected span.',
'trace.manage.drawer.attributes.group-action': 'Group',
'trace.manage.drawer.attributes.group-action.aria': 'Group traces by
{{name}}',
+ 'trace.manage.drawer.attributes.operator.placeholder': 'Choose',
+ 'trace.manage.drawer.attributes.operator-action.aria': 'Choose a trace
attribute operator for {{name}} {{value}}',
+ 'trace.manage.drawer.attributes.operator.filter': 'Filter',
+ 'trace.manage.drawer.attributes.operator.exclude': 'Exclude',
+ 'trace.manage.drawer.attributes.operator.contains': 'Contains',
+ 'trace.manage.drawer.attributes.operator.not-contains': 'Not contains',
+ 'trace.manage.drawer.attributes.operator.in': 'In',
+ 'trace.manage.drawer.attributes.operator.not-in': 'Not in',
+ 'trace.manage.drawer.attributes.operator.exists': 'Exists',
+ 'trace.manage.drawer.attributes.operator.not-exists': 'Not exists',
+ 'trace.manage.drawer.attributes.operator.replace': 'Replace',
+ 'trace.manage.drawer.attributes.operator.group': 'Group',
'trace.manage.drawer.related-logs.title': 'Related logs',
'trace.manage.drawer.related-logs.aria': 'Related logs for the selected
trace span',
'trace.manage.drawer.related-logs.loading.title': 'Loading related logs',
@@ -7296,6 +7308,18 @@ export const SUPPLEMENTAL_MESSAGES:
Partial<Record<LocaleCode, Messages>> = {
'trace.manage.drawer.attributes.empty.copy': '当前选中跨度没有可用属性。',
'trace.manage.drawer.attributes.group-action': '分组',
'trace.manage.drawer.attributes.group-action.aria': '按 {{name}} 对链路分组',
+ 'trace.manage.drawer.attributes.operator.placeholder': '选择',
+ 'trace.manage.drawer.attributes.operator-action.aria': '为 {{name}}
{{value}} 选择链路属性操作',
+ 'trace.manage.drawer.attributes.operator.filter': '过滤',
+ 'trace.manage.drawer.attributes.operator.exclude': '排除',
+ 'trace.manage.drawer.attributes.operator.contains': '包含',
+ 'trace.manage.drawer.attributes.operator.not-contains': '不包含',
+ 'trace.manage.drawer.attributes.operator.in': '属于',
+ 'trace.manage.drawer.attributes.operator.not-in': '不属于',
+ 'trace.manage.drawer.attributes.operator.exists': '存在',
+ 'trace.manage.drawer.attributes.operator.not-exists': '不存在',
+ 'trace.manage.drawer.attributes.operator.replace': '替换',
+ 'trace.manage.drawer.attributes.operator.group': '分组',
'trace.manage.drawer.related-logs.title': '相关日志',
'trace.manage.drawer.related-logs.aria': '当前选中跨度的相关日志',
'trace.manage.drawer.related-logs.loading.title': '正在加载相关日志',
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]