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

feat(query): Add a "Search" tab in the Sidebar for wildcard queries. #107

Merged
merged 27 commits into from
Nov 16, 2024

Conversation

Henry8192
Copy link
Collaborator

@Henry8192 Henry8192 commented Oct 25, 2024

References

A rework of #82.

Description

UI Description

  • Added a search tab panel. The search panel is expanded by clicking the magnifier button on the side bar panel.
  • The search input box comes with two search options. Button "Aa" detects case sensitivity and "*" accepts regular expressions.
  • On search, a progress bar will show the current search progress, and turns green when search completes.
  • The results are listed according to their belonging page. Notice that currently our results are capped at 1000. Clicking the result will jump to the according log event in the editor.
  • The button right to "SEARCH" is used for expanding / collapsing all results.

Code Structure Description

Front end search result structure

All results are shown in an AccordionGroup in SearchTabPanel/index.tsx. The AccordionGroup parses queryResults and convert them to arrays of Result Groups, each includes a page of results. Inside a ResultGroup a are list of Results, which are essentially ListItemButtons.

Back end protocol re-design

Add queryProgress property to QUERY_RESULT protocol so that the current query progress is updated in the front end.

Validation performed

Results integrity

  • Search "123". Results jump to the matched log event numbers in editor.
  • After above is performed, clear search inputs and observe no results are left over.
  • Perform another search "124", notice the results are indeed "124" and doesn't include previous results of "123".

Result displaying

  • Collapse and expand any of the results. No overflowing or result disappearing issues occur.
  • Similarly, collapse and expand all of the results using the collpase / expand button. No issues discovered.

Progress bar

  • Progress bar always turns green in the end including search results over 1000, within 1000, and no results.
  • After previous search is performed, the next search will reset the progress bar.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new search tab in the sidebar, enhancing navigation and search functionality.
    • Added a results display component that highlights matched text for better visibility.
    • Implemented a progress indicator for search queries, improving user feedback during loading.
    • Enhanced the CustomTabPanel to support additional title buttons for improved usability.
    • Added a new PanelTitleButton component for better title management in tabs.
  • Bug Fixes

    • Resolved layout issues in various components to ensure consistent styling.
  • Styling Enhancements

    • Updated CSS for improved responsiveness and visual appeal across components.
    • Added new styles to manage button interactions and text overflow effectively.
    • Introduced new classes for better layout control in the SearchTabPanel and CustomTabPanel.

Copy link

coderabbitai bot commented Oct 25, 2024

Walkthrough

This pull request introduces several new components and updates existing ones within the sidebar of the application. Key changes include the addition of the SearchTabPanel, which facilitates search functionality, and the ResultsGroup and Result components for displaying search results. The CustomTabPanel component is updated to accept additional title buttons. CSS modifications enhance the styling of various components. Additionally, new state management features and constants are introduced to handle query progress and results more effectively.

Changes

File Change Summary
src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx Added titleButtons prop to CustomTabPanelProps, updated function signature and return structure to include buttons in the title area.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css Introduced .result-button and .result-button-text classes for button styling and text handling.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx Added Result component with props for displaying highlighted search results and handling clicks.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.css Added multiple CSS classes for styling the ResultsGroup component.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx Introduced ResultsGroup component to manage and display a group of search results.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx Created SearchTabPanel component for search input and results display.
src/components/CentralContainer/Sidebar/SidebarTabs/index.tsx Added SearchTabPanel to sidebar tabs and updated tab information to include a search tab.
src/typings/tab.ts Added SEARCH enum value to TAB_NAME and updated TAB_DISPLAY_NAMES.
src/contexts/StateContextProvider.tsx Introduced queryProgress state variable and updated related methods for handling query results.
src/services/LogFileManager/index.ts Increased ESLint line limit, added MAX_QUERY_RESULT_COUNT, and updated query handling logic.
src/typings/worker.ts Updated WorkerRespMap to include progress in query results and added new constants for query progress.
src/services/MainWorker.ts Updated onQueryResults function to include progress reporting.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.css Added new CSS classes for styling the SearchTabPanel.
src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.css Added .sidebar-tab-panel-container class and modified existing title class for flex layout.
src/components/CentralContainer/Sidebar/SidebarTabs/index.css Modified .sidebar-tabs class to adjust height dynamically and prevent vertical overflow.
src/typings/states.ts Added QUERY_INPUT_BOX to UI_ELEMENT enum and updated UI state management.
src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.css Introduced .tab-panel-title-button class for layout control.
src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.tsx Added PanelTitleButton component for rendering an icon button with custom styles.

Possibly related PRs

Suggested reviewers

  • Henry8192

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Henry8192
Copy link
Collaborator Author

Henry8192 commented Oct 28, 2024

@junhaoliao Currently, the results are shown only if you switch to info and switch back to search panel, which I guess is a re-render issue. Plus, the browser seems to be freezed when facing a large amount of results.

@Henry8192 Henry8192 changed the title WIP: Add <SearchTabPanel/> for submitting queries and viewing search results. Add <SearchTabPanel/> for submitting queries and viewing search results. Oct 28, 2024
@Henry8192 Henry8192 marked this pull request as ready for review October 28, 2024 16:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Outside diff range and nitpick comments (7)
src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.tsx (1)

9-14: Enhance component documentation for better maintainability.

The JSDoc comments could be more descriptive. Consider adding:

  • @param props description detailing expected IconButtonProps
  • @returns documentation explaining the rendered component
 /**
  * Renders an IconButton with an additional CSS class 'sidebar-tab-title-button'.
  *
- * @param props
- * @return
+ * @param props - IconButton properties to be passed to the underlying button
+ * @returns {JSX.Element} A styled IconButton component with minimal dimensions
  */
src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx (2)

18-18: Add property documentation

Consider adding TSDoc comments for the titleButtons property to describe its purpose and expected content.

 interface CustomTabPanelProps {
     children: React.ReactNode,
     tabName: string,
     title: string,
+    /** Buttons to be rendered in the title area of the panel */
     titleButtons?: React.ReactNode,
 }

28-28: Enhance parameter documentation

The titleButtons parameter documentation could be more descriptive about its purpose and usage.

- * @param props.titleButtons
+ * @param props.titleButtons Optional buttons to be rendered in the panel's title area, typically used for panel-specific actions
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1)

14-22: Documentation needs improvement.

The JSDoc comments should include information about accessibility features and error handling.

Add the following to the documentation:

  • @throws description for invalid matchRange values
  • @accessibility description for how the highlight is perceived by screen readers
src/components/CentralContainer/Sidebar/SidebarTabs/index.tsx (1)

Line range hint 1-107: Consider lazy loading tab panels

To improve initial load time and reduce memory usage, consider implementing lazy loading for tab panels, especially the SearchTabPanel which might have heavy dependencies.

Example implementation using React.lazy():

const SearchTabPanel = lazy(() => import('./SearchTabPanel'));

Then wrap the tab panels in a Suspense component:

<Suspense fallback={<LoadingSpinner />}>
  <FileInfoTabPanel/>
  <SearchTabPanel/>
</Suspense>
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1)

31-36: Enhance component documentation for better maintainability.

The current JSDoc lacks essential information about the component's purpose, behaviour, and return value.

Consider expanding the documentation:

 /**
+ * A component that displays search results grouped by page number in expandable accordions.
+ * Each group shows the page number and result count in the header, with detailed results when expanded.
  *
  * @param props
  * @param props.isAllExpanded - Controls whether all result groups are expanded
  * @param props.queryResults - Map of page numbers to their corresponding search results
+ * @returns A React component rendering grouped search results in accordions
+ *
+ * @example
+ * <ResultsGroup
+ *   isAllExpanded={false}
+ *   queryResults={new Map([[1, results]])}
+ * />
  */
🧰 Tools
🪛 GitHub Check: lint-check

[warning] 31-31:
Missing JSDoc block description


[warning] 31-31:
Missing JSDoc @return declaration

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1)

67-67: Improve code readability by breaking down long line.

The line exceeds the maximum length of 100 characters.

- slotProps={{textarea: {ref: searchTextRef}, endDecorator: {sx: {marginBlockStart: 0, display: "block"}}}}
+ slotProps={{
+   textarea: { ref: searchTextRef },
+   endDecorator: {
+     sx: { marginBlockStart: 0, display: "block" }
+   }
+ }}
🧰 Tools
🪛 GitHub Check: lint-check

[warning] 67-67:
This line has a length of 121. Maximum allowed is 100

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4c69bc1 and ac22041.

📒 Files selected for processing (10)
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx (4 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/index.tsx (3 hunks)
  • src/typings/tab.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.css
🧰 Additional context used
🪛 GitHub Check: lint-check
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.css

[failure] 20-20: Stylelint problem
Expected custom property name "--joy-palette-neutral-outlinedBorder" to be kebab-case - https://stylelint.io/user-guide/rules/custom-property-pattern

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx

[warning] 31-31:
Missing JSDoc block description


[warning] 31-31:
Missing JSDoc @return declaration

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx

[warning] 67-67:
This line has a length of 121. Maximum allowed is 100

🪛 Biome
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx

[error] 70-94: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🔇 Additional comments (11)
src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.tsx (3)

1-6: LGTM! Imports are well-organized.

The imports are clean and necessary for the component's functionality.


21-21: LGTM! Export statement is appropriate.

The default export is suitable for this single-component file.


15-19: Verify performance impact in search results context.

Given the reported performance issues with search results, let's verify if this component is rendered multiple times in the results view.

✅ Verification successful

TitleButton component has no performance impact on search results

The TitleButton is used only once in the SearchTabPanel as an expand/collapse toggle for search results. It is not rendered within any result list iterations and has minimal styling. The performance issues with search results are unrelated to this component.

  • TitleButton appears only in the panel header, outside the results rendering loop
  • The search results rendering is handled separately in ResultsGroup component
  • The component has minimal CSS with no expensive calculations or animations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential multiple instances of TitleButton in search results
echo "Checking for TitleButton usage in search-related components..."
rg -l "TitleButton" | grep -i "search"

echo "Checking for list rendering patterns that might cause performance issues..."
ast-grep --pattern 'map($$$)' | grep -A 5 "TitleButton"

Length of output: 465


Script:

#!/bin/bash
# Let's examine the SearchTabPanel component to understand TitleButton usage
echo "Checking SearchTabPanel implementation:"
cat src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx

# Let's also check if TitleButton is used in any array rendering contexts
echo -e "\nChecking for array operations near TitleButton:"
rg -B 5 -A 5 "TitleButton"

Length of output: 8654


Script:

#!/bin/bash
# Let's check the ResultsGroup component to understand how search results are rendered
echo "Checking ResultsGroup implementation:"
cat src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx

# Let's also check the CSS file to see if there are any performance-related styles
echo -e "\nChecking TitleButton styles:"
cat src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.css

Length of output: 3460

src/typings/tab.ts (1)

4-4: Changes look good, eh!

The addition of the SEARCH enum value and its corresponding display name is well-structured and properly integrated into the existing type system. The changes maintain consistency with the established patterns and support the new search functionality.

Also applies to: 14-14

src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx (2)

4-4: LGTM: ButtonGroup import is properly placed

The ButtonGroup import is correctly grouped with other MUI Joy components.


49-55: 🛠️ Refactor suggestion

Optimize ButtonGroup rendering and consider performance implications

  1. The ButtonGroup should be conditionally rendered to avoid unnecessary DOM elements when titleButtons is undefined.
  2. Given the reported performance issues with search results, consider implementing virtualization for large result sets.

Apply this optimization:

-                <ButtonGroup
-                    size={"sm"}
-                    spacing={"1px"}
-                    variant={"plain"}
-                >
-                    {titleButtons}
-                </ButtonGroup>
+                {titleButtons && (
+                    <ButtonGroup
+                        size={"sm"}
+                        spacing={"1px"}
+                        variant={"plain"}
+                    >
+                        {titleButtons}
+                    </ButtonGroup>
+                )}

Let's verify the search results rendering implementation:

src/components/CentralContainer/Sidebar/SidebarTabs/index.tsx (3)

13-13: LGTM: Import statements are properly structured

The imports for SearchIcon and SearchTabPanel follow the project's conventions.

Also applies to: 19-19


33-33: LGTM: Tab configuration follows existing pattern

The search tab entry is properly integrated into the TABS_INFO_LIST array.


99-99: ⚠️ Potential issue

Address re-rendering and performance issues

Based on the reported issues:

  1. Search results only appear after tab switching, suggesting a potential re-rendering problem
  2. Browser freezes with large result sets, indicating performance concerns

Consider these improvements:

  • Pass the active tab value to SearchTabPanel to ensure proper rendering
  • Implement virtualization or pagination for large result sets

Let's verify the SearchTabPanel implementation:

Example implementation:

-                <SearchTabPanel/>
+                <SearchTabPanel
+                    isActive={activeTabName === TAB_NAME.SEARCH}
+                    pageSize={50}
+                />
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1)

1-29: LGTM! Well-organized imports and clear prop interface definition.

The imports are properly structured and the ResultsGroupProps interface clearly defines the component's contract.

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1)

1-30: LGTM! Well-organized imports and clean enum definition.

The code structure follows best practices with logical grouping of imports and clear enum definition.

Comment on lines 97 to 104
<AccordionGroup
disableDivider={true}
size={"sm"}
>
<ResultsGroup
isAllExpanded={isAllExpanded}
queryResults={queryResults}/>
</AccordionGroup>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize rendering performance for large result sets.

To address the re-rendering issues mentioned in the PR comments, consider implementing:

  1. Memoization of the ResultsGroup component
  2. Virtualization for large result sets
+ import { memo } from 'react';
+ import { FixedSizeList } from 'react-window';

- <AccordionGroup
-     disableDivider={true}
-     size={"sm"}
- >
-     <ResultsGroup
-         isAllExpanded={isAllExpanded}
-         queryResults={queryResults}/>
- </AccordionGroup>

+ const MemoizedResultsGroup = memo(ResultsGroup);
+ 
+ <AccordionGroup
+     disableDivider={true}
+     size={"sm"}
+ >
+     <MemoizedResultsGroup
+         isAllExpanded={isAllExpanded}
+         queryResults={queryResults}/>
+ </AccordionGroup>

Consider implementing virtualization if the results can be large:

interface ResultRowProps {
  index: number;
  style: React.CSSProperties;
  data: {
    results: typeof queryResults;
    isExpanded: boolean;
  };
}

const ResultRow = memo(({ index, style, data }: ResultRowProps) => {
  const result = data.results[index];
  return (
    <div style={style}>
      <MemoizedResultsGroup
        isAllExpanded={data.isExpanded}
        queryResults={[result]}
      />
    </div>
  );
});

// In the render method:
<FixedSizeList
  height={400}
  width="100%"
  itemCount={queryResults.length}
  itemSize={50}
  itemData={{ results: queryResults, isExpanded: isAllExpanded }}
>
  {ResultRow}
</FixedSizeList>

Comment on lines 38 to 49
const [isAllExpanded, setIsAllExpanded] = useState<boolean>(true);
const [searchOptions, setSearchOptions] = useState<SEARCH_OPTION[]>([]);
const searchTextRef = useRef<HTMLTextAreaElement>(null);
const {queryResults, startQuery} = useContext(StateContext);
const handleSearch = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if ("Enter" === event.key && searchTextRef.current) {
event.preventDefault();
const isCaseSensitive = searchOptions.includes(SEARCH_OPTION.IS_CASE_SENSITIVE);
const isRegex = searchOptions.includes(SEARCH_OPTION.IS_REGEX);
startQuery(searchTextRef.current.value, isRegex, isCaseSensitive);
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add performance optimizations and error handling.

Given the reported issues with browser freezing on large result sets, consider implementing:

  1. Debounce the search operation to prevent excessive queries
  2. Add loading state to provide user feedback
  3. Implement error handling for failed searches

Here's a suggested implementation:

+ import { useState, useRef, useContext, useCallback } from 'react';
+ import debounce from 'lodash/debounce';

  const SearchTabPanel = () => {
    const [isAllExpanded, setIsAllExpanded] = useState<boolean>(true);
    const [searchOptions, setSearchOptions] = useState<SEARCH_OPTION[]>([]);
+   const [isLoading, setIsLoading] = useState<boolean>(false);
+   const [error, setError] = useState<string | null>(null);
    const searchTextRef = useRef<HTMLTextAreaElement>(null);
    const {queryResults, startQuery} = useContext(StateContext);

-   const handleSearch = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
+   const executeSearch = useCallback(async (searchText: string) => {
+     try {
+       setIsLoading(true);
+       setError(null);
        const isCaseSensitive = searchOptions.includes(SEARCH_OPTION.IS_CASE_SENSITIVE);
        const isRegex = searchOptions.includes(SEARCH_OPTION.IS_REGEX);
-       startQuery(searchTextRef.current.value, isRegex, isCaseSensitive);
+       await startQuery(searchText, isRegex, isCaseSensitive);
+     } catch (err) {
+       setError(err instanceof Error ? err.message : 'Search failed');
+     } finally {
+       setIsLoading(false);
+     }
+   }, [searchOptions, startQuery]);

+   const debouncedSearch = useCallback(
+     debounce((searchText: string) => executeSearch(searchText), 300),
+     [executeSearch]
+   );

+   const handleSearch = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
      if ("Enter" === event.key && searchTextRef.current) {
        event.preventDefault();
+       debouncedSearch(searchTextRef.current.value);
      }
    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [isAllExpanded, setIsAllExpanded] = useState<boolean>(true);
const [searchOptions, setSearchOptions] = useState<SEARCH_OPTION[]>([]);
const searchTextRef = useRef<HTMLTextAreaElement>(null);
const {queryResults, startQuery} = useContext(StateContext);
const handleSearch = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if ("Enter" === event.key && searchTextRef.current) {
event.preventDefault();
const isCaseSensitive = searchOptions.includes(SEARCH_OPTION.IS_CASE_SENSITIVE);
const isRegex = searchOptions.includes(SEARCH_OPTION.IS_REGEX);
startQuery(searchTextRef.current.value, isRegex, isCaseSensitive);
}
};
const [isAllExpanded, setIsAllExpanded] = useState<boolean>(true);
const [searchOptions, setSearchOptions] = useState<SEARCH_OPTION[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const searchTextRef = useRef<HTMLTextAreaElement>(null);
const {queryResults, startQuery} = useContext(StateContext);
const executeSearch = useCallback(async (searchText: string) => {
try {
setIsLoading(true);
setError(null);
const isCaseSensitive = searchOptions.includes(SEARCH_OPTION.IS_CASE_SENSITIVE);
const isRegex = searchOptions.includes(SEARCH_OPTION.IS_REGEX);
await startQuery(searchText, isRegex, isCaseSensitive);
} catch (err) {
setError(err instanceof Error ? err.message : 'Search failed');
} finally {
setIsLoading(false);
}
}, [searchOptions, startQuery]);
const debouncedSearch = useCallback(
debounce((searchText: string) => executeSearch(searchText), 300),
[executeSearch]
);
const handleSearch = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if ("Enter" === event.key && searchTextRef.current) {
event.preventDefault();
debouncedSearch(searchTextRef.current.value);
}
};

@Henry8192 Henry8192 requested a review from junhaoliao October 30, 2024 18:05
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (8)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css (1)

11-15: Multiple !important flags indicate potential CSS architecture issues.

The repeated use of !important suggests that the CSS architecture might need restructuring. Consider:

  1. Using BEM methodology for better CSS organization
  2. Implementing CSS modules to avoid specificity issues
  3. Using more specific selectors if needed
-.result-text {
-    display: inline !important;
-    font-size: 0.875rem !important;
-    font-weight: 400 !important;
-}
+/* Using BEM methodology */
+.search-result__text {
+    display: inline;
+    font-size: 0.875rem;
+    font-weight: 400;
+}
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (2)

9-12: Consider enhancing type documentation.

The interface could benefit from JSDoc comments to describe:

  • Expected format and constraints for the message string
  • Valid ranges and edge cases for matchRange tuple
+/**
+ * @interface ResultProps
+ * @property {string} message - The complete text content to display
+ * @property {[number, number]} matchRange - Tuple of [startIndex, endIndex] where
+ *                                          0 <= startIndex <= endIndex <= message.length
+ */
 interface ResultProps {
     message: string,
     matchRange: [number, number]
 }

14-22: Enhance component documentation.

The JSDoc comments could be more comprehensive by including:

  • Return type annotation
  • Usage examples
  • Edge cases handling
 /**
  * Displays a button containing a message, which highlights a specific range of text.
  *
+ * @example
+ * <Result
+ *   message="Error: File not found"
+ *   matchRange={[7, 11]}  // Highlights "File"
+ * />
  *
  * @param props
  * @param props.message
  * @param props.matchRange A two-element array indicating the start and end indices of the substring
  * to be highlighted.
- * @return
+ * @returns {JSX.Element} A button containing the message with highlighted text
  */
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (3)

1-25: Consider grouping related imports together.

Group imports into logical sections with a blank line between groups:

  1. React core
  2. Third-party components (MUI Joy)
  3. Third-party icons
  4. Internal components and types
 import {
     useContext,
     useRef,
     useState,
 } from "react";
+
 import {
     AccordionGroup,
     IconButton,
     Textarea,
     ToggleButtonGroup,
 } from "@mui/joy";
+
 import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
 import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
+
 import {StateContext} from "../../../../../contexts/StateContextProvider";
 import {
     TAB_DISPLAY_NAMES,
     TAB_NAME,
 } from "../../../../../typings/tab";
 import CustomTabPanel from "../CustomTabPanel";
 import TitleButton from "../TitleButton";
 import ResultsGroup from "./ResultsGroup";

27-30: Add documentation for the SEARCH_OPTION enum.

Document the purpose and usage of each enum value to improve code maintainability.

+/**
+ * Options that modify search behaviour
+ */
 enum SEARCH_OPTION {
+    /** Enable case-sensitive search */
     IS_CASE_SENSITIVE = "isCaseSensitive",
+    /** Enable regular expression search */
     IS_REGEX = "isRegex"
 }

63-99: Enhance textarea accessibility and user experience.

Consider the following improvements:

  1. Add aria-label for screen readers
  2. Set minRows for consistent height
  3. Add maxLength to prevent excessive input
 <Textarea
     maxRows={7}
+    minRows={2}
+    maxLength={1000}
     placeholder={"Search"}
     size={"sm"}
     sx={{flexDirection: "row"}}
+    aria-label="Search input"
     endDecorator={
🧰 Tools
🪛 Biome

[error] 69-93: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

src/contexts/StateContextProvider.tsx (2)

Line range hint 310-319: Consider optimizing query results handling for better performance

The current implementation deep clones the entire results map on each update, which could be inefficient for large result sets. Consider these alternatives:

  1. Use immutable update patterns:
-                    v = structuredClone(v);
-                    args.results.forEach((queryPageNum, resultsPerPage) => {
-                        if (false === v.has(queryPageNum)) {
-                            v.set(queryPageNum, []);
-                        }
-                        v.get(queryPageNum)?.push(...resultsPerPage);
-                    });
+                    const newResults = new Map(v);
+                    args.results.forEach((resultsPerPage, queryPageNum) => {
+                        const existingResults = newResults.get(queryPageNum) || [];
+                        newResults.set(queryPageNum, [...existingResults, ...resultsPerPage]);
+                    });
+                    return newResults;
  1. Consider implementing pagination or virtualization in the UI to handle large result sets more efficiently.

Line range hint 304-321: Consider implementing a more efficient search results management strategy

Given the performance issues with large result sets, consider these architectural improvements:

  1. Implement windowing/pagination in the worker to send smaller result chunks
  2. Use a more efficient data structure for results storage
  3. Consider using a virtual scroll implementation in the UI

Would you like assistance in implementing any of these architectural improvements?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ac22041 and 908b6db.

📒 Files selected for processing (4)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1 hunks)
  • src/contexts/StateContextProvider.tsx (1 hunks)
🧰 Additional context used
📓 Learnings (2)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (3)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-10-28T18:40:29.816Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:9-12
Timestamp: 2024-10-28T18:39:28.680Z
Learning: In the `Result` component (`Result.tsx`), if `end < start`, the for loop won't run and it will return nothing, so additional validation for `matchRange` indices is not necessary.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-10-28T18:46:21.189Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (2)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-10-28T18:40:29.816Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-10-28T18:46:21.189Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
🪛 GitHub Check: lint-check
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css

[failure] 20-20: Stylelint problem
Delete "·" (prettier/prettier)


[failure] 21-21: Stylelint problem
Expected "overflow" to come before "flex" (order/properties-order)

🪛 Biome
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx

[error] 69-93: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🔇 Additional comments (3)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1)

23-59: Consider performance optimizations for large result sets.

While this component is lightweight, the PR mentions browser freezing with large results. Consider these architectural improvements:

  1. Implement virtualization in the parent component using react-window
  2. Add pagination or infinite scroll
  3. Chunk large result sets on the service side
src/contexts/StateContextProvider.tsx (2)

Line range hint 308-321: Investigate potential React re-rendering issue

The reported behaviour where "search results are only displayed after switching tabs" suggests a potential React re-rendering issue that might not be fully addressed by the state update fix.

Let's check for proper React hooks usage in the search results display component:

#!/bin/bash
# Look for SearchTabPanel component implementation
ast-grep --pattern 'const SearchTabPanel = $_'

# Check for proper useEffect dependencies
rg -A 5 "useEffect.*queryResults"

310-310: ⚠️ Potential issue

Good catch on preventing state mutations, but consider performance impact

While using structuredClone prevents direct state mutations, it might impact performance when handling large result sets. This could be contributing to the reported browser freezing issues.

Let's verify the size of query results being handled:

✅ Verification successful

Using structuredClone is appropriate here, performance impact should be minimal

The verification shows that QueryResults is a Map containing arrays of QueryResultsType, which is a relatively lightweight structure. Since it's a Map and not a deeply nested object, using structuredClone for immutability is a reasonable approach that shouldn't significantly impact performance.

  • The Map structure suggests the results are organized by line numbers (number keys)
  • The implementation properly maintains state immutability
  • The browser freezing issue is likely unrelated to this clone operation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for query result size logging
rg -A 2 "QUERY_RESULT.*results\.forEach" 

Length of output: 9006


Script:

#!/bin/bash
# Let's check how query results are handled in the state context
ast-grep --pattern 'case "QUERY_RESULT": {
  $$$
}'

# Also check the type of queryResults
rg -A 2 'queryResults.*:.*=' 

Length of output: 990


Script:

#!/bin/bash
# Let's check the QueryResults type definition
rg -A 5 "type QueryResults"

# And check how the results are processed in StateContextProvider
rg -A 10 "QUERY_RESULT.*case" src/contexts/StateContextProvider.tsx

Length of output: 373

Comment on lines 1 to 9
.result-button {
user-select: none;

overflow: hidden;
display: flex !important;

text-overflow: ellipsis;
white-space: nowrap;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider removing the !important flag from the display property.

The use of !important suggests potential specificity issues. Consider increasing selector specificity or restructuring CSS to avoid specificity wars.

-    display: flex !important;
+    display: flex;

If you need to override conflicting styles, consider using a more specific selector like:

.sidebar .search-tab-panel .result-button {
    display: flex;
}

Comment on lines 17 to 23
.before-match,
.match,
.after-match {
flex: 1 1 0; /* Allow each item to take an equal part */
overflow: hidden;
text-overflow: ellipsis;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider performance optimizations for text truncation.

Given the PR objectives mentioning browser freezing with large search results, consider:

  1. Using CSS containment to optimize rendering
  2. Adding will-change for smoother animations if transitions are planned
 .before-match,
 .match,
 .after-match {
-    flex: 1 1 0;  /* Allow each item to take an equal part */
-    overflow: hidden;
+    flex: 1 1 0;
+    contain: content;
+    overflow: hidden;
     text-overflow: ellipsis;
 }

Also, fix the linting issues by reordering properties:

 .before-match,
 .match,
 .after-match {
+    overflow: hidden;
     flex: 1 1 0;
-    overflow: hidden;
     text-overflow: ellipsis;
 }

Committable suggestion was skipped due to low confidence.

🧰 Tools
🪛 GitHub Check: lint-check

[failure] 20-20: Stylelint problem
Delete "·" (prettier/prettier)


[failure] 21-21: Stylelint problem
Expected "overflow" to come before "flex" (order/properties-order)

Comment on lines 34 to 58
return (
<ListItemButton className={"result-button"}>
<Typography
className={"result-text before-match"}
level={"body-xs"}
>
{beforeMatch}
</Typography>
<Typography
className={"result-text match"}
level={"body-xs"}
sx={{
backgroundColor: "warning.softBg",
}}
>
{match}
</Typography>
<Typography
className={"result-text after-match"}
level={"body-xs"}
>
{afterMatch}
</Typography>
</ListItemButton>
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance accessibility and error handling.

The component needs improvements in:

  1. Accessibility attributes for screen readers
  2. Keyboard navigation support
  3. Error boundaries for invalid ranges
-    <ListItemButton className={"result-button"}>
+    <ListItemButton
+        className={"result-button"}
+        role="option"
+        aria-selected={false}
+        onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+                // Handle selection
+            }
+        }}
+    >
         <Typography
             className={"result-text before-match"}
             level={"body-xs"}
+            component="span"
         >
             {beforeMatch}
         </Typography>
         <Typography
             className={"result-text match"}
             level={"body-xs"}
+            component="mark"
             sx={{
                 backgroundColor: "warning.softBg",
             }}
+            aria-label={`Matched text: ${match}`}
         >
             {match}
         </Typography>

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (2)

1-5: Consolidate React imports into a single statement

The imports from "react" can be consolidated into one statement for better readability and consistency.

Apply this diff to streamline the imports:

-import {
-    useEffect,
-    useState,
-} from "react";
-import * as React from "react";
+import React, { useEffect, useState } from "react";

31-36: Add missing JSDoc description and @return declaration

The JSDoc comment for the ResultsGroup component is missing a block description and the @return declaration. Providing these will enhance documentation and maintainability.

Apply this diff to improve the JSDoc:

 /**
+ * Displays search results grouped by page numbers in accordions.
  *
  * @param props
  * @param props.isAllExpanded - Indicates if all accordions should be expanded.
  * @param props.queryResults - The search results to display.
+ * @returns {JSX.Element} The rendered ResultsGroup component.
  */
🧰 Tools
🪛 GitHub Check: lint-check

[warning] 31-31:
Missing JSDoc block description


[warning] 31-31:
Missing JSDoc @return declaration

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 908b6db and b931505.

📒 Files selected for processing (1)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: lint-check
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx

[warning] 31-31:
Missing JSDoc block description


[warning] 31-31:
Missing JSDoc @return declaration


[warning] 59-59:
React Hook useEffect has a missing dependency: 'expandedMap'. Either include it or remove the dependency array

Comment on lines 52 to 59
useEffect(() => {
const newExpandedMap = Object.fromEntries(
Object.entries(expandedMap).map(([key]) => [key,
isAllExpanded])
);

setExpandedMap(newExpandedMap);
}, [isAllExpanded]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Include expandedMap in useEffect dependency array or refactor effect

The useEffect hook uses expandedMap but doesn't include it in the dependency array. This might lead to unexpected behaviour due to stale closures. Consider including expandedMap in the dependencies or refactor the effect.

Option 1: Include expandedMap in the dependency array:

-        }, [isAllExpanded]);
+        }, [isAllExpanded, expandedMap]);

Option 2: Refactor the useEffect to use a functional state update, avoiding the need for expandedMap in dependencies:

        useEffect(() => {
-            const newExpandedMap = Object.fromEntries(
-                Object.entries(expandedMap).map(([key]) => [key, isAllExpanded])
-            );
-
-            setExpandedMap(newExpandedMap);
+            setExpandedMap((prevExpandedMap) => {
+                const newExpandedMap = Object.fromEntries(
+                    Object.keys(prevExpandedMap).map((key) => [key, isAllExpanded])
+                );
+                return newExpandedMap;
+            });
        }, [isAllExpanded]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const newExpandedMap = Object.fromEntries(
Object.entries(expandedMap).map(([key]) => [key,
isAllExpanded])
);
setExpandedMap(newExpandedMap);
}, [isAllExpanded]);
useEffect(() => {
setExpandedMap((prevExpandedMap) => {
const newExpandedMap = Object.fromEntries(
Object.keys(prevExpandedMap).map((key) => [key, isAllExpanded])
);
return newExpandedMap;
});
}, [isAllExpanded]);
🧰 Tools
🪛 GitHub Check: lint-check

[warning] 59-59:
React Hook useEffect has a missing dependency: 'expandedMap'. Either include it or remove the dependency array

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (3)

1-5: Remove redundant React import

The separate import of React is unnecessary since you're already importing specific hooks. Modern React doesn't require the namespace import when using JSX.

import {
    useEffect,
    useState,
} from "react";
-import * as React from "react";

63-78: Add cleanup function to prevent memory leaks

The useEffect should include a cleanup function to handle component unmounting, especially important when dealing with large datasets.

 useEffect(() => {
     setExpandedMap((prevMap) => {
         const updatedMap = {...prevMap};
         queryResults.forEach((_, pageNum) => {
             if (!(pageNum in updatedMap)) {
                 updatedMap[pageNum] = isAllExpanded;
             }
         });
         return updatedMap;
     });
+    return () => {
+        // Cleanup by resetting the map when component unmounts
+        setExpandedMap({});
+    };
 }, [
     isAllExpanded,
     queryResults,
 ]);

26-29: Consider implementing server-side pagination

The current architecture loads all results client-side, which isn't scalable. Consider implementing server-side pagination and filtering to handle large result sets more efficiently.

Key considerations:

  1. Modify the QueryResults type to include pagination metadata
  2. Implement lazy loading of page results
  3. Add server-side filtering capabilities

Would you like assistance in designing a paginated API contract for this component?

src/services/LogFileManager/index.ts (2)

1-1: Consider code splitting and making MAX_RESULT_COUNT configurable.

The increased ESLint line limit suggests this file might benefit from being split into smaller, more focused modules. Additionally, MAX_RESULT_COUNT should ideally be:

  1. Configurable via environment or runtime settings
  2. Determined through performance testing with various result set sizes

Consider moving MAX_RESULT_COUNT to a configuration file:

-const MAX_RESULT_COUNT = 1_000;
+import { SEARCH_CONFIG } from '../../utils/config';
+const { MAX_RESULT_COUNT } = SEARCH_CONFIG;

Also applies to: 36-36


56-56: Add documentation for query count tracking.

The #queryCount field and its reset logic would benefit from documentation explaining their purpose in managing result limits.

-    #queryCount: number = 0;
+    /** Tracks the number of results found in the current query to enforce MAX_RESULT_COUNT limit */
+    #queryCount: number = 0;

Also applies to: 294-294

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b931505 and 3ddd96a.

📒 Files selected for processing (2)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
  • src/services/LogFileManager/index.ts (6 hunks)
🧰 Additional context used
🪛 GitHub Check: lint-check
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx

[warning] 61-61:
React Hook useEffect has a missing dependency: 'expandedMap'. Either include it or remove the dependency array

🔇 Additional comments (1)
src/services/LogFileManager/index.ts (1)

326-326: ⚠️ Potential issue

Improve result limiting efficiency and user feedback.

The current implementation has several concerns:

  1. Double result limit checking is inefficient
  2. Users aren't notified when the result limit is reached
  3. Abrupt termination might contribute to the re-rendering issues mentioned in PR comments

Consider this refactor:

     #queryChunkAndScheduleNext (
         queryId: number,
         chunkBeginIdx: number,
         queryRegex: RegExp
     ): void {
-        if (queryId !== this.#queryId || MAX_RESULT_COUNT < this.#queryCount) {
+        if (queryId !== this.#queryId) {
             return;
         }
         const chunkEndIdx = Math.min(chunkBeginIdx + QUERY_CHUNK_SIZE, this.#numEvents);
         const results: QueryResults = new Map();
+        let reachedLimit = false;
         const decodedEvents = this.#decoder.decodeRange(
             chunkBeginIdx,
             chunkEndIdx,
             null !== this.#decoder.getFilteredLogEventMap()
         );

         decodedEvents?.forEach(([message, , , logEventNum]) => {
+            if (reachedLimit) return;
             const matchResult = message.match(queryRegex);
             if (null !== matchResult && "number" === typeof matchResult.index) {
                 this.#queryCount++;
-                if (MAX_RESULT_COUNT < this.#queryCount) {
-                    return;
+                if (MAX_RESULT_COUNT < this.#queryCount) {
+                    reachedLimit = true;
+                    results.set('metadata', { limitReached: true, totalFound: this.#queryCount });
+                    return;
                 }
                 const pageNum = Math.ceil(logEventNum / this.#pageSize);

Let's verify the re-rendering issue:

Also applies to: 341-344

Comment on lines 82 to 83
{Array.from(queryResults.entries()).map(([pageNum, results]) => (
<Accordion
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement immediate performance optimizations

Given the reported browser freezing issues, here are some immediate optimizations to implement while working on virtualization:

  1. Batch render updates
  2. Add result limit with "Show More" button
+const INITIAL_RESULTS_LIMIT = 50;
+
 const ResultsGroup = ({
     isAllExpanded,
     queryResults,
 }: ResultsGroupProps) => {
+    const [resultsLimit, setResultsLimit] = useState(INITIAL_RESULTS_LIMIT);
+    
     // ... existing code ...
     
     return (
         <>
             {Array.from(queryResults.entries()).map(([pageNum, results]) => (
                 <Accordion
                     // ... existing props ...
                 >
                     // ... existing AccordionSummary ...
                     <AccordionDetails className={"results-group-content"}>
                         <List size={"sm"}>
-                            {results.map((r) => (
+                            {results.slice(0, resultsLimit).map((r) => (
                                 <Result
                                     key={r.logEventNum}
                                     matchRange={r.matchRange}
                                     message={r.message}/>
                             ))}
+                            {results.length > resultsLimit && (
+                                <Button
+                                    onClick={() => setResultsLimit(prev => prev + INITIAL_RESULTS_LIMIT)}
+                                    variant="outlined"
+                                    fullWidth
+                                >
+                                    Show More Results
+                                </Button>
+                            )}
                         </List>
                     </AccordionDetails>
                 </Accordion>
             ))}
         </>
     );
 };

Also applies to: 114-119

@junhaoliao
Copy link
Collaborator

TODO: display search progress

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (5)
src/components/MenuBar/SmallIconButton.tsx (3)

Line range hint 1-18: Implementation looks good, but could benefit from prop documentation.

The component is well-structured and follows React best practices. The use of TypeScript's React.ComponentProps ensures proper type safety.

Consider enhancing the JSDoc documentation to include:

  • Description of inherited props from IconButton
  • Example usage
  • Return value description

Here's a suggested documentation improvement:

 /**
  * A small JoyUI IconButton.
  *
+ * This component wraps MUI Joy's IconButton with a predefined small size,
+ * while maintaining all other IconButton functionality.
  *
  * @param props The props for IconButton. Accepts all props from MUI Joy's IconButton
+ *             component (e.g., color, disabled, onClick).
+ * @example
+ * <SmallIconButton onClick={() => console.log('clicked')}>
+ *   <SearchIcon />
+ * </SmallIconButton>
  * @return A small-sized IconButton component
  */

Line range hint 11-15: Consider adding a display name for debugging purposes.

The component could benefit from a explicit display name for better debugging experience.

 const SmallIconButton = (props: React.ComponentProps<typeof IconButton>) => (
     <IconButton
         size={"sm"}
         {...props}/>
 );
+SmallIconButton.displayName = 'SmallIconButton';

2-3: Consider grouping imports by type.

While not critical, it's generally good practice to group imports with a single blank line between third-party and local imports.

 import React from "react";
-
 import {IconButton} from "@mui/joy";
+
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1)

27-30: Add documentation for the search options enum.

Consider adding JSDoc comments to document the purpose and behaviour of each enum value, especially for the regex option which may have specific pattern requirements.

 enum SEARCH_OPTION {
+    /** Enables case-sensitive text matching */
     IS_CASE_SENSITIVE = "isCaseSensitive",
+    /** Enables regular expression pattern matching */
     IS_REGEX = "isRegex"
 }
src/typings/worker.ts (1)

Line range hint 123-127: Consider adding JSDoc comments for better type documentation.

The QueryResultsType interface would benefit from documentation explaining:

  • Purpose of the matchRange tuple and its values
  • Relationship between logEventNum and the actual log entry
  • Format expectations for the message field
+/**
+ * Represents a single search result within a log file.
+ * @property logEventNum - The sequential number of the log event containing the match
+ * @property message - The complete message content of the matching log event
+ * @property matchRange - Tuple of [startIndex, endIndex] indicating the matched text position
+ */
 interface QueryResultsType {
     logEventNum: number;
     message: string;
     matchRange: TextRange;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3ddd96a and 0889361.

📒 Files selected for processing (8)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.css (1 hunks)
  • src/components/MenuBar/SmallIconButton.tsx (1 hunks)
  • src/services/LogFileManager/index.ts (6 hunks)
  • src/typings/worker.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx
  • src/services/LogFileManager/index.ts
🧰 Additional context used
📓 Learnings (1)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (2)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-10-28T18:40:29.816Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-10-28T18:46:21.189Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
🪛 Biome
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx

[error] 69-93: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🔇 Additional comments (4)
src/components/CentralContainer/Sidebar/SidebarTabs/TitleButton.css (1)

1-5: Consider accessibility implications and document style overrides.

Setting minimum dimensions to zero might affect the button's touch target size on mobile devices. The Web Content Accessibility Guidelines (WCAG) recommend touch targets be at least 44x44 pixels.

Run this script to check for accessibility documentation:

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1)

37-41: 🛠️ Refactor suggestion

Optimize component performance with memoization.

To address the re-rendering issues mentioned in the PR comments, consider implementing:

  1. Memoize expensive computations
  2. Use callback functions for state updates
+import React, { useCallback, useMemo } from 'react';
+
 const SearchTabPanel = () => {
     const [isAllExpanded, setIsAllExpanded] = useState<boolean>(true);
     const [searchOptions, setSearchOptions] = useState<SEARCH_OPTION[]>([]);
     const searchTextRef = useRef<HTMLTextAreaElement>(null);
     const {queryResults, startQuery} = useContext(StateContext);
+    
+    const setIsAllExpandedCallback = useCallback((v: boolean | ((prev: boolean) => boolean)) => {
+        setIsAllExpanded(v);
+    }, []);
⛔ Skipped due to learnings
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-10-28T18:46:21.189Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
src/typings/worker.ts (2)

186-186: Export statement looks good.

The addition of QueryResultsType to the exports follows the established pattern and maintains proper alphabetical ordering.


Line range hint 129-129: Consider pagination strategy for large result sets.

Given the reported browser freezing with large search results, consider:

  • Implementing pagination or virtual scrolling support in the type definition
  • Adding a count or size limit property
  • Including metadata for result set navigation

This aligns with the performance concerns mentioned in the PR comments.

Also applies to: 166-166

Comment on lines 1 to 5
.sidebar-tab-title-button {
z-index: 0 !important;
min-width: 0 !important;
min-height: 0 !important;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider alternatives to !important declarations.

Using !important suggests specificity conflicts with MUI's styles. Consider these alternatives:

  1. Use more specific selectors
  2. Customize MUI's theme to override default styles
  3. Use MUI's sx prop for component-specific styles

Example of using a more specific selector:

-.sidebar-tab-title-button {
+.sidebar-tabs .MuiIconButton-root.sidebar-tab-title-button {
     z-index: 0;
     min-width: 0;
     min-height: 0;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.sidebar-tab-title-button {
z-index: 0 !important;
min-width: 0 !important;
min-height: 0 !important;
}
.sidebar-tabs .MuiIconButton-root.sidebar-tab-title-button {
z-index: 0;
min-width: 0;
min-height: 0;
}

Comment on lines 100 to 111
<AccordionGroup
disableDivider={true}
size={"sm"}
>
{Array.from(queryResults.entries()).map(([pageNum, results], index) => (
<ResultsGroup
isAllExpanded={isAllExpanded}
key={index}
pageNum={pageNum}
results={results}/>
))}
</AccordionGroup>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize results rendering for large datasets.

To address the browser freezing issue with large result sets, implement:

  1. Virtualized list rendering
  2. Pagination or infinite scroll
  3. Memoized ResultsGroup components
+import { FixedSizeList } from 'react-window';
+
+const MemoizedResultsGroup = memo(ResultsGroup);
+
+const ResultRow = ({ index, style, data }) => {
+    const [pageNum, results] = data.results[index];
+    return (
+        <div style={style}>
+            <MemoizedResultsGroup
+                isAllExpanded={data.isAllExpanded}
+                pageNum={pageNum}
+                results={results}
+            />
+        </div>
+    );
+};
+
-<AccordionGroup
-    disableDivider={true}
-    size={"sm"}
->
-    {Array.from(queryResults.entries()).map(([pageNum, results], index) => (
-        <ResultsGroup
-            isAllExpanded={isAllExpanded}
-            key={index}
-            pageNum={pageNum}
-            results={results}/>
-    ))}
-</AccordionGroup>
+<FixedSizeList
+    height={400}
+    width="100%"
+    itemCount={queryResults.size}
+    itemSize={50}
+    itemData={{
+        results: Array.from(queryResults.entries()),
+        isAllExpanded
+    }}
+>
+    {ResultRow}
+</FixedSizeList>

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 63 to 99
<Textarea
maxRows={7}
placeholder={"Search"}
size={"sm"}
sx={{flexDirection: "row", zIndex: 0}}
endDecorator={
<>
<ToggleButtonGroup
size={"sm"}
spacing={0.3}
sx={{borderRadius: "2px"}}
value={searchOptions}
variant={"plain"}
onChange={(_, newValue) => {
setSearchOptions(newValue);
}}
>
<IconButton
sx={{fontFamily: "Inter"}}
value={SEARCH_OPTION.IS_CASE_SENSITIVE}
>
Aa
</IconButton>
<IconButton
sx={{fontFamily: "Inter"}}
value={SEARCH_OPTION.IS_REGEX}
>
.*
</IconButton>
</ToggleButtonGroup>
</>
}
slotProps={{
textarea: {ref: searchTextRef},
endDecorator: {sx: {marginBlockStart: 0, display: "block"}},
}}
onKeyDown={handleSearch}/>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve search input accessibility and structure.

The search input needs accessibility improvements and structural cleanup:

  1. Remove unnecessary Fragment
  2. Add ARIA labels
  3. Add loading indicator
 <Textarea
     maxRows={7}
-    placeholder={"Search"}
+    placeholder={"Enter search query"}
+    aria-label="Search input"
     size={"sm"}
     sx={{flexDirection: "row", zIndex: 0}}
     endDecorator={
-        <>
         <ToggleButtonGroup
             size={"sm"}
             spacing={0.3}
             sx={{borderRadius: "2px"}}
             value={searchOptions}
             variant={"plain"}
+            aria-label="Search options"
             onChange={(_, newValue) => {
                 setSearchOptions(newValue);
             }}
         >
             <IconButton
                 sx={{fontFamily: "Inter"}}
                 value={SEARCH_OPTION.IS_CASE_SENSITIVE}
+                aria-label="Toggle case sensitivity"
             >
                 Aa
             </IconButton>
             <IconButton
                 sx={{fontFamily: "Inter"}}
                 value={SEARCH_OPTION.IS_REGEX}
+                aria-label="Toggle regex mode"
             >
                 .*
             </IconButton>
         </ToggleButtonGroup>
-        </>
     }
     slotProps={{
         textarea: {ref: searchTextRef},
         endDecorator: {sx: {marginBlockStart: 0, display: "block"}},
     }}
-    onKeyDown={handleSearch}/>
+    onKeyDown={handleSearch}
+    disabled={isLoading}
+/>
+{isLoading && <CircularProgress size="sm" />}
+{error && <Alert color="danger">{error}</Alert>}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 69-93: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

Comment on lines 42 to 49
const handleSearch = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if ("Enter" === event.key && searchTextRef.current) {
event.preventDefault();
const isCaseSensitive = searchOptions.includes(SEARCH_OPTION.IS_CASE_SENSITIVE);
const isRegex = searchOptions.includes(SEARCH_OPTION.IS_REGEX);
startQuery(searchTextRef.current.value, isRegex, isCaseSensitive);
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance search handler with debouncing and error handling.

The current implementation may cause performance issues with large result sets. Consider implementing:

  1. Debounce the search operation
  2. Add loading state
  3. Implement error handling
  4. Validate regex patterns
+import { debounce } from 'lodash';
+
 const SearchTabPanel = () => {
+    const [isLoading, setIsLoading] = useState(false);
+    const [error, setError] = useState<string | null>(null);
+
+    const executeSearch = useCallback(async (
+        searchText: string,
+        isRegex: boolean,
+        isCaseSensitive: boolean
+    ) => {
+        try {
+            if (isRegex) {
+                new RegExp(searchText); // Validate regex
+            }
+            setIsLoading(true);
+            setError(null);
+            await startQuery(searchText, isRegex, isCaseSensitive);
+        } catch (err) {
+            setError(err instanceof Error ? err.message : 'Search failed');
+        } finally {
+            setIsLoading(false);
+        }
+    }, [startQuery]);
+
+    const debouncedSearch = useMemo(
+        () => debounce(executeSearch, 300),
+        [executeSearch]
+    );
+
     const handleSearch = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
         if ("Enter" === event.key && searchTextRef.current) {
             event.preventDefault();
             const isCaseSensitive = searchOptions.includes(SEARCH_OPTION.IS_CASE_SENSITIVE);
             const isRegex = searchOptions.includes(SEARCH_OPTION.IS_REGEX);
-            startQuery(searchTextRef.current.value, isRegex, isCaseSensitive);
+            debouncedSearch(searchTextRef.current.value, isRegex, isCaseSensitive);
         }
     };

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
src/services/MainWorker.ts (1)

44-46: Documentation needs enhancement, eh?

The function documentation should be more comprehensive for this critical worker communication function. Consider adding:

  • Parameter types and constraints
  • Return value documentation
  • Example usage
  • Impact on performance with large result sets

Here's a suggested improvement:

/**
 * Post a response for a chunk of query results.
 *
+ * @param {number} queryProgress - Search progress percentage (0-100)
+ * @param {QueryResults} queryResults - Chunk of search results
+ * @returns {void} Posts message to main thread
+ * 
+ * @example
+ * onQueryResults(50, {
+ *   matches: [...],
+ *   isComplete: false
+ * });
 */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0889361 and d8cd28c.

📒 Files selected for processing (3)
  • src/services/LogFileManager/index.ts (8 hunks)
  • src/services/MainWorker.ts (1 hunks)
  • src/typings/worker.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/services/LogFileManager/index.ts
  • src/typings/worker.ts
🔇 Additional comments (1)
src/services/MainWorker.ts (1)

47-48: ⚠️ Potential issue

Performance improvements needed for large result sets

Given the reported browser freezing issues with large search results, consider these improvements:

  1. Validate and normalize the progress value
  2. Implement result batching
  3. Add throttling for UI responsiveness

Here's a suggested implementation:

-const onQueryResults = (queryProgress: number, queryResults: QueryResults) => {
-    postResp(WORKER_RESP_CODE.QUERY_RESULT, {progress: queryProgress, results: queryResults});
-};
+const BATCH_SIZE = 1000;
+const THROTTLE_MS = 16; // ~60fps
+
+const onQueryResults = (queryProgress: number, queryResults: QueryResults) => {
+    // Normalize progress to 0-100 range
+    const normalizedProgress = Math.max(0, Math.min(100, queryProgress));
+    
+    // Batch large result sets
+    if (queryResults.matches.length > BATCH_SIZE) {
+        const batches = chunk(queryResults.matches, BATCH_SIZE);
+        batches.forEach((batch, index) => {
+            const batchResults = {
+                ...queryResults,
+                matches: batch,
+                isPartialBatch: index < batches.length - 1
+            };
+            
+            // Throttle to maintain UI responsiveness
+            setTimeout(() => {
+                postResp(WORKER_RESP_CODE.QUERY_RESULT, {
+                    progress: normalizedProgress,
+                    results: batchResults
+                });
+            }, index * THROTTLE_MS);
+        });
+    } else {
+        postResp(WORKER_RESP_CODE.QUERY_RESULT, {
+            progress: normalizedProgress,
+            results: queryResults
+        });
+    }
+};

This implementation:

  • Prevents UI freezing by batching large results
  • Maintains responsiveness with throttling
  • Provides accurate progress reporting

Let's verify the current result handling:

@Henry8192
Copy link
Collaborator Author

Henry8192 commented Nov 6, 2024

FYI, the current progress is using Math.max(chunkEndIdx / this.#numEvents, this.#queryCount / MAX_RESULT_COUNT), a decimal starting from 0 to 1. This can guarantee the search progress is 1 in the end, reguardless of whether queryResults being capped by MAX_RESULT_COUNT.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (6)
src/components/StatusBar/index.tsx (2)

Line range hint 34-56: Optimize component performance for large result sets

Given the reported browser freezing with large result sets, we should optimize the component's rendering performance.

Consider these optimizations:

+import {useContext, useMemo, useCallback} from "react";

 const StatusBar = () => {
     const {uiState, numEvents, queryProgress = 0} = useContext(StateContext);
     const {logEventNum} = useContext(UrlContext);
 
+    const formattedProgress = useMemo(() => {
+        return queryProgress === 1 
+            ? "Search complete" 
+            : `Searching... ${Math.round(queryProgress * 100)}%`;
+    }, [queryProgress]);
+
+    const handleCopyLink = useCallback(() => {
+        copyPermalinkToClipboard({}, {});
+    }, []);

     return (
         <Sheet className={"status-bar"}>
             <Typography className={"status-message"}>
-                {queryProgress}
+                {formattedProgress}
             </Typography>
             <Button
                 color={"primary"}
                 disabled={isDisabled(uiState, UI_ELEMENT.LOG_EVENT_NUM_DISPLAY)}
                 size={"sm"}
                 variant={"soft"}
-                onClick={handleCopyLinkButtonClick}
+                onClick={handleCopyLink}
             >

40-40: Enhance progress indicator with visual feedback

The current text-only progress display could be improved with visual indicators.

Consider adding a progress bar component:

+import {LinearProgress} from "@mui/joy";

// ... in the render method ...
 <Typography className={"status-message"}>
-    {queryProgress}
+    <LinearProgress
+        determinate
+        size="sm"
+        value={queryProgress * 100}
+        sx={{ width: 200, mr: 2, display: 'inline-block' }}
+    />
+    {formattedProgress}
 </Typography>
src/services/LogFileManager/index.ts (4)

36-36: Document the rationale for MAX_RESULT_COUNT value.

The choice of 1,000 as the maximum result count appears arbitrary. Please add a comment explaining why this specific limit was chosen and its implications on memory usage and performance.


52-52: Consider using a more descriptive type for query progress.

The queryProgress parameter is a number between 0 and 1, but this isn't immediately clear from the type signature. Consider creating a specific type:

type QueryProgress = number; // Value between 0 and 1 representing search progress

This would make the API more self-documenting.

Also applies to: 74-74, 118-118


339-365: Optimize result collection for large datasets.

The current implementation might cause performance issues with large datasets:

  1. All results are kept in memory until MAX_RESULT_COUNT is reached
  2. String matching is done synchronously which could block the UI

Consider:

  1. Using a streaming approach to emit results in smaller batches
  2. Moving the regex matching to a Web Worker

Line range hint 372-376: Add error handling for deferred execution.

The deferred execution of the next chunk could fail silently. Consider adding error handling:

 if (chunkEndIdx < this.#numEvents && MAX_RESULT_COUNT > this.#queryCount) {
-    defer(() => {
+    defer(() => {
+      try {
         this.#queryChunkAndScheduleNext(queryId, chunkEndIdx, queryRegex);
-    });
+      } catch (error) {
+        console.error('Error processing search chunk:', error);
+        // Notify the UI that the search failed
+        this.#onQueryResults(
+          { progress: chunkEndIdx / this.#numEvents, error: true },
+          new Map()
+        );
+      }
+    });
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d8cd28c and 4709312.

📒 Files selected for processing (5)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1 hunks)
  • src/components/StatusBar/index.tsx (1 hunks)
  • src/contexts/StateContextProvider.tsx (6 hunks)
  • src/services/LogFileManager/index.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx
🔇 Additional comments (5)
src/components/StatusBar/index.tsx (1)

34-34: Verify progress calculation accuracy

Based on the PR comments, there's concern about progress calculation when maximum results are reached.

Let's verify the progress calculation implementation:

src/contexts/StateContextProvider.tsx (4)

66-66: LGTM: Query progress type declaration and default value

The addition of queryProgress as a number type is appropriate for tracking search progress values between 0 and 1.

Also applies to: 89-89


513-514: LGTM: Context value updates

The addition of queryProgress and queryResults to the context value object is consistent with the interface definition and follows the established pattern.


236-249: Verify consistent line-breaking style for long variable declarations

The line-breaking style for beginLineNumToLogEventNumRef differs from other declarations in the file.

Consider maintaining consistent line-breaking style across all variable declarations:

-    const beginLineNumToLogEventNumRef =
-        useRef<BeginLineNumToLogEventNumMap>(STATE_DEFAULT.beginLineNumToLogEventNum);
+    const beginLineNumToLogEventNumRef = useRef<BeginLineNumToLogEventNumMap>(
+        STATE_DEFAULT.beginLineNumToLogEventNum
+    );

Line range hint 313-323: Consider optimizing query results aggregation

While using structuredClone correctly prevents state mutations, the current implementation could be optimized:

  1. The nested forEach loop with array spread could be inefficient for large result sets
  2. Multiple Map operations could be consolidated

Consider this more efficient approach:

-                setQueryResults((v) => {
-                    v = structuredClone(v);
-                    args.results.forEach((resultsPerPage, queryPageNum) => {
-                        if (false === v.has(queryPageNum)) {
-                            v.set(queryPageNum, []);
-                        }
-                        v.get(queryPageNum)?.push(...resultsPerPage);
-                    });
-
-                    return v;
-                });
+                setQueryResults((prevResults) => {
+                    const newResults = new Map(prevResults);
+                    for (const [queryPageNum, results] of args.results) {
+                        newResults.set(
+                            queryPageNum,
+                            [...(newResults.get(queryPageNum) || []), ...results]
+                        );
+                    }
+                    return newResults;
+                });

This approach:

  • Eliminates unnecessary checks and method calls
  • Uses a more efficient iteration method
  • Maintains immutability while reducing memory operations

@@ -31,13 +31,13 @@ const handleCopyLinkButtonClick = () => {
* @return
*/
const StatusBar = () => {
const {uiState, numEvents} = useContext(StateContext);
const {uiState, numEvents, queryProgress} = useContext(StateContext);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Format and validate the progress value for better user experience

The raw progress value needs formatting and validation to provide meaningful feedback to users.

Apply these changes:

-const {uiState, numEvents, queryProgress} = useContext(StateContext);
+const {uiState, numEvents, queryProgress = 0} = useContext(StateContext);

// ... other code ...

-{queryProgress}
+{queryProgress === 1 
+  ? "Search complete" 
+  : `Searching... ${Math.round(queryProgress * 100)}%`}

Also applies to: 40-40

Comment on lines 367 to 369
this.#onQueryResults(
Math.max(chunkEndIdx / this.#numEvents, this.#queryCount / MAX_RESULT_COUNT),
results
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

Revise progress calculation logic.

The current progress calculation:

Math.max(chunkEndIdx / this.#numEvents, this.#queryCount / MAX_RESULT_COUNT)

might be misleading as it doesn't clearly indicate when results are truncated due to reaching MAX_RESULT_COUNT. Consider adding a flag to indicate truncated results.

Suggested implementation:

type QueryProgress = {
  progress: number;  // Between 0 and 1
  isTruncated: boolean;  // True if results were limited by MAX_RESULT_COUNT
};

this.#onQueryResults(
  {
    progress: chunkEndIdx / this.#numEvents,
    isTruncated: this.#queryCount >= MAX_RESULT_COUNT
  },
  results
);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (4)

11-15: Enhance type safety with readonly props and named tuple elements.

Consider improving the interface definition for better type safety and readability.

 interface ResultProps {
-    logEventNum: number,
-    message: string,
-    matchRange: [number, number]
+    readonly logEventNum: number;
+    readonly message: string;
+    readonly matchRange: readonly [start: number, end: number];
 }

17-26: Complete the JSDoc documentation.

The documentation is missing important details:

  • Add description for @returns
  • Add description for @param props.logEventNum
  • Consider adding example usage
 /**
  * Displays a button containing a message, which highlights a specific range of text.
  *
  * @param props
  * @param props.message
+ * The complete text message to display
  * @param props.matchRange A two-element array indicating the start and end indices of the substring
  * to be highlighted.
- * @param props.logEventNum
+ * @param props.logEventNum The unique identifier for the log event
+ * @returns A button component displaying the message with highlighted match
+ * @example
+ * <Result
+ *   logEventNum={123}
+ *   message="Error occurred in process"
+ *   matchRange={[6, 14]}
+ * />
  */

27-36: Optimize performance for large messages.

Consider memoizing the sliced message parts to prevent unnecessary recalculations on re-renders.

+import { useMemo } from 'react';
+
 const Result = ({logEventNum, message, matchRange}: ResultProps) => {
-    const [
+    const [beforeMatch, match, afterMatch] = useMemo(() => [
         beforeMatch,
         match,
         afterMatch,
-    ] = [
         message.slice(0, matchRange[0]),
         message.slice(...matchRange),
         message.slice(matchRange[1]),
-    ];
+    ], [message, matchRange]);

27-69: Consider wrapping with ErrorBoundary.

Given the potential for large search results and browser freezing issues mentioned in the PR comments, consider implementing error boundaries to gracefully handle rendering failures.

Example implementation:

import { ErrorBoundary } from 'react-error-boundary';

const ResultErrorFallback = ({ error, resetErrorBoundary }) => (
  <ListItemButton
    onClick={resetErrorBoundary}
    sx={{ color: 'error.main' }}
  >
    <Typography level="body-xs">
      Error rendering result: {error.message}
    </Typography>
  </ListItemButton>
);

// Wrap the Result component with ErrorBoundary where it's used
<ErrorBoundary
  FallbackComponent={ResultErrorFallback}
  onReset={() => {
    // Reset error state
  }}
>
  <Result {...props} />
</ErrorBoundary>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a8a1551 and 67957ca.

📒 Files selected for processing (2)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx
🧰 Additional context used
📓 Learnings (1)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (3)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-10-28T18:40:29.816Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-10-28T18:46:21.189Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:9-12
Timestamp: 2024-10-28T18:39:28.680Z
Learning: In the `Result` component (`Result.tsx`), if `end < start`, the for loop won't run and it will return nothing, so additional validation for `matchRange` indices is not necessary.

Comment on lines 41 to 68
return (
<ListItemButton
className={"result-button"}
onClick={handleResultButtonClick}
>
<Typography
className={"result-text before-match"}
level={"body-xs"}
>
{beforeMatch}
</Typography>
<Typography
className={"result-text match"}
level={"body-xs"}
sx={{
backgroundColor: "warning.softBg",
}}
>
{match}
</Typography>
<Typography
className={"result-text after-match"}
level={"body-xs"}
>
{afterMatch}
</Typography>
</ListItemButton>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance accessibility and keyboard navigation.

The component needs proper ARIA attributes and keyboard support for better accessibility.

     return (
         <ListItemButton
             className={"result-button"}
             onClick={handleResultButtonClick}
+            role="option"
+            aria-selected={false}
+            onKeyDown={(e) => {
+                if (e.key === 'Enter' || e.key === ' ') {
+                    handleResultButtonClick();
+                }
+            }}
         >
             <Typography
                 className={"result-text before-match"}
                 level={"body-xs"}
+                component="span"
             >
                 {beforeMatch}
             </Typography>
             <Typography
                 className={"result-text match"}
                 level={"body-xs"}
+                component="mark"
                 sx={{
                     backgroundColor: "warning.softBg",
                 }}
+                aria-label={`Matched text: ${match}`}
             >
                 {match}
             </Typography>
             <Typography
                 className={"result-text after-match"}
                 level={"body-xs"}
+                component="span"
             >
                 {afterMatch}
             </Typography>
         </ListItemButton>
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<ListItemButton
className={"result-button"}
onClick={handleResultButtonClick}
>
<Typography
className={"result-text before-match"}
level={"body-xs"}
>
{beforeMatch}
</Typography>
<Typography
className={"result-text match"}
level={"body-xs"}
sx={{
backgroundColor: "warning.softBg",
}}
>
{match}
</Typography>
<Typography
className={"result-text after-match"}
level={"body-xs"}
>
{afterMatch}
</Typography>
</ListItemButton>
);
return (
<ListItemButton
className={"result-button"}
onClick={handleResultButtonClick}
role="option"
aria-selected={false}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleResultButtonClick();
}
}}
>
<Typography
className={"result-text before-match"}
level={"body-xs"}
component="span"
>
{beforeMatch}
</Typography>
<Typography
className={"result-text match"}
level={"body-xs"}
component="mark"
sx={{
backgroundColor: "warning.softBg",
}}
aria-label={`Matched text: ${match}`}
>
{match}
</Typography>
<Typography
className={"result-text after-match"}
level={"body-xs"}
component="span"
>
{afterMatch}
</Typography>
</ListItemButton>
);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (4)

11-15: Add JSDoc documentation for the ResultProps interface.

Consider documenting the interface to improve code maintainability and developer experience.

+/**
+ * Props for the Result component.
+ */
 interface ResultProps {
+    /** The log event number used for navigation */
     logEventNum: number,
+    /** The complete message text */
     message: string,
+    /** A tuple of [start, end) indices for the matching text */
     matchRange: [number, number]
 }

19-27: Complete the JSDoc documentation.

The JSDoc is missing the @returns tag description.

 /**
  * Renders a query result as a button with a message, highlighting the first matching text range.
  *
  * @param props
  * @param props.message
  * @param props.matchRange A two-element array [begin, end) representing the indices of the matching text range.
  * @param props.logEventNum
- * @return
+ * @returns {JSX.Element} A ListItemButton containing the formatted message with highlighted match
  */
🧰 Tools
🪛 GitHub Check: lint-check

[warning] 24-24:
This line has a comment length of 112. Maximum allowed is 100


47-63: Simplify the Typography nesting and text truncation.

The current implementation has nested Typography components and inline text truncation logic that could be simplified.

-            <Typography
-                className={"result-button-text"}
-                level={"body-sm"}
-            >
-                <span>
-                    {(SEARCH_RESULT_PREFIX_MAX_CHARACTERS < beforeMatch.length) && "..."}
-                    {beforeMatch.slice(-SEARCH_RESULT_PREFIX_MAX_CHARACTERS)}
-                </span>
-                <Typography
-                    className={"result-button-text"}
-                    level={"body-sm"}
-                    sx={{backgroundColor: "warning.softBg"}}
-                >
-                    {match}
-                </Typography>
-                {afterMatch}
-            </Typography>
+            <>
+                <Typography
+                    className={"result-button-text"}
+                    level={"body-sm"}
+                    component="span"
+                >
+                    {beforeMatch.length > SEARCH_RESULT_PREFIX_MAX_CHARACTERS
+                        ? `...${beforeMatch.slice(-SEARCH_RESULT_PREFIX_MAX_CHARACTERS)}`
+                        : beforeMatch}
+                </Typography>
+                <Typography
+                    className={"result-button-text"}
+                    level={"body-sm"}
+                    component="span"
+                    sx={{backgroundColor: "warning.softBg"}}
+                >
+                    {match}
+                </Typography>
+                <Typography
+                    className={"result-button-text"}
+                    level={"body-sm"}
+                    component="span"
+                >
+                    {afterMatch}
+                </Typography>
+            </>

67-69: Remove extra blank line before export.

Maintain consistent code style by removing the extra blank line.

};
-

export default Result;
src/services/LogFileManager/index.ts (1)

370-379: Consider adding a separate truncation indicator

While the progress calculation is functional, it might be misleading when results are truncated. Consider separating the progress indication from the truncation status.

-        const progress = Math.max(
-            chunkEndIdx / this.#numEvents,
-            this.#queryCount / MAX_QUERY_RESULT_COUNT
-        );
-
-        this.#onQueryResults(progress, results);
+        const fileProgress = chunkEndIdx / this.#numEvents;
+        const isTruncated = this.#queryCount >= MAX_QUERY_RESULT_COUNT;
+
+        this.#onQueryResults(
+            fileProgress,
+            results,
+            isTruncated  // Add this parameter to the callback
+        );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 30481f9 and 8a81a87.

📒 Files selected for processing (5)
  • src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1 hunks)
  • src/services/LogFileManager/index.ts (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.tsx
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx
🧰 Additional context used
📓 Learnings (2)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (4)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-11-10T16:46:53.300Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-11-10T16:46:53.300Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:9-12
Timestamp: 2024-11-10T16:46:58.543Z
Learning: In the `Result` component (`Result.tsx`), if `end < start`, the for loop won't run and it will return nothing, so additional validation for `matchRange` indices is not necessary.
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx:99-105
Timestamp: 2024-11-14T04:34:08.410Z
Learning: In the React component `ResultsGroup` in `src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx`, it's acceptable to use array indices as keys when rendering `Result` components from the `results` array, since the results are inserted in order and items are not reordered or deleted.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1)
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx:99-105
Timestamp: 2024-11-14T04:34:08.410Z
Learning: In the React component `ResultsGroup` in `src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx`, it's acceptable to use array indices as keys when rendering `Result` components from the `results` array, since the results are inserted in order and items are not reordered or deleted.
🪛 GitHub Check: lint-check
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx

[warning] 24-24:
This line has a comment length of 112. Maximum allowed is 100

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx

[failure] 4-4:
'/home/runner/work/yscope-log-viewer/yscope-log-viewer/node_modules/@types/react/index.d.ts' imported multiple times


[failure] 5-5:
'react' import is duplicated


[failure] 5-5:
'/home/runner/work/yscope-log-viewer/yscope-log-viewer/node_modules/@types/react/index.d.ts' imported multiple times


[warning] 41-41:
Caution: React also has a named export memo. Check if you meant to write import {memo} from 'react' instead

🔇 Additional comments (5)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1)

41-59: Well-structured component implementation

The component implementation demonstrates good practices:

  • Proper memoization to prevent unnecessary re-renders
  • Clean state management for accordion expansion
  • Effective prop synchronization through useEffect
🧰 Tools
🪛 GitHub Check: lint-check

[warning] 41-41:
Caution: React also has a named export memo. Check if you meant to write import {memo} from 'react' instead

src/services/LogFileManager/index.ts (4)

52-52: LGTM: Consistent progress tracking implementation

The callback signature has been updated consistently throughout the file to support progress tracking.

Also applies to: 74-74, 118-118


294-298: LGTM: Proper query state initialization

Good practice to reset the query state and clear previous results before starting a new query.


342-344: LGTM: Proper null check handling

Good defensive programming practice to handle potential null results from decodeRange.


36-36: Document the rationale for MAX_QUERY_RESULT_COUNT value

Consider adding a comment explaining how this limit was determined and what factors influenced the choice of 1,000 results.

Let's check if this limit is documented or discussed elsewhere:

Comment on lines +97 to +105
<List size={"sm"}>
{results.map((r, index) => (
<Result
key={index}
logEventNum={r.logEventNum}
matchRange={r.matchRange}
message={r.message}/>
))}
</List>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement virtualization for large result sets

While the current implementation works, it may cause performance issues with large result sets. Consider implementing virtualization to improve performance.

Here's a suggested implementation using react-window:

import { FixedSizeList } from 'react-window';

const ResultsList = ({ results }) => (
  <List size="sm">
    <FixedSizeList
      height={400}
      width="100%"
      itemCount={results.length}
      itemSize={35}
    >
      {({ index, style }) => (
        <Result
          style={style}
          key={index}
          logEventNum={results[index].logEventNum}
          matchRange={results[index].matchRange}
          message={results[index].message}
        />
      )}
    </FixedSizeList>
  </List>
);

Would you like me to create a GitHub issue to track this performance enhancement?

Comment on lines 1 to 5
import {
useEffect,
useState,
} from "react";
import React from "react";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consolidate React imports

The React imports are duplicated. Combine them into a single import statement.

Apply this diff to fix the imports:

-import {
-    useEffect,
-    useState,
-} from "react";
-import React from "react";
+import React, {
+    useEffect,
+    useState,
+} from "react";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {
useEffect,
useState,
} from "react";
import React from "react";
import React, {
useEffect,
useState,
} from "react";
🧰 Tools
🪛 GitHub Check: lint-check

[failure] 4-4:
'/home/runner/work/yscope-log-viewer/yscope-log-viewer/node_modules/@types/react/index.d.ts' imported multiple times


[failure] 5-5:
'react' import is duplicated


[failure] 5-5:
'/home/runner/work/yscope-log-viewer/yscope-log-viewer/node_modules/@types/react/index.d.ts' imported multiple times

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (7)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (4)

11-15: Consider enhancing the interface definition with JSDoc and a more specific type.

The interface would benefit from documentation and a more specific type for matchRange.

+/**
+ * Props for the Result component.
+ */
 interface ResultProps {
+    /** The log event number used for navigation */
     logEventNum: number,
+    /** The complete message text */
     message: string,
-    matchRange: [number, number]
+    /** The range of text to highlight */
+    matchRange: readonly [start: number, end: number]
 }

19-28: Enhance JSDoc documentation with examples and return type.

The documentation would be more helpful with usage examples and return type information.

 /**
  * Renders a query result as a button with a message, highlighting the first matching text range.
  *
  * @param props
  * @param props.message The complete message text to display
  * @param props.matchRange A two-element array [begin, end) representing the indices of the matching
  * text range.
  * @param props.logEventNum The log event number used for navigation
+ * @example
+ * <Result
+ *   logEventNum={1}
+ *   message="Error occurred in process"
+ *   matchRange={[0, 5]}
+ * />
+ * @returns {JSX.Element} A button containing the message with highlighted match
  */

30-38: Extract text processing logic into a separate function.

The message slicing logic could be moved to a separate function for better readability and reusability.

+const processMessage = (message: string, matchRange: readonly [number, number]) => {
+    return {
+        beforeMatch: message.slice(0, matchRange[0]),
+        match: message.slice(...matchRange),
+        afterMatch: message.slice(matchRange[1]),
+    };
+};

 const Result = ({logEventNum, message, matchRange}: ResultProps) => {
-    const [
-        beforeMatch,
-        match,
-        afterMatch,
-    ] = [
-        message.slice(0, matchRange[0]),
-        message.slice(...matchRange),
-        message.slice(matchRange[1]),
-    ];
+    const { beforeMatch, match, afterMatch } = processMessage(message, matchRange);

48-64: Simplify nested Typography components.

The current implementation has unnecessary nesting of Typography components.

-            <Typography
-                className={"result-button-text"}
-                level={"body-sm"}
-            >
-                <span>
-                    {(QUERY_RESULT_PREFIX_MAX_CHARACTERS < beforeMatch.length) && "..."}
-                    {beforeMatch.slice(-QUERY_RESULT_PREFIX_MAX_CHARACTERS)}
-                </span>
-                <Typography
-                    className={"result-button-text"}
-                    level={"body-sm"}
-                    sx={{backgroundColor: "warning.softBg"}}
-                >
-                    {match}
-                </Typography>
-                {afterMatch}
-            </Typography>
+            <Typography
+                className={"result-button-text"}
+                level={"body-sm"}
+                component="div"
+            >
+                {(QUERY_RESULT_PREFIX_MAX_CHARACTERS < beforeMatch.length) && "..."}
+                {beforeMatch.slice(-QUERY_RESULT_PREFIX_MAX_CHARACTERS)}
+                <span style={{backgroundColor: "var(--joy-palette-warning-softBg)"}}>
+                    {match}
+                </span>
+                {afterMatch}
+            </Typography>
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (3)

32-40: Add return type to JSDoc documentation.

While the documentation is thorough for props, consider adding the return type for completeness:

 * @param props.pageNum
 * @param props.results
- * @return
+ * @return {JSX.Element} The rendered ResultsGroup component
 */

46-58: Consider using a callback ref for accordion state management.

The current implementation uses local state and useEffect for managing accordion expansion. Consider using a callback ref to handle expansion state, which could be more efficient:

-const [isExpanded, setIsExpanded] = useState<boolean>(isAllExpanded);
-
-useEffect(() => {
-    setIsExpanded(isAllExpanded);
-}, [isAllExpanded]);
+const [accordionRef, setAccordionRef] = useState<HTMLElement | null>(null);
+
+const handleAccordionRef = useCallback((node: HTMLElement | null) => {
+    if (node) {
+        setAccordionRef(node);
+        node.setAttribute('aria-expanded', isAllExpanded.toString());
+    }
+}, [isAllExpanded]);

70-92: Consider adding aria-label for better accessibility.

The summary section could benefit from an aria-label that includes both the page number and result count:

 <Box className={"results-group-summary-container"}>
+    <Box
+        aria-label={`Page ${pageNum} with ${results.length} results`}
+        role="heading"
+    >
     <Stack
         className={"results-group-summary-text-container"}
         direction={"row"}
     >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8a81a87 and fb12080.

📒 Files selected for processing (8)
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx (3 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1 hunks)
  • src/services/LogFileManager/index.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx
  • src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx
  • src/services/LogFileManager/index.ts
🧰 Additional context used
📓 Learnings (2)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (4)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-11-10T16:46:53.300Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-11-10T16:46:53.300Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:9-12
Timestamp: 2024-11-10T16:46:58.543Z
Learning: In the `Result` component (`Result.tsx`), if `end < start`, the for loop won't run and it will return nothing, so additional validation for `matchRange` indices is not necessary.
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx:99-105
Timestamp: 2024-11-14T04:34:08.410Z
Learning: In the React component `ResultsGroup` in `src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx`, it's acceptable to use array indices as keys when rendering `Result` components from the `results` array, since the results are inserted in order and items are not reordered or deleted.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1)
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx:99-105
Timestamp: 2024-11-14T04:34:08.410Z
Learning: In the React component `ResultsGroup` in `src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx`, it's acceptable to use array indices as keys when rendering `Result` components from the `results` array, since the results are inserted in order and items are not reordered or deleted.
🔇 Additional comments (3)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1)

29-67: Address performance concerns with large result sets.

Based on the PR objectives, there are concerns about browser freezing with large result sets.

Let's verify the component's usage and potential performance impact:

Consider implementing virtualization using react-window or react-virtualized to handle large result sets efficiently.

src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (2)

26-30: LGTM! Well-structured interface definition.

The ResultsGroupProps interface is clear and properly typed, making the component's API contract explicit.


111-114: LGTM! Proper component export setup.

The component export follows React best practices by setting the displayName property, which is helpful for debugging.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (7)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (4)

11-15: Consider enhancing type safety for the ResultProps interface.

The interface could benefit from more specific types to prevent potential runtime errors.

 interface ResultProps {
-    logEventNum: number,
-    message: string,
-    matchRange: [number, number]
+    logEventNum: number & { __brand: 'LogEventNumber' },  // Branded type for compile-time safety
+    message: string & { __brand: 'NonEmptyString' },      // Ensure non-empty string
+    matchRange: readonly [start: number, end: number]     // Named tuple with readonly modifier
 }

19-28: Enhance JSDoc documentation for better clarity.

The documentation could be more comprehensive about the component's behaviour and constraints.

 /**
  * Renders a query result as a button with a message, highlighting the first matching text range.
  *
+ * The message is truncated if it exceeds QUERY_RESULT_PREFIX_MAX_CHARACTERS characters before the match.
  *
  * @param props
- * @param props.message
+ * @param props.message The full message text to display
  * @param props.matchRange A two-element array [begin, end) representing the indices of the matching
  * text range.
- * @param props.logEventNum
- * @return
+ * @param props.logEventNum The unique identifier for the log event
+ * @returns {JSX.Element} A button containing the formatted message with highlighted match
  */

30-38: Consider memoizing the message slicing operation.

For large messages or frequent re-renders, memoization could improve performance.

+    const messageSegments = useMemo(() => [
+        message.slice(0, matchRange[0]),
+        message.slice(...matchRange),
+        message.slice(matchRange[1]),
+    ], [message, matchRange]);
+
     const [
         beforeMatch,
         match,
         afterMatch,
-    ] = [
-        message.slice(0, matchRange[0]),
-        message.slice(...matchRange),
-        message.slice(matchRange[1]),
-    ];
+    ] = messageSegments;

48-64: Simplify the Typography component structure.

The nested Typography components can be simplified for better maintainability.

-            <Typography
-                className={"result-button-text"}
-                level={"body-sm"}
-            >
-                <span>
-                    {(QUERY_RESULT_PREFIX_MAX_CHARACTERS < beforeMatch.length) && "..."}
-                    {beforeMatch.slice(-QUERY_RESULT_PREFIX_MAX_CHARACTERS)}
-                </span>
-                <Typography
-                    className={"result-button-text"}
-                    level={"body-sm"}
-                    sx={{backgroundColor: "warning.softBg"}}
-                >
-                    {match}
-                </Typography>
-                {afterMatch}
-            </Typography>
+            <Typography
+                className={"result-button-text"}
+                level={"body-sm"}
+                component="div"
+                sx={{ display: 'flex', flexWrap: 'nowrap' }}
+            >
+                {(QUERY_RESULT_PREFIX_MAX_CHARACTERS < beforeMatch.length) && "..."}
+                {beforeMatch.slice(-QUERY_RESULT_PREFIX_MAX_CHARACTERS)}
+                <span style={{ backgroundColor: "var(--joy-palette-warning-softBg)" }}>
+                    {match}
+                </span>
+                {afterMatch}
+            </Typography>
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1)

32-40: Enhance component documentation

The JSDoc comments could be more descriptive:

  • Add return type documentation
  • Include parameter descriptions

Apply these improvements to the JSDoc:

 /**
  * Renders a group of results, where each group represents a list of results from a single page.
  *
- * @param props
- * @param props.isAllExpanded
- * @param props.pageNum
- * @param props.results
- * @return
+ * @param props Component properties
+ * @param props.isAllExpanded Whether all result groups should be expanded
+ * @param props.pageNum The page number for this group of results
+ * @param props.results Array of query results for this page
+ * @returns {JSX.Element} A collapsible accordion containing the results
  */
src/services/LogFileManager/index.ts (2)

37-37: Consider a more descriptive constant name.

The constant name MAX_QUERY_RESULT_COUNT could be more descriptive about its purpose, such as MAX_SEARCH_RESULTS_PER_QUERY to better indicate its role in limiting search results.


57-57: Add JSDoc documentation for the queryCount field.

The private field #queryCount should be documented with JSDoc to explain its purpose, constraints, and relationship with MAX_QUERY_RESULT_COUNT.

+    /**
+     * Tracks the number of matches found during the current query execution.
+     * Used to limit results to MAX_QUERY_RESULT_COUNT.
+     * @private
+     */
     #queryCount: number = 0;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8a81a87 and fb12080.

📒 Files selected for processing (8)
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx (3 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.css (1 hunks)
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx (1 hunks)
  • src/services/LogFileManager/index.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/CustomTabPanel.tsx
  • src/components/CentralContainer/Sidebar/SidebarTabs/PanelTitleButton.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.css
  • src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx
🧰 Additional context used
📓 Learnings (2)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx (4)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-11-10T16:46:53.300Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:34-58
Timestamp: 2024-11-10T16:46:53.300Z
Learning: When suggesting performance optimizations, ensure they are significant and applicable to the specific context of the code being reviewed.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:9-12
Timestamp: 2024-11-10T16:46:58.543Z
Learning: In the `Result` component (`Result.tsx`), if `end < start`, the for loop won't run and it will return nothing, so additional validation for `matchRange` indices is not necessary.
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx:99-105
Timestamp: 2024-11-14T04:34:08.410Z
Learning: In the React component `ResultsGroup` in `src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx`, it's acceptable to use array indices as keys when rendering `Result` components from the `results` array, since the results are inserted in order and items are not reordered or deleted.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (1)
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx:99-105
Timestamp: 2024-11-14T04:34:08.410Z
Learning: In the React component `ResultsGroup` in `src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx`, it's acceptable to use array indices as keys when rendering `Result` components from the `results` array, since the results are inserted in order and items are not reordered or deleted.
🔇 Additional comments (6)
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/ResultsGroup.tsx (3)

111-114: LGTM!

The component export follows React best practices by setting the displayName property and using default export.


97-105: 🛠️ Refactor suggestion

Implement virtualization for large result sets

Given the reported browser freezing issues with large result sets, implement virtualization to improve performance.

Consider using react-window for virtualization. I notice this suggestion was made in a previous review. Would you like me to create a GitHub issue to track this enhancement?


56-58: 🛠️ Refactor suggestion

Add missing dependency to useEffect

The effect hook should include setIsExpanded in its dependency array to satisfy the exhaustive-deps rule.

Apply this diff:

     useEffect(() => {
         setIsExpanded(isAllExpanded);
-    }, [isAllExpanded]);
+    }, [isAllExpanded, setIsExpanded]);

Likely invalid or redundant comment.

src/services/LogFileManager/index.ts (3)

1-1: 🛠️ Refactor suggestion

Consider breaking down the class instead of increasing the line limit.

The increase in the ESLint max-lines limit suggests that the class might be growing too large. Consider breaking it down into smaller, more focused components for better maintainability.


388-393: ⚠️ Potential issue

Improve progress reporting when results are truncated.

The current progress calculation might be misleading when MAX_QUERY_RESULT_COUNT is reached, as it will show 100% even if only a portion of the file was processed.

Consider separating the progress and truncation status:

-        const progress = Math.max(
-            chunkEndIdx / this.#numEvents,
-            this.#queryCount / MAX_QUERY_RESULT_COUNT
-        );
-
-        this.#onQueryResults(progress, results);
+        const fileProgress = chunkEndIdx / this.#numEvents;
+        const isTruncated = this.#queryCount >= MAX_QUERY_RESULT_COUNT;
+        
+        this.#onQueryResults(
+            fileProgress,
+            results,
+            isTruncated  // Add this parameter to the callback
+        );

325-354: Consider improving result distribution across pages.

The current implementation stops processing as soon as MAX_QUERY_RESULT_COUNT is reached, which might lead to uneven result distribution across pages. Earlier pages might be overrepresented in the results while later pages might be underrepresented.

Consider implementing a more balanced distribution strategy, such as:

  1. Collecting all matches first and then sampling evenly across pages
  2. Reserving a portion of MAX_QUERY_RESULT_COUNT for each page

Comment on lines +39 to +41
const handleResultButtonClick = () => {
updateWindowUrlHashParams({logEventNum});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling to the click handler.

The click handler should handle potential errors when updating URL parameters.

     const handleResultButtonClick = () => {
+        try {
             updateWindowUrlHashParams({logEventNum});
+        } catch (error) {
+            console.error('Failed to update URL parameters:', error);
+            // Consider showing a user-friendly error message
+        }
     };

Committable suggestion skipped: line range outside the PR's diff.

@Henry8192 Henry8192 requested a review from junhaoliao November 14, 2024 21:53
Copy link
Collaborator

@junhaoliao junhaoliao left a comment

Choose a reason for hiding this comment

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

One final change before I approve.

Henry8192 and others added 2 commits November 15, 2024 14:12
…anel/ResultsGroup.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (1)
src/services/LogFileManager/index.ts (1)

37-37: Consider making the query result limit configurable.

While 1,000 is a reasonable default, different use cases might require different limits. Consider:

  1. Making this configurable through environment variables or constructor options
  2. Adding documentation about the rationale behind this limit
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f71269e and e002f4f.

📒 Files selected for processing (3)
  • src/services/LogFileManager/index.ts (8 hunks)
  • src/services/MainWorker.ts (1 hunks)
  • src/typings/worker.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/services/MainWorker.ts
  • src/typings/worker.ts
🧰 Additional context used
🪛 GitHub Check: lint-check
src/services/LogFileManager/index.ts

[failure] 331-331:
Unsafe assignment of an error typed value


[failure] 331-331:
Unsafe call of an error type typed value


[failure] 331-331:
Unsafe member access .match on an error typed value


[failure] 332-332:
Unsafe member access .index on an error typed value


[failure] 342-342:
Unsafe assignment of an error typed value


[failure] 343-343:
Unsafe assignment of an error typed value


[failure] 344-344:
Unsafe assignment of type [any, any] to a variable of type TextRange


[failure] 345-345:
Unsafe member access .index on an error typed value


[failure] 346-346:
Invalid operand for a '+' operation. Operands must each be a number or string. Got any


[failure] 346-346:
Unsafe member access .index on an error typed value

🔇 Additional comments (3)
src/services/LogFileManager/index.ts (3)

53-53: LGTM: Consistent callback signature updates.

The addition of the progress parameter to onQueryResults is implemented consistently across all occurrences.

Also applies to: 75-75, 119-119


57-57: LGTM: Well-encapsulated query count tracking.

The private field is properly initialized and serves dual purposes for progress tracking and result limiting.


295-299: LGTM: Good initialization of query state.

The changes properly reset the query state and initialize the UI, addressing the re-rendering issue mentioned in the PR objectives.

@@ -1,6 +1,7 @@
/* eslint max-lines: ["error", 400] */
/* eslint max-lines: ["error", 450] */
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider breaking down this large file into smaller modules.

The increase in the ESLint max-lines limit suggests that this file is becoming too large and may be violating the Single Responsibility Principle. Consider splitting the functionality into separate modules:

  • Query handling logic
  • Page loading logic
  • Decoder management

Comment on lines +388 to +393
// The query progress takes the maximum of the progress based on the number of events
// queried over total log events, and the number of results over the maximum result limit.
const progress = Math.max(
chunkEndIdx / this.#numEvents,
this.#queryCount / MAX_QUERY_RESULT_COUNT
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve progress calculation for truncated results.

The current progress calculation might be misleading when results are truncated. Consider separating the progress from the truncation status:

-        const progress = Math.max(
-            chunkEndIdx / this.#numEvents,
-            this.#queryCount / MAX_QUERY_RESULT_COUNT
-        );
+        const searchProgress = chunkEndIdx / this.#numEvents;
+        const isTruncated = this.#queryCount >= MAX_QUERY_RESULT_COUNT;
+        this.#onQueryResults(
+            searchProgress,
+            results,
+            isTruncated  // Add this parameter to the callback
+        );

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 318 to 355
/**
* Processes decoded log events and populates the results map with matched entries.
*
* @param decodedEvents
* @param queryRegex
* @param results The map to store query results.
*/
#processQueryDecodedEvents (
decodedEvents: DecodeResultType[],
queryRegex: RegExp,
results: QueryResults
): void {
for (const [message, , , logEventNum] of decodedEvents) {
const matchResult = message.match(queryRegex);
if (null === matchResult || "number" !== typeof matchResult.index) {
continue;
}

const pageNum = Math.ceil(logEventNum / this.#pageSize);
if (false === results.has(pageNum)) {
results.set(pageNum, []);
}

results.get(pageNum)?.push({
logEventNum: logEventNum,
message: message,
matchRange: [
matchResult.index,
matchResult.index + matchResult[0].length,
],
});

this.#queryCount++;
if (this.#queryCount >= MAX_QUERY_RESULT_COUNT) {
break;
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Address type safety concerns in query processing.

The extracted method improves code organization, but there are type safety issues to address:

  1. Ensure proper type checking for matchResult and its properties
  2. Add type assertions or guards for the TextRange assignment

Apply this diff to improve type safety:

     #processQueryDecodedEvents (
         decodedEvents: DecodeResultType[],
         queryRegex: RegExp,
         results: QueryResults
     ): void {
         for (const [message, , , logEventNum] of decodedEvents) {
-            const matchResult = message.match(queryRegex);
-            if (null === matchResult || "number" !== typeof matchResult.index) {
+            if (typeof message !== 'string') {
+                continue;
+            }
+            const matchResult = message.match(queryRegex);
+            if (!matchResult?.index) {
                 continue;
             }

             const pageNum = Math.ceil(logEventNum / this.#pageSize);
             if (false === results.has(pageNum)) {
                 results.set(pageNum, []);
             }

+            const matchIndex = matchResult.index;
+            const matchLength = matchResult[0].length;
             results.get(pageNum)?.push({
                 logEventNum: logEventNum,
                 message: message,
                 matchRange: [
-                    matchResult.index,
-                    matchResult.index + matchResult[0].length,
+                    matchIndex,
+                    matchIndex + matchLength,
                 ],
             });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Processes decoded log events and populates the results map with matched entries.
*
* @param decodedEvents
* @param queryRegex
* @param results The map to store query results.
*/
#processQueryDecodedEvents (
decodedEvents: DecodeResultType[],
queryRegex: RegExp,
results: QueryResults
): void {
for (const [message, , , logEventNum] of decodedEvents) {
const matchResult = message.match(queryRegex);
if (null === matchResult || "number" !== typeof matchResult.index) {
continue;
}
const pageNum = Math.ceil(logEventNum / this.#pageSize);
if (false === results.has(pageNum)) {
results.set(pageNum, []);
}
results.get(pageNum)?.push({
logEventNum: logEventNum,
message: message,
matchRange: [
matchResult.index,
matchResult.index + matchResult[0].length,
],
});
this.#queryCount++;
if (this.#queryCount >= MAX_QUERY_RESULT_COUNT) {
break;
}
}
}
/**
* Processes decoded log events and populates the results map with matched entries.
*
* @param decodedEvents
* @param queryRegex
* @param results The map to store query results.
*/
#processQueryDecodedEvents (
decodedEvents: DecodeResultType[],
queryRegex: RegExp,
results: QueryResults
): void {
for (const [message, , , logEventNum] of decodedEvents) {
if (typeof message !== 'string') {
continue;
}
const matchResult = message.match(queryRegex);
if (!matchResult?.index) {
continue;
}
const pageNum = Math.ceil(logEventNum / this.#pageSize);
if (false === results.has(pageNum)) {
results.set(pageNum, []);
}
const matchIndex = matchResult.index;
const matchLength = matchResult[0].length;
results.get(pageNum)?.push({
logEventNum: logEventNum,
message: message,
matchRange: [
matchIndex,
matchIndex + matchLength,
],
});
this.#queryCount++;
if (this.#queryCount >= MAX_QUERY_RESULT_COUNT) {
break;
}
}
}
🧰 Tools
🪛 GitHub Check: lint-check

[failure] 331-331:
Unsafe assignment of an error typed value


[failure] 331-331:
Unsafe call of an error type typed value


[failure] 331-331:
Unsafe member access .match on an error typed value


[failure] 332-332:
Unsafe member access .index on an error typed value


[failure] 342-342:
Unsafe assignment of an error typed value


[failure] 343-343:
Unsafe assignment of an error typed value


[failure] 344-344:
Unsafe assignment of type [any, any] to a variable of type TextRange


[failure] 345-345:
Unsafe member access .index on an error typed value


[failure] 346-346:
Invalid operand for a '+' operation. Operands must each be a number or string. Got any


[failure] 346-346:
Unsafe member access .index on an error typed value

Copy link
Collaborator

@junhaoliao junhaoliao left a comment

Choose a reason for hiding this comment

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

sorry for missing this when I was merging main into the current branch

src/services/LogFileManager/index.ts Outdated Show resolved Hide resolved
src/services/LogFileManager/index.ts Outdated Show resolved Hide resolved
Copy link
Collaborator

@junhaoliao junhaoliao left a comment

Choose a reason for hiding this comment

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

For the PR title, how about:

feat(query): Add a "Search" tab in the Sidebar for wildcard queries.

@junhaoliao
Copy link
Collaborator

Can we update the PR description and validations performed?

@Henry8192 Henry8192 changed the title Add <SearchTabPanel/> for submitting queries and viewing search results. feat(query): Add a "Search" tab in the Sidebar for wildcard queries. Nov 15, 2024
@Henry8192
Copy link
Collaborator Author

Henry8192 commented Nov 16, 2024

sorry for missing this when I was merging main into the current branch

np. Just reviewed the changes. LGTM.

@Henry8192 Henry8192 requested a review from junhaoliao November 16, 2024 01:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants