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, '"');
+}
+
+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 =
"checkout""');
+
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]