Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new-log-viewer: Add event cursor, refactor state context, and redesign load page methods to support discontinuous log event numbers in preparation for log level filtering. #76

Merged
merged 33 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
370af54
first draft
davemarco Sep 24, 2024
96927bf
next pass
davemarco Sep 24, 2024
fcd0ee9
fix lint
davemarco Sep 24, 2024
d88bc3d
small changes
davemarco Sep 24, 2024
ceb8a4d
small change
davemarco Sep 24, 2024
479c1a8
Merge branch 'main' into page
davemarco Sep 24, 2024
4d1e70a
small change after merge
davemarco Sep 24, 2024
662619b
add utils to shorten file
davemarco Sep 24, 2024
ed2d9c4
fix page size error
davemarco Sep 24, 2024
26a0d02
fix bug with loadpage rerender
davemarco Sep 24, 2024
b32c5bd
add comments describing broken behaviour until cursor done
davemarco Sep 24, 2024
c6f1f5f
small changes
davemarco Sep 24, 2024
d2ed5b9
move newLogEvent so better placed for future PR
davemarco Sep 24, 2024
0050180
remove print
davemarco Sep 24, 2024
a523d18
added basic log event cursor and cleanup cursor switch in logFileManager
davemarco Sep 25, 2024
cf096fa
small changes
davemarco Sep 25, 2024
99f4003
Apply suggestions from code review
davemarco Sep 27, 2024
78a1f8f
kirk review
davemarco Sep 27, 2024
a11295a
add comment
davemarco Sep 27, 2024
18599f1
Apply suggestions from code review
davemarco Sep 30, 2024
829ba10
kirk review 2
davemarco Sep 30, 2024
1572a91
new comment from kirk
davemarco Sep 30, 2024
1e6ad3f
small nit
davemarco Sep 30, 2024
31012ff
fix off-by-error related to end num now exclusive
davemarco Sep 30, 2024
6200bd4
Apply suggestions from code review
davemarco Oct 1, 2024
6f43e7f
move function and other
davemarco Oct 1, 2024
d266ce7
move null check
davemarco Oct 1, 2024
0419552
add more lines
davemarco Oct 1, 2024
3887f76
Remove log.
kirkrodrigues Oct 1, 2024
808a7e1
Remove extra newline.
kirkrodrigues Oct 1, 2024
d8b32ea
small wording change so i am last pusher
davemarco Oct 1, 2024
a55a3c8
Merge branch 'page' of github.com:davemarco/yscope-log-viewer-fork in…
davemarco Oct 1, 2024
c26459d
small change so last pusher
davemarco Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions new-log-viewer/src/components/Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {BeginLineNumToLogEventNumMap} from "../../typings/worker";
import {
ACTION_NAME,
EDITOR_ACTIONS,
handleAction,
} from "../../utils/actions";
import {
CONFIG_DEFAULT,
Expand Down Expand Up @@ -63,7 +62,7 @@ const resetCachedPageSize = () => {
const Editor = () => {
const {mode, systemMode} = useColorScheme();

const {beginLineNumToLogEventNum, logData, numEvents} = useContext(StateContext);
const {beginLineNumToLogEventNum, logData, loadPageAction} = useContext(StateContext);
const {logEventNum} = useContext(UrlContext);

const [lineNum, setLineNum] = useState<number>(1);
Expand All @@ -72,23 +71,18 @@ const Editor = () => {
);
const editorRef = useRef<Nullable<monaco.editor.IStandaloneCodeEditor>>(null);
const isMouseDownRef = useRef<boolean>(false);
const logEventNumRef = useRef<Nullable<number>>(logEventNum);
const numEventsRef = useRef<Nullable<number>>(numEvents);
const pageSizeRef = useRef(getConfig(CONFIG_KEY.PAGE_SIZE));

const handleEditorCustomAction = useCallback((
editor: monaco.editor.IStandaloneCodeEditor,
actionName: ACTION_NAME
) => {
if (null === logEventNumRef.current || null === numEventsRef.current) {
return;
}
switch (actionName) {
case ACTION_NAME.FIRST_PAGE:
case ACTION_NAME.PREV_PAGE:
case ACTION_NAME.NEXT_PAGE:
case ACTION_NAME.LAST_PAGE:
handleAction(actionName, logEventNumRef.current, numEventsRef.current);
loadPageAction(ACTION_NAME.LAST_PAGE);
davemarco marked this conversation as resolved.
Show resolved Hide resolved
break;
case ACTION_NAME.PAGE_TOP:
goToPositionAndCenter(editor, {lineNumber: 1, column: 1});
Expand All @@ -104,7 +98,7 @@ const Editor = () => {
default:
break;
}
}, []);
}, [loadPageAction]);

/**
* Sets `editorRef` and configures callbacks for mouse down detection.
Expand Down Expand Up @@ -162,16 +156,6 @@ const Editor = () => {
beginLineNumToLogEventNumRef.current = beginLineNumToLogEventNum;
}, [beginLineNumToLogEventNum]);

// Synchronize `logEventNumRef` with `logEventNum`.
useEffect(() => {
logEventNumRef.current = logEventNum;
}, [logEventNum]);

// Synchronize `numEventsRef` with `numEvents`.
useEffect(() => {
numEventsRef.current = numEvents;
}, [numEvents]);

// On `logEventNum` update, update line number in the editor.
useEffect(() => {
if (null === editorRef.current || isMouseDownRef.current) {
Expand Down
14 changes: 3 additions & 11 deletions new-log-viewer/src/components/MenuBar/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import SkipNext from "@mui/icons-material/SkipNext";
import SkipPrevious from "@mui/icons-material/SkipPrevious";

import {StateContext} from "../../contexts/StateContextProvider";
import {UrlContext} from "../../contexts/UrlContextProvider";
import {
ACTION_NAME,
handleAction,
} from "../../utils/actions";
import {ACTION_NAME} from "../../utils/actions";
import PageNumInput from "./PageNumInput";
import SmallIconButton from "./SmallIconButton";

Expand All @@ -21,16 +17,12 @@ import SmallIconButton from "./SmallIconButton";
* @return
*/
const NavigationBar = () => {
const {numEvents} = useContext(StateContext);
const {logEventNum} = useContext(UrlContext);
const {loadPageAction} = useContext(StateContext);

const handleNavButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
if (null === logEventNum) {
return;
}
const {actionName} = event.currentTarget.dataset as { actionName: ACTION_NAME };
if (Object.values(ACTION_NAME).includes(actionName)) {
handleAction(actionName, logEventNum, numEvents);
loadPageAction(actionName);
}
};

Expand Down
12 changes: 5 additions & 7 deletions new-log-viewer/src/components/MenuBar/PageNumInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {Typography} from "@mui/joy";
import Input from "@mui/joy/Input";

import {StateContext} from "../../contexts/StateContextProvider";
import {ACTION_NAME} from "../../utils/actions";

import "./PageNumInput.css";

Expand All @@ -22,10 +23,7 @@ const PAGE_NUM_INPUT_FIT_EXTRA_WIDTH = 2;
* @return
*/
const PageNumInput = () => {
const {loadPage, numPages, pageNum} = useContext(StateContext);
const adjustedPageNum = (null === pageNum) ?
0 :
pageNum;
const {loadPageAction, numPages, pageNum} = useContext(StateContext);

const [isEditing, setIsEditing] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);
Expand All @@ -38,7 +36,7 @@ const PageNumInput = () => {
return;
}

loadPage(Number(inputRef.current.value));
loadPageAction(ACTION_NAME.SPECIFIC_PAGE, Number(inputRef.current.value));
setIsEditing(false);
};

Expand Down Expand Up @@ -68,9 +66,9 @@ const PageNumInput = () => {
if (null === inputRef.current) {
return;
}
inputRef.current.value = adjustedPageNum.toString();
inputRef.current.value = pageNum.toString();
adjustInputWidth();
}, [adjustedPageNum]);
}, [pageNum]);

return (
<form
Expand Down
150 changes: 69 additions & 81 deletions new-log-viewer/src/contexts/StateContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ import {
WORKER_RESP_CODE,
WorkerReq,
} from "../typings/worker";
import {
ACTION_NAME,
getPageNumCursorArgs,
} from "../utils/actions";
import {
EXPORT_LOGS_CHUNK_SIZE,
getConfig,
} from "../utils/config";
import {
clamp,
getChunkNum,
} from "../utils/math";
import {getChunkNum} from "../utils/math";
import {
updateWindowUrlHashParams,
updateWindowUrlSearchParams,
Expand All @@ -46,11 +47,11 @@ interface StateContextType {
logData: string,
numEvents: number,
numPages: number,
pageNum: Nullable<number>,
pageNum: number,

exportLogs: () => void,
loadFile: (fileSrc: FileSrcType, cursor: CursorType) => void,
loadPage: (newPageNum: number) => void,
loadPageAction: (action: ACTION_NAME, specificPageNum?: Nullable<number>) => void,
davemarco marked this conversation as resolved.
Show resolved Hide resolved
}
const StateContext = createContext<StateContextType>({} as StateContextType);

Expand All @@ -68,50 +69,13 @@ const STATE_DEFAULT: Readonly<StateContextType> = Object.freeze({

exportLogs: () => null,
loadFile: () => null,
loadPage: () => null,
loadPageAction: () => null,
});

interface StateContextProviderProps {
children: React.ReactNode
}

/**
* Updates the log event number in the current window's URL hash parameters.
*
* @param lastLogEventNum The last log event number value.
* @param inputLogEventNum The log event number to set. If `null`, the hash parameter log event
* number will be set to `lastLogEventNum`. If it's outside the range `[1, lastLogEventNum]`, the
* hash parameter log event number will be clamped to that range.
*/
const updateLogEventNumInUrl = (
lastLogEventNum: number,
inputLogEventNum: Nullable<number>
) => {
const newLogEventNum = (null === inputLogEventNum) ?
lastLogEventNum :
clamp(inputLogEventNum, 1, lastLogEventNum);

updateWindowUrlHashParams({
logEventNum: newLogEventNum,
});
};

/**
* Gets the last log event number from a map of begin line numbers to log event numbers.
*
* @param beginLineNumToLogEventNum
* @return The last log event number.
*/
const getLastLogEventNum = (beginLineNumToLogEventNum: BeginLineNumToLogEventNumMap) => {
const allLogEventNums = Array.from(beginLineNumToLogEventNum.values());
let lastLogEventNum = allLogEventNums.at(-1);
if ("undefined" === typeof lastLogEventNum) {
lastLogEventNum = 1;
}

return lastLogEventNum;
};

/**
* Sends a post message to a worker with the given code and arguments. This wrapper around
* `worker.postMessage()` ensures type safety for both the request code and its corresponding
Expand All @@ -137,7 +101,7 @@ const workerPostReq = <T extends WORKER_REQ_CODE>(
* @param props.children
* @return
*/
// eslint-disable-next-line max-lines-per-function
// eslint-disable-next-line max-lines-per-function, max-statements
davemarco marked this conversation as resolved.
Show resolved Hide resolved
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Consider refactoring for maintainability and fix lint error

The StateContextProvider component and its associated functions have grown quite complex. While the current implementation is functional, it might become harder to maintain as the codebase evolves.

Consider breaking down the StateContextProvider into smaller, more focused components or hooks. This could improve readability and make it easier to test individual pieces of functionality.

Also, there's a minor lint error:

On line 323, there are trailing spaces that should be removed to comply with the project's linting rules.

-        
+

Addressing these points will improve the overall code quality and maintainability of this file.

Also applies to: 323-323

const StateContextProvider = ({children}: StateContextProviderProps) => {
const {filePath, logEventNum} = useContext(UrlContext);

Expand All @@ -153,10 +117,11 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
// Refs
const logEventNumRef = useRef(logEventNum);
const numPagesRef = useRef<number>(STATE_DEFAULT.numPages);
const pageNumRef = useRef<Nullable<number>>(STATE_DEFAULT.pageNum);
const pageNumRef = useRef<number>(STATE_DEFAULT.pageNum);
const logExportManagerRef = useRef<null|LogExportManager>(null);
const mainWorkerRef = useRef<null|Worker>(null);

// eslint-disable-next-line max-lines-per-function
const handleMainWorkerResp = useCallback((ev: MessageEvent<MainWorkerRespMessage>) => {
const {code, args} = ev.data;
console.log(`[MainWorker -> Renderer] code=${code}`);
Expand All @@ -179,9 +144,14 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
break;
case WORKER_RESP_CODE.PAGE_DATA: {
setLogData(args.logs);
pageNumRef.current = args.pageNum;
beginLineNumToLogEventNumRef.current = args.beginLineNumToLogEventNum;
const lastLogEventNum = getLastLogEventNum(args.beginLineNumToLogEventNum);
updateLogEventNumInUrl(lastLogEventNum, logEventNumRef.current);

// Assume page data always provides a valid log event num. i.e. non null or
// outside range.
updateWindowUrlHashParams({
logEventNum: args.logEventNum,
});
break;
}
default:
Expand Down Expand Up @@ -240,17 +210,45 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
handleMainWorkerResp,
]);

const loadPage = (newPageNum: number) => {
const loadPage = useCallback((
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

  • loadPage -> loadPageByCursor
  • loadPageAction -> loadPageByAction

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

cursor: CursorType,
) => {
if (null === mainWorkerRef.current) {
console.error("Unexpected null mainWorkerRef.current");

return;
}

workerPostReq(mainWorkerRef.current, WORKER_REQ_CODE.LOAD_PAGE, {
cursor: {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}},
cursor: cursor,
decoderOptions: getConfig(CONFIG_KEY.DECODER_OPTIONS),
});
};
}, []);

const loadPageAction = useCallback((
action: ACTION_NAME,
specificPageNum: Nullable<number> = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing specificPageNum separately, can we create a type like CursorType that includes args? I know we already have ActionType, so we'll probably need to rename that to something like UiActionType.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried something similar to worker req/response type, but slightly more complex. See in actions.ts

) => {
const [newPageNum, anchor] = getPageNumCursorArgs(
action,
specificPageNum,
pageNumRef.current,
numPagesRef.current
);

if (null === newPageNum || null === anchor) {
console.error(`Error with page action ${action}.`);

return;
}

const cursor: CursorType = {
code: CURSOR_CODE.PAGE_NUM,
args: {pageNum: newPageNum, logEventAnchor: anchor},
};

loadPage(cursor);
}, [loadPage]);
davemarco marked this conversation as resolved.
Show resolved Hide resolved
davemarco marked this conversation as resolved.
Show resolved Hide resolved

// Synchronize `logEventNumRef` with `logEventNum`.
useEffect(() => {
Expand All @@ -272,29 +270,24 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {
return;
}

const newPageNum = clamp(
getChunkNum(logEventNum, getConfig(CONFIG_KEY.PAGE_SIZE)),
1,
numPagesRef.current
);
Comment on lines -275 to -279
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still useful to clamp the log event number, right? The idea being if we clamp the event number and it ends up being on the current page, then we save a round-trip to the worker. And in the case that we don't need to change pages, we should still update the URL with the clamped log event number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I believe this can save a request. I updated so we have the feature.

const logEventNumsOnPage: number [] =
Array.from(beginLineNumToLogEventNumRef.current.values());

// Request a page switch only if it's not the initial page load.
if (STATE_DEFAULT.pageNum !== pageNumRef.current) {
if (newPageNum === pageNumRef.current) {
// Don't need to switch pages so just update `logEventNum` in the URL.
updateLogEventNumInUrl(numEvents, logEventNumRef.current);
} else {
// NOTE: We don't need to call `updateLogEventNumInUrl()` since it's called when
// handling the `WORKER_RESP_CODE.PAGE_DATA` response (the response to
// `WORKER_REQ_CODE.LOAD_PAGE` requests) .
loadPage(newPageNum);
}
// Do nothing if log event is on the current page. There is no need to update it, since
// it was the URL change that triggered this useEffect.
if (logEventNumsOnPage.includes(logEventNum)) {
return;
}

pageNumRef.current = newPageNum;
const cursor: CursorType = {
code: CURSOR_CODE.EVENT_NUM,
args: {logEventNum: logEventNum},
};

loadPage(cursor);
}, [
numEvents,
logEventNum,
loadPage,
]);

// On `filePath` update, load file.
Expand All @@ -305,15 +298,8 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {

let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null};
if (URL_HASH_PARAMS_DEFAULT.logEventNum !== logEventNumRef.current) {
// Set which page to load since the user specified a specific `logEventNum`.
// NOTE: Since we don't know how many pages the log file contains, we only clamp the
// minimum of the page number.
const newPageNum = Math.max(
getChunkNum(logEventNumRef.current, getConfig(CONFIG_KEY.PAGE_SIZE)),
1
);

cursor = {code: CURSOR_CODE.PAGE_NUM, args: {pageNum: newPageNum}};
cursor = {code: CURSOR_CODE.EVENT_NUM,
args: {logEventNum: logEventNumRef.current}};
davemarco marked this conversation as resolved.
Show resolved Hide resolved
}
loadFile(filePath, cursor);
}, [
Expand All @@ -334,14 +320,16 @@ const StateContextProvider = ({children}: StateContextProviderProps) => {

exportLogs: exportLogs,
loadFile: loadFile,
loadPage: loadPage,
loadPageAction: loadPageAction,
}}
>
{children}
</StateContext.Provider>
);
};


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Junhao intends to have two newlines separating imports/exports from code, but it's not easy to enforce with eslint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

export default StateContextProvider;
export {StateContext};
export {
STATE_DEFAULT,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to export this any more, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

StateContext,
};
Loading
Loading