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]

Reply via email to