Skip to content

Commit

Permalink
pw_web: Add optional parameters to createLogViewer
Browse files Browse the repository at this point in the history
Fix: 333537914
Change-Id: I8954e48ea0f0f90f94b055e85f83d2e0b5f95827
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/217052
Reviewed-by: Asad Memon <[email protected]>
Commit-Queue: Amy Hu <[email protected]>
Lint: Lint 🤖 <[email protected]>
Presubmit-Verified: CQ Bot Account <[email protected]>
  • Loading branch information
Amy Hu authored and CQ Bot Account committed Jun 25, 2024
1 parent e3e1a1f commit ef4e6e1
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 64 deletions.
9 changes: 7 additions & 2 deletions pw_web/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
71 changes: 66 additions & 5 deletions pw_web/log-viewer/src/components/log-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -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;

Expand All @@ -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<string, SourceData> = 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(
Expand Down Expand Up @@ -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 });
}
Expand Down
55 changes: 14 additions & 41 deletions pw_web/log-viewer/src/createLogViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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);
});
};
}
6 changes: 2 additions & 4 deletions pw_web/log-viewer/src/index.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,17 +16,15 @@ 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(
'#log-viewer-container',
) as HTMLElement;

if (containerEl) {
createLogViewer(logSources, containerEl, undefined, logStore);
createLogViewer(logSources, containerEl);
}

// Start reading log data
Expand Down
9 changes: 3 additions & 6 deletions pw_web/log-viewer/test/log-store.test.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 };
}
Expand Down
8 changes: 2 additions & 6 deletions pw_web/log-viewer/test/log-viewer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}
Expand Down

0 comments on commit ef4e6e1

Please sign in to comment.