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 b76ac05048 Add trace attribute exclude actions
b76ac05048 is described below

commit b76ac05048295f694d87fa58004e07010120b5d8
Author: Logic <[email protected]>
AuthorDate: Tue Jun 9 22:21:16 2026 +0800

    Add trace attribute exclude actions
---
 web-next/app/trace/manage/page.test.tsx         |  9 ++++
 web-next/app/trace/manage/trace-manage-page.tsx | 71 ++++++++++++++++++++++++-
 web-next/lib/i18n-runtime-messages.ts           |  4 ++
 3 files changed, 82 insertions(+), 2 deletions(-)

diff --git a/web-next/app/trace/manage/page.test.tsx 
b/web-next/app/trace/manage/page.test.tsx
index b1030f9fa2..4af6d12017 100644
--- a/web-next/app/trace/manage/page.test.tsx
+++ b/web-next/app/trace/manage/page.test.tsx
@@ -1793,12 +1793,16 @@ describe('trace manage page', () => {
     expect(source).toContain('selectedSpanAttributeRows');
     expect(source).toContain('selectedResourceAttributeRows');
     expect(source).toContain('buildTraceResourceFilterExpression');
+    expect(source).toContain('buildTraceResourceExcludeFilterExpression');
     expect(source).toContain('buildTraceSpanAttributeFilterExpression');
+    expect(source).toContain('buildTraceSpanAttributeExcludeFilterExpression');
     expect(source).toContain('buildTraceSpanAttributeGroupBy');
     expect(source).toContain('mergeTraceResourceFilterExpression');
     expect(source).toContain('applyTraceResourceFilter');
+    expect(source).toContain('excludeTraceResourceFilter');
     expect(source).toContain('replaceTraceResourceFilter');
     expect(source).toContain('applyTraceSpanAttributeFilter');
+    expect(source).toContain('excludeTraceSpanAttributeFilter');
     expect(source).toContain('replaceTraceSpanAttributeFilter');
     expect(source).toContain('applyTraceSpanAttributeGroupBy');
     expect(source).toContain('buildTraceResourceGroupBy');
@@ -1807,6 +1811,8 @@ describe('trace manage page', () => {
     
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-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"');
@@ -1820,6 +1826,9 @@ describe('trace manage page', () => {
     
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-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')");
diff --git a/web-next/app/trace/manage/trace-manage-page.tsx 
b/web-next/app/trace/manage/trace-manage-page.tsx
index 25c32ae4fd..23d8130095 100644
--- a/web-next/app/trace/manage/trace-manage-page.tsx
+++ b/web-next/app/trace/manage/trace-manage-page.tsx
@@ -506,10 +506,23 @@ function buildTraceResourceFilterExpression(name: 
React.ReactNode, value: React.
   return `${key}=${filterValue}`;
 }
 
+function buildTraceResourceExcludeFilterExpression(name: React.ReactNode, 
value: React.ReactNode) {
+  const key = String(name ?? '').trim();
+  const filterValue = String(value ?? '').trim();
+  if (!isSafeTraceResourceFilterKey(key) || 
!isSafeTraceResourceFilterValue(filterValue)) {
+    return null;
+  }
+  return `${key}!=${filterValue}`;
+}
+
 function buildTraceSpanAttributeFilterExpression(name: React.ReactNode, value: 
React.ReactNode) {
   return buildTraceResourceFilterExpression(name, value);
 }
 
+function buildTraceSpanAttributeExcludeFilterExpression(name: React.ReactNode, 
value: React.ReactNode) {
+  return buildTraceResourceExcludeFilterExpression(name, value);
+}
+
 function buildTraceSpanAttributeGroupBy(name: React.ReactNode) {
   const key = String(name ?? '').trim();
   if (!isSafeTraceResourceFilterKey(key)) {
@@ -763,9 +776,11 @@ function TraceWaterfallDrawer({
   onSelectEvent,
   onApplyOperationFilter,
   onApplySpanAttributeFilter,
+  onExcludeSpanAttributeFilter,
   onReplaceSpanAttributeFilter,
   onApplySpanAttributeGroupBy,
   onApplyResourceFilter,
+  onExcludeResourceFilter,
   onReplaceResourceFilter,
   onApplyResourceGroupBy
 }: {
@@ -779,9 +794,11 @@ function TraceWaterfallDrawer({
   onSelectEvent: (eventKey: string | null, spanId?: string) => void;
   onApplyOperationFilter: (operationName: string) => void;
   onApplySpanAttributeFilter: (name: string, value: string) => void;
+  onExcludeSpanAttributeFilter: (name: string, value: string) => void;
   onReplaceSpanAttributeFilter: (name: string, value: string) => void;
   onApplySpanAttributeGroupBy: (name: string) => void;
   onApplyResourceFilter: (name: string, value: string) => void;
+  onExcludeResourceFilter: (name: string, value: string) => void;
   onReplaceResourceFilter: (name: string, value: string) => void;
   onApplyResourceGroupBy: (name: string) => void;
 }) {
@@ -1217,7 +1234,7 @@ function TraceWaterfallDrawer({
                     title: row.title,
                     copy: row.copy,
                     meta: row.meta,
-                    action: buildTraceSpanAttributeFilterExpression(row.title, 
row.copy) || buildTraceSpanAttributeGroupBy(row.title) ? (
+                    action: buildTraceSpanAttributeFilterExpression(row.title, 
row.copy) || buildTraceSpanAttributeExcludeFilterExpression(row.title, 
row.copy) || buildTraceSpanAttributeGroupBy(row.title) ? (
                       <HzActionGroup
                         
data-trace-manage-drawer-span-attribute-action-group="filter-group"
                         
data-trace-manage-drawer-span-attribute-action-group-owner="hertzbeat-ui-action-group"
@@ -1238,6 +1255,19 @@ function TraceWaterfallDrawer({
                               <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-replace-action="true"
                               
data-trace-manage-drawer-span-attribute-replace-action-owner="hertzbeat-ui-button"
@@ -1281,7 +1311,7 @@ function TraceWaterfallDrawer({
                     title: row.title,
                     copy: row.copy,
                     meta: row.meta,
-                    action: buildTraceResourceFilterExpression(row.title, 
row.copy) || buildTraceResourceGroupBy(row.title) ? (
+                    action: buildTraceResourceFilterExpression(row.title, 
row.copy) || buildTraceResourceExcludeFilterExpression(row.title, row.copy) || 
buildTraceResourceGroupBy(row.title) ? (
                       <HzActionGroup
                         
data-trace-manage-drawer-resource-action-group="filter-group"
                         
data-trace-manage-drawer-resource-action-group-owner="hertzbeat-ui-action-group"
@@ -1302,6 +1332,19 @@ function TraceWaterfallDrawer({
                             <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-replace-action="true"
                             
data-trace-manage-drawer-resource-replace-action-owner="hertzbeat-ui-button"
@@ -1821,6 +1864,17 @@ function TraceExplorer({
     applyQuery(nextQuery);
   }, [applyQuery, draft, setDraft]);
 
+  const excludeTraceResourceFilter = useCallback((name: string, value: string) 
=> {
+    const expression = buildTraceResourceExcludeFilterExpression(name, value);
+    if (!expression) return;
+    const nextQuery: TraceQueryState = {
+      ...draft,
+      resourceFilter: mergeTraceResourceFilterExpression(draft.resourceFilter, 
expression)
+    };
+    setDraft(nextQuery);
+    applyQuery(nextQuery);
+  }, [applyQuery, draft, setDraft]);
+
   const replaceTraceResourceFilter = useCallback((name: string, value: string) 
=> {
     const expression = buildTraceResourceFilterExpression(name, value);
     if (!expression) return;
@@ -1843,6 +1897,17 @@ function TraceExplorer({
     applyQuery(nextQuery);
   }, [applyQuery, draft, setDraft]);
 
+  const excludeTraceSpanAttributeFilter = useCallback((name: string, value: 
string) => {
+    const expression = buildTraceSpanAttributeExcludeFilterExpression(name, 
value);
+    if (!expression) return;
+    const nextQuery: TraceQueryState = {
+      ...draft,
+      attributeFilter: 
mergeTraceResourceFilterExpression(draft.attributeFilter, expression)
+    };
+    setDraft(nextQuery);
+    applyQuery(nextQuery);
+  }, [applyQuery, draft, setDraft]);
+
   const replaceTraceSpanAttributeFilter = useCallback((name: string, value: 
string) => {
     const expression = buildTraceSpanAttributeFilterExpression(name, value);
     if (!expression) return;
@@ -3400,9 +3465,11 @@ function TraceExplorer({
           onSelectEvent={(eventKey, spanId) => setTraceDetailDrawer(previous 
=> ({ ...previous, selectedSpanId: spanId || previous.selectedSpanId, 
selectedEventKey: eventKey }))}
           onApplyOperationFilter={operationName => 
applyTraceQuickFilter('operationName', operationName)}
           onApplySpanAttributeFilter={applyTraceSpanAttributeFilter}
+          onExcludeSpanAttributeFilter={excludeTraceSpanAttributeFilter}
           onReplaceSpanAttributeFilter={replaceTraceSpanAttributeFilter}
           onApplySpanAttributeGroupBy={applyTraceSpanAttributeGroupBy}
           onApplyResourceFilter={applyTraceResourceFilter}
+          onExcludeResourceFilter={excludeTraceResourceFilter}
           onReplaceResourceFilter={replaceTraceResourceFilter}
           onApplyResourceGroupBy={applyTraceResourceGroupBy}
         />
diff --git a/web-next/lib/i18n-runtime-messages.ts 
b/web-next/lib/i18n-runtime-messages.ts
index d96900c35c..80cc7506a9 100644
--- a/web-next/lib/i18n-runtime-messages.ts
+++ b/web-next/lib/i18n-runtime-messages.ts
@@ -2737,6 +2737,8 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'trace.manage.drawer.attributes.resource.meta': 'resourceAttributes',
     'trace.manage.drawer.attributes.filter-action': 'Filter',
     'trace.manage.drawer.attributes.filter-action.aria': 'Filter {{name}} 
equals {{value}}',
+    'trace.manage.drawer.attributes.filter-out-action': 'Exclude',
+    'trace.manage.drawer.attributes.filter-out-action.aria': 'Exclude traces 
where {{name}} equals {{value}}',
     'trace.manage.drawer.attributes.replace-action': 'Replace',
     'trace.manage.drawer.attributes.replace-action.aria': 'Replace filters 
with {{name}} equals {{value}}',
     'trace.manage.drawer.attributes.empty.title': 'No attributes',
@@ -7195,6 +7197,8 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'trace.manage.drawer.attributes.resource.meta': 'resourceAttributes',
     'trace.manage.drawer.attributes.filter-action': '过滤',
     'trace.manage.drawer.attributes.filter-action.aria': '过滤 {{name}} 等于 
{{value}}',
+    'trace.manage.drawer.attributes.filter-out-action': '排除',
+    'trace.manage.drawer.attributes.filter-out-action.aria': '排除 {{name}} 等于 
{{value}} 的链路',
     'trace.manage.drawer.attributes.replace-action': '替换',
     'trace.manage.drawer.attributes.replace-action.aria': '替换为 {{name}} 等于 
{{value}} 的过滤条件',
     'trace.manage.drawer.attributes.empty.title': '无属性',


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

Reply via email to