Skip to content

Commit 04f98a7

Browse files
iOvergaardclaudeCopilot
authored
Log viewer: Improves search functionality and code quality (#20913)
* fix: adds correct fallback for dates to avoid console error * fix: resolves a TODO by using UmbStringState over rxjs Subject * Refactor log viewer search to use UmbStringState and improve architecture - Replace RxJS Subject with UmbStringState to follow Umbraco patterns - Move debounced search observation to messages list component - Only triggers when component is mounted (logs are visible) - Prevents unnecessary API calls on other views - Simplify search input to just update context state - Add semantic form structure with role="search" for accessibility - Add visually-hidden submit button for keyboard navigation - Allow re-running same query via form submission (bypasses debounce) - Follow same architecture pattern as date range selector This resolves the TODO to not use RxJS directly and significantly improves separation of concerns where the data consumer (messages list) owns the fetching logic. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add visible refresh button to log viewer search input - Add refresh button with icon-refresh next to save and clear buttons - Allows users to re-run search with same query (bypasses debounce) - Remove form structure that couldn't work due to Shadow DOM boundaries - Simplify parent component by removing form submission logic - Keep role="search" for accessibility The refresh button provides a more discoverable UI than the hidden submit button approach and avoids Shadow DOM event bubbling issues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix debouncing by adding local state in search input - Add local UmbStringState to debounce user input (250ms) - Only update context filterExpression after debounce - Remove debouncing from messages list (now handled at input level) - Saved searches and refresh button still bypass debounce for immediate feedback This restores the expected debouncing behavior while maintaining the clean architecture where the messages list triggers searches based on context changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: cleans up in docs * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent bb88be9 commit 04f98a7

File tree

4 files changed

+44
-36
lines changed

4 files changed

+44
-36
lines changed

src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/log-viewer-date-range-selector.element.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { UmbLogViewerDateRange } from '../workspace/logviewer-workspace.context.js';
21
import { UMB_APP_LOG_VIEWER_CONTEXT } from '../workspace/logviewer-workspace.context-token.js';
32
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
43
import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
@@ -32,9 +31,9 @@ export class UmbLogViewerDateRangeSelectorElement extends UmbLitElement {
3231
#observeStuff() {
3332
this.observe(
3433
this._logViewerContext?.dateRange,
35-
(dateRange: UmbLogViewerDateRange) => {
36-
this._startDate = dateRange.startDate;
37-
this._endDate = dateRange.endDate;
34+
(dateRange) => {
35+
this._startDate = dateRange?.startDate ?? '';
36+
this._endDate = dateRange?.endDate ?? '';
3837
},
3938
'_observeDateRange',
4039
);

src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-messages-list.element.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
55
import type { LogMessageResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
66
import { DirectionModel } from '@umbraco-cms/backoffice/external/backend-api';
77
import { consumeContext } from '@umbraco-cms/backoffice/context-api';
8+
import { skip } from '@umbraco-cms/backoffice/external/rxjs';
89

910
@customElement('umb-log-viewer-messages-list')
1011
export class UmbLogViewerMessagesListElement extends UmbLitElement {
@@ -50,6 +51,17 @@ export class UmbLogViewerMessagesListElement extends UmbLitElement {
5051
this.observe(this._logViewerContext?.sortingDirection, (direction) => {
5152
this._sortingDirection = direction ?? this._sortingDirection;
5253
});
54+
55+
// Observe filter expression changes to trigger search
56+
// Only observes when this component is mounted (when logs are visible)
57+
this.observe(
58+
this._logViewerContext?.filterExpression.pipe(
59+
skip(1), // Skip initial value to avoid duplicate search on page load
60+
),
61+
() => {
62+
this._logViewerContext?.getLogs();
63+
},
64+
);
5365
}
5466

5567
#sortLogs() {

src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/components/log-viewer-search-input.element.ts

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ import { UMB_LOG_VIEWER_SAVE_SEARCH_MODAL } from './log-viewer-search-input-moda
33
import { css, html, customElement, query, state } from '@umbraco-cms/backoffice/external/lit';
44
import { escapeHTML } from '@umbraco-cms/backoffice/utils';
55
import { query as getQuery, path, toQueryString } from '@umbraco-cms/backoffice/router';
6-
import { Subject, debounceTime, tap } from '@umbraco-cms/backoffice/external/rxjs';
76
import { umbConfirmModal, umbOpenModal } from '@umbraco-cms/backoffice/modal';
87
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
98
import type { SavedLogSearchResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
109
import type { UmbDropdownElement } from '@umbraco-cms/backoffice/components';
1110
import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
11+
import { consumeContext } from '@umbraco-cms/backoffice/context-api';
12+
import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
13+
import { debounceTime, skip } from '@umbraco-cms/backoffice/external/rxjs';
1214

1315
import './log-viewer-search-input-modal.element.js';
14-
import { consumeContext } from '@umbraco-cms/backoffice/context-api';
1516

1617
@customElement('umb-log-viewer-search-input')
1718
export class UmbLogViewerSearchInputElement extends UmbLitElement {
@@ -24,14 +25,11 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement {
2425
@state()
2526
private _inputQuery = '';
2627

27-
@state()
28-
private _showLoader = false;
29-
3028
@state()
3129
private _isQuerySaved = false;
3230

33-
// TODO: Revisit this code, to not use RxJS directly:
34-
#inputQuery$ = new Subject<string>();
31+
// Local state for debouncing user input before updating context
32+
#localQueryState = new UmbStringState('');
3533

3634
#logViewerContext?: typeof UMB_APP_LOG_VIEWER_CONTEXT.TYPE;
3735

@@ -48,17 +46,17 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement {
4846
constructor() {
4947
super();
5048

51-
this.#inputQuery$
52-
.pipe(
53-
tap(() => (this._showLoader = true)),
49+
// Debounce local input and update context
50+
this.observe(
51+
this.#localQueryState.asObservable().pipe(
52+
skip(1), // Skip initial value
5453
debounceTime(250),
55-
)
56-
.subscribe((query) => {
54+
),
55+
(query) => {
5756
this._logViewerContext?.setFilterExpression(query);
5857
this.#persist(query);
59-
this._isQuerySaved = this._savedSearches.some((search) => search.query === query);
60-
this._showLoader = false;
61-
});
58+
},
59+
);
6260
}
6361

6462
#observeStuff() {
@@ -75,11 +73,14 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement {
7573

7674
#setQuery(event: Event) {
7775
const target = event.target as UUIInputElement;
78-
this.#inputQuery$.next(target.value as string);
76+
const query = target.value as string;
77+
// Update local state which will debounce before updating context
78+
this.#localQueryState.setValue(query);
7979
}
8080

8181
#setQueryFromSavedSearch(query: string) {
82-
this.#inputQuery$.next(query);
82+
this._logViewerContext?.setFilterExpression(query);
83+
this.#persist(query);
8384
this._searchDropdownElement.open = false;
8485
}
8586

@@ -95,8 +96,14 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement {
9596
}
9697

9798
#clearQuery() {
98-
this.#inputQuery$.next('');
9999
this._logViewerContext?.setFilterExpression('');
100+
this.#persist('');
101+
this.#localQueryState.setValue('');
102+
}
103+
104+
#refreshSearch() {
105+
// Force immediate search, bypassing debounce
106+
this._logViewerContext?.getLogs();
100107
}
101108

102109
#saveSearch(savedSearch: SavedLogSearchResponseModel) {
@@ -137,17 +144,14 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement {
137144
slot="trigger"
138145
@input=${this.#setQuery}
139146
.value=${this._inputQuery}>
140-
${this._showLoader
141-
? html`<div id="loader-container" slot="append">
142-
<uui-loader-circle></uui-loader-circle>
143-
</div>`
144-
: ''}
145147
${this._inputQuery
146148
? html`${!this._isQuerySaved
147149
? html`<uui-button compact slot="append" label="Save search" @click=${this.#openSaveSearchDialog}
148150
><uui-icon name="icon-favorite"></uui-icon
149151
></uui-button>`
150-
: ''}<uui-button compact slot="append" label="Clear" @click=${this.#clearQuery}
152+
: ''}<uui-button compact slot="append" label="Refresh search" @click=${this.#refreshSearch}
153+
><uui-icon name="icon-refresh"></uui-icon></uui-button
154+
><uui-button compact slot="append" label="Clear" @click=${this.#clearQuery}
151155
><uui-icon name="icon-delete"></uui-icon
152156
></uui-button>`
153157
: html``}
@@ -198,13 +202,6 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement {
198202
flex: 1;
199203
}
200204
201-
#loader-container {
202-
display: flex;
203-
justify-content: center;
204-
align-items: center;
205-
margin: 0 var(--uui-size-space-4);
206-
}
207-
208205
.saved-search-item {
209206
display: flex;
210207
justify-content: space-between;

src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/search/log-search-view.element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class UmbLogViewerSearchViewElement extends UmbLitElement {
3434
override render() {
3535
return html`
3636
<umb-body-layout header-transparent header-fit-height>
37-
<div id="header" slot="header">
37+
<div id="header" slot="header" role="search" aria-label="Filter logs">
3838
<div id="levels-container">
3939
<umb-log-viewer-log-level-filter-menu></umb-log-viewer-log-level-filter-menu>
4040
<div id="dates-polling-container">

0 commit comments

Comments
 (0)