From ef4e6e150fae762fc1fcc4363c4a55d6eccaeb86 Mon Sep 17 00:00:00 2001 From: Amy Hu Date: Tue, 25 Jun 2024 23:48:35 +0000 Subject: [PATCH] pw_web: Add optional parameters to createLogViewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix: 333537914 Change-Id: I8954e48ea0f0f90f94b055e85f83d2e0b5f95827 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/217052 Reviewed-by: Asad Memon Commit-Queue: Amy Hu Lint: Lint 🤖 Presubmit-Verified: CQ Bot Account --- pw_web/docs.rst | 9 ++- .../log-viewer/src/components/log-viewer.ts | 71 +++++++++++++++++-- pw_web/log-viewer/src/createLogViewer.ts | 55 ++++---------- pw_web/log-viewer/src/index.ts | 6 +- pw_web/log-viewer/test/log-store.test.js | 9 +-- pw_web/log-viewer/test/log-viewer.test.js | 8 +-- 6 files changed, 94 insertions(+), 64 deletions(-) diff --git a/pw_web/docs.rst b/pw_web/docs.rst index 53a02dca98..af4e378467 100644 --- a/pw_web/docs.rst +++ b/pw_web/docs.rst @@ -298,13 +298,18 @@ Only fields that exist in the Log Source will render as columns in the Log Viewe .. code-block:: typescript - createLogViewer(logSource, root, state, logStore, columnOrder) + createLogViewer(logSource, root, { columnOrder }) ``columnOrder`` accepts an ``string[]`` and defaults to ``[log_source, time, timestamp]`` .. code-block:: typescript - createLogViewer(logSource, root, state, logStore, ['log_source', 'time', 'timestamp']) + createLogViewer( + logSource, + root, + { columnOrder: ['log_source', 'time', 'timestamp'] } + + ) Note, columns will always start with ``severity`` and end with ``message``, these fields do not need to be defined. Columns are ordered in the following format: diff --git a/pw_web/log-viewer/src/components/log-viewer.ts b/pw_web/log-viewer/src/components/log-viewer.ts index e63f23c679..455b0ceab8 100644 --- a/pw_web/log-viewer/src/components/log-viewer.ts +++ b/pw_web/log-viewer/src/components/log-viewer.ts @@ -14,7 +14,7 @@ import { LitElement, PropertyValues, TemplateResult, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { LogEntry, SourceData } from '../shared/interfaces'; +import { LogEntry, LogSourceEvent, SourceData } from '../shared/interfaces'; import { LocalStateStorage, LogViewerState, @@ -25,6 +25,8 @@ import { styles } from './log-viewer.styles'; import { themeDark } from '../themes/dark'; import { themeLight } from '../themes/light'; import { LogView } from './log-view/log-view'; +import { LogSource } from '../log-source'; +import { LogStore } from '../log-store'; import CloseViewEvent from '../events/close-view'; import SplitViewEvent from '../events/split-view'; import InputChangeEvent from '../events/input-change'; @@ -43,10 +45,15 @@ type ColorScheme = 'dark' | 'light'; export class LogViewer extends LitElement { static styles = [styles, themeDark, themeLight]; + logStore: LogStore; + /** An array of log entries to be displayed. */ @property({ type: Array }) logs: LogEntry[] = []; + @property({ type: Array }) + logSources: LogSource[] | LogSource = []; + @property({ type: String, reflect: true }) colorScheme?: ColorScheme; @@ -62,28 +69,78 @@ export class LogViewer extends LitElement { /** An array that stores the preferred column order of columns */ @state() - _columnOrder: string[]; + private _columnOrder: string[] = ['log_source', 'time', 'timestamp']; /** A map containing data from present log sources */ private _sources: Map = new Map(); + private _sourcesArray: LogSource[] = []; + + private _lastUpdateTimeoutId: NodeJS.Timeout | undefined; + private _stateService: StateService = new StateService( new LocalStateStorage(), ); - constructor(state: LogViewerState | undefined, columnOrder: string[]) { + /** + * Create a log-viewer + * @param logSources - Collection of sources from where logs originate + * @param options - Optional parameters to change default settings + * @param options.columnOrder - defines column order between severity and + * message undefined fields are added between defined order and message. + * @param options.state - handles state between sessions, defaults to localStorage + */ + constructor( + logSources: LogSource[] | LogSource, + options?: { + columnOrder?: string[] | undefined; + logStore?: LogStore | undefined; + state?: LogViewerState | undefined; + }, + ) { super(); - this._columnOrder = columnOrder; - const savedState = state ?? this._stateService.loadState(); + + this.logSources = logSources; + this.logStore = options?.logStore ?? new LogStore(); + this.logStore.setColumnOrder(this._columnOrder); + + const savedState = options?.state ?? this._stateService.loadState(); this._rootNode = savedState?.rootNode || new ViewNode({ type: NodeType.View }); + if (options?.columnOrder) { + this._columnOrder = [...new Set(options?.columnOrder)]; + } this.loadShoelaceComponents(); } + logEntryListener = (event: LogSourceEvent) => { + if (event.type === 'log-entry') { + const logEntry = event.data; + this.logStore.addLogEntry(logEntry); + this.logs = this.logStore.getLogs(); + + if (this._lastUpdateTimeoutId) { + clearTimeout(this._lastUpdateTimeoutId); + } + + // Call requestUpdate at most once every 100 milliseconds. + this._lastUpdateTimeoutId = setTimeout(() => { + this.logs = [...this.logStore.getLogs()]; + }, 100); + } + }; + connectedCallback() { super.connectedCallback(); this.addEventListener('close-view', this.handleCloseView); + this._sourcesArray = Array.isArray(this.logSources) + ? this.logSources + : [this.logSources]; + this._sourcesArray.forEach((logSource: LogSource) => { + logSource.addEventListener('log-entry', this.logEntryListener); + }); + // If color scheme isn't set manually, retrieve it from localStorage if (!this.colorScheme) { const storedScheme = localStorage.getItem( @@ -120,6 +177,10 @@ export class LogViewer extends LitElement { super.disconnectedCallback(); this.removeEventListener('close-view', this.handleCloseView); + this._sourcesArray.forEach((logSource: LogSource) => { + logSource.removeEventListener('log-entry', this.logEntryListener); + }); + // Save state before disconnecting this._stateService.saveState({ rootNode: this._rootNode }); } diff --git a/pw_web/log-viewer/src/createLogViewer.ts b/pw_web/log-viewer/src/createLogViewer.ts index 39ba1f662a..72357a5fae 100644 --- a/pw_web/log-viewer/src/createLogViewer.ts +++ b/pw_web/log-viewer/src/createLogViewer.ts @@ -14,7 +14,6 @@ import { LogViewer as RootComponent } from './components/log-viewer'; import { LogViewerState } from './shared/state'; -import { LogSourceEvent } from '../src/shared/interfaces'; import { LogSource } from '../src/log-source'; import { LogStore } from './log-store'; @@ -31,57 +30,31 @@ import '@material/web/menu/menu-item.js'; /** * Create an instance of log-viewer - * @param logSources - collection of sources from where logs originate + * @param logSources - Collection of sources from where logs originate * @param root - HTML component to append log-viewer to - * @param state - handles state between sessions, defaults to localStorage - * @param logStore - stores and handles management of all logs - * @param columnOrder - defines column order between severity and message - * undefined fields are appended between defined order and message. + * @param options - Optional parameters to change default settings + * @param options.columnOrder - Defines column order between severity and + * message. Undefined fields are added between defined order and message. + * @param options.logStore - Stores and handles management of all logs + * @param options.state - Handles state between sessions, defaults to localStorage */ export function createLogViewer( - logSources: LogSource | LogSource[], + logSources: LogSource[] | LogSource, root: HTMLElement, - state?: LogViewerState, - logStore: LogStore = new LogStore(), - columnOrder: string[] = ['log_source', 'time', 'timestamp'], + options?: { + columnOrder?: string[] | undefined; + logSources?: LogSource | LogSource[] | undefined; + logStore?: LogStore | undefined; + state?: LogViewerState | undefined; + }, ) { - const logViewer = new RootComponent(state, columnOrder); + const logViewer = new RootComponent(logSources, options); root.appendChild(logViewer); - let lastUpdateTimeoutId: NodeJS.Timeout; - logStore.setColumnOrder(columnOrder); - - const logEntryListener = (event: LogSourceEvent) => { - if (event.type === 'log-entry') { - const logEntry = event.data; - logStore.addLogEntry(logEntry); - logViewer.logs = logStore.getLogs(); - if (lastUpdateTimeoutId) { - clearTimeout(lastUpdateTimeoutId); - } - - // Call requestUpdate at most once every 100 milliseconds. - lastUpdateTimeoutId = setTimeout(() => { - const updatedLogs = [...logStore.getLogs()]; - logViewer.logs = updatedLogs; - }, 100); - } - }; - - const sourcesArray = Array.isArray(logSources) ? logSources : [logSources]; - - sourcesArray.forEach((logSource: LogSource) => { - // Add the event listener to the LogSource instance - logSource.addEventListener('log-entry', logEntryListener); - }); // Method to destroy and unsubscribe return () => { if (logViewer.parentNode) { logViewer.parentNode.removeChild(logViewer); } - - sourcesArray.forEach((logSource: LogSource) => { - logSource.removeEventListener('log-entry', logEntryListener); - }); }; } diff --git a/pw_web/log-viewer/src/index.ts b/pw_web/log-viewer/src/index.ts index 3daaa45309..d2d35758b4 100644 --- a/pw_web/log-viewer/src/index.ts +++ b/pw_web/log-viewer/src/index.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Pigweed Authors +// Copyright 2024 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of @@ -16,9 +16,7 @@ import { JsonLogSource } from './custom/json-log-source'; import { BrowserLogSource } from './custom/browser-log-source'; import { createLogViewer } from './createLogViewer'; import { LogSource } from './log-source'; -import { LogStore } from './log-store'; -const logStore = new LogStore(); const logSources = [new JsonLogSource(), new BrowserLogSource()] as LogSource[]; const containerEl = document.querySelector( @@ -26,7 +24,7 @@ const containerEl = document.querySelector( ) as HTMLElement; if (containerEl) { - createLogViewer(logSources, containerEl, undefined, logStore); + createLogViewer(logSources, containerEl); } // Start reading log data diff --git a/pw_web/log-viewer/test/log-store.test.js b/pw_web/log-viewer/test/log-store.test.js index 2ef07888a3..54ae3a4af8 100644 --- a/pw_web/log-viewer/test/log-store.test.js +++ b/pw_web/log-viewer/test/log-store.test.js @@ -1,4 +1,4 @@ -// Copyright 2023 The Pigweed Authors +// Copyright 2024 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of @@ -21,12 +21,9 @@ import { LogStore } from '../src/log-store'; function setUpLogViewer() { const mockLogSource = new MockLogSource(); const logStore = new LogStore(); - const destroyLogViewer = createLogViewer( - mockLogSource, - document.body, - undefined, + const destroyLogViewer = createLogViewer(mockLogSource, document.body, { logStore, - ); + }); const logViewer = document.querySelector('log-viewer'); return { mockLogSource, destroyLogViewer, logViewer, logStore }; } diff --git a/pw_web/log-viewer/test/log-viewer.test.js b/pw_web/log-viewer/test/log-viewer.test.js index 1ad69da921..ae07d7cebd 100644 --- a/pw_web/log-viewer/test/log-viewer.test.js +++ b/pw_web/log-viewer/test/log-viewer.test.js @@ -20,13 +20,9 @@ import { createLogViewer } from '../src/createLogViewer'; // Initialize the log viewer component with a mock log source function setUpLogViewer(columnOrder) { const mockLogSource = new MockLogSource(); - const destroyLogViewer = createLogViewer( - mockLogSource, - document.body, - undefined, - undefined, + const destroyLogViewer = createLogViewer(mockLogSource, document.body, { columnOrder, - ); + }); const logViewer = document.querySelector('log-viewer'); return { mockLogSource, destroyLogViewer, logViewer }; }