Skip to content

feat: Implemented log search functionality with highlighted results #647

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

harshit078
Copy link

@harshit078 harshit078 commented Apr 21, 2025

Linked Issue(s)

#556

Acceptance Criteria fulfillment

  • Added Search in Logs
  • Improved Search UI and functionality

Proposed changes

  • Added Search functionality in logs
  • Added highlighted logs with ease to scroll through the list of selected logs
Screen.Recording.2025-04-21.at.4.27.38.PM.mov
Screenshot 2025-04-21 at 4 27 24 PM

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added a search bar for system logs with dynamic text highlighting of matching terms in log entries.
    • Introduced navigation controls to cycle through search results, showing current and total matches.
    • Log entries now highlight all occurrences of the search query for better readability.
  • Improvements

    • Improved scrolling behavior with smooth scroll-to-bottom functionality.
    • Enhanced highlight visuals for active search matches within logs.
    • Unified search state management and improved performance with memoized components and refined event handling.

Copy link

coderabbitai bot commented Apr 21, 2025

Walkthrough

The changes introduce a comprehensive search and navigation feature to the system logs interface. A new LogSearch component provides a search input with navigation controls, allowing users to find and cycle through matching log entries. Both PlainLog and FormattedLog components are updated to highlight text matching the search query. The main SystemLogs component is enhanced with state and logic to manage search queries, highlight matches, and handle smooth scrolling to matched entries. Scroll detection is refactored from using an IntersectionObserver to a scroll-to-bottom function, simplifying the logic for displaying the scroll down button. Additionally, the useSystemLogs hook now requires a serviceName parameter.

Changes

File(s) Change Summary
web-server/src/components/Service/SystemLog/FormattedLog.tsx
web-server/src/components/Service/SystemLog/PlainLog.tsx
Added a SearchHighlightText helper component to highlight search matches; updated main components to accept optional searchQuery and isCurrentMatch props and render highlighted text accordingly.
web-server/src/components/Service/SystemLog/LogSearch.tsx Introduced a new LogSearch component with search input, debounced query updates, navigation controls for previous/next matches, and callbacks for searching and navigating between results.
web-server/src/content/Service/SystemLogs.tsx Enhanced the SystemLogs component with unified search state management via useReducer, integrated LogSearch, added debounced search matching and highlight tracking, replaced IntersectionObserver with scroll event listener and smooth scroll-to-bottom function, updated scroll button logic, and memoized log rendering with search highlighting and current match indication. Removed early return for missing serviceName and wrapped component with memo.
web-server/src/hooks/useSystemLogs.tsx Updated useSystemLogs hook to require the serviceName parameter instead of making it optional.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LogSearch
    participant SystemLogs
    participant LogEntry (PlainLog/FormattedLog)
    
    User->>LogSearch: Types search query / uses navigation controls
    LogSearch->>SystemLogs: onSearch(query) / onNavigate(direction)
    SystemLogs->>SystemLogs: Update search state, find matches, update highlights
    SystemLogs->>LogEntry: Pass searchQuery and isCurrentMatch props
    LogEntry->>LogEntry: Highlight matching text
    SystemLogs->>SystemLogs: Scroll to highlighted match (if navigating)
Loading

Possibly related PRs

  • feat(system_logs): Fixed auto scroll #630: Refactors and replaces the IntersectionObserver-based scroll detection and scroll down button logic in SystemLogs.tsx, directly modifying the same scrolling behavior and UI elements as this PR.

Poem

In the warren of logs where the data flows,
A bunny now searches, and every match glows.
With highlights of yellow, and scrolls oh-so-smooth,
Hopping through entries, each click on the move.
Search, skip, and find—what a clever new trick!
Logs now respond, and the answers come quick.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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.

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: 5

♻️ Duplicate comments (1)
web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)

13-30: 🛠️ Refactor suggestion

Factor‑out HighlightedText to avoid code duplication

See earlier comment in PlainLog.tsx.

🧹 Nitpick comments (4)
web-server/src/components/Service/SystemLog/PlainLog.tsx (1)

3-7: Unused prop: index

index is accepted in PlainLogProps but never used in the component. Either remove it from the prop list or use it (e.g. as key when mapping). Keeping unused props increases cognitive load.

web-server/src/components/Service/SystemLog/LogSearch.tsx (2)

7-16: gap has no effect without display:flex (or grid)

SearchContainer sets gap: 5 but the default display is block, so the property is ignored. Add display: flex (or grid) to get the intended spacing:

-const SearchContainer = styled('div')(() => ({
+const SearchContainer = styled('div')(() => ({
   position: 'sticky',
   top: 0,
   zIndex: 1,
   gap: 5,
+  display: 'flex',
   paddingBottom: 8,
   alignItems: 'center',
   backdropFilter: 'blur(10px)',
   borderRadius: 5,
 }));

64-71: Prevent the default “Enter” behaviour in the text field

Pressing in a single‑line TextField can submit the surrounding form (if any) or trigger an unintended click on the first <button> in the DOM. Add event.preventDefault() after handling navigation.

-      if (event.key === 'Enter' && event.shiftKey) {
+      if (event.key === 'Enter' && event.shiftKey) {
+        event.preventDefault();
         onNavigate('prev');
       } else if (event.key === 'Enter') {
+        event.preventDefault();
         onNavigate('next');
       }
web-server/src/content/Service/SystemLogs.tsx (1)

75-78: Use a data‑attribute instead of inline style to mark matches

Selecting by span[style*="background-color: yellow"] can yield false positives and is brittle if the style changes. When rendering highlights, add data-log-match and query via [data-log-match] to decouple logic from styling.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a545c0 and 3a3abb0.

📒 Files selected for processing (4)
  • web-server/src/components/Service/SystemLog/FormattedLog.tsx (2 hunks)
  • web-server/src/components/Service/SystemLog/LogSearch.tsx (1 hunks)
  • web-server/src/components/Service/SystemLog/PlainLog.tsx (1 hunks)
  • web-server/src/content/Service/SystemLogs.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)
web-server/src/types/resources.ts (1)
  • ParsedLog (1056-1062)

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

♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

75-87: ⚠️ Potential issue

Critical issue: useEffect inside a callback function

React hooks can only be called at the top level of your component or other hooks, not inside regular JavaScript functions, conditions, or loops. The useEffect hook inside your handleSearch callback will cause runtime errors.

Move this effect to the component's top level:

- const handleSearch = useCallback((query: string) => {
-   setSearchQuery(query);
-   setCurrentMatch(0);
-   setTotalMatches(0);
-   setHighlightedElements([]);
-   updateHighlight(null);
-
-   if (!query) return;
-
-   useEffect(() => {
-     const elements = Array.from(
-       containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? []
-     ) as HTMLElement[];
-     setHighlightedElements(elements);
-     setTotalMatches(elements.length);
-     setCurrentMatch(elements.length ? 1 : 0);
-
-     if (elements.length) {
-       updateHighlight(elements[0]);
-       elements[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
-     }
-   }, [searchQuery, logs, updateHighlight]);
-
- }, []);
+ const handleSearch = useCallback((query: string) => {
+   setSearchQuery(query);
+   setCurrentMatch(0);
+   setTotalMatches(0);
+   setHighlightedElements([]);
+   updateHighlight(null);
+ }, [updateHighlight]);
+
+ useEffect(() => {
+   if (!searchQuery) return;
+   
+   const elements = Array.from(
+     containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? []
+   ) as HTMLElement[];
+   setHighlightedElements(elements);
+   setTotalMatches(elements.length);
+   setCurrentMatch(elements.length ? 1 : 0);
+
+   if (elements.length) {
+     updateHighlight(elements[0]);
+     elements[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
+   }
+ }, [searchQuery, logs, updateHighlight]);
🧹 Nitpick comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

51-64: Consider using CSS classes for highlights instead of inline styles

Using inline styles for highlights works, but it can be harder to maintain. Consider using CSS classes with defined styles in your stylesheet.

- if (currentHighlightRef.current) {
-   currentHighlightRef.current.style.backgroundColor = 'yellow';
-   currentHighlightRef.current.style.color = 'black';
- }
-
- if (element) {
-   element.style.backgroundColor = 'rgb(255, 161, 10)';
-   element.style.color = 'white';
-   currentHighlightRef.current = element;
- } else {
-   currentHighlightRef.current = null;
- }
+ if (currentHighlightRef.current) {
+   currentHighlightRef.current.classList.remove('active-highlight');
+   currentHighlightRef.current.classList.add('search-highlight');
+ }
+
+ if (element) {
+   element.classList.remove('search-highlight');
+   element.classList.add('active-highlight');
+   currentHighlightRef.current = element;
+ } else {
+   currentHighlightRef.current = null;
+ }

And add these CSS classes to your stylesheet:

.search-highlight {
  background-color: yellow;
  color: black;
}

.active-highlight {
  background-color: rgb(255, 161, 10);
  color: white;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a3abb0 and 940f740.

📒 Files selected for processing (1)
  • web-server/src/content/Service/SystemLogs.tsx (4 hunks)
🔇 Additional comments (7)
web-server/src/content/Service/SystemLogs.tsx (7)

128-137: Properly implemented container ref on scrollable element

Great job fixing the container ref issue from the previous review! The containerRef is now correctly attached to the scrollable FlexBox, which will allow the scroll logic to work properly.


91-107: The navigation logic works well

The navigation function properly handles cycling through matches in both directions. It updates the current match index, scrolls to the selected element, and updates the highlight styling.


33-36: Simplified scroll detection logic

Good simplification of the scroll detection logic! Using a direct scroll event listener is more straightforward than an IntersectionObserver for this use case.


133-134: Properly integrated search query with log components

The search query is correctly passed down to both PlainLog and FormattedLog components, which will enable highlighting of matched text in the logs.

Also applies to: 135-136


116-121: Well-integrated search UI component

The LogSearch component is properly integrated with all the necessary props for search functionality and navigation.


42-49: Smooth scrolling implementation

The scrollToBottom function properly implements smooth scrolling behavior to enhance the user experience.


77-77: 🛠️ Refactor suggestion

Potential null reference issue

The optional chaining on containerRef.current?.querySelectorAll protects against null, but if the component hasn't rendered yet, there won't be any elements to query.

Add a check to ensure the search is only performed when the container is available:

- const elements = Array.from(
-   containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? []
- ) as HTMLElement[];
+ const elements = containerRef.current
+   ? Array.from(containerRef.current.querySelectorAll('span[style*="background-color: yellow"]')) as HTMLElement[]
+   : [];

Likely an incorrect or invalid review comment.

@samad-yar-khan
Copy link
Contributor

samad-yar-khan commented Apr 23, 2025

from the looks of it is just highlighting the logs, not actually searching through them or filtering the search results. Seems satisfactory from the issue. But can we make this more usable ?

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 comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

154-167: 🛠️ Refactor suggestion

Remove direct reference to DOM elements in JSX calculation.

Accessing DOM properties directly in render calculations can cause issues during SSR and is generally not a React best practice. Move this calculation to a useEffect or memoized value.

+ const [buttonMarginLeft, setButtonMarginLeft] = useState(0);
+
+ useEffect(() => {
+   if (containerRef.current) {
+     setButtonMarginLeft(containerRef.current.clientWidth / 2 - 67);
+   }
+ }, []);

  // In the JSX
  <Button
    onClick={scrollToBottom}
    component={MotionBox}
    initial={{ opacity: 0, scale: 0.7 }}
    animate={{ opacity: 1, scale: 1 }}
    transition={{
      ease: 'easeOut'
    }}
    bottom={20}
    sx={{
      position: 'fixed',
-     marginLeft: `calc(${containerRef.current
-         ? containerRef.current.clientWidth / 2 - 67
-         : 0
-       }px)`
+     marginLeft: `${buttonMarginLeft}px`
    }}
  >
♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

69-84: DOM query for highlighted elements is brittle and should be improved.

The current approach of querying elements by their inline style attribute is fragile. React might reformat or reorder style properties, causing the selector to fail. This was noted in a previous review comment as well.

As suggested in the previous review, use a dedicated class or data attribute for highlighted elements and update your query selector:

- const elements = Array.from(
-   containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? []
- ) as HTMLElement[];
+ const elements = Array.from(
+   containerRef.current?.querySelectorAll('span.log-highlight') ?? []
+ ) as HTMLElement[];

Make sure to add the corresponding class to the highlighted spans in both PlainLog and FormattedLog components.

🧹 Nitpick comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)

54-67: Consider debouncing the search input to improve performance.

For an improved user experience, consider adding debounce to prevent excessive processing during rapid typing. This is especially important since you're performing DOM operations in response to search input.

+import { debounce } from 'lodash';
+
 const SystemLogs = memo(({ serviceName }: { serviceName?: ServiceNames }) => {
   // ... existing code
 
-  const handleSearch = useCallback((query: string) => {
+  const debouncedSearch = useCallback(
+    debounce((query: string) => {
+      // Reset states only when query is empty or below a threshold
+      if (!query || query.length < 3) {
+        setCurrentMatch(0);
+        setTotalMatches(0);
+        setHighlightedElements([]);
+        updateHighlight(null);
+        return;
+      }
+      // Let the effect handle DOM updates
+    }, 300),
+    [updateHighlight]
+  );
+
+  const handleSearch = useCallback((query: string) => {
     setSearchQuery(query);
-  
-    // Reset states only when query is empty or below a threshold
-    if (!query || query.length < 3) {
-      setCurrentMatch(0);
-      setTotalMatches(0);
-      setHighlightedElements([]);
-      updateHighlight(null);
-      return;
-    }
-  
-    // Let the effect handle DOM updates
+    debouncedSearch(query);
   }, [updateHighlight]);

87-103: Add error handling for navigation edge cases.

The navigation function works well, but lacks error handling for edge cases. Consider adding checks to ensure the element still exists in the DOM before attempting to scroll to it.

 const handleNavigate = useCallback((direction: 'prev' | 'next') => {
   if (highlightedElements.length === 0) return;

   let newIndex = currentMatch;
   if (direction === 'next') {
     newIndex = currentMatch < totalMatches ? currentMatch + 1 : 1;
   } else {
     newIndex = currentMatch > 1 ? currentMatch - 1 : totalMatches;
   }

   setCurrentMatch(newIndex);
   const element = highlightedElements[newIndex - 1];
-  if (element) {
+  if (element && document.body.contains(element)) {
     element.scrollIntoView({ behavior: 'smooth', block: 'center' });
     updateHighlight(element);
+  } else {
+    // Element no longer in DOM, re-query highlighted elements
+    const freshElements = Array.from(
+      containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? []
+    ) as HTMLElement[];
+    setHighlightedElements(freshElements);
+    if (freshElements.length) {
+      setTotalMatches(freshElements.length);
+      setCurrentMatch(1);
+      updateHighlight(freshElements[0]);
+      freshElements[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
+    }
   }
 }, [currentMatch, totalMatches, highlightedElements, updateHighlight]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 901cd26 and e2e5018.

📒 Files selected for processing (2)
  • web-server/src/components/Service/SystemLog/LogSearch.tsx (1 hunks)
  • web-server/src/content/Service/SystemLogs.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-server/src/components/Service/SystemLog/LogSearch.tsx
🔇 Additional comments (8)
web-server/src/content/Service/SystemLogs.tsx (8)

3-3: Component optimization with React.memo and new imports look good.

The component is properly wrapped with memo for performance optimization, and all necessary hooks and components are imported. This will prevent unnecessary re-renders when the serviceName prop doesn't change.

Also applies to: 8-8, 19-19, 175-175


22-28: State management for search functionality is well-structured.

The state variables are appropriately separated for different aspects of the search functionality. Good use of the ref for tracking the currently highlighted element across renders.


30-37: Clean implementation of scrollToBottom function.

The scroll functionality is simplified with direct scrollTo calls, which is more straightforward than the previous approach.


39-52: The highlight management callback looks good.

The updateHighlight callback effectively toggles between the default yellow background and the distinct orange highlight for the active match.


105-115: Initial scroll implementation looks good.

The initial scroll implementation now correctly uses a ref to track the initial load and requestAnimationFrame to ensure the DOM is ready before scrolling.


117-125: Memoized renderLogs function is a good optimization.

Extracting the logs rendering logic into a memoized callback is a good optimization that prevents unnecessary re-renders.


134-139: Good integration of the LogSearch component.

The LogSearch component is properly integrated with all the necessary props. This cleanly separates the search UI from the main component.


146-148: Container ref placement is now correct.

The containerRef is now properly attached to the scrollable container, fixing the previous issue where it was attached to an empty element.

@harshit078
Copy link
Author

Screenshot 2025-04-27 at 1 12 38 PM

Improved pointer and key input interactions from 150 ms to 80ms

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

♻️ Duplicate comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)

88-90: Replace fragile style selector with semantic class / data-attribute

The DOM lookup relies on matching the literal string background-color: yellow, which is brittle (style order, minification, different color formats, etc.).
A robust approach is to mark the highlighted <span> with a class (log-highlight) or data- attribute and query that instead.

This point was raised in a previous review but is still unresolved.


22-23: showScrollDownButton is never toggled – add scroll listener

showScrollDownButton is initialised but never updated when the user scrolls away from the bottom, so the floating button will never appear.

A simple scroll handler on the containerRef will fix this:

+  /* show / hide “scroll-to-bottom” button */
+  useEffect(() => {
+    const el = containerRef.current;
+    if (!el) return;
+
+    const handleScroll = () => {
+      const { scrollTop, scrollHeight, clientHeight } = el;
+      const atBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
+      showScrollDownButton.set(!atBottom);
+    };
+
+    el.addEventListener('scroll', handleScroll);
+    handleScroll(); // initialise
+    return () => el.removeEventListener('scroll', handleScroll);
+  }, [showScrollDownButton]);

Also applies to: 179-185

🧹 Nitpick comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

191-195: Hard-coded marginLeft breaks on resize – use CSS centering

The floating button’s horizontal position is calculated once during render and won’t adjust if the log pane resizes.
Using left: 50% with transform: translateX(-50%) is simpler and fully responsive:

-            bottom={20}
-            sx={{
-              position: 'fixed',
-              marginLeft: `calc(${containerRef.current
-                  ? containerRef.current.clientWidth / 2 - 67
-                  : 0
-                }px)`
-            }}
+            sx={{
+              position: 'fixed',
+              bottom: 20,
+              left: '50%',
+              transform: 'translateX(-50%)'
+            }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2e5018 and 65c860e.

📒 Files selected for processing (1)
  • web-server/src/content/Service/SystemLogs.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
web-server/src/content/Service/SystemLogs.tsx (5)
web-server/src/hooks/useSystemLogs.tsx (1)
  • useSystemLogs (5-22)
web-server/src/utils/logFormatter.ts (1)
  • parseLogLine (12-95)
web-server/src/components/Service/SystemLog/PlainLog.tsx (1)
  • PlainLog (34-40)
web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)
  • FormattedLog (37-82)
web-server/src/components/Service/SystemLog/SystemLogsErrorFllback.tsx (1)
  • SystemLogsErrorFallback (12-60)

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

♻️ Duplicate comments (3)
web-server/src/content/Service/SystemLogs.tsx (3)

87-89: Use a stable marker for highlighted spans instead of relying on inline style

The selector 'span[style*="background-color: yellow"]' is fragile as React may normalize styles or reorder properties.

- const elements = Array.from(
-   containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? []
- ) as HTMLElement[];
+ const elements = Array.from(
+   containerRef.current?.querySelectorAll('.log-highlight') ?? []
+ ) as HTMLElement[];

This means you'll need to add the className="log-highlight" to the highlighted spans in PlainLog and FormattedLog components.


84-102: Re-run highlight scan when new logs arrive

The useLayoutEffect only depends on debouncedSearchQuery. If new logs load while a search query is active, matches in those logs won't be highlighted or included in navigation.

- }, [debouncedSearchQuery]);
+ }, [debouncedSearchQuery, logs]);

22-22: Missing implementation for showScrollDownButton toggle

You've removed the IntersectionObserver but there's no mechanism to detect when the user scrolls up to show the scroll-down button.

 const SystemLogs = memo(({ serviceName }: { serviceName?: ServiceNames }) => {
   // ... existing code
+  useEffect(() => {
+    const handleScroll = () => {
+      if (!containerRef.current) return;
+      
+      const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
+      const isScrolledToBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
+      showScrollDownButton.set(!isScrolledToBottom);
+    };
+    
+    const scrollContainer = containerRef.current;
+    if (scrollContainer) {
+      scrollContainer.addEventListener('scroll', handleScroll);
+    }
+    
+    return () => {
+      if (scrollContainer) {
+        scrollContainer.removeEventListener('scroll', handleScroll);
+      }
+    };
+  }, [showScrollDownButton]);
🧹 Nitpick comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)

53-66: Direct DOM manipulation may conflict with React's rendering

You're directly manipulating DOM element styles instead of using React's state-based rendering. This can lead to issues if React re-renders the elements.

Consider modifying the HighlightedText component to accept an isActive prop, so React can handle the highlighting:

// In PlainLog and FormattedLog components:
<HighlightedText
  text={text}
  searchQuery={searchQuery}
+ isActiveMatch={matchIndex === currentActiveMatchIndex}
/>

// In HighlightedText component:
const getStyle = (isMatch, isActive) => {
  if (isMatch) {
    return isActive 
      ? { backgroundColor: 'rgb(255, 161, 10)', color: 'white' }
      : { backgroundColor: 'yellow', color: 'black' };
  }
  return {};
};

This would eliminate the need for direct DOM manipulation.


72-81: Consider extracting magic numbers as constants

The minimum search query length (3 characters) is hardcoded in multiple places.

+const MIN_SEARCH_LENGTH = 3;

const handleSearch = useCallback((query: string) => {
  setSearchQuery(query);
  isSearchingRef.current = !!query;

-  if (!query || query.length < 3) {
+  if (!query || query.length < MIN_SEARCH_LENGTH) {
    // ...

Also update line 85 to use the constant.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65c860e and 113e483.

📒 Files selected for processing (1)
  • web-server/src/content/Service/SystemLogs.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
web-server/src/content/Service/SystemLogs.tsx (6)
web-server/src/hooks/useSystemLogs.tsx (1)
  • useSystemLogs (5-22)
web-server/src/utils/logFormatter.ts (1)
  • parseLogLine (12-95)
web-server/src/components/Service/SystemLog/PlainLog.tsx (1)
  • PlainLog (34-40)
web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)
  • FormattedLog (37-82)
web-server/src/components/Service/SystemLog/SystemLogsErrorFllback.tsx (1)
  • SystemLogsErrorFallback (12-60)
web-server/src/components/Service/SystemLog/LogSearch.tsx (1)
  • LogSearch (178-178)
🔇 Additional comments (5)
web-server/src/content/Service/SystemLogs.tsx (5)

176-178: Proper implementation of container with ref

This is a good improvement that addresses a previous critical issue - you've correctly attached the containerRef to the scrollable container.


164-169: Good integration of the search component

The LogSearch component is well-integrated with proper props being passed for search functionality and navigation.


142-155: Good extraction of renderLogs as a memoized callback

The extraction of the log rendering logic into a memoized callback with the correct dependencies is a good performance optimization.


27-34: 🛠️ Refactor suggestion

Redundant state and ref duplications

You have duplicate state and refs for highlightedElements, currentMatch, and totalMatches. This creates potential synchronization issues.

Remove the redundant state variables and use either the state or the refs consistently:

- const [highlightedElements, setHighlightedElements] = useState<HTMLElement[]>([]);
- const [currentMatch, setCurrentMatch] = useState(0);
- const [totalMatches, setTotalMatches] = useState(0);
  const currentHighlightRef = useRef<HTMLElement | null>(null);
  const isInitialLoad = useRef(true);
  const isSearchingRef = useRef(false);
  const currentMatchRef = useRef(0);
  const totalMatchesRef = useRef(0);
  const highlightedElementsRef = useRef<HTMLElement[]>([]);
  const isNavigatingRef = useRef(false);

Then update your component to only use the refs for DOM manipulation and state for rendering:

// When updating for UI rendering
- setHighlightedElements(elements);
  highlightedElementsRef.current = elements;
- setTotalMatches(newTotalMatches);
  totalMatchesRef.current = newTotalMatches;
- setCurrentMatch(newTotalMatches ? 1 : 0);
  currentMatchRef.current = newTotalMatches ? 1 : 0;

// In LogSearch component props
  <LogSearch
    onSearch={handleSearch}
    onNavigate={handleNavigate}
-   currentMatch={currentMatch}
-   totalMatches={totalMatches}
+   currentMatch={currentMatchRef.current}
+   totalMatches={totalMatchesRef.current}
  />

Likely an incorrect or invalid review comment.


68-82:

✅ Verification successful

Consider enhancing search with filtering capability

According to the PR comments, the current implementation only highlights matches but doesn't filter the logs to show only matching entries.

Consider enhancing the feature by adding an option to filter logs to show only entries containing the search query:

+ const [shouldFilterLogs, setShouldFilterLogs] = useState(false);

// In handleSearch:
  const handleSearch = useCallback((query: string) => {
    // ...existing code...
  }, [updateHighlight]);

+ const filteredLogs = useMemo(() => {
+   if (!debouncedSearchQuery || !shouldFilterLogs) return logs;
+   return logs.filter(log => log.toLowerCase().includes(debouncedSearchQuery.toLowerCase()));
+ }, [logs, debouncedSearchQuery, shouldFilterLogs]);

// In renderLogs:
- return logs.map((log, index) => {
+ return (shouldFilterLogs ? filteredLogs : logs).map((log, index) => {

// Update LogSearch component:
<LogSearch
  onSearch={handleSearch}
  onNavigate={handleNavigate}
  currentMatch={currentMatch}
  totalMatches={totalMatches}
+ onToggleFilter={setShouldFilterLogs}
+ isFilterEnabled={shouldFilterLogs}
/>

Then update your LogSearch component to include a filter toggle button.


🏁 Script executed:

#!/bin/bash
# Check if this enhancement aligns with the PR requirements

echo "PR objectives mention:"
cat << EOF
The pull request titled "feat: Implemented log search functionality with highlighted results"
aims to enhance the log viewing experience by adding search capabilities within logs.

The comment on the PR by user samad-yar-khan notes that the current implementation
appears to highlight the logs but does not actually perform searching through or filtering
of the search results. The commenter suggests considering enhancements to make the feature
more usable.
EOF

echo -e "\nChecking the current LogSearch component implementation:"
fd -t f LogSearch.tsx | xargs cat

Length of output: 5083


🏁 Script executed:

#!/bin/bash
# Locate SystemLogs.tsx and show relevant sections around log rendering
file=$(fd -t f SystemLogs.tsx)
echo "----- File: $file -----"
sed -n '1,200p' "$file"
echo "----- Rendering logs in SystemLogs.tsx -----"
grep -R "renderLogs" -n "$file"
grep -R "logs" -n "$file" | sed -n '1,50p'

Length of output: 1131


🏁 Script executed:

#!/bin/bash
set -e

# Find SystemLogs.tsx files
files=$(fd -t f SystemLogs.tsx)

for f in $files; do
  echo "===== File: $f ====="
  
  # Show first 200 lines for context
  sed -n '1,200p' "$f"
  
  echo -e "\n--- Occurrences of 'logs', 'render', and 'renderLogs' in $f ---"
  grep -E -n "(logs|renderLogs|render)" "$f" || true
  
  echo -e "\n\n"
done

Length of output: 9077


No changes needed: filtering logs is a future enhancement
The current PR implements in-line match highlighting per its scope. Introducing entry‐level filtering would be a valuable follow-up but isn’t required to fulfill the “highlight search results” objective here.

• The existing handleSearch + renderLogs flow correctly highlights matches without altering the log list.
• If you’d like to expose a “show only matching entries” toggle, consider capturing it in a separate issue or PR to avoid expanding the current feature scope.

Copy link
Contributor

@jayantbh jayantbh left a comment

Choose a reason for hiding this comment

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

Okay, first things first, my review might be overwhelming.
Sorry about that, should have done this in parts perhaps.

But given that I rarely get time to review PRs in our OSS repo, I decided doing it all in one go might save you more time.

That being said, great first attempt, because it functions alright. :)

However, the code quality leaves a lot to be desired.
Take this as a challenge, I'm hoping you'll come away having learnt a lot of serious stuff from this.

Some learning material I've linked in one of the comments:
https://kentcdodds.com/blog/make-impossible-states-impossible
https://www.youtube.com/watch?v=IcgmSRJHu_8 (link is about Elm lang, but focus on the concept - it's beautifully presented)

}

const HighlightedText = ({ text, searchQuery }: { text: string; searchQuery?: string }) => {
if (!searchQuery) return <>{text}</>;
Copy link
Contributor

Choose a reason for hiding this comment

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

A component can directly return text. Text nodes are valid react nodes.

searchQuery?: string;
}

const HighlightedText = ({ text, searchQuery }: { text: string; searchQuery?: string }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Calling the component HighlightedText is misleading because this is used to render logs regardless of whether it's highlighted or not.

FormattedLog was perhaps a better name.

Comment on lines 107 to 126
requestAnimationFrame(() => {
let newIndex = currentMatchRef.current;
const total = totalMatchesRef.current;

if (direction === 'next') {
newIndex = newIndex < total ? newIndex + 1 : 1;
} else {
newIndex = newIndex > 1 ? newIndex - 1 : total;
}
};
}, [showScrollDownButton]);

setCurrentMatch(newIndex);
currentMatchRef.current = newIndex;
const element = highlightedElementsRef.current[newIndex - 1];
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
updateHighlight(element);
}
isNavigatingRef.current = false;
});
}, [updateHighlight]);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an example of adding hacky code to make existing hacky code work.

Using a proper state machine architecture will make this SO MUCH EASIER to handle.

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

♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

76-96: 🛠️ Refactor suggestion

Search effect needs to watch for new logs

The search effect only depends on searchQuery, but it should also re-run when new logs are loaded. If new logs arrive while a search is active, the new content won't be searched or highlighted.

-  }, [searchState.query]);
+  }, [searchState.query, logs]);
🧹 Nitpick comments (3)
web-server/src/content/Service/SystemLogs.tsx (3)

166-177: Good implementation of scroll detection

The scroll event listener correctly implements the scroll detection for showing/hiding the scroll-down button, fixing the previously identified issue. Consider debouncing the scroll handler for better performance, especially with large log volumes.

+ import { debounce } from '@mui/material/utils';

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    
-   const handleScroll = () => {
+   const handleScroll = debounce(() => {
      const isScrolledUp = container.scrollTop < container.scrollHeight - container.clientHeight - 100;
      showScrollDownButton.set(isScrolledUp);
-   };
+   }, 100);
    
    container.addEventListener('scroll', handleScroll);
-   return () => container.removeEventListener('scroll', handleScroll);
+   return () => {
+     container.removeEventListener('scroll', handleScroll);
+     handleScroll.clear();
+   };
  }, [showScrollDownButton]);

98-105: Consider optimizing the scrollToBottom function

The scrollToBottom function works correctly, but could be optimized to avoid unnecessary scrolling when already at the bottom.

  const scrollToBottom = useCallback(() => {
    if (containerRef.current) {
+     const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
+     const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
+     
+     if (isAtBottom) return;
+     
      containerRef.current.scrollTo({
        top: containerRef.current.scrollHeight,
        behavior: 'smooth'
      });
    }
  }, []);

246-249: Consider refactoring button position calculation

The inline calculation for the button position is complex and could be extracted for better readability. Also, there's potential for unnecessary re-renders since the calculation references the DOM directly in the JSX.

+ const getButtonPosition = useCallback(() => {
+   if (!containerRef.current) return 0;
+   return containerRef.current.clientWidth / 2 - 67;
+ }, []);
+
+ useEffect(() => {
+   // Force re-calculation when container size changes
+   const resizeObserver = new ResizeObserver(() => {
+     // Trigger re-render if button is visible
+     if (showScrollDownButton.value) {
+       showScrollDownButton.set(true);
+     }
+   });
+   
+   if (containerRef.current) {
+     resizeObserver.observe(containerRef.current);
+   }
+   
+   return () => {
+     if (containerRef.current) {
+       resizeObserver.unobserve(containerRef.current);
+     }
+     resizeObserver.disconnect();
+   };
+ }, [showScrollDownButton]);

  // In the JSX:
  sx={{
    position: 'fixed',
-   marginLeft: `calc(${containerRef.current
-       ? containerRef.current.clientWidth / 2 - 67
-       : 0
-     }px)`
+   marginLeft: `${getButtonPosition()}px`
  }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d41efb and ebdb7bf.

📒 Files selected for processing (3)
  • web-server/src/components/Service/SystemLog/FormattedLog.tsx (2 hunks)
  • web-server/src/components/Service/SystemLog/PlainLog.tsx (1 hunks)
  • web-server/src/content/Service/SystemLogs.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • web-server/src/components/Service/SystemLog/PlainLog.tsx
  • web-server/src/components/Service/SystemLog/FormattedLog.tsx
🔇 Additional comments (8)
web-server/src/content/Service/SystemLogs.tsx (8)

19-66: Excellent refactoring to a reducer pattern! 👍

The implementation of a unified search state with a reducer pattern is a significant improvement over managing multiple separate state variables. This addresses the previous feedback about making "impossible states impossible" and reduces the potential for bugs from inconsistent state.

The state structure clearly represents all possible search states, and the reducer actions are well-defined with appropriate state transitions.


87-89: Great improvement using data attributes instead of style selectors

Using data-highlighted="true" instead of the previous style-based selector (span[style*="background-color: yellow"]) is a significant improvement. This addressing previous feedback and provides a more reliable way to identify highlighted elements.


111-122: Solid navigation logic with proper boundary handling

The navigation implementation correctly handles edge cases:

  • Skips navigation when there are no matches or search is in progress
  • Properly cycles through matches (wrapping from last to first and vice versa)
  • Updates the current index in the state

179-204: Well-structured log rendering with memoization

The memoized rendering logic is well-implemented and correctly passes the search query and match status to child components. The use of data-log-index attribute is a good practice for DOM traversal.


225-231: Properly addressed previous container ref issue

The container ref is now correctly attached to the scrollable FlexBox, resolving the previous issue where the ref was attached to an empty element. This ensures the scroll functionality works as expected.


68-68: Good use of memoization and clean export

Wrapping the component with memo is good for performance optimization, and the explicit export is cleaner than using default export.

Also applies to: 258-260


152-164: Smooth initial scroll implementation

The implementation for initial scrolling to the bottom is well done, with proper checks for loading state and log availability. Using behavior: 'auto' ensures the initial scroll is immediate rather than animated.


124-150:

✅ Verification successful

Verify highlighted element selection works in all browsers

The DOM traversal logic to find the parent log element is well-implemented, but there's a potential edge case: if the DOM structure changes, the data-log-index attribute might not be found.


🏁 Script executed:

#!/bin/bash
# Verify that PlainLog and FormattedLog components properly set data-highlighted attribute

echo "Checking PlainLog component implementation:"
rg -A 10 -B 5 "data-highlighted" --glob "*.tsx" src/components/Service/SystemLog/PlainLog.tsx

echo -e "\nChecking FormattedLog component implementation:"
rg -A 10 -B 5 "data-highlighted" --glob "*.tsx" src/components/Service/SystemLog/FormattedLog.tsx

Length of output: 575


I want to locate the actual component files and see how they annotate each log entry. Let’s search for PlainLog, FormattedLog, and any use of data-log-index or data-highlighted in the repo:

This will tell us the correct paths and whether those components set the necessary attributes.


🏁 Script executed:

#!/bin/bash
# Find where PlainLog and FormattedLog are defined
echo "=== PlainLog definitions ==="
rg -n "PlainLog" --glob "*.tsx"

echo -e "\n=== FormattedLog definitions ==="
rg -n "FormattedLog" --glob "*.tsx"

# Find uses of data-log-index
echo -e "\n=== data-log-index usage ==="
rg -n "data-log-index" --glob "*.tsx"

# Find uses of data-highlighted (highlight attr)
echo -e "\n=== data-highlighted usage ==="
rg -n "data-highlighted" --glob "*.tsx"

Length of output: 2304


Let’s confirm how PlainLog leverages HighlightedText (from FormattedLog) to set the data-highlighted attribute:


🏁 Script executed:

#!/bin/bash
# Check where PlainLog wraps matches in HighlightedText
echo "=== HighlightedText import & usage in PlainLog.tsx ==="
rg -n "HighlightedText" --glob "web-server/src/components/Service/SystemLog/PlainLog.tsx"

echo -e "\n=== Context around HighlightedText in PlainLog.tsx ==="
rg -A 5 -B 5 "HighlightedText" --glob "web-server/src/components/Service/SystemLog/PlainLog.tsx"

Length of output: 2083


We should inspect the HighlightedText implementation in FormattedLog.tsx to confirm it applies data-highlighted correctly. Let’s pull that definition:


🏁 Script executed:

#!/bin/bash
# Show HighlightedText implementation including data-highlighted spans
rg -A5 -B20 "const HighlightedText" --glob "web-server/src/components/Service/SystemLog/FormattedLog.tsx"

Length of output: 2413


Confirmed data-highlighted on all highlighted spans
We’ve verified that:

  • The HighlightedText component in
    web-server/src/components/Service/SystemLog/FormattedLog.tsx renders each match wrapped in
    <span data-highlighted="true">…</span>.
  • PlainLog imports and uses HighlightedText directly.
  • SystemLogs.tsx’s lookup
    containerRef.current?.querySelectorAll('span[data-highlighted="true"]') (line 88) will therefore find every highlighted element.

No changes are required—your DOM traversal and attribute usage will work consistently across browsers.

@harshit078 harshit078 requested a review from jayantbh May 1, 2025 12:50
@samad-yar-khan
Copy link
Contributor

samad-yar-khan commented May 6, 2025

@harshit078 one of the linter checks is failing.

Comment on lines +79 to +91
const handleSearchChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const query = event.target.value;
setSearchQuery(query);
debouncedSearch(query);
},
[debouncedSearch]
);

const handleClear = useCallback(() => {
setSearchQuery('');
onSearch('');
}, [onSearch]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Think about it, when resetting, you're doing both - setting the search query to something, and also triggering search function - separately - with that query.

Could there be a better way to do this?

);

const showSearchControls = useMemo(
() => searchQuery && totalMatches > 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

What if search query is just a bunch of spaces?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this really doesn't need to be memoized.

Comment on lines 93 to 98
const handleNavigate = useCallback(
(direction: 'prev' | 'next') => {
onNavigate(direction);
},
[onNavigate]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

If handleNavigate just calls onNavigate and nothing else, and it re-triggers when onNavigate changes, why even wrap it in a useCallback?

<SearchIcon color="action" />
</InputAdornment>
),
endAdornment: searchQuery && (
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps using Boolean(searchQuery.length) (or !!searchQuery.length) will be a more obvious way of doing this.

dispatch({ type: 'SET_SEARCHING', payload: true });

const timer = setTimeout(() => {
requestAnimationFrame(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why requestAnimationFrame here?

if (total === 0 || isSearching) return;

const newIndex = direction === 'next'
? (currentIndex < total ? currentIndex + 1 : 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

This indexing starts at 1?

Copy link
Contributor

Choose a reason for hiding this comment

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

For next, you can just use modulus.
curr + 1 % total or something.
prev is fine.

Comment on lines 135 to 140
while (parentElement && !parentElement.hasAttribute('data-log-index')) {
parentElement = parentElement.parentElement;
}

if (parentElement) {
const lineIndex = parseInt(parentElement.getAttribute('data-log-index') || '-1', 10);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a better way than reading to/writing from HTML attributes?

Copy link
Author

Choose a reason for hiding this comment

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

We can use React's createRef and then alter our approach using react ref system. Shall we implement this way, What do you think ?

if (containerRef.current && !showScrollDownButton.value) {
scrollDown();
const { currentIndex, elements } = searchState;
if (currentIndex === 0 || !elements.length) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think index being 0 as a "null" state will confused folks.
In almost all languages indices start at 0.

If you MUST start from 1, maybe call it rank, or order, etc. But I really think with very little refactoring you can make it start from 0.

Copy link
Author

Choose a reason for hiding this comment

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

I agree, I made the refactoring 0 based indexing

@jayantbh
Copy link
Contributor

jayantbh commented May 6, 2025

Much better. But still has lint failures, and some feedback remaining.

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

♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

97-99: 🛠️ Refactor suggestion

Use a more specific selector for highlighted elements

Using span[data-highlighted="true"] as a selector could potentially conflict with third-party extensions or other components. Consider using a more specific and namespaced attribute or class name.

-          containerRef.current?.querySelectorAll(
-            'span[data-highlighted="true"]'
-          ) ?? []
+          containerRef.current?.querySelectorAll(
+            'span[data-mhq-log-highlighted="true"]'
+          ) ?? []

Also make sure to update the corresponding attributes in your PlainLog and FormattedLog components.

🧹 Nitpick comments (4)
web-server/src/content/Service/SystemLogs.tsx (4)

122-136: Simplify navigation logic with modulo operator

The navigation logic can be simplified using the modulo operator for the "next" case, making it more concise and easier to understand.

-    const newIndex = direction === 'next'
-      ? (currentIndex < total ? currentIndex + 1 : 1)
-      : (currentIndex > 1 ? currentIndex - 1 : total);
+    const newIndex = direction === 'next'
+      ? ((currentIndex % total) + 1)
+      : (currentIndex > 1 ? currentIndex - 1 : total);

130-130: Consider zero-based indexing for consistency

Starting the indexing at 1 instead of 0 is inconsistent with JavaScript conventions and could lead to confusion. Consider refactoring to use zero-based indexing throughout the codebase.

If changing to zero-based indexing isn't possible at this stage, consider renaming variables to make it clear that they're one-based (e.g., matchPosition instead of currentIndex).


140-166: Extract element navigation logic to a separate function

The element navigation logic is complex and could benefit from being extracted into a separate named function for better readability and maintainability.

Consider extracting this logic to a function like scrollToMatchedElement(element: HTMLElement) that handles finding the parent with data attributes and scrolling to the element.


232-236: Type assertion may not be necessary

The type assertion serviceName as ServiceNames may be unnecessary if the component typing is correctly set up. Consider reviewing if this can be handled through proper typing of the component props.

- serviceName={serviceName as ServiceNames} 
+ serviceName={serviceName}

If type assertion is truly needed, add a comment explaining why.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebdb7bf and 89d9329.

📒 Files selected for processing (4)
  • web-server/src/components/Service/SystemLog/FormattedLog.tsx (2 hunks)
  • web-server/src/components/Service/SystemLog/LogSearch.tsx (1 hunks)
  • web-server/src/components/Service/SystemLog/PlainLog.tsx (1 hunks)
  • web-server/src/content/Service/SystemLogs.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • web-server/src/components/Service/SystemLog/PlainLog.tsx
  • web-server/src/components/Service/SystemLog/LogSearch.tsx
  • web-server/src/components/Service/SystemLog/FormattedLog.tsx
🔇 Additional comments (9)
web-server/src/content/Service/SystemLogs.tsx (9)

26-73: Well-implemented reducer pattern for search state management! 👍

Great job implementing a reducer pattern for the search state. This is a significant improvement that addresses previous feedback about having too many separate states. The SearchState type and reducer implementation are clean and well-organized, making the component's state logic more maintainable.


75-84: Good component organization with memo optimization

Using memo to wrap the component and properly organizing the state declarations shows thoughtful implementation. The initial state setup is clear and the reducer integration is well done.


95-95: Consider if requestAnimationFrame is necessary here

The use of requestAnimationFrame may be unnecessary in this callback that's already wrapped in a setTimeout. Both functions defer execution, and the double deferral could potentially cause a slight delay in updating the UI.

Is there a specific reason for using both setTimeout and requestAnimationFrame? In most cases, just one of these timing mechanisms would be sufficient.


160-165: Ensure smooth scrolling works consistently

Using scrollIntoView with behavior: 'smooth' can be inconsistent across browsers. Consider adding a fallback or polyfill for browsers that don't support smooth scrolling.

Test this functionality in different browsers to ensure consistent behavior.


169-186: Good attention to initial load UX

The effect to scroll to the bottom on initial load is a nice touch for user experience. Using 'auto' for the behavior ensures it happens immediately without animation, which is appropriate for initial load.


188-201: Well-implemented scroll listener for button visibility

This is a good implementation of the scroll listener to toggle the scroll button visibility. The cleanup in the effect's return function properly removes the event listener when the component unmounts.


203-228: Well-structured log rendering with memoization

The memoized renderLogs function is a good performance optimization. It correctly passes the search query and current match status to the log components.


240-245: Great integration of search UI component

The LogSearch component integration looks clean and well-implemented, with all necessary props passed correctly.


252-258: Properly structured container with ref

The container structure with ref attachment is correctly implemented. This addresses previous issues where the ref was attached to an empty element.

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

♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)

85-107: Add logs dependency to search effect

The search effect only depends on searchState.query, but if new logs arrive while the same search query is active, those new logs won't be highlighted until the query changes.

-  }, [searchState.query]);
+  }, [searchState.query, logs]);
🧹 Nitpick comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)

96-100: Consider using a more specific class name for highlighted logs

The .mhq--highlighted-log class is better than querying by style attributes, but consider using an even more specific namespace like .mhq-systemlogs-highlight to avoid potential conflicts with third-party extensions or other components.

         const elements = Array.from(
           containerRef.current?.querySelectorAll(
-            '.mhq--highlighted-log'
+            '.mhq-systemlogs-highlight'
           ) ?? []
         ) as HTMLElement[];

140-169: Consider using a ref-based approach for highlight scrolling

While the DOM traversal approach works, consider using React refs for managing highlighted elements instead of direct DOM manipulation. This would better align with React's declarative paradigm.

You could maintain a map of refs for highlighted elements and use those for scrolling instead of querying the DOM directly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89d9329 and 25c1dee.

📒 Files selected for processing (5)
  • web-server/src/components/Service/SystemLog/FormattedLog.tsx (2 hunks)
  • web-server/src/components/Service/SystemLog/LogSearch.tsx (1 hunks)
  • web-server/src/components/Service/SystemLog/PlainLog.tsx (1 hunks)
  • web-server/src/content/Service/SystemLogs.tsx (3 hunks)
  • web-server/src/hooks/useSystemLogs.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • web-server/src/components/Service/SystemLog/PlainLog.tsx
  • web-server/src/components/Service/SystemLog/FormattedLog.tsx
  • web-server/src/components/Service/SystemLog/LogSearch.tsx
🔇 Additional comments (9)
web-server/src/hooks/useSystemLogs.tsx (1)

9-9: LGTM: Type signature update correctly makes serviceName required

Making serviceName a required parameter is a good improvement that aligns with the search functionality implementation, ensuring logs are always associated with a specific service.

web-server/src/content/Service/SystemLogs.tsx (8)

26-73: Nice implementation of reducer pattern for search state

The use of a reducer pattern with well-defined types for managing complex search state is a good design choice. It makes state transitions explicit and easier to reason about, which will help maintain the code as this feature evolves.


95-95: Consider if requestAnimationFrame is necessary here

The requestAnimationFrame call wraps a DOM query operation, which is already synchronous. While this won't cause issues, it may not be necessary in this specific case and adds complexity.

Do you have specific performance reasons for using requestAnimationFrame here instead of directly querying the DOM?


122-138: Good implementation of navigation with modulus for wrapping

The navigation logic is well-implemented, especially the use of modulus for wrapping around to the first match when navigating past the last one. The code also correctly handles edge cases (no matches, search in progress).


171-188: Good implementation of initial scroll behavior

The initial scroll behavior is well-implemented, using a ref to track the initial load state and only scrolling on the first load. Using requestAnimationFrame here is appropriate to ensure scrolling happens after rendering.


190-203: Good addition of scroll event listener

The scroll event listener correctly replaces the previous IntersectionObserver approach and properly cleans up the listener in the effect cleanup function to prevent memory leaks.


205-230: Well-structured memoized log rendering function

The memoized rendering function is well-structured and correctly passes the search query and current match status to the log components. Using useCallback here is good for performance.


242-247: Good integration of LogSearch component

The LogSearch component is well integrated with the necessary props for search and navigation functionality.


254-260: Good implementation of logs container with proper ref

The logs container is correctly implemented with a ref and uses the memoized rendering function. The styling for overflow and max height is appropriate for a scrollable container.

@harshit078 harshit078 requested a review from jayantbh May 12, 2025 13:26
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.

3 participants