From ed98646a6bce9a7f8c094b21ca616b08e074766f Mon Sep 17 00:00:00 2001 From: davemarco <83603688+davemarco@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:19:19 -0400 Subject: [PATCH] new-log-viewer: Finish support for filtering by log-level and associated refactoring. (#89) - Add new worker request for setting a log-level filter and handling the change in number of pages. - Use log event indices rather than numbers in loadPage. - Clarify the difference in indices into the complete log events collection and the filtered log events map. - Remove unnecessary decoder options arg from `EXPORT_FILE` and `LOAD_PAGE` requests. --- .../src/contexts/StateContextProvider.tsx | 101 ++++++++---- .../src/services/LogFileManager/index.ts | 91 ++++++----- .../src/services/LogFileManager/utils.ts | 152 +++++++++--------- new-log-viewer/src/services/MainWorker.ts | 16 +- new-log-viewer/src/typings/decoders.ts | 11 ++ new-log-viewer/src/typings/worker.ts | 50 +++++- 6 files changed, 269 insertions(+), 152 deletions(-) diff --git a/new-log-viewer/src/contexts/StateContextProvider.tsx b/new-log-viewer/src/contexts/StateContextProvider.tsx index bf65ca90..149ea5b7 100644 --- a/new-log-viewer/src/contexts/StateContextProvider.tsx +++ b/new-log-viewer/src/contexts/StateContextProvider.tsx @@ -11,6 +11,7 @@ import React, { import LogExportManager, {EXPORT_LOG_PROGRESS_VALUE_MIN} from "../services/LogExportManager"; import {Nullable} from "../typings/common"; import {CONFIG_KEY} from "../typings/config"; +import {LogLevelFilter} from "../typings/logs"; import {SEARCH_PARAM_NAMES} from "../typings/url"; import { BeginLineNumToLogEventNumMap, @@ -32,9 +33,10 @@ import { getConfig, } from "../utils/config"; import { - clamp, - getChunkNum, -} from "../utils/math"; + findNearestLessThanOrEqualElement, + isWithinBounds, +} from "../utils/data"; +import {clamp} from "../utils/math"; import { updateWindowUrlHashParams, updateWindowUrlSearchParams, @@ -56,6 +58,7 @@ interface StateContextType { exportLogs: () => void, loadFile: (fileSrc: FileSrcType, cursor: CursorType) => void, loadPageByAction: (navAction: NavigationAction) => void, + setLogLevelFilter: (newLogLevelFilter: LogLevelFilter) => void, } const StateContext = createContext({} as StateContextType); @@ -74,6 +77,7 @@ const STATE_DEFAULT: Readonly = Object.freeze({ exportLogs: () => null, loadFile: () => null, loadPageByAction: () => null, + setLogLevelFilter: () => null, }); interface StateContextProviderProps { @@ -166,10 +170,47 @@ const loadPageByCursor = ( ) => { workerPostReq(worker, WORKER_REQ_CODE.LOAD_PAGE, { cursor: cursor, - decoderOptions: getConfig(CONFIG_KEY.DECODER_OPTIONS), }); }; +/** + * Updates the log event number in the URL to `logEventNum` if it's within the bounds of + * `logEventNumsOnPage`. + * + * @param logEventNum + * @param logEventNumsOnPage + * @return Whether `logEventNum` is within the bounds of `logEventNumsOnPage`. + */ +const updateUrlIfEventOnPage = ( + logEventNum: number, + logEventNumsOnPage: number[] +): boolean => { + if (false === isWithinBounds(logEventNumsOnPage, logEventNum)) { + return false; + } + + const nearestIdx = findNearestLessThanOrEqualElement( + logEventNumsOnPage, + logEventNum + ); + + // Since `isWithinBounds` returned `true`, then: + // - `logEventNumsOnPage` must bound `logEventNum`. + // - `logEventNumsOnPage` cannot be empty. + // - `nearestIdx` cannot be `null`. + // + // Therefore, we can safely cast: + // - `nearestIdx` from `Nullable` to `number`. + // - `logEventNumsOnPage[nearestIdx]` from `number | undefined` to `number`. + const nearestLogEventNum = logEventNumsOnPage[nearestIdx as number] as number; + + updateWindowUrlHashParams({ + logEventNum: nearestLogEventNum, + }); + + return true; +}; + /** * Provides state management for the application. This provider must be wrapped by * UrlContextProvider to function correctly. @@ -186,6 +227,8 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const [fileName, setFileName] = useState(STATE_DEFAULT.fileName); const [logData, setLogData] = useState(STATE_DEFAULT.logData); const [numEvents, setNumEvents] = useState(STATE_DEFAULT.numEvents); + const [numPages, setNumPages] = useState(STATE_DEFAULT.numPages); + const [pageNum, setPageNum] = useState(STATE_DEFAULT.pageNum); const beginLineNumToLogEventNumRef = useRef(STATE_DEFAULT.beginLineNumToLogEventNum); const [exportProgress, setExportProgress] = @@ -193,8 +236,8 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { // Refs const logEventNumRef = useRef(logEventNum); - const numPagesRef = useRef(STATE_DEFAULT.numPages); - const pageNumRef = useRef(STATE_DEFAULT.pageNum); + const numPagesRef = useRef(numPages); + const pageNumRef = useRef(pageNum); const logExportManagerRef = useRef(null); const mainWorkerRef = useRef(null); @@ -220,7 +263,8 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { break; case WORKER_RESP_CODE.PAGE_DATA: { setLogData(args.logs); - pageNumRef.current = args.pageNum; + setNumPages(args.numPages); + setPageNum(args.pageNum); beginLineNumToLogEventNumRef.current = args.beginLineNumToLogEventNum; updateWindowUrlHashParams({ logEventNum: args.logEventNum, @@ -253,7 +297,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { workerPostReq( mainWorkerRef.current, WORKER_REQ_CODE.EXPORT_LOG, - {decoderOptions: getConfig(CONFIG_KEY.DECODER_OPTIONS)} + null ); }, [ numEvents, @@ -298,20 +342,32 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { loadPageByCursor(mainWorkerRef.current, cursor); }, []); - // On `numEvents` update, recalculate `numPagesRef`. - useEffect(() => { - if (STATE_DEFAULT.numEvents === numEvents) { + const setLogLevelFilter = (newLogLevelFilter: LogLevelFilter) => { + if (null === mainWorkerRef.current) { return; } - numPagesRef.current = getChunkNum(numEvents, getConfig(CONFIG_KEY.PAGE_SIZE)); - }, [numEvents]); + workerPostReq(mainWorkerRef.current, WORKER_REQ_CODE.SET_FILTER, { + cursor: {code: CURSOR_CODE.EVENT_NUM, args: {eventNum: logEventNumRef.current ?? 1}}, + logLevelFilter: newLogLevelFilter, + }); + }; // Synchronize `logEventNumRef` with `logEventNum`. useEffect(() => { logEventNumRef.current = logEventNum; }, [logEventNum]); + // Synchronize `pageNumRef` with `numPages`. + useEffect(() => { + pageNumRef.current = pageNum; + }, [pageNum]); + + // Synchronize `numPagesRef` with `numPages`. + useEffect(() => { + numPagesRef.current = numPages; + }, [numPages]); + // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { if (null === mainWorkerRef.current) { @@ -327,18 +383,8 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const clampedLogEventNum = clamp(logEventNum, 1, numEvents); - // eslint-disable-next-line no-warning-comments - // TODO: After filter is added, will need to find the largest <= log event number on the - // current page. Once found, we update the event number in the URL instead of sending a new - // request since the page has not changed. - - if (logEventNumsOnPage.includes(clampedLogEventNum)) { - if (clampedLogEventNum !== logEventNum) { - updateWindowUrlHashParams({ - logEventNum: clampedLogEventNum, - }); - } - + if (updateUrlIfEventOnPage(clampedLogEventNum, logEventNumsOnPage)) { + // No need to request a new page since the log event is on the current page. return; } @@ -380,12 +426,13 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { fileName: fileName, logData: logData, numEvents: numEvents, - numPages: numPagesRef.current, - pageNum: pageNumRef.current, + numPages: numPages, + pageNum: pageNum, exportLogs: exportLogs, loadFile: loadFile, loadPageByAction: loadPageByAction, + setLogLevelFilter: setLogLevelFilter, }} > {children} diff --git a/new-log-viewer/src/services/LogFileManager/index.ts b/new-log-viewer/src/services/LogFileManager/index.ts index 3767c92a..271ba2bd 100644 --- a/new-log-viewer/src/services/LogFileManager/index.ts +++ b/new-log-viewer/src/services/LogFileManager/index.ts @@ -3,11 +3,16 @@ import { DecoderOptionsType, } from "../../typings/decoders"; import {MAX_V8_STRING_LENGTH} from "../../typings/js"; +import {LogLevelFilter} from "../../typings/logs"; import { BeginLineNumToLogEventNumMap, CURSOR_CODE, + CursorData, CursorType, + EMPTY_PAGE_RESP, FileSrcType, + WORKER_RESP_CODE, + WorkerResp, } from "../../typings/worker"; import {EXPORT_LOGS_CHUNK_SIZE} from "../../utils/config"; import {getChunkNum} from "../../utils/math"; @@ -128,6 +133,19 @@ class LogFileManager { this.#decoder.setFormatterOptions(options); } + /** + * Sets the log level filter. + * + * @param logLevelFilter + * @throws {Error} If the log level filter couldn't be set. + */ + setLogLevelFilter (logLevelFilter: LogLevelFilter) { + const result = this.#decoder.setLogLevelFilter(logLevelFilter); + if (false === result) { + throw new Error("Failed to set log level filter for the decoder."); + } + } + /** * Loads log events in the range * [`beginLogEventIdx`, `beginLogEventIdx + EXPORT_LOGS_CHUNK_SIZE`), or all remaining log @@ -168,33 +186,32 @@ class LogFileManager { * numbers, and the line number of the first line in the cursor identified event. * @throws {Error} if any error occurs during decode. */ - loadPage (cursor: CursorType): { - beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap, - cursorLineNum: number - logEventNum: number - logs: string, - pageNum: number - } { + loadPage (cursor: CursorType): WorkerResp { console.debug(`loadPage: cursor=${JSON.stringify(cursor)}`); + const filteredLogEventMap = this.#decoder.getFilteredLogEventMap(); + const numActiveEvents: number = filteredLogEventMap ? + filteredLogEventMap.length : + this.#numEvents; + if (0 === numActiveEvents) { + return EMPTY_PAGE_RESP; + } const { - pageBeginLogEventNum, - pageEndLogEventNum, - matchingLogEventNum, - } = this.#getCursorData(cursor); - + pageBegin, + pageEnd, + matchingEvent, + } = this.#getCursorData(cursor, numActiveEvents); const results = this.#decoder.decodeRange( - pageBeginLogEventNum - 1, - pageEndLogEventNum - 1, - false + pageBegin, + pageEnd, + null !== filteredLogEventMap, ); if (null === results) { throw new Error("Error occurred during decoding. " + - `pageBeginLogEventNum=${pageBeginLogEventNum}, ` + - `pageEndLogEventNum=${pageEndLogEventNum}`); + `pageBegin=${pageBegin}, ` + + `pageEnd=${pageEnd}`); } - const messages: string[] = []; const beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap = new Map(); let currentLine = 1; @@ -210,14 +227,20 @@ class LogFileManager { beginLineNumToLogEventNum.set(currentLine, logEventNum); currentLine += msg.split("\n").length - 1; }); - - const newPageNum: number = getChunkNum(pageBeginLogEventNum, this.#pageSize); + const newNumPages: number = getChunkNum(numActiveEvents, this.#pageSize); + const newPageNum: number = getChunkNum(pageBegin + 1, this.#pageSize); + const matchingLogEventNum = 1 + ( + null !== filteredLogEventMap ? + (filteredLogEventMap[matchingEvent] as number) : + matchingEvent + ); return { beginLineNumToLogEventNum: beginLineNumToLogEventNum, cursorLineNum: 1, logEventNum: matchingLogEventNum, logs: messages.join(""), + numPages: newNumPages, pageNum: newPageNum, }; } @@ -226,39 +249,29 @@ class LogFileManager { * Gets the data that corresponds to the cursor. * * @param cursor - * @return Log event numbers for: - * - the range [begin, end) of the page containing the matching log event. - * - the log event number that matches the cursor. + * @param numActiveEvents + * @return Cursor data. * @throws {Error} if the type of cursor is not supported. */ - #getCursorData (cursor: CursorType): { - pageBeginLogEventNum: number, - pageEndLogEventNum: number, - matchingLogEventNum: number - } { + #getCursorData (cursor: CursorType, numActiveEvents: number): CursorData { const {code, args} = cursor; switch (code) { case CURSOR_CODE.PAGE_NUM: return getPageNumCursorData( args.pageNum, args.eventPositionOnPage, - this.#numEvents, - this.#pageSize + numActiveEvents, + this.#pageSize, ); - case CURSOR_CODE.LAST_EVENT: - return getLastEventCursorData( - this.#numEvents, - this.#pageSize - ); - + return getLastEventCursorData(numActiveEvents, this.#pageSize); case CURSOR_CODE.EVENT_NUM: return getEventNumCursorData( args.eventNum, - this.#numEvents, - this.#pageSize + numActiveEvents, + this.#pageSize, + this.#decoder.getFilteredLogEventMap(), ); - default: throw new Error(`Unsupported cursor type: ${code}`); } diff --git a/new-log-viewer/src/services/LogFileManager/utils.ts b/new-log-viewer/src/services/LogFileManager/utils.ts index 2a871c02..3c7605b2 100644 --- a/new-log-viewer/src/services/LogFileManager/utils.ts +++ b/new-log-viewer/src/services/LogFileManager/utils.ts @@ -1,7 +1,16 @@ import { + ActiveLogCollectionEventIdx, + FilteredLogEventMap, +} from "../../typings/decoders"; +import { + CursorData, EVENT_POSITION_ON_PAGE, FileSrcType, } from "../../typings/worker"; +import { + clampWithinBounds, + findNearestLessThanOrEqualElement, +} from "../../utils/data"; import {getUint8ArrayFrom} from "../../utils/http"; import { clamp, @@ -11,109 +20,106 @@ import {getBasenameFromUrlOrDefault} from "../../utils/url"; /** - * Gets the log event number range [begin, end) of the page that starts at the given log event - * index. + * Gets the data for the `PAGE_NUM` cursor. * - * @param beginLogEventIdx - * @param numEvents + * @param pageNum + * @param eventPositionOnPage + * @param numActiveEvents * @param pageSize - * @return An array: - * - pageBeginLogEventNum - * - pageEndLogEventNum + * @return */ -const getPageBoundaries = ( - beginLogEventIdx: number, - numEvents: number, - pageSize: number -): [number, number] => { - const pageBeginLogEventNum: number = beginLogEventIdx + 1; +const getPageNumCursorData = ( + pageNum: number, + eventPositionOnPage: EVENT_POSITION_ON_PAGE, + numActiveEvents: number, + pageSize: number, +): CursorData => { + const pageBegin: ActiveLogCollectionEventIdx = (pageNum - 1) * pageSize; + const pageEnd: ActiveLogCollectionEventIdx = Math.min(numActiveEvents, pageBegin + pageSize); - // Clamp ending index using total number of events. - const pageEndLogEventNum: number = Math.min(numEvents + 1, pageBeginLogEventNum + pageSize); + const matchingEvent: ActiveLogCollectionEventIdx = + eventPositionOnPage === EVENT_POSITION_ON_PAGE.TOP ? + pageBegin : + pageEnd - 1; - return [ - pageBeginLogEventNum, - pageEndLogEventNum, - ]; + return {pageBegin, pageEnd, matchingEvent}; }; /** - * Gets the data for the `PAGE_NUM` cursor. + * Gets the `ActiveLogCollectionEventIdx` that's nearest to `logEventIdx`. Specifically: + * - If no filter is set, the nearest `ActiveLogCollectionEventIdx` is: + * - `logEventIdx` if it's in the range of the unfiltered log events collection. + * - the bound of the collection nearest to `logEventIdx` if it's not in the collection's range. + * - If a filter is set, the nearest `ActiveLogCollectionEventIdx` is: + * - the largest index, `i`, where `filteredLogEventMap[i] <= logEventIdx`; or + * - `0` if `logEventIdx < filteredLogEventMap[0]`. * - * @param pageNum - * @param eventPositionOnPage - * @param numEvents - * @param pageSize - * @return Log event numbers for: - * - the range [begin, end) of page `pageNum`. - * - the log event indicated by `eventPositionOnPage`. + * @param logEventIdx + * @param numActiveEvents + * @param filteredLogEventMap + * @return */ -const getPageNumCursorData = ( - pageNum: number, - eventPositionOnPage: EVENT_POSITION_ON_PAGE, - numEvents: number, - pageSize: number -): { pageBeginLogEventNum: number; pageEndLogEventNum: number; matchingLogEventNum: number } => { - const beginLogEventIdx = (pageNum - 1) * pageSize; - const [pageBeginLogEventNum, pageEndLogEventNum] = getPageBoundaries( - beginLogEventIdx, - numEvents, - pageSize - ); - const matchingLogEventNum = eventPositionOnPage === EVENT_POSITION_ON_PAGE.TOP ? - pageBeginLogEventNum : - pageEndLogEventNum - 1; +const getNearestActiveLogCollectionEventIdx = ( + logEventIdx: number, + numActiveEvents: number, + filteredLogEventMap: FilteredLogEventMap, +): ActiveLogCollectionEventIdx => { + if (null === filteredLogEventMap) { + return clamp(logEventIdx, 0, numActiveEvents - 1); + } + const clampedLogEventIdx = clampWithinBounds(filteredLogEventMap, logEventIdx); - return {pageBeginLogEventNum, pageEndLogEventNum, matchingLogEventNum}; + // Explicit cast since TypeScript thinks the return value can be null, but it can't be since + // filteredLogEventMap is non-empty and `clampedLogEventIdx` is within the bounds of + // `filteredLogEventMap`. + return findNearestLessThanOrEqualElement(filteredLogEventMap, clampedLogEventIdx) as number; }; /** * Gets the data for the `EVENT_NUM` cursor. * * @param logEventNum - * @param numEvents + * @param numActiveEvents * @param pageSize - * @return Log event numbers for: - * - the range [begin, end) of the page containing `logEventNum`. - * - log event `logEventNum`. + * @param filteredLogEventMap + * @return */ const getEventNumCursorData = ( logEventNum: number, - numEvents: number, - pageSize: number -): { pageBeginLogEventNum: number; pageEndLogEventNum: number; matchingLogEventNum: number } => { - const validLogEventNum = clamp(logEventNum, 1, numEvents); - const beginLogEventIdx = (getChunkNum(validLogEventNum, pageSize) - 1) * pageSize; - const [pageBeginLogEventNum, pageEndLogEventNum] = getPageBoundaries( - beginLogEventIdx, - numEvents, - pageSize - ); - const matchingLogEventNum: number = validLogEventNum; - return {pageBeginLogEventNum, pageEndLogEventNum, matchingLogEventNum}; + numActiveEvents: number, + pageSize: number, + filteredLogEventMap: FilteredLogEventMap +): CursorData => { + const matchingEvent: ActiveLogCollectionEventIdx = + getNearestActiveLogCollectionEventIdx( + logEventNum - 1, + numActiveEvents, + filteredLogEventMap + ); + const pageBegin: ActiveLogCollectionEventIdx = + (getChunkNum(matchingEvent + 1, pageSize) - 1) * pageSize; + const pageEnd: ActiveLogCollectionEventIdx = + Math.min(numActiveEvents, pageBegin + pageSize); + + return {pageBegin, pageEnd, matchingEvent}; }; /** * Gets the data for the `LAST` cursor. * - * @param numEvents + * @param numActiveEvents * @param pageSize - * @return Log event numbers for: - * - the range [begin, end) of the last page. - * - the last log event on the last page. + * @return */ const getLastEventCursorData = ( - numEvents: number, + numActiveEvents: number, pageSize: number -): { pageBeginLogEventNum: number; pageEndLogEventNum: number; matchingLogEventNum: number } => { - const beginLogEventIdx = (getChunkNum(numEvents, pageSize) - 1) * pageSize; - const [pageBeginLogEventNum, pageEndLogEventNum] = getPageBoundaries( - beginLogEventIdx, - numEvents, - pageSize - ); - const matchingLogEventNum: number = pageEndLogEventNum - 1; - return {pageBeginLogEventNum, pageEndLogEventNum, matchingLogEventNum}; +): CursorData => { + const pageBegin: ActiveLogCollectionEventIdx = + (getChunkNum(numActiveEvents, pageSize) - 1) * pageSize; + const pageEnd: ActiveLogCollectionEventIdx = Math.min(numActiveEvents, pageBegin + pageSize); + const matchingEvent: ActiveLogCollectionEventIdx = pageEnd - 1; + return {pageBegin, pageEnd, matchingEvent}; }; /** diff --git a/new-log-viewer/src/services/MainWorker.ts b/new-log-viewer/src/services/MainWorker.ts index 4fe43c8d..56313e47 100644 --- a/new-log-viewer/src/services/MainWorker.ts +++ b/new-log-viewer/src/services/MainWorker.ts @@ -49,10 +49,6 @@ onmessage = async (ev: MessageEvent) => { if (null === LOG_FILE_MANAGER) { throw new Error("Log file manager hasn't been initialized"); } - if ("undefined" !== typeof args.decoderOptions) { - LOG_FILE_MANAGER.setFormatterOptions(args.decoderOptions); - } - let decodedEventIdx = 0; while (decodedEventIdx < LOG_FILE_MANAGER.numEvents) { postResp( @@ -84,9 +80,17 @@ onmessage = async (ev: MessageEvent) => { if (null === LOG_FILE_MANAGER) { throw new Error("Log file manager hasn't been initialized"); } - if ("undefined" !== typeof args.decoderOptions) { - LOG_FILE_MANAGER.setFormatterOptions(args.decoderOptions); + postResp( + WORKER_RESP_CODE.PAGE_DATA, + LOG_FILE_MANAGER.loadPage(args.cursor) + ); + break; + case WORKER_REQ_CODE.SET_FILTER: + if (null === LOG_FILE_MANAGER) { + throw new Error("Log file manager hasn't been initialized"); } + + LOG_FILE_MANAGER.setLogLevelFilter(args.logLevelFilter); postResp( WORKER_RESP_CODE.PAGE_DATA, LOG_FILE_MANAGER.loadPage(args.cursor) diff --git a/new-log-viewer/src/typings/decoders.ts b/new-log-viewer/src/typings/decoders.ts index d1b1eeae..27713d9c 100644 --- a/new-log-viewer/src/typings/decoders.ts +++ b/new-log-viewer/src/typings/decoders.ts @@ -39,6 +39,16 @@ type DecodeResultType = [string, number, number, number]; */ type FilteredLogEventMap = Nullable; +/** + * Index into the active log events collection. The active log events collection is either: + * - the filtered log events collection, if the log level filter is set; or + * - the unfiltered log events collection. + * + * NOTE: The filtered log events collection is currently represented using a `FilteredLogEventMap` + * (so the index goes through a layer of indirection). + */ +type ActiveLogCollectionEventIdx = number; + interface Decoder { /** @@ -102,6 +112,7 @@ const LOG_EVENT_FILE_END_IDX: number = 0; export {LOG_EVENT_FILE_END_IDX}; export type { + ActiveLogCollectionEventIdx, Decoder, DecodeResultType, DecoderOptionsType, diff --git a/new-log-viewer/src/typings/worker.ts b/new-log-viewer/src/typings/worker.ts index 27e188ee..24b2afb9 100644 --- a/new-log-viewer/src/typings/worker.ts +++ b/new-log-viewer/src/typings/worker.ts @@ -1,5 +1,12 @@ -import {DecoderOptionsType} from "./decoders"; -import {LOG_LEVEL} from "./logs"; +import {Nullable} from "./common"; +import { + ActiveLogCollectionEventIdx, + DecoderOptionsType, +} from "./decoders"; +import { + LOG_LEVEL, + LogLevelFilter, +} from "./logs"; /** @@ -41,6 +48,17 @@ type CursorType = { [T in keyof CursorArgMap]: { code: T, args: CursorArgMap[T] }; }[keyof CursorArgMap]; +/** + * Active log collection indices for: + * - the range [begin, end) of the page containing the matching log event. + * - the log event that matches the cursor. + */ +type CursorData = { + pageBegin: ActiveLogCollectionEventIdx; + pageEnd: ActiveLogCollectionEventIdx; + matchingEvent: ActiveLogCollectionEventIdx; +}; + /** * Type mapping the first line number of each log event to the log event number. */ @@ -53,6 +71,7 @@ enum WORKER_REQ_CODE { EXPORT_LOG = "exportLog", LOAD_FILE = "loadFile", LOAD_PAGE = "loadPage", + SET_FILTER = "setFilter", } enum WORKER_RESP_CODE { @@ -63,9 +82,7 @@ enum WORKER_RESP_CODE { } type WorkerReqMap = { - [WORKER_REQ_CODE.EXPORT_LOG]: { - decoderOptions: DecoderOptionsType - } + [WORKER_REQ_CODE.EXPORT_LOG]: null [WORKER_REQ_CODE.LOAD_FILE]: { fileSrc: FileSrcType, pageSize: number, @@ -74,7 +91,10 @@ type WorkerReqMap = { }, [WORKER_REQ_CODE.LOAD_PAGE]: { cursor: CursorType, - decoderOptions?: DecoderOptionsType + }, + [WORKER_REQ_CODE.SET_FILTER]: { + cursor: CursorType, + logLevelFilter: LogLevelFilter, }, }; @@ -89,8 +109,9 @@ type WorkerRespMap = { [WORKER_RESP_CODE.PAGE_DATA]: { beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap, cursorLineNum: number - logEventNum: number + logEventNum: Nullable logs: string, + numPages: number pageNum: number }, [WORKER_RESP_CODE.NOTIFICATION]: { @@ -115,14 +136,29 @@ type MainWorkerRespMessage = { [T in keyof WorkerRespMap]: { code: T, args: WorkerRespMap[T] }; }[keyof WorkerRespMap]; +/** + * Empty page response. + */ +const EMPTY_PAGE_RESP: WorkerResp = Object.freeze({ + beginLineNumToLogEventNum: new Map(), + cursorLineNum: 1, + logEventNum: null, + logs: "", + numPages: 1, + pageNum: 1, +}); + + export { CURSOR_CODE, + EMPTY_PAGE_RESP, EVENT_POSITION_ON_PAGE, WORKER_REQ_CODE, WORKER_RESP_CODE, }; export type { BeginLineNumToLogEventNumMap, + CursorData, CursorType, FileSrcType, MainWorkerReqMessage,