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 06f2966639 Expose trace span attribute filters
06f2966639 is described below

commit 06f296663998fac9bd56a347db99d4ba12fe9819
Author: Logic <[email protected]>
AuthorDate: Tue Jun 9 21:58:40 2026 +0800

    Expose trace span attribute filters
---
 web-next/app/trace/manage/page.test.tsx         | 25 ++++++++++++++++---------
 web-next/app/trace/manage/route-state.test.ts   |  3 ++-
 web-next/app/trace/manage/trace-manage-page.tsx | 15 +++++++++++++++
 web-next/lib/i18n-runtime-messages.ts           |  4 ++++
 web-next/lib/trace-manage/query-state.test.ts   | 12 ++++++++----
 web-next/lib/trace-manage/query-state.ts        | 13 ++++++++++---
 web-next/lib/trace-manage/view-model.ts         |  1 +
 7 files changed, 56 insertions(+), 17 deletions(-)

diff --git a/web-next/app/trace/manage/page.test.tsx 
b/web-next/app/trace/manage/page.test.tsx
index 11d5a92b11..55894d5aa1 100644
--- a/web-next/app/trace/manage/page.test.tsx
+++ b/web-next/app/trace/manage/page.test.tsx
@@ -220,6 +220,7 @@ vi.mock('@/lib/trace-manage/query-state', () => {
     if (query.spanId) params.set('spanId', query.spanId);
     if (query.serviceName) params.set('serviceName', query.serviceName);
     if (query.resourceFilter) params.set('resourceFilter', 
query.resourceFilter);
+    if (query.attributeFilter) params.set('attributeFilter', 
query.attributeFilter);
     if (query.operationName) params.set('operationName', query.operationName);
     if (query.minDurationMs) params.set('minDurationMs', query.minDurationMs);
     if (query.maxDurationMs) params.set('maxDurationMs', query.maxDurationMs);
@@ -254,6 +255,10 @@ vi.mock('@/lib/trace-manage/query-state', () => {
       listParams.set('resourceFilter', query.resourceFilter);
       overviewParams.set('resourceFilter', query.resourceFilter);
     }
+    if (query.attributeFilter) {
+      listParams.set('attributeFilter', query.attributeFilter);
+      overviewParams.set('attributeFilter', query.attributeFilter);
+    }
     if (query.operationName) {
       listParams.set('operationName', query.operationName);
       overviewParams.set('operationName', query.operationName);
@@ -294,6 +299,7 @@ vi.mock('@/lib/trace-manage/query-state', () => {
     spanId: searchParams.get('spanId') || '',
     serviceName: searchParams.get('serviceName') || '',
     resourceFilter: searchParams.get('resourceFilter') || '',
+    attributeFilter: searchParams.get('attributeFilter') || '',
     operationName: searchParams.get('operationName') || '',
     minDurationMs: searchParams.get('minDurationMs') || '',
     maxDurationMs: searchParams.get('maxDurationMs') || '',
@@ -417,6 +423,7 @@ function buildTraceManageRouteState(): 
TraceManageRouteState {
     spanId: mockState.searchParams.get('spanId') || '',
     serviceName: mockState.searchParams.get('serviceName') || '',
     resourceFilter: mockState.searchParams.get('resourceFilter') || '',
+    attributeFilter: mockState.searchParams.get('attributeFilter') || '',
     operationName: mockState.searchParams.get('operationName') || '',
     minDurationMs: mockState.searchParams.get('minDurationMs') || '',
     maxDurationMs: mockState.searchParams.get('maxDurationMs') || '',
@@ -520,7 +527,7 @@ afterEach(() => {
 
 describe('trace manage page', () => {
   it('renders the OTLP cold trace workbench and keeps the filtered trace 
endpoints', async () => {
-    mockState.searchParams = new 
URLSearchParams('traceId=trace-123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=POST%20%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true');
+    mockState.searchParams = new 
URLSearchParams('traceId=trace-123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route%20CONTAINS%20checkout&operationName=POST%20%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true');
     apiMessageGet
       .mockResolvedValueOnce({ totalTraceCount: 8, errorTraceCount: 2, 
latestObservedAt: 1713200000000, hasActiveTrace: true })
       .mockResolvedValueOnce({ content: [] });
@@ -531,7 +538,7 @@ describe('trace manage page', () => {
     
expect(html).toContain('data-trace-manage-route="otlp-hertzbeat-ui-trace-workbench"');
     expect(html).toContain('data-loading-copy="Loading trace workbench"');
     expect(html).toContain(
-      
'data-cache-key="trace-manage:list:/traces/list?pageIndex=0&amp;pageSize=8&amp;traceId=trace-123&amp;serviceName=checkout&amp;resourceFilter=service.version%3D1.2.3&amp;operationName=POST+%2Fcheckout&amp;minDurationMs=100&amp;maxDurationMs=500&amp;errorOnly=true&amp;spanScope=root|/traces/stats/overview?traceId=trace-123&amp;serviceName=checkout&amp;resourceFilter=service.version%3D1.2.3&amp;operationName=POST+%2Fcheckout&amp;minDurationMs=100&amp;maxDurationMs=500&amp;errorOnly=tr
 [...]
+      
'data-cache-key="trace-manage:list:/traces/list?pageIndex=0&amp;pageSize=8&amp;traceId=trace-123&amp;serviceName=checkout&amp;resourceFilter=service.version%3D1.2.3&amp;attributeFilter=http.route+CONTAINS+checkout&amp;operationName=POST+%2Fcheckout&amp;minDurationMs=100&amp;maxDurationMs=500&amp;errorOnly=true&amp;spanScope=root|/traces/stats/overview?traceId=trace-123&amp;serviceName=checkout&amp;resourceFilter=service.version%3D1.2.3&amp;attributeFilter=http.route+CONTAINS+checko
 [...]
     );
     expect(html).toContain('data-cache-settled-ttl="10000"');
     
expect(html).toContain('data-trace-manage-style-baseline="hertzbeat-ui-matte"');
@@ -722,13 +729,13 @@ describe('trace manage page', () => {
     expect(html).not.toContain('Create an Alert');
     expect(html).not.toContain('Add to Dashboard');
     expect(apiMessageGet.mock.calls).toEqual([
-      
['/traces/stats/overview?traceId=trace-123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=POST+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
 { signal: expect.any(AbortSignal) }],
-      
['/traces/list?pageIndex=0&pageSize=8&traceId=trace-123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=POST+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
 { signal: expect.any(AbortSignal) }]
+      
['/traces/stats/overview?traceId=trace-123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&operationName=POST+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
 { signal: expect.any(AbortSignal) }],
+      
['/traces/list?pageIndex=0&pageSize=8&traceId=trace-123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&operationName=POST+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
 { signal: expect.any(AbortSignal) }]
     ]);
   }, 60000);
 
   it('loads and renders trace group-by results when the route has an active 
groupBy', async () => {
-    mockState.searchParams = new 
URLSearchParams('serviceName=checkout&resourceFilter=service.version%3D1.2.3&groupBy=resource%3Aservice.version&groupLimit=7&groupOrder=latency-p95-desc&groupMinCount=5');
+    mockState.searchParams = new 
URLSearchParams('serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route%20CONTAINS%20checkout&groupBy=resource%3Aservice.version&groupLimit=7&groupOrder=latency-p95-desc&groupMinCount=5');
     apiMessageGet
       .mockResolvedValueOnce({ totalTraceCount: 8, errorTraceCount: 2, 
latestObservedAt: 1713200000000, hasActiveTrace: true })
       .mockResolvedValueOnce({ content: [] })
@@ -749,7 +756,7 @@ describe('trace manage page', () => {
     await mockState.lastLoad?.();
 
     expect(html).toContain('data-cache-key="trace-manage:list:');
-    
expect(html).toContain('/traces/stats/group-by?serviceName=checkout&amp;resourceFilter=service.version%3D1.2.3&amp;spanScope=root&amp;groupBy=resource%3Aservice.version&amp;limit=7&amp;orderBy=latency-p95-desc&amp;minCount=5');
+    
expect(html).toContain('/traces/stats/group-by?serviceName=checkout&amp;resourceFilter=service.version%3D1.2.3&amp;attributeFilter=http.route+CONTAINS+checkout&amp;spanScope=root&amp;groupBy=resource%3Aservice.version&amp;limit=7&amp;orderBy=latency-p95-desc&amp;minCount=5');
     
expect(html).toContain('data-trace-manage-group-panel="hertzbeat-ui-trace-group-results"');
     
expect(html).toContain('data-trace-manage-group-panel-owner="hertzbeat-ui-panel-surface"');
     
expect(html).toContain('data-trace-manage-group-by="resource:service.version"');
@@ -769,9 +776,9 @@ describe('trace manage page', () => {
     expect(html).toContain('84.5ms');
     expect(html).toContain('210ms');
     expect(apiMessageGet.mock.calls).toEqual([
-      
['/traces/stats/overview?serviceName=checkout&resourceFilter=service.version%3D1.2.3&spanScope=root',
 { signal: expect.any(AbortSignal) }],
-      
['/traces/list?pageIndex=0&pageSize=8&serviceName=checkout&resourceFilter=service.version%3D1.2.3&spanScope=root',
 { signal: expect.any(AbortSignal) }],
-      
['/traces/stats/group-by?serviceName=checkout&resourceFilter=service.version%3D1.2.3&spanScope=root&groupBy=resource%3Aservice.version&limit=7&orderBy=latency-p95-desc&minCount=5',
 { signal: expect.any(AbortSignal) }]
+      
['/traces/stats/overview?serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&spanScope=root',
 { signal: expect.any(AbortSignal) }],
+      
['/traces/list?pageIndex=0&pageSize=8&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&spanScope=root',
 { signal: expect.any(AbortSignal) }],
+      
['/traces/stats/group-by?serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&spanScope=root&groupBy=resource%3Aservice.version&limit=7&orderBy=latency-p95-desc&minCount=5',
 { signal: expect.any(AbortSignal) }]
     ]);
   }, 15000);
 
diff --git a/web-next/app/trace/manage/route-state.test.ts 
b/web-next/app/trace/manage/route-state.test.ts
index b75a8dbfbe..3773a17c09 100644
--- a/web-next/app/trace/manage/route-state.test.ts
+++ b/web-next/app/trace/manage/route-state.test.ts
@@ -19,6 +19,7 @@ describe('trace manage route state', () => {
         spanId: 'span-456',
         serviceName: 'checkout',
         resourceFilter: 'service.version=1.2.3',
+        attributeFilter: 'http.route CONTAINS checkout',
         operationName: 'GET /checkout',
         minDurationMs: '100',
         maxDurationMs: '500',
@@ -28,7 +29,7 @@ describe('trace manage route state', () => {
     );
 
     expect(route).toBe(
-      
'/trace/manage?traceId=trace-123&spanId=span-456&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&start=1713200000000&end=1713203600000&timeRange=last-1h&refresh=30&live=false&tz=Asia%2FShanghai&entityId=42&entityName=checkout&returnTo=%2Foverview&serviceNamespace=payments&environment=prod&codeRepo=https%3A%2F%2Fexample.test%2Frepo&codeProvider=github&codePath=src%2Ftrace.ts&codeSearch=Trace
 [...]
+      
'/trace/manage?traceId=trace-123&spanId=span-456&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&start=1713200000000&end=1713203600000&timeRange=last-1h&refresh=30&live=false&tz=Asia%2FShanghai&entityId=42&entityName=checkout&returnTo=%2Foverview&serviceNamespace=payments&environment=prod&codeRepo=https%3A%2F%2Fexample.test%2Frepo&codeProvider=gi
 [...]
     );
     expect(route).not.toContain('returnLabel=');
   });
diff --git a/web-next/app/trace/manage/trace-manage-page.tsx 
b/web-next/app/trace/manage/trace-manage-page.tsx
index ad5abb5d30..bc8ace5d71 100644
--- a/web-next/app/trace/manage/trace-manage-page.tsx
+++ b/web-next/app/trace/manage/trace-manage-page.tsx
@@ -148,6 +148,7 @@ const emptyTraceQuery: TraceQueryState = {
   spanId: '',
   serviceName: '',
   resourceFilter: '',
+  attributeFilter: '',
   operationName: '',
   minDurationMs: '',
   maxDurationMs: '',
@@ -389,6 +390,7 @@ function buildTraceSavedViewDescription(query: 
TraceQueryState, routeContext: Si
     query.serviceName.trim() ? `${t('trace.manage.saved-view.field.service')}: 
${compactTraceSavedViewValue(query.serviceName)}` : '',
     query.operationName?.trim() ? 
`${t('trace.manage.saved-view.field.operation')}: 
${compactTraceSavedViewValue(query.operationName)}` : '',
     query.resourceFilter?.trim() ? 
`${t('trace.manage.saved-view.field.resource-filter')}: 
${compactTraceSavedViewValue(query.resourceFilter)}` : '',
+    query.attributeFilter?.trim() ? 
`${t('trace.manage.saved-view.field.attribute-filter')}: 
${compactTraceSavedViewValue(query.attributeFilter)}` : '',
     query.minDurationMs?.trim() ? 
`${t('trace.manage.saved-view.field.min-duration')}: 
${compactTraceSavedViewValue(query.minDurationMs)}ms` : '',
     query.maxDurationMs?.trim() ? 
`${t('trace.manage.saved-view.field.max-duration')}: 
${compactTraceSavedViewValue(query.maxDurationMs)}ms` : '',
     query.errorOnly ? t('trace.manage.saved-view.field.errors-only') : '',
@@ -2258,6 +2260,19 @@ function TraceExplorer({
                 'data-trace-manage-resource-filter-input-owner': 
'hertzbeat-ui-query-token-field'
               }}
             />
+            <HzQueryTokenField
+              width="trace-id"
+              aria-label={t('trace.manage.route.query.attribute-filter')}
+              value={draft.attributeFilter || ''}
+              onChange={event => setDraft(updateDraftField('attributeFilter', 
event.target.value))}
+              placeholder={t('trace.manage.route.query.attribute-filter')}
+              data-trace-manage-attribute-filter-input="true"
+              fieldProps={{
+                'data-trace-manage-query-token-field': 'attribute-filter',
+                'data-trace-manage-query-token-field-owner': 
'hertzbeat-ui-query-token-field',
+                'data-trace-manage-attribute-filter-input-owner': 
'hertzbeat-ui-query-token-field'
+              }}
+            />
             <HzQueryTokenField
               width="root-span"
               aria-label={t('trace.manage.route.query.operation')}
diff --git a/web-next/lib/i18n-runtime-messages.ts 
b/web-next/lib/i18n-runtime-messages.ts
index 8274ab2d1e..d96900c35c 100644
--- a/web-next/lib/i18n-runtime-messages.ts
+++ b/web-next/lib/i18n-runtime-messages.ts
@@ -2577,6 +2577,7 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'trace.manage.route.action.catalog': 'Entity catalog',
     'trace.manage.route.query.service': 'Service name',
     'trace.manage.route.query.resource-filter': 'Resource filter',
+    'trace.manage.route.query.attribute-filter': 'Span attribute filter',
     'trace.manage.route.query.operation': 'Operation',
     'trace.manage.route.query.min-duration': 'Minimum duration',
     'trace.manage.route.query.min-duration.placeholder': 'Min ms',
@@ -2626,6 +2627,7 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'trace.manage.saved-view.field.service': 'service',
     'trace.manage.saved-view.field.operation': 'operation',
     'trace.manage.saved-view.field.resource-filter': 'resource',
+    'trace.manage.saved-view.field.attribute-filter': 'attribute',
     'trace.manage.saved-view.field.min-duration': 'min',
     'trace.manage.saved-view.field.max-duration': 'max',
     'trace.manage.saved-view.field.errors-only': 'errors only',
@@ -7033,6 +7035,7 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'trace.manage.route.action.catalog': '对象目录',
     'trace.manage.route.query.service': '服务名称',
     'trace.manage.route.query.resource-filter': '资源过滤',
+    'trace.manage.route.query.attribute-filter': 'Span 属性过滤',
     'trace.manage.route.query.operation': '操作',
     'trace.manage.route.query.min-duration': '最短耗时',
     'trace.manage.route.query.min-duration.placeholder': '最小 ms',
@@ -7082,6 +7085,7 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'trace.manage.saved-view.field.service': '服务',
     'trace.manage.saved-view.field.operation': '操作',
     'trace.manage.saved-view.field.resource-filter': '资源',
+    'trace.manage.saved-view.field.attribute-filter': '属性',
     'trace.manage.saved-view.field.min-duration': '最小',
     'trace.manage.saved-view.field.max-duration': '最大',
     'trace.manage.saved-view.field.errors-only': '仅错误',
diff --git a/web-next/lib/trace-manage/query-state.test.ts 
b/web-next/lib/trace-manage/query-state.test.ts
index 6baa476399..44781f7de7 100644
--- a/web-next/lib/trace-manage/query-state.test.ts
+++ b/web-next/lib/trace-manage/query-state.test.ts
@@ -3,12 +3,13 @@ import { DEFAULT_TRACE_TABLE_COLUMNS, buildTraceRouteUrl, 
buildTraceUrls, queryS
 
 describe('trace query state codec', () => {
   it('reads query state from search params', () => {
-    const params = new 
URLSearchParams('traceId=abc123&spanId=span456&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=GET%20%2Fcheckout&minDurationMs=100&maxDurationMs=500&groupBy=resource%3Aservice.version&groupLimit=7&groupOrder=latency-p95-desc&groupMinCount=5&errorOnly=true');
+    const params = new 
URLSearchParams('traceId=abc123&spanId=span456&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route%20CONTAINS%20checkout&operationName=GET%20%2Fcheckout&minDurationMs=100&maxDurationMs=500&groupBy=resource%3Aservice.version&groupLimit=7&groupOrder=latency-p95-desc&groupMinCount=5&errorOnly=true');
     expect(queryStateFromParams(params)).toEqual({
       traceId: 'abc123',
       spanId: 'span456',
       serviceName: 'checkout',
       resourceFilter: 'service.version=1.2.3',
+      attributeFilter: 'http.route CONTAINS checkout',
       operationName: 'GET /checkout',
       minDurationMs: '100',
       maxDurationMs: '500',
@@ -30,6 +31,7 @@ describe('trace query state codec', () => {
       spanId: [' span-456 ', 'span-ignored'],
       serviceName: [' checkout ', 'payments'],
       resourceFilter: [' service.version=1.2.3 ', 'service.version=ignored'],
+      attributeFilter: [' http.route CONTAINS checkout ', 'http.route CONTAINS 
ignored'],
       groupBy: [' resource:service.version ', 'resource:host.name'],
       groupLimit: [' 7 ', '12'],
       groupOrder: [' error-count-desc ', 'latency-p95-desc'],
@@ -51,6 +53,7 @@ describe('trace query state codec', () => {
       spanId: 'span-456',
       serviceName: 'checkout',
       resourceFilter: 'service.version=1.2.3',
+      attributeFilter: 'http.route CONTAINS checkout',
       operationName: '',
       minDurationMs: '',
       maxDurationMs: '',
@@ -143,6 +146,7 @@ describe('trace query state codec', () => {
         spanId: 'span456',
         serviceName: 'checkout',
         resourceFilter: 'service.version=1.2.3',
+        attributeFilter: 'http.route CONTAINS checkout',
         operationName: 'GET /checkout',
         minDurationMs: '100',
         maxDurationMs: '500',
@@ -156,9 +160,9 @@ describe('trace query state codec', () => {
         listPageIndex: '2'
       })
     ).toEqual({
-      listUrl: 
'/traces/list?pageIndex=2&pageSize=50&traceId=abc123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
-      overviewUrl: 
'/traces/stats/overview?traceId=abc123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
-      groupByUrl: 
'/traces/stats/group-by?traceId=abc123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root&groupBy=resource%3Aservice.version&limit=7&orderBy=latency-p95-desc&minCount=5'
+      listUrl: 
'/traces/list?pageIndex=2&pageSize=50&traceId=abc123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
+      overviewUrl: 
'/traces/stats/overview?traceId=abc123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root',
+      groupByUrl: 
'/traces/stats/group-by?traceId=abc123&serviceName=checkout&resourceFilter=service.version%3D1.2.3&attributeFilter=http.route+CONTAINS+checkout&operationName=GET+%2Fcheckout&minDurationMs=100&maxDurationMs=500&errorOnly=true&spanScope=root&groupBy=resource%3Aservice.version&limit=7&orderBy=latency-p95-desc&minCount=5'
     });
     expect(buildTraceRouteUrl({ traceId: 'abc123', spanId: 'span456', 
serviceName: 'checkout', errorOnly: false, spanScope: 'root', listPageSize: 
'50', listPageIndex: '2' })).toBe(
       
'/trace/manage?traceId=abc123&spanId=span456&serviceName=checkout&listPageSize=50&listPageIndex=2'
diff --git a/web-next/lib/trace-manage/query-state.ts 
b/web-next/lib/trace-manage/query-state.ts
index 42f693317c..4d3ee17e09 100644
--- a/web-next/lib/trace-manage/query-state.ts
+++ b/web-next/lib/trace-manage/query-state.ts
@@ -13,6 +13,7 @@ export type TraceQueryState = {
   spanId: string;
   serviceName: string;
   resourceFilter?: string;
+  attributeFilter?: string;
   operationName?: string;
   minDurationMs?: string;
   maxDurationMs?: string;
@@ -97,6 +98,7 @@ export function queryStateFromParams(searchParams: 
SearchParamReader): TraceQuer
     spanId: readSearchParam(searchParams, 'spanId'),
     serviceName: readSearchParam(searchParams, 'serviceName'),
     resourceFilter: readSearchParam(searchParams, 'resourceFilter'),
+    attributeFilter: readSearchParam(searchParams, 'attributeFilter'),
     operationName: readSearchParam(searchParams, 'operationName'),
     minDurationMs: readNonNegativeIntegerSearchParam(searchParams, 
'minDurationMs'),
     maxDurationMs: readNonNegativeIntegerSearchParam(searchParams, 
'maxDurationMs'),
@@ -224,6 +226,7 @@ export function buildTraceRouteUrl(query: TraceQueryState, 
options?: { view?: Tr
   if (query.spanId.trim()) params.set('spanId', query.spanId.trim());
   if (query.serviceName.trim()) params.set('serviceName', 
query.serviceName.trim());
   if (query.resourceFilter?.trim()) params.set('resourceFilter', 
query.resourceFilter.trim());
+  if (query.attributeFilter?.trim()) params.set('attributeFilter', 
query.attributeFilter.trim());
   if (query.operationName?.trim()) params.set('operationName', 
query.operationName.trim());
   const minDurationMs = readNonNegativeDurationValue(query.minDurationMs);
   const maxDurationMs = readNonNegativeDurationValue(query.maxDurationMs);
@@ -278,7 +281,7 @@ export function buildTraceUrls(
   const listParams = new URLSearchParams({ pageIndex: listPageIndex, pageSize: 
listPageSize });
   if (query.traceId.trim()) listParams.set('traceId', query.traceId.trim());
   if (query.serviceName.trim()) listParams.set('serviceName', 
query.serviceName.trim());
-  appendTraceResourceFilterParam(listParams, query);
+  appendTraceFilterParams(listParams, query);
   if (query.operationName?.trim()) listParams.set('operationName', 
query.operationName.trim());
   appendTraceDurationParams(listParams, query);
   if (query.errorOnly) listParams.set('errorOnly', 'true');
@@ -288,7 +291,7 @@ export function buildTraceUrls(
   const overviewParams = new URLSearchParams();
   if (query.traceId.trim()) overviewParams.set('traceId', 
query.traceId.trim());
   if (query.serviceName.trim()) overviewParams.set('serviceName', 
query.serviceName.trim());
-  appendTraceResourceFilterParam(overviewParams, query);
+  appendTraceFilterParams(overviewParams, query);
   if (query.operationName?.trim()) overviewParams.set('operationName', 
query.operationName.trim());
   appendTraceDurationParams(overviewParams, query);
   if (query.errorOnly) overviewParams.set('errorOnly', 'true');
@@ -327,9 +330,13 @@ function appendTraceDurationParams(params: 
URLSearchParams, query: TraceQuerySta
   if (maxDurationMs) params.set('maxDurationMs', maxDurationMs);
 }
 
-function appendTraceResourceFilterParam(params: URLSearchParams, query: 
TraceQueryState) {
+function appendTraceFilterParams(params: URLSearchParams, query: 
TraceQueryState) {
   const resourceFilter = query.resourceFilter?.trim() || '';
   if (resourceFilter) {
     params.set('resourceFilter', resourceFilter);
   }
+  const attributeFilter = query.attributeFilter?.trim() || '';
+  if (attributeFilter) {
+    params.set('attributeFilter', attributeFilter);
+  }
 }
diff --git a/web-next/lib/trace-manage/view-model.ts 
b/web-next/lib/trace-manage/view-model.ts
index d78980e959..67e7988944 100644
--- a/web-next/lib/trace-manage/view-model.ts
+++ b/web-next/lib/trace-manage/view-model.ts
@@ -772,6 +772,7 @@ export function buildTraceAlertRuleDraft(query: 
TraceQueryState, routeContext: S
     ['spanId', query.spanId || routeContext.spanId],
     ['serviceName', query.serviceName || routeContext.serviceName],
     ['resourceFilter', query.resourceFilter],
+    ['attributeFilter', query.attributeFilter],
     ['operationName', query.operationName],
     ['minDurationMs', query.minDurationMs],
     ['maxDurationMs', query.maxDurationMs],


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

Reply via email to