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 22549189ac Add trace span attribute filter actions
22549189ac is described below

commit 22549189ac9a5dc34c04cbd00863a94aac6db478
Author: Logic <[email protected]>
AuthorDate: Tue Jun 9 22:02:40 2026 +0800

    Add trace span attribute filter actions
---
 web-next/app/trace/manage/page.test.tsx         |  9 ++++
 web-next/app/trace/manage/trace-manage-page.tsx | 68 ++++++++++++++++++++++++-
 2 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/web-next/app/trace/manage/page.test.tsx 
b/web-next/app/trace/manage/page.test.tsx
index 55894d5aa1..725e4a8763 100644
--- a/web-next/app/trace/manage/page.test.tsx
+++ b/web-next/app/trace/manage/page.test.tsx
@@ -1766,13 +1766,22 @@ describe('trace manage page', () => {
     expect(source).toContain('selectedSpanAttributeRows');
     expect(source).toContain('selectedResourceAttributeRows');
     expect(source).toContain('buildTraceResourceFilterExpression');
+    expect(source).toContain('buildTraceSpanAttributeFilterExpression');
     expect(source).toContain('mergeTraceResourceFilterExpression');
     expect(source).toContain('applyTraceResourceFilter');
     expect(source).toContain('replaceTraceResourceFilter');
+    expect(source).toContain('applyTraceSpanAttributeFilter');
+    expect(source).toContain('replaceTraceSpanAttributeFilter');
     expect(source).toContain('buildTraceResourceGroupBy');
     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-replace-action="true"');
+    
expect(source).toContain('data-trace-manage-drawer-span-attribute-replace-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"');
diff --git a/web-next/app/trace/manage/trace-manage-page.tsx 
b/web-next/app/trace/manage/trace-manage-page.tsx
index bc8ace5d71..85f3ea1178 100644
--- a/web-next/app/trace/manage/trace-manage-page.tsx
+++ b/web-next/app/trace/manage/trace-manage-page.tsx
@@ -506,6 +506,10 @@ function buildTraceResourceFilterExpression(name: 
React.ReactNode, value: React.
   return `${key}=${filterValue}`;
 }
 
+function buildTraceSpanAttributeFilterExpression(name: React.ReactNode, value: 
React.ReactNode) {
+  return buildTraceResourceFilterExpression(name, value);
+}
+
 function buildTraceResourceGroupBy(name: React.ReactNode) {
   const key = String(name ?? '').trim();
   if (!isSafeTraceResourceFilterKey(key)) {
@@ -745,6 +749,8 @@ function TraceWaterfallDrawer({
   onSelectSpan,
   onSelectEvent,
   onApplyOperationFilter,
+  onApplySpanAttributeFilter,
+  onReplaceSpanAttributeFilter,
   onApplyResourceFilter,
   onReplaceResourceFilter,
   onApplyResourceGroupBy
@@ -758,6 +764,8 @@ function TraceWaterfallDrawer({
   onSelectSpan: (spanId: string) => void;
   onSelectEvent: (eventKey: string | null, spanId?: string) => void;
   onApplyOperationFilter: (operationName: string) => void;
+  onApplySpanAttributeFilter: (name: string, value: string) => void;
+  onReplaceSpanAttributeFilter: (name: string, value: string) => void;
   onApplyResourceFilter: (name: string, value: string) => void;
   onReplaceResourceFilter: (name: string, value: string) => void;
   onApplyResourceGroupBy: (name: string) => void;
@@ -1193,7 +1201,41 @@ function TraceWaterfallDrawer({
                     key: `span-attribute-${row.title}`,
                     title: row.title,
                     copy: row.copy,
-                    meta: row.meta
+                    meta: row.meta,
+                    action: buildTraceSpanAttributeFilterExpression(row.title, 
row.copy) ? (
+                      <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"
+                      >
+                        <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-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>
+                      </HzActionGroup>
+                    ) : null
                   }))}
                 />
                 <HzDetailRows
@@ -1757,6 +1799,28 @@ function TraceExplorer({
     applyQuery(nextQuery);
   }, [applyQuery, draft, setDraft]);
 
+  const applyTraceSpanAttributeFilter = useCallback((name: string, value: 
string) => {
+    const expression = buildTraceSpanAttributeFilterExpression(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;
+    const nextQuery: TraceQueryState = {
+      ...draft,
+      attributeFilter: expression
+    };
+    setDraft(nextQuery);
+    applyQuery(nextQuery);
+  }, [applyQuery, draft, setDraft]);
+
   const applyTraceResourceGroupBy = useCallback((name: string) => {
     const groupBy = buildTraceResourceGroupBy(name);
     if (!groupBy) return;
@@ -3289,6 +3353,8 @@ function TraceExplorer({
           onSelectSpan={spanId => setTraceDetailDrawer(previous => ({ 
...previous, selectedSpanId: spanId, selectedEventKey: null }))}
           onSelectEvent={(eventKey, spanId) => setTraceDetailDrawer(previous 
=> ({ ...previous, selectedSpanId: spanId || previous.selectedSpanId, 
selectedEventKey: eventKey }))}
           onApplyOperationFilter={operationName => 
applyTraceQuickFilter('operationName', operationName)}
+          onApplySpanAttributeFilter={applyTraceSpanAttributeFilter}
+          onReplaceSpanAttributeFilter={replaceTraceSpanAttributeFilter}
           onApplyResourceFilter={applyTraceResourceFilter}
           onReplaceResourceFilter={replaceTraceResourceFilter}
           onApplyResourceGroupBy={applyTraceResourceGroupBy}


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

Reply via email to