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 45866c886f Expose log resource attribute filters
45866c886f is described below

commit 45866c886f501098ba6149bf01a21bbbab725578
Author: Logic <[email protected]>
AuthorDate: Tue Jun 9 22:47:23 2026 +0800

    Expose log resource attribute filters
---
 web-next/app/log/manage/log-manage-page.tsx | 20 ++++++++++
 web-next/app/log/manage/page.test.tsx       | 59 +++++++++++++++++++++++++++++
 web-next/lib/i18n-runtime-messages.ts       |  8 ++++
 3 files changed, 87 insertions(+)

diff --git a/web-next/app/log/manage/log-manage-page.tsx 
b/web-next/app/log/manage/log-manage-page.tsx
index 80af690b2f..3f35418a19 100644
--- a/web-next/app/log/manage/log-manage-page.tsx
+++ b/web-next/app/log/manage/log-manage-page.tsx
@@ -214,6 +214,8 @@ const EMPTY_QUERY: LogQueryState = {
   logContent: '',
   traceId: '',
   spanId: '',
+  resourceFilter: '',
+  attributeFilter: '',
   severityNumber: '',
   severityText: ''
 };
@@ -3639,6 +3641,24 @@ function LogManageExplorer({
               data-log-manage-query-body-input="shared-log-body-input"
               data-log-manage-query-body-input-owner="hertzbeat-ui-input"
             />
+            <HzInput
+              aria-label={t('log.manage.query.resource-filter.aria')}
+              value={draft.resourceFilter || ''}
+              onChange={event => setDraft(updateDraftField('resourceFilter', 
event.target.value))}
+              placeholder={t('log.manage.query.resource-filter.placeholder')}
+              width="log-query-filter"
+              data-log-manage-query-resource-filter-input="true"
+              
data-log-manage-query-resource-filter-input-owner="hertzbeat-ui-input"
+            />
+            <HzInput
+              aria-label={t('log.manage.query.attribute-filter.aria')}
+              value={draft.attributeFilter || ''}
+              onChange={event => setDraft(updateDraftField('attributeFilter', 
event.target.value))}
+              placeholder={t('log.manage.query.attribute-filter.placeholder')}
+              width="log-query-filter"
+              data-log-manage-query-attribute-filter-input="true"
+              
data-log-manage-query-attribute-filter-input-owner="hertzbeat-ui-input"
+            />
           </div>
           <HzControlStack
             layout="inline-wrap"
diff --git a/web-next/app/log/manage/page.test.tsx 
b/web-next/app/log/manage/page.test.tsx
index 4d123795c5..e188b7ace4 100644
--- a/web-next/app/log/manage/page.test.tsx
+++ b/web-next/app/log/manage/page.test.tsx
@@ -493,6 +493,16 @@ function tZh(key: string, params?: TranslationParams) {
   return zhT(key, params);
 }
 
+function htmlAttributeValue(value: string) {
+  return value.replace(/"/g, '&quot;');
+}
+
+function typeInputValue(input: HTMLInputElement, value: string) {
+  const valueSetter = 
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
+  valueSetter?.call(input, value);
+  input.dispatchEvent(new Event('input', { bubbles: true }));
+}
+
 function buildLogManageRouteState(): LogManageRouteState {
   const query: LogQueryState = {
     search: mockState.searchParams.get('search') || 
mockState.searchParams.get('content') || '',
@@ -1104,7 +1114,14 @@ describe('log manage page', () => {
     
expect(html).toContain('data-log-manage-query-body-input="shared-log-body-input"');
     
expect(html).toContain('data-log-manage-query-body-input-owner="hertzbeat-ui-input"');
     expect(html).toContain('data-hz-input-width="log-query-body"');
+    
expect(html).toContain('data-log-manage-query-resource-filter-input="true"');
+    
expect(html).toContain('data-log-manage-query-resource-filter-input-owner="hertzbeat-ui-input"');
+    
expect(html).toContain('data-log-manage-query-attribute-filter-input="true"');
+    
expect(html).toContain('data-log-manage-query-attribute-filter-input-owner="hertzbeat-ui-input"');
+    expect(html).toContain('data-hz-input-width="log-query-filter"');
     expect(html).toContain('placeholder="service.name = 
&quot;checkout&quot;"');
+    
expect(html).toContain(`placeholder="${htmlAttributeValue(tZh('log.manage.query.resource-filter.placeholder'))}"`);
+    
expect(html).toContain(`placeholder="${htmlAttributeValue(tZh('log.manage.query.attribute-filter.placeholder'))}"`);
     
expect(html).toContain('data-log-manage-quick-filter-controls="logs-quick-filters"');
     
expect(html).toContain('data-log-manage-quick-filter-controls-owner="hertzbeat-ui-control-stack"');
     expect(html).toContain('data-log-manage-quick-filter="severity"');
@@ -1257,6 +1274,48 @@ describe('log manage page', () => {
     expect(html).toContain('span-456');
   });
 
+  it('applies typed log resource and attribute filters from the shared query 
row', async () => {
+    mockState.searchParams = new URLSearchParams('view=list');
+    interactionContainer = document.createElement('div');
+    document.body.appendChild(interactionContainer);
+    interactionRoot = createRoot(interactionContainer);
+
+    await act(async () => {
+      renderInteractiveLogManagePage();
+      await Promise.resolve();
+    });
+
+    const resourceInput = 
interactionContainer.querySelector('[data-log-manage-query-resource-filter-input="true"]')
 as HTMLInputElement | null;
+    const attributeInput = 
interactionContainer.querySelector('[data-log-manage-query-attribute-filter-input="true"]')
 as HTMLInputElement | null;
+    expect(resourceInput).toBeTruthy();
+    expect(attributeInput).toBeTruthy();
+    
expect(resourceInput?.getAttribute('data-log-manage-query-resource-filter-input-owner')).toBe('hertzbeat-ui-input');
+    
expect(attributeInput?.getAttribute('data-log-manage-query-attribute-filter-input-owner')).toBe('hertzbeat-ui-input');
+
+    await act(async () => {
+      typeInputValue(resourceInput!, 'service.version=1.2.3');
+      typeInputValue(attributeInput!, 'http.route CONTAINS checkout');
+      await Promise.resolve();
+    });
+
+    const runAction = 
interactionContainer.querySelector('[data-log-manage-run-query-action="true"]') 
as HTMLButtonElement | null;
+    expect(runAction).toBeTruthy();
+    mockState.replace.mockClear();
+
+    await act(async () => {
+      runAction?.click();
+      await Promise.resolve();
+    });
+
+    const route = String(mockState.replace.mock.calls[0]?.[0]);
+    const params = new URL(route, 'http://localhost').searchParams;
+    expect(mockState.replace).toHaveBeenCalledTimes(1);
+    expect(params.get('resourceFilter')).toBe('service.version=1.2.3');
+    expect(params.get('attributeFilter')).toBe('http.route CONTAINS checkout');
+    expect(params.get('view')).toBe('list');
+    expect(params.get('source')).toBe('otlp');
+  });
+
   it('renders route-backed log resource and attribute field columns', async () 
=> {
     mockState.searchParams = new 
URLSearchParams('view=table&columns=service,body&fieldColumns=resource:hertzbeat.entity_id,attribute:region');
     const html = renderLogManagePage();
diff --git a/web-next/lib/i18n-runtime-messages.ts 
b/web-next/lib/i18n-runtime-messages.ts
index 2731eee298..91bdac484b 100644
--- a/web-next/lib/i18n-runtime-messages.ts
+++ b/web-next/lib/i18n-runtime-messages.ts
@@ -3945,6 +3945,10 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'log.manage.query.span-id': 'Span ID',
     'log.manage.query.body.aria': 'Log body',
     'log.manage.query.body.placeholder': 'Search log body',
+    'log.manage.query.resource-filter.aria': 'Log resource filter',
+    'log.manage.query.resource-filter.placeholder': 'service.version=1.2.3, 
host.name CONTAINS checkout, k8s.pod.name EXISTS',
+    'log.manage.query.attribute-filter.aria': 'Log attribute filter',
+    'log.manage.query.attribute-filter.placeholder': 'http.route CONTAINS 
checkout, error.message EXISTS, status_code IN ("500", "503")',
     'log.manage.summary.total': 'Total logs',
     'log.manage.summary.errors': 'Error logs',
     'log.manage.summary.trace-coverage': 'Trace coverage',
@@ -8417,6 +8421,10 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'log.manage.query.span-id': '跨度 ID',
     'log.manage.query.body.aria': '日志正文',
     'log.manage.query.body.placeholder': '搜索日志正文',
+    'log.manage.query.resource-filter.aria': '日志资源过滤',
+    'log.manage.query.resource-filter.placeholder': 
'service.version=1.2.3、host.name CONTAINS checkout、k8s.pod.name EXISTS',
+    'log.manage.query.attribute-filter.aria': '日志属性过滤',
+    'log.manage.query.attribute-filter.placeholder': 'http.route CONTAINS 
checkout、error.message EXISTS、status_code IN ("500", "503")',
     'log.manage.summary.total': '日志总数',
     'log.manage.summary.errors': '错误日志',
     'log.manage.summary.trace-coverage': '链路关联',


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

Reply via email to