Skip to content

Conversation

@gorkem-bwl
Copy link
Contributor

@gorkem-bwl gorkem-bwl commented Oct 13, 2025

Summary

Frontend implementation for monitor grouping functionality. Depends on backend PR #3019.

What's Done

  • Group Field: Added group input to uptime/infrastructure forms with validation (max 50 chars)
  • Grouped Display: Created collapsible group sections in monitor tables
  • Reusable Components: Built GroupedDataTable component and useMonitorGrouping hook
  • Test Coverage: Added comprehensive unit and integration tests

Key Files

  • src/Components/v1/GroupedDataTable/index.jsx - Main grouped display component
  • src/Hooks/v1/useMonitorGrouping.js - Grouping logic hook
  • src/Hooks/v1/useGroups.js - API hook for groups
  • Form updates in both uptime and infrastructure pages

Test Cases Added

  • Unit tests for useMonitorGrouping hook (edge cases, sorting, validation)
  • Unit tests for useGetGroupsByTeamId hook (API integration, error handling)
  • Component tests for GroupedDataTable (rendering, collapsing, empty states)
  • Integration tests for form submission with group field (both monitor types)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Monitors tables now support grouping, with collapsible sections, counts, and an “Other Monitors” section for ungrouped items.
    • Added an optional “Group” field to Uptime and Infrastructure monitor creation forms, including display, state handling, and submission.
  • Improvements
    • Validation added for the Group field (optional, trimmed, max 50 characters) with clear error messages.
  • Tests
    • Comprehensive tests for grouping UI, grouping hooks, and form integration for the Group field.
  • Chores
    • Test tooling added (Vitest, Testing Library) with setup, helpers, and scripts.

Add complete frontend support for monitor grouping:

- Add group field to uptime and infrastructure monitor forms
- Implement validation for group field (max 50 chars)
- Create reusable hooks for monitor grouping logic
- Add grouped display with collapsible sections
- Integrate with backend API for group fetching
- Support both grouped and ungrouped monitor views

Features:
- Group input fields in monitor create/edit forms
- Automatic grouping and sorting of monitors
- Collapsible group sections with monitor counts
- Fallback to standard table when no groups exist
- Consistent design using Checkmate UI components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Oct 13, 2025

Walkthrough

Adds monitor grouping: new GroupedDataTable UI, hooks to compute and fetch groups, inclusion of an optional monitor "group" field across forms, payloads, and validation, NetworkService support, and extensive tests and test tooling for the feature.

Changes

Cohort / File(s) Summary
Grouped data table UI
client/src/Components/v1/GroupedDataTable/index.jsx
New GroupedDataTable and GroupSection components with expand/collapse headers, monitor count badges, ungrouped handling, PropTypes, and rendering of nested DataTable.
Grouping logic hooks
client/src/Hooks/v1/useMonitorGrouping.js, client/src/Hooks/v1/useGroups.js
Adds useMonitorGrouping to compute sorted grouped/ungrouped monitors and useGetGroupsByTeamId to fetch team groups with loading/error/toast handling.
Monitor payload & hooks update
client/src/Hooks/v1/monitorHooks.js
Updates update payload to include `group: monitor.group
Form state & submit integration (Uptime & Infrastructure)
client/src/Pages/v1/Uptime/Create/index.jsx, client/src/Pages/v1/Infrastructure/Create/index.jsx, client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx, client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
Adds group field to form state, inputs, validation wiring, and ensures group is included in create/update payloads (`group
Tables integration (Monitors pages)
client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx, client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx
Integrates useMonitorGrouping; conditionally renders GroupedDataTable when groups exist, otherwise DataTable; consolidates table config and row click behavior.
Network service
client/src/Utils/NetworkService.js
Adds getGroupsByTeamId() performing GET /monitors/team/groups.
Validation
client/src/Validation/validation.js
Adds optional trimmed group string (max 50 chars) to monitor and infrastructure monitor validations.
Testing: component & hook tests
client/src/Components/v1/GroupedDataTable/__tests__/index.test.jsx, client/src/Hooks/v1/__tests__/useGroups.test.js, client/src/Hooks/v1/__tests__/useMonitorGrouping.test.js, client/src/Pages/v1/Infrastructure/Create/__tests__/form-integration.test.jsx, client/src/Pages/v1/Uptime/Create/__tests__/form-integration.test.jsx
Adds comprehensive tests for grouping UI, hooks, and form integration around the new group field and grouping behaviors; uses mocks for network, DataTable, icons, and toasts.
Test tooling & utilities
client/src/test/setup.js, client/src/test/utils.jsx, client/src/test/i18n-mock.js, client/vitest.config.js, client/package.json
Adds test setup (jsdom polyfills, observers), test utilities (renderWithProviders, mockMonitors, mockGroups), i18n test mock, Vitest config, and testing deps/scripts in package.json.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Page as Monitors Page
  participant Hook as useMonitorGrouping
  participant GDT as GroupedDataTable
  participant DT as DataTable

  Page->>Hook: supply monitors[]
  Hook-->>Page: { grouped, ungrouped }, hasGroups
  alt hasGroups == true
    Page->>GDT: headers, groupedMonitors, config
    GDT->>GDT: render GroupSection per group
    GDT->>DT: render DataTable for each group's monitors
  else
    Page->>DT: headers, monitors, config
    DT-->>Page: render flat table
  end
Loading
sequenceDiagram
  autonumber
  participant Comp as Component
  participant UseHook as useGetGroupsByTeamId
  participant Net as NetworkService
  participant API as /monitors/team/groups

  Comp->>UseHook: mount
  UseHook->>UseHook: set isLoading=true
  UseHook->>Net: getGroupsByTeamId()
  Net->>API: GET /monitors/team/groups
  API-->>Net: 200 JSON [...]
  Net-->>UseHook: data
  UseHook->>UseHook: set groups, isLoading=false
  UseHook-->>Comp: [groups, isLoading, error]
  note over UseHook: on error -> set error, isLoading=false, toast shown
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I grouped my checks in burrows neat,
Headers fold and badges beat.
Hooks fetch clans and tables sing,
Payloads carry a tidy string.
A rabbit tests, then hops away—reports in spring. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The provided description does not adhere to the repository’s required template because it omits the “Fixes #” section and entirely lacks the mandatory checklist of preparatory steps. Please revise the description to include a “Fixes #” line referencing the relevant issue and add the full checklist with all required verification items as checkboxes according to the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the main purpose of the PR by indicating it implements monitor grouping on the frontend and directly aligns with the core changes in the code. It is concise, clear, and free of extraneous details or emojis.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/monitor-grouping-frontend

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Critical fix: The useUpdateMonitor hook was missing the group field
in the updatedFields object, which meant group assignments would not
be saved when editing existing monitors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
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

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97a14ed and ab5118b.

📒 Files selected for processing (11)
  • client/src/Components/v1/GroupedDataTable/index.jsx (1 hunks)
  • client/src/Hooks/v1/useGroups.js (1 hunks)
  • client/src/Hooks/v1/useMonitorGrouping.js (1 hunks)
  • client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (3 hunks)
  • client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (1 hunks)
  • client/src/Pages/v1/Infrastructure/Create/index.jsx (1 hunks)
  • client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (4 hunks)
  • client/src/Pages/v1/Uptime/Create/index.jsx (3 hunks)
  • client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (4 hunks)
  • client/src/Utils/NetworkService.js (1 hunks)
  • client/src/Validation/validation.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (1)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (1)
  • infrastructureMonitor (3-21)
client/src/Pages/v1/Uptime/Create/index.jsx (1)
client/src/Pages/v1/Infrastructure/Create/index.jsx (1)
  • onChange (113-117)
client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (3)
client/src/Hooks/v1/useMonitorGrouping.js (4)
  • useMonitorGrouping (8-57)
  • useMonitorGrouping (8-57)
  • hasGroups (54-54)
  • groupedMonitors (9-52)
client/src/Components/v1/GroupedDataTable/index.jsx (5)
  • theme (27-27)
  • theme (127-127)
  • hasGroups (136-136)
  • GroupedDataTable (121-190)
  • groupedMonitors (134-134)
client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx (1)
  • LoadingSpinner (4-40)
client/src/Hooks/v1/useMonitorGrouping.js (3)
client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (1)
  • useMonitorGrouping (133-133)
client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (1)
  • useMonitorGrouping (64-64)
client/src/Components/v1/GroupedDataTable/index.jsx (2)
  • groupedMonitors (134-134)
  • hasGroups (136-136)
client/src/Pages/v1/Infrastructure/Create/index.jsx (2)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (1)
  • infrastructureMonitor (3-21)
client/src/Pages/v1/Uptime/Create/index.jsx (2)
  • onChange (250-284)
  • errors (68-68)
client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (3)
client/src/Hooks/v1/useMonitorGrouping.js (4)
  • useMonitorGrouping (8-57)
  • useMonitorGrouping (8-57)
  • hasGroups (54-54)
  • groupedMonitors (9-52)
client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (4)
  • useMonitorGrouping (133-133)
  • tableConfig (135-146)
  • theme (30-30)
  • headers (39-103)
client/src/Components/v1/GroupedDataTable/index.jsx (5)
  • theme (27-27)
  • theme (127-127)
  • hasGroups (136-136)
  • GroupedDataTable (121-190)
  • groupedMonitors (134-134)
client/src/Hooks/v1/useGroups.js (1)
client/src/Utils/NetworkService.js (1)
  • networkService (1163-1163)
client/src/Components/v1/GroupedDataTable/index.jsx (1)
client/src/Hooks/v1/useMonitorGrouping.js (2)
  • groupedMonitors (9-52)
  • hasGroups (54-54)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (2)
client/src/Pages/v1/Infrastructure/Create/index.jsx (1)
  • monitor (42-45)
client/src/Pages/v1/Uptime/Create/index.jsx (1)
  • monitor (56-67)
🪛 GitHub Actions: Format Check (Client & Server)
client/src/Hooks/v1/useMonitorGrouping.js

[warning] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix.

client/src/Hooks/v1/useGroups.js

[warning] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix.

client/src/Components/v1/GroupedDataTable/index.jsx

[warning] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix.

🔇 Additional comments (30)
client/src/Validation/validation.js (2)

234-236: LGTM! Group validation is well-defined.

The validation appropriately defines group as an optional field with a 50-character limit, trimming, and support for null/empty values.


454-456: LGTM! Consistent validation across schemas.

The group validation in infrastructureMonitorValidation matches the one in monitorValidation, ensuring consistency across monitor types.

client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (1)

17-17: LGTM! Appropriate handling of optional group field.

Using || undefined ensures that falsy values (including empty strings) are sent as undefined in the payload, which is appropriate for optional fields.

client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (3)

6-6: LGTM! Group field properly initialized.

The group field is correctly added to the initial form state with an empty string default, consistent with other optional text fields.


41-41: LGTM! Create path initializes group correctly.

The group field is properly reset to an empty string when initializing for creation.


63-63: LGTM! Update path handles group appropriately.

Using monitor.group || "" ensures the field defaults to an empty string if the monitor doesn't have a group set.

client/src/Pages/v1/Infrastructure/Create/index.jsx (1)

263-273: LGTM! Group input field is well-integrated.

The Group field is properly added to the form with appropriate bindings, validation, and helpful placeholder text. The placement is logical and consistent with other optional fields.

client/src/Pages/v1/Uptime/Create/index.jsx (3)

194-194: LGTM! Group field added to create payload.

The group field is appropriately included in the create monitor payload using the || undefined pattern for optional fields.


214-214: LGTM! Group field added to update payload.

The group field is consistently included in the update monitor payload.


612-622: LGTM! Group input field properly integrated.

The Group TextInput is well-integrated into the uptime monitor form with proper state binding, validation, and helpful placeholder text.

client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (4)

4-4: LGTM! Appropriate imports for grouping functionality.

The imports for GroupedDataTable and useMonitorGrouping are correctly added to support the new grouping feature.

Also applies to: 18-18


64-64: LGTM! Grouping hook properly integrated.

The useMonitorGrouping hook is correctly called with filteredMonitors to derive grouping information.


191-203: LGTM! Excellent refactor with shared table configuration.

Extracting the common table configuration eliminates duplication between the grouped and ungrouped rendering paths. This improves maintainability.


208-220: LGTM! Clean conditional rendering logic.

The conditional rendering appropriately switches between GroupedDataTable and DataTable based on whether groups exist, while maintaining consistent configuration.

client/src/Hooks/v1/useGroups.js (1)

14-34: Verify fetch-on-mount is the intended behavior.

The empty dependency array [] means groups are fetched only once when the component mounts. If groups can change during the component lifecycle (e.g., when a user creates/deletes a group), you may need to expose a refetch mechanism or add dependencies.

Is fetch-on-mount the intended behavior, or should there be a way to refetch groups when they change?

client/src/Utils/NetworkService.js (2)

1035-1040: LGTM! Method implementation is correct.

The getGroupsByTeamId() method correctly makes a GET request to /monitors/team/groups with appropriate headers.


1032-1041: No duplicate getGroupsByTeamId declarations. Only one async getGroupsByTeamId() exists in NetworkService.js; no further action needed.

client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (4)

4-4: LGTM: Imports are correct.

The new imports for GroupedDataTable and useMonitorGrouping are properly added and follow the existing import patterns.

Also applies to: 17-17


123-123: LGTM: Group field correctly included.

The group field is properly included in the mapped monitor data, which enables the grouping functionality.


133-146: LGTM: Good refactor to extract common configuration.

The extraction of tableConfig reduces duplication between the grouped and non-grouped rendering paths. The useMonitorGrouping hook is correctly invoked with the transformed data.

Note: The TODO comment on Line 136 suggests that similar row styling behavior exists elsewhere. Consider consolidating this pattern across tables in a future refactor.


151-165: LGTM: Conditional rendering correctly implemented.

The conditional rendering between GroupedDataTable and DataTable based on hasGroups is correct. Both branches properly pass the required props and share the common tableConfig for consistent behavior.

client/src/Hooks/v1/useMonitorGrouping.js (4)

8-12: LGTM: Clean hook signature with proper early return.

The hook properly handles edge cases with a default empty array parameter and early return for null/empty input, avoiding unnecessary computation.


17-30: LGTM: Robust grouping logic.

The grouping logic correctly handles:

  • Different data structures via the dual check monitor.group || monitor.monitor?.group
  • Whitespace with trim() before validation and usage
  • Edge cases like empty/null groups

32-49: LGTM: Consistent and locale-aware sorting.

The sorting implementation:

  • Uses case-insensitive comparison (sensitivity: 'base')
  • Provides fallback from name to url for sorting keys
  • Applies consistent sorting to groups, monitors within groups, and ungrouped monitors
  • Uses localeCompare for proper internationalization support

9-9: LGTM: Proper memoization and return values.

The hook correctly:

  • Memoizes the grouping computation with useMemo
  • Specifies the correct dependency (monitors)
  • Computes hasGroups for conditional rendering
  • Returns the expected shape for consumers

Also applies to: 51-56

client/src/Components/v1/GroupedDataTable/index.jsx (5)

19-108: LGTM: Well-structured collapsible group component.

GroupSection provides a clean, user-friendly interface with:

  • Customizable default expanded state
  • Clear group name and monitor count display with proper i18n pluralization
  • Intuitive click-to-toggle interaction
  • Reuse of existing DataTable component for consistency

121-132: LGTM: Clean component signature with conditional rendering.

The component signature and early return pattern are consistent with the codebase's conventions for conditional rendering.


134-162: LGTM: Proper fallback handling for edge cases.

The component correctly handles:

  • Empty state when no monitors exist (grouped or ungrouped)
  • Graceful degradation to regular DataTable when grouping is not applicable
  • Uses fallback empty view message when config doesn't provide one

164-189: LGTM: Thoughtful grouped rendering with smart defaults.

The rendering logic shows good UX consideration:

  • Groups are rendered first, each with a unique key
  • Ungrouped section uses i18n-friendly label
  • Smart defaultExpanded for ungrouped: expanded when no groups exist, ensuring visibility when ungrouped monitors are the only content

192-208: LGTM: Comprehensive PropTypes definitions.

The PropTypes properly validate:

  • Required vs optional props
  • Shape of groupedMonitors object with grouped and ungrouped fields
  • All component props for both GroupSection and GroupedDataTable

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

🧹 Nitpick comments (5)
client/src/Utils/NetworkService.js (1)

1032-1041: Consider adding JSDoc for consistency.

All other methods in this file have detailed JSDoc comments. Adding documentation for getGroupsByTeamId() would maintain codebase consistency and improve developer experience.

Example JSDoc:

/**
 * ************************************
 * Fetch all monitor groups for a team
 * ************************************
 *
 * @async
 * @returns {Promise<AxiosResponse>} The response from the axios GET request containing the groups data.
 */
async getGroupsByTeamId() {
	return this.axiosInstance.get(`/monitors/team/groups`, {
		headers: {
			"Content-Type": "application/json",
		},
	});
}
client/src/Hooks/v1/useGroups.js (1)

14-34: Consider adding a refetch mechanism.

The hook currently fetches groups only on mount (empty dependency array). If you need to refetch groups after certain actions (e.g., after creating a monitor with a new group), consider returning a refetch function.

Example implementation:

export const useGetGroupsByTeamId = () => {
	const [groups, setGroups] = useState([]);
	const [isLoading, setIsLoading] = useState(false);
	const [error, setError] = useState(null);
	const [refetchTrigger, setRefetchTrigger] = useState(0);

	const refetch = useCallback(() => {
		setRefetchTrigger(prev => prev + 1);
	}, []);

	useEffect(() => {
		const fetchGroups = async () => {
			// ... existing fetch logic ...
		};

		fetchGroups();
	}, [refetchTrigger]); // Add refetchTrigger to dependencies

	return [groups, isLoading, error, refetch];
};

This allows consumers to trigger a refetch when needed: const [groups, isLoading, error, refetch] = useGetGroupsByTeamId();

client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (1)

135-146: Address the TODO: consolidate repeated table configuration.

The inline TODO correctly identifies that the tableConfig pattern (row hover styles, cursor pointer, and onRowClick) is duplicated across monitor tables. This configuration appears in both Infrastructure and Uptime monitor tables.

Consider extracting this configuration into a shared utility or higher-order component. Would you like me to open an issue to track this refactoring task, or generate a proposal for consolidating this pattern?

client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (1)

191-203: Duplicate table configuration pattern.

This tableConfig is nearly identical to the one in MonitorsTable/index.jsx (lines 135-146). Both define the same row hover styling, cursor pointer, and click navigation pattern.

Consider consolidating this pattern as noted in the TODO comment in MonitorsTable/index.jsx. A shared utility or configuration factory would reduce duplication and improve maintainability.

client/src/Hooks/v1/useMonitorGrouping.js (1)

17-30: Remove unused nested group fallback
No references to monitor.monitor.group exist elsewhere—either drop the || monitor.monitor?.group fallback to simplify or add documentation linking it to the API’s nested monitor shape.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97a14ed and 20f6949.

📒 Files selected for processing (12)
  • client/src/Components/v1/GroupedDataTable/index.jsx (1 hunks)
  • client/src/Hooks/v1/monitorHooks.js (1 hunks)
  • client/src/Hooks/v1/useGroups.js (1 hunks)
  • client/src/Hooks/v1/useMonitorGrouping.js (1 hunks)
  • client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (3 hunks)
  • client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (1 hunks)
  • client/src/Pages/v1/Infrastructure/Create/index.jsx (1 hunks)
  • client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (4 hunks)
  • client/src/Pages/v1/Uptime/Create/index.jsx (3 hunks)
  • client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (4 hunks)
  • client/src/Utils/NetworkService.js (1 hunks)
  • client/src/Validation/validation.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (1)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (1)
  • infrastructureMonitor (3-21)
client/src/Hooks/v1/useMonitorGrouping.js (3)
client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (1)
  • useMonitorGrouping (133-133)
client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (1)
  • useMonitorGrouping (64-64)
client/src/Components/v1/GroupedDataTable/index.jsx (2)
  • groupedMonitors (134-134)
  • hasGroups (136-136)
client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (3)
client/src/Hooks/v1/useMonitorGrouping.js (4)
  • useMonitorGrouping (8-57)
  • useMonitorGrouping (8-57)
  • hasGroups (54-54)
  • groupedMonitors (9-52)
client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (4)
  • useMonitorGrouping (133-133)
  • tableConfig (135-146)
  • theme (30-30)
  • headers (39-103)
client/src/Components/v1/GroupedDataTable/index.jsx (5)
  • theme (27-27)
  • theme (127-127)
  • hasGroups (136-136)
  • GroupedDataTable (121-190)
  • groupedMonitors (134-134)
client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (2)
client/src/Hooks/v1/useMonitorGrouping.js (4)
  • useMonitorGrouping (8-57)
  • useMonitorGrouping (8-57)
  • hasGroups (54-54)
  • groupedMonitors (9-52)
client/src/Components/v1/GroupedDataTable/index.jsx (5)
  • theme (27-27)
  • theme (127-127)
  • hasGroups (136-136)
  • GroupedDataTable (121-190)
  • groupedMonitors (134-134)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (2)
client/src/Pages/v1/Infrastructure/Create/index.jsx (1)
  • monitor (42-45)
client/src/Pages/v1/Uptime/Create/index.jsx (1)
  • monitor (56-67)
client/src/Pages/v1/Uptime/Create/index.jsx (1)
client/src/Pages/v1/Infrastructure/Create/index.jsx (1)
  • onChange (113-117)
client/src/Components/v1/GroupedDataTable/index.jsx (1)
client/src/Hooks/v1/useMonitorGrouping.js (1)
  • groupedMonitors (9-52)
client/src/Pages/v1/Infrastructure/Create/index.jsx (2)
client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (1)
  • infrastructureMonitor (3-21)
client/src/Pages/v1/Uptime/Create/index.jsx (2)
  • onChange (250-284)
  • errors (68-68)
client/src/Hooks/v1/useGroups.js (2)
client/src/Pages/v1/Uptime/Create/index.jsx (1)
  • isLoading (96-96)
client/src/Utils/NetworkService.js (1)
  • networkService (1163-1163)
🪛 GitHub Actions: Format Check (Client & Server)
client/src/Hooks/v1/useMonitorGrouping.js

Prettier formatting check failed. Run 'prettier --write' to fix code style issues.

client/src/Components/v1/GroupedDataTable/index.jsx

Prettier formatting check failed. Run 'prettier --write' to fix code style issues.

client/src/Hooks/v1/useGroups.js

Prettier formatting check failed. Run 'prettier --write' to fix code style issues.

🔇 Additional comments (20)
client/src/Hooks/v1/monitorHooks.js (1)

372-372: LGTM!

The group field is correctly added to the update payload with the || undefined pattern, ensuring explicit undefined values are sent when the field is empty, which aligns with the backend's optional field handling.

client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (1)

6-6: LGTM!

The group field is correctly initialized in all three state management scenarios: initial state, create initialization, and update initialization. The consistent use of empty string "" aligns with the form's text input expectations.

Also applies to: 41-41, 63-63

client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (1)

17-17: LGTM!

The group field is correctly added to the infrastructure monitor payload using the || undefined pattern, ensuring consistent optional field handling.

client/src/Pages/v1/Uptime/Create/index.jsx (1)

194-194: LGTM!

The group field is correctly included in both create and update payloads with the || undefined pattern.

Also applies to: 214-214

client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (4)

4-4: LGTM!

The imports for GroupedDataTable and useMonitorGrouping are correctly added to support the new grouping feature.

Also applies to: 17-17


123-123: LGTM!

The group field is correctly added to the data mapping, enabling the grouping functionality.


133-133: LGTM!

The useMonitorGrouping hook is correctly applied to compute grouped monitors and determine if groups exist.


151-165: LGTM!

The conditional rendering correctly switches between GroupedDataTable (when groups exist) and standard DataTable (when no groups exist), maintaining consistent configuration and loading state handling.

client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx (3)

4-4: LGTM!

The imports for GroupedDataTable and useMonitorGrouping are correctly added.

Also applies to: 18-18


64-64: LGTM!

The useMonitorGrouping hook correctly receives filteredMonitors as input, ensuring grouping respects search and filter operations.


208-220: LGTM!

The conditional rendering correctly switches between grouped and standard table views, maintaining consistent header and configuration handling.

client/src/Hooks/v1/useMonitorGrouping.js (3)

8-12: LGTM!

The hook structure is well-designed with proper use of useMemo for performance optimization and an early return for the empty array edge case.


32-49: LGTM!

The sorting logic is well-implemented with:

  • Case-insensitive alphabetical sorting for both groups and monitors
  • Proper fallback to empty string when name or url is missing
  • Consistent sorting applied to both grouped and ungrouped monitors

54-56: LGTM!

The return value correctly provides both groupedMonitors (the sorted data structure) and hasGroups (a convenient boolean flag) for consumers.

client/src/Components/v1/GroupedDataTable/index.jsx (6)

26-32: LGTM!

The state management for the collapsible group is straightforward and correctly implements expand/collapse functionality.


46-91: LGTM!

The group header is well-designed with:

  • Accessible click target (entire header)
  • Proper i18n pluralization for monitor count
  • Theme-consistent styling
  • Clear visual feedback with expand/collapse icons

94-106: LGTM!

The collapsible content correctly wraps the DataTable and provides a group-specific empty state message.


130-162: LGTM!

The guard clauses are well-structured, progressively handling edge cases:

  1. Early return when shouldRender is false
  2. Empty state when no monitors exist
  3. Standard table when only ungrouped monitors exist
  4. Grouped view for all other cases

This provides a clear fallback strategy and maintains backward compatibility.


164-189: LGTM!

The rendering logic is well-designed:

  • Proper React key for mapped groups
  • Smart default expansion (ungrouped section only expands when it's the only section)
  • Consistent i18n usage with fallback text

192-208: LGTM!

The PropTypes definitions are comprehensive and correctly validate the component APIs, including the shape of groupedMonitors with its grouped and ungrouped properties.

Comment on lines +25 to +27
createToast({
body: "Failed to load monitor groups",
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hard-coded error message breaks i18n/l10n support.

The error message should use the t() function for internationalization, consistent with other toasts in the codebase.

Apply this diff:

+import { useTranslation } from "react-i18next";
+
 export const useGetGroupsByTeamId = () => {
+	const { t } = useTranslation();
 	const [groups, setGroups] = useState([]);
 	// ... other state ...
 	
 	// ... in the catch block:
 	createToast({
-		body: "Failed to load monitor groups",
+		body: t("failedToLoadGroups"),
 	});

Note: Add the translation key failedToLoadGroups to your i18n translation files.

🧰 Tools
🪛 GitHub Actions: Format Check (Client & Server)

Prettier formatting check failed. Run 'prettier --write' to fix code style issues.

🤖 Prompt for AI Agents
In client/src/Hooks/v1/useGroups.js around lines 25-27, replace the hard-coded
toast message with a localized string by passing t('failedToLoadGroups') to
createToast; if t isn't in scope import or obtain it via the project i18n hook
(e.g., useTranslation()) and use that t function, and add the new key
failedToLoadGroups to the i18n translation files as requested.

Comment on lines +263 to +273
<TextInput
type="text"
id="group"
name="group"
label="Group"
placeholder="e.g., Production, Development, Network"
isOptional={true}
value={infrastructureMonitor.group}
onChange={onChange}
error={errors["group"]}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hard-coded label breaks i18n/l10n support.

The label "Group" should use the t() function for internationalization, consistent with other labels in this form (e.g., t("infrastructureDisplayNameLabel")).

Apply this diff to use a translation key:

 <TextInput
 	type="text"
 	id="group"
 	name="group"
-	label="Group"
+	label={t("infrastructureGroupLabel")}
 	placeholder="e.g., Production, Development, Network"
 	isOptional={true}
 	value={infrastructureMonitor.group}
 	onChange={onChange}
 	error={errors["group"]}
 />

Note: Ensure the translation key infrastructureGroupLabel is added to your i18n translation files.

📝 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
<TextInput
type="text"
id="group"
name="group"
label="Group"
placeholder="e.g., Production, Development, Network"
isOptional={true}
value={infrastructureMonitor.group}
onChange={onChange}
error={errors["group"]}
/>
<TextInput
type="text"
id="group"
name="group"
label={t("infrastructureGroupLabel")}
placeholder="e.g., Production, Development, Network"
isOptional={true}
value={infrastructureMonitor.group}
onChange={onChange}
error={errors["group"]}
/>
🤖 Prompt for AI Agents
In client/src/Pages/v1/Infrastructure/Create/index.jsx around lines 263 to 273
the TextInput label is hard-coded as "Group", breaking i18n; replace the literal
with a call to the t() translation function (e.g.,
t("infrastructureGroupLabel")) and pass that result as the label prop, and
ensure the key infrastructureGroupLabel is added to the i18n translation files
for all supported locales.

Comment on lines +612 to +622
<TextInput
name="group"
type="text"
label="Group"
isOptional={true}
placeholder="e.g., Production, Development, Network"
value={monitor.group || ""}
onChange={onChange}
error={errors["group"] ? true : false}
helperText={errors["group"]}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hard-coded label breaks i18n/l10n support.

The label "Group" should use the t() function for internationalization, consistent with other labels in this form (e.g., t("displayName"), t("checkFrequency")).

Apply this diff to use a translation key:

 <TextInput
 	name="group"
 	type="text"
-	label="Group"
+	label={t("groupLabel")}
 	isOptional={true}
 	placeholder="e.g., Production, Development, Network"
 	value={monitor.group || ""}
 	onChange={onChange}
 	error={errors["group"] ? true : false}
 	helperText={errors["group"]}
 />

Note: Ensure the translation key groupLabel is added to your i18n translation files.

📝 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
<TextInput
name="group"
type="text"
label="Group"
isOptional={true}
placeholder="e.g., Production, Development, Network"
value={monitor.group || ""}
onChange={onChange}
error={errors["group"] ? true : false}
helperText={errors["group"]}
/>
<TextInput
name="group"
type="text"
label={t("groupLabel")}
isOptional={true}
placeholder="e.g., Production, Development, Network"
value={monitor.group || ""}
onChange={onChange}
error={errors["group"] ? true : false}
helperText={errors["group"]}
/>
🤖 Prompt for AI Agents
In client/src/Pages/v1/Uptime/Create/index.jsx around lines 612 to 622, the
TextInput label is hard-coded as "Group" which breaks i18n; replace the literal
with the translation call t("groupLabel") (and ensure the translation key
groupLabel exists in i18n files), i.e., import/use the existing t function in
scope and pass t("groupLabel") to the label prop to match other localized labels
in the form.

- Fixed formatting issues for CI/CD compliance
- Added test infrastructure with Vitest and React Testing Library
- Created comprehensive test suite:
  - Unit tests for useMonitorGrouping hook
  - Unit tests for useGetGroupsByTeamId hook
  - Component tests for GroupedDataTable
  - Integration tests for form submission with group field
- Added test utilities and mocks for consistent testing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link

@llamapreview llamapreview bot left a comment

Choose a reason for hiding this comment

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

AI Code Review by LlamaPReview

🎯 TL;DR & Recommendation

Recommendation: Approve with suggestions

This PR implements monitor grouping with solid architecture but has minor issues affecting user experience, performance, and maintainability that should be addressed.

📄 Documentation Diagram

This diagram documents the new monitor grouping workflow implemented in the frontend.

sequenceDiagram
    participant U as User
    participant F as Frontend
    participant B as Backend
    participant G as Grouping Logic
    participant T as Grouped Data Table

    U->>F: View monitor list
    F->>B: Fetch monitors
    B-->>F: Return monitors
    F->>G: Group monitors by group field
    G-->>F: Return grouped data
    F->>T: Render grouped table with collapsible sections
    T-->>U: Display organized monitors
    note over T: PR #35;3021 adds grouping display
Loading

🌟 Strengths

  • Well-designed reusable components and hooks for consistent grouping logic.
  • Good integration with existing UI components and validation patterns.
Priority File Category Impact Summary Anchors
P2 Components/v1/.../index.jsx Maintainability Collapse state not persisted, poor UX on re-renders
P2 Hooks/v1/useMonitorGrouping.js Performance Expensive sorting could slow performance with many monitors MonitorsTable, UptimeDataTable
P2 Hooks/v1/useGroups.js Architecture No caching or team scope, unnecessary API calls
P2 Pages/v1/.../index.jsx Architecture Duplicated validation schema risks inconsistencies validation.js
P2 Components/v1/.../index.jsx Documentation Missing translation keys hinders internationalization
P2 Hooks/v1/useMonitorGrouping.js Bug Inconsistent data access could cause grouping errors MonitorsTable, UptimeDataTable

🔍 Notable Themes

  • Internationalization Debt: Multiple components use hardcoded strings without translation keys, hindering multi-language support.
  • Code Consistency: Duplicated validation logic and inconsistent data access patterns increase maintenance burden and risk of errors.
⚠️ **Unanchored Suggestions (Manual Review Recommended)**

The following suggestions could not be precisely anchored to a specific line in the diff. This can happen if the code is outside the changed lines, has been significantly refactored, or if the suggestion is a general observation. Please review them carefully in the context of the full file.


📁 File: client/src/Pages/v1/Uptime/Create/index.jsx

The group validation schema is duplicated across multiple files (Uptime Create and validation.js). This violates DRY principles and could lead to validation inconsistencies. Changes to group validation rules would need to be made in multiple places.

Suggestion:

// In validation.js - export the group validation
export const groupValidation = joi.string().max(50).trim().allow(null, "").optional().messages({
	"string.max": "Group name must be 50 characters or less.",
});

// In Uptime Create - import and use
import { groupValidation } from "@/Validation/validation.js";

const validationSchema = {
	group: groupValidation,
	// ... other validations
};

Related Code:

const validationSchema = {
	group: joi.string().max(50).trim().allow(null, "").optional().messages({
		"string.max": "Group name must be 50 characters or less.",
	}),
};


💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.

Comment on lines +20 to +29
const GroupSection = ({
groupName,
monitors,
headers,
config,
defaultExpanded = true,
}) => {
const [expanded, setExpanded] = useState(defaultExpanded);
const theme = useTheme();
const { t } = useTranslation();
Copy link

Choose a reason for hiding this comment

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

P2 | Confidence: High

The defaultExpanded prop defaults to true but there's no mechanism to persist collapse state across component re-renders. When users collapse groups and then perform actions that trigger re-renders (like refreshing data), all groups will expand again. This creates a poor user experience where users lose their preferred view state.

Code Suggestion:

const GroupSection = ({
	groupName,
	monitors,
	headers,
	config,
	defaultExpanded = true
}) => {
	// Use groupName as part of the key for persistence
	const storageKey = `group-${groupName}-expanded`;
	const [expanded, setExpanded] = useState(() => {
		const saved = localStorage.getItem(storageKey);
		return saved !== null ? JSON.parse(saved) : defaultExpanded;
	});

	const toggleExpanded = () => {
		const newExpanded = !expanded;
		setExpanded(newExpanded);
		localStorage.setItem(storageKey, JSON.stringify(newExpanded));
	};

P2 | Confidence: High

The component uses translation function t() but doesn't provide translation keys for hardcoded strings like "No monitors in ${groupName}". This creates internationalization debt and inconsistent user experience across languages.

Code Suggestion:

// In the DataTable usage within GroupSection
<DataTable
	headers={headers}
	data={monitors}
	config={{
		...config,
		emptyView: t('groupedDataTable.emptyGroup', { group: groupName }),
	}}
/>

// And ensure translation keys are defined:
// groupedDataTable.emptyGroup: "No monitors in {{group}}"

Comment on lines +9 to +16
export const useGetGroupsByTeamId = () => {
const [groups, setGroups] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
const fetchGroups = async () => {
setIsLoading(true);
Copy link

Choose a reason for hiding this comment

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

P2 | Confidence: High

The hook fetches groups on every mount without any caching strategy or dependency injection for teamId. This could lead to unnecessary API calls when components re-mount. In a multi-team environment, this would fail to scope groups to the current team.

Code Suggestion:

export const useGetGroupsByTeamId = (teamId) => {
	const [groups, setGroups] = useState([]);
	const [isLoading, setIsLoading] = useState(false);
	const [error, setError] = useState(null);

	useEffect(() => {
		if (!teamId) return;
		
		const fetchGroups = async () => {
			setIsLoading(true);
			setError(null);
			try {
				const response = await networkService.getGroupsByTeamId(teamId);
				setGroups(response?.data?.data || []);
			} catch (err) {
				// error handling
			} finally {
				setIsLoading(false);
			}
		};

		fetchGroups();
	}, [teamId]);

Comment on lines +8 to +12
export const useMonitorGrouping = (monitors = []) => {
const groupedMonitors = useMemo(() => {
if (!monitors || monitors.length === 0) {
return { grouped: {}, ungrouped: [] };
}
Copy link

Choose a reason for hiding this comment

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

P2 | Confidence: Medium

The hook performs expensive sorting operations on every render when monitors change. In large deployments with hundreds of monitors, the localeCompare operations with sensitivity settings could cause performance issues. Both MonitorsTable and UptimeDataTable use this hook, potentially doubling the impact during data refreshes.

Code Suggestion:

export const useMonitorGrouping = (monitors = []) => {
	const groupedMonitors = useMemo(() => {
		if (!monitors || monitors.length === 0) {
			return { grouped: {}, ungrouped: [] };
		}

		// Use simpler comparison for better performance
		const caseInsensitiveCompare = (a, b) => a.localeCompare(b);
		
		// ... rest of sorting logic with caseInsensitiveCompare

Evidence: path:client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx, path:client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx


monitors.forEach((monitor) => {
// Check if monitor has a group (either from the monitor object or monitor.monitor object)
const group = monitor.group || monitor.monitor?.group;
Copy link

Choose a reason for hiding this comment

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

P2 | Confidence: Medium

The grouping logic handles both monitor.group and monitor.monitor.group but this creates inconsistent data access patterns. The infrastructure and uptime tables pass different monitor object structures, which could lead to grouping inconsistencies. This dual access pattern suggests an architectural inconsistency in how monitor data is structured across the application.

Code Suggestion:

// Standardize on a single approach - either always use monitor.group
// or create a helper function to extract group consistently
const getMonitorGroup = (monitor) => {
	return monitor.group || monitor.monitor?.group || '';
};

// Use throughout the grouping logic
const group = getMonitorGroup(monitor);

Evidence: path:client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx, path:client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx

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

♻️ Duplicate comments (1)
client/src/Components/v1/GroupedDataTable/index.jsx (1)

1-210: Run Prettier to fix formatting issues.

Previous reviews identified Prettier formatting inconsistencies in this file. Please run:

prettier --write client/src/Components/v1/GroupedDataTable/index.jsx

Then verify the formatting passes CI checks before merging.

🧹 Nitpick comments (2)
client/src/Hooks/v1/__tests__/useMonitorGrouping.test.js (1)

178-200: Consider clarifying the test name.

The test name "should be case-sensitive for group names" is slightly misleading. Group names are indeed case-sensitive (creating distinct groups), but the sorting uses case-insensitive comparison ({ sensitivity: "base" }). Consider renaming to something like "should treat different casing as distinct groups" to better reflect what's being tested.

Example:

-	it("should be case-sensitive for group names", () => {
+	it("should treat different casing as distinct groups", () => {
client/src/Components/v1/GroupedDataTable/__tests__/index.test.jsx (1)

88-116: Complete the expand/collapse test.

The test logic for expand/collapse is incomplete. After clicking to collapse, the test has a comment "This test might need adjustment based on actual implementation" and doesn't verify the collapsed state.

Consider completing the test:

 		// Click to collapse
 		if (expandButton) {
 			fireEvent.click(expandButton);
 		} else {
 			// If no specific button, click the whole header
 			fireEvent.click(infrastructureHeader);
 		}
 
-		// After collapse, there should be fewer data tables
-		// Note: This test might need adjustment based on actual implementation
+		// After collapse, the Infrastructure group's table should not be visible
+		// but other groups should still be visible
+		await waitFor(() => {
+			const tables = screen.getAllByTestId("data-table");
+			expect(tables.length).toBeLessThan(3);
+		});
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20f6949 and 1bf770d.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • client/package.json (3 hunks)
  • client/src/Components/v1/GroupedDataTable/__tests__/index.test.jsx (1 hunks)
  • client/src/Components/v1/GroupedDataTable/index.jsx (1 hunks)
  • client/src/Hooks/v1/__tests__/useGroups.test.js (1 hunks)
  • client/src/Hooks/v1/__tests__/useMonitorGrouping.test.js (1 hunks)
  • client/src/Hooks/v1/useGroups.js (1 hunks)
  • client/src/Hooks/v1/useMonitorGrouping.js (1 hunks)
  • client/src/Pages/v1/Infrastructure/Create/__tests__/form-integration.test.jsx (1 hunks)
  • client/src/Pages/v1/Uptime/Create/__tests__/form-integration.test.jsx (1 hunks)
  • client/src/test/i18n-mock.js (1 hunks)
  • client/src/test/setup.js (1 hunks)
  • client/src/test/utils.jsx (1 hunks)
  • client/vitest.config.js (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • client/src/test/i18n-mock.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/src/Hooks/v1/useGroups.js
  • client/src/Hooks/v1/useMonitorGrouping.js
🧰 Additional context used
🧬 Code graph analysis (8)
client/src/Hooks/v1/__tests__/useMonitorGrouping.test.js (2)
client/src/Hooks/v1/useMonitorGrouping.js (3)
  • useMonitorGrouping (8-57)
  • useMonitorGrouping (8-57)
  • groupedMonitors (9-52)
client/src/test/utils.jsx (2)
  • mockMonitors (47-168)
  • mockMonitors (47-168)
client/src/test/setup.js (1)
server/src/middleware/v1/sanitization.js (1)
  • window (5-5)
client/src/Pages/v1/Infrastructure/Create/__tests__/form-integration.test.jsx (2)
client/src/test/utils.jsx (1)
  • renderWithProviders (34-44)
client/src/Pages/v1/Infrastructure/Create/index.jsx (1)
  • CreateInfrastructureMonitor (30-377)
client/src/Hooks/v1/__tests__/useGroups.test.js (2)
client/src/test/utils.jsx (2)
  • mockGroups (171-175)
  • mockGroups (171-175)
client/src/Hooks/v1/useGroups.js (2)
  • useGetGroupsByTeamId (9-37)
  • useGetGroupsByTeamId (9-37)
client/src/Components/v1/GroupedDataTable/index.jsx (2)
client/src/test/utils.jsx (1)
  • theme (8-31)
client/src/Hooks/v1/useMonitorGrouping.js (1)
  • groupedMonitors (9-52)
client/src/Pages/v1/Uptime/Create/__tests__/form-integration.test.jsx (2)
client/src/test/utils.jsx (1)
  • renderWithProviders (34-44)
client/src/Pages/v1/Uptime/Create/index.jsx (1)
  • UptimeCreate (51-819)
client/src/test/utils.jsx (1)
client/src/Components/v1/GroupedDataTable/index.jsx (2)
  • theme (28-28)
  • theme (127-127)
client/src/Components/v1/GroupedDataTable/__tests__/index.test.jsx (2)
client/src/test/utils.jsx (1)
  • renderWithProviders (34-44)
client/src/Components/v1/GroupedDataTable/index.jsx (1)
  • GroupedDataTable (126-190)
🔇 Additional comments (20)
client/src/Hooks/v1/__tests__/useMonitorGrouping.test.js (9)

7-26: LGTM!

The test correctly verifies basic grouping functionality, including the hasGroups flag, group structure, monitor counts, and alphabetical ordering within groups.


28-35: LGTM!

The test correctly validates that monitors with null or empty string groups are treated as ungrouped and sorted alphabetically.


37-42: LGTM!

The test correctly verifies alphabetical sorting of group names.


44-50: LGTM!

The test correctly handles the edge case of an empty monitors array.


52-58: LGTM!

The test correctly handles the edge case of undefined monitors input.


60-84: LGTM!

The test thoroughly covers all variations of ungrouped monitors (null, empty string, missing property).


86-113: LGTM!

The test correctly validates behavior when all monitors have groups assigned.


115-136: LGTM!

The test correctly validates the fallback behavior for nested monitor.monitor.group structure.


138-156: LGTM!

The test correctly validates that direct group property takes precedence over nested monitor.group.

client/src/Pages/v1/Infrastructure/Create/__tests__/form-integration.test.jsx (3)

1-96: LGTM!

The mock setup is comprehensive and properly isolated. The beforeEach/afterEach hooks ensure clean test state between runs.


98-227: LGTM!

The tests comprehensively cover Group field rendering, user input, validation, and state management. The assertions correctly verify form behavior and integration with the submission flow.


229-334: LGTM!

The tests thoroughly verify that the Group field integrates correctly with all other form sections (HTTPS protocol, notifications, incident config, custom alerts, frequency selection, and validation flow).

client/src/Components/v1/GroupedDataTable/__tests__/index.test.jsx (3)

1-66: LGTM!

The mock setup effectively isolates the GroupedDataTable component for testing. The mock DataTable renders data as JSON for easy verification, and the test data structure aligns with expected input format.


131-213: LGTM!

The tests thoroughly cover edge cases including empty data, ungrouped-only scenarios, grouped-only scenarios, and the shouldRender flag.


215-337: LGTM!

The tests correctly verify config propagation, custom and default empty views, and handling of multiple groups with varying monitor counts.

client/src/Pages/v1/Uptime/Create/__tests__/form-integration.test.jsx (2)

1-57: LGTM!

The mock setup is comprehensive and follows good testing practices with proper cleanup between tests.


219-345: LGTM!

The tests comprehensively cover Group field behavior across different monitor types (ping, port) and protocol switching (HTTP/HTTPS). The error clearing test is particularly valuable for ensuring proper form state management.

client/src/Components/v1/GroupedDataTable/index.jsx (3)

1-113: LGTM!

The GroupSection component is well-implemented with proper state management, theme integration, and accessibility features (clickable header, expand/collapse icons). The monitor count badge correctly handles singular/plural forms.


115-190: LGTM!

The GroupedDataTable component correctly handles all rendering scenarios:

  • Early return for shouldRender=false
  • Empty state with fallback message
  • Ungrouped-only as regular table
  • Grouped sections with optional ungrouped section
  • Smart defaultExpanded logic for ungrouped section

192-210: LGTM!

The PropTypes definitions are comprehensive and correctly mark required vs optional props. The shape validation for groupedMonitors ensures proper data structure.

Comment on lines +121 to +149
it("should reset error state on new fetch attempt", async () => {
const mockError = new Error("Network error");
mockNetworkService.getGroupsByTeamId.mockRejectedValueOnce(mockError);

const { result, rerender } = renderHook(() => useGetGroupsByTeamId());

// Wait for the error to be handled
await waitFor(() => {
expect(result.current[2]).toBe("Network error");
});

// Mock successful response for retry
const mockGroups = [{ name: "Infrastructure" }];
mockNetworkService.getGroupsByTeamId.mockResolvedValueOnce({
data: {
data: mockGroups,
},
});

// Trigger re-render (simulating component re-mount or dependency change)
rerender();

await waitFor(() => {
expect(result.current[1]).toBe(false); // isLoading should be false
});

expect(result.current[0]).toEqual(mockGroups); // groups should be set
expect(result.current[2]).toBe(null); // error should be reset
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

rerender() won't trigger a refetch here.

useGetGroupsByTeamId runs its effect only on mount ([] deps). Calling rerender() never re-executes the fetch, so this test will keep error === "Network error" and fail. Remount the hook (e.g., via renderHook with a changing key or unmount + fresh render) or expose an explicit refetch API instead of assuming rerender resets state.

🤖 Prompt for AI Agents
In client/src/Hooks/v1/__tests__/useGroups.test.js around lines 121 to 149, the
test incorrectly uses rerender() to trigger a new fetch even though
useGetGroupsByTeamId only runs its effect on mount; update the test to remount
the hook so the effect runs again — either call unmount() then renderHook()
again (or renderHook with a changing key prop) to simulate a fresh mount before
mocking the successful response, then assert isLoading, groups and error as
currently written.

Comment on lines +158 to +176
it("should handle whitespace-only group names", () => {
const monitorsWithWhitespaceGroups = [
{
_id: "1",
name: "Monitor 1",
group: " ",
},
{
_id: "2",
name: "Monitor 2",
group: "\\t\\n",
},
];

const { result } = renderHook(() => useMonitorGrouping(monitorsWithWhitespaceGroups));

expect(result.current.hasGroups).toBe(false);
expect(result.current.groupedMonitors.ungrouped).toHaveLength(2);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix escaped characters in whitespace test.

Line 168 uses the literal string "\\t\\n" instead of actual whitespace characters. This doesn't test the intended scenario of tab and newline characters.

Apply this diff to use actual whitespace characters:

 		{
 			_id: "2",
 			name: "Monitor 2",
-			group: "\\t\\n",
+			group: "\t\n",
 		},
📝 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
it("should handle whitespace-only group names", () => {
const monitorsWithWhitespaceGroups = [
{
_id: "1",
name: "Monitor 1",
group: " ",
},
{
_id: "2",
name: "Monitor 2",
group: "\\t\\n",
},
];
const { result } = renderHook(() => useMonitorGrouping(monitorsWithWhitespaceGroups));
expect(result.current.hasGroups).toBe(false);
expect(result.current.groupedMonitors.ungrouped).toHaveLength(2);
});
it("should handle whitespace-only group names", () => {
const monitorsWithWhitespaceGroups = [
{
_id: "1",
name: "Monitor 1",
group: " ",
},
{
_id: "2",
name: "Monitor 2",
group: "\t\n",
},
];
const { result } = renderHook(() => useMonitorGrouping(monitorsWithWhitespaceGroups));
expect(result.current.hasGroups).toBe(false);
expect(result.current.groupedMonitors.ungrouped).toHaveLength(2);
});
🤖 Prompt for AI Agents
In client/src/Hooks/v1/__tests__/useMonitorGrouping.test.js around lines 158 to
176, the test uses the literal string "\\t\\n" rather than actual tab/newline
characters; replace the double-escaped string with the proper escaped characters
so the group contains a tab and newline (i.e., use "\t\n" instead of "\\t\\n"),
then re-run the test to ensure whitespace-only group names are handled and the
two monitors are classified as ungrouped.

Comment on lines +193 to +217
it("should trim whitespace from group name", async () => {
const user = userEvent.setup();
renderUptimeCreateForm();

const groupInput = screen.getByLabelText(/group/i);
await user.type(groupInput, " Production ");

const urlInput = screen.getByLabelText(/website url/i);
await user.type(urlInput, "example.com");

const nameInput = screen.getByLabelText(/display name/i);
await user.type(nameInput, "Test Monitor");

const submitButton = screen.getByRole("button", { name: /save/i });
await user.click(submitButton);

await waitFor(() => {
expect(mockCreateMonitor).toHaveBeenCalledWith({
monitor: expect.objectContaining({
group: " Production ", // Frontend validation should handle trimming
}),
redirect: "/uptime",
});
});
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test name is misleading.

The test is named "should trim whitespace from group name" but actually verifies that whitespace is NOT trimmed (line 212 expects " Production "). Either the test name should be updated to reflect actual behavior, or trimming logic should be implemented if desired.

Consider one of these options:

  1. Update the test name to reflect current behavior:
-	it("should trim whitespace from group name", async () => {
+	it("should preserve whitespace in group name", async () => {
  1. Or implement trimming in the form (if that's the intended behavior):
group: monitor.group?.trim() || undefined
🤖 Prompt for AI Agents
In client/src/Pages/v1/Uptime/Create/__tests__/form-integration.test.jsx around
lines 193-217 the test is misnamed — it claims "should trim whitespace from
group name" but asserts that the submitted group contains leading/trailing
spaces; either rename the test to reflect current behavior (e.g., "should
preserve whitespace in group name" and keep the expectation), or change the code
that builds the monitor payload to trim the group value before submission (call
trim() on monitor.group or set group to monitor.group?.trim() || undefined) and
update the test expectation to expect "Production" without surrounding spaces.

Comment on lines +4 to +9
global.process = {
...global.process,
env: {
NODE_ENV: "test",
},
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not replace global.process.

Reassigning global.process to a plain object drops Node’s process prototype and wipes existing environment variables, breaking any code (or Vitest itself) that expects standard process APIs/config. Set process.env.NODE_ENV instead of replacing the whole object.

-global.process = {
-	...global.process,
-	env: {
-		NODE_ENV: "test",
-	},
-};
+process.env = {
+	...process.env,
+	NODE_ENV: "test",
+};
📝 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
global.process = {
...global.process,
env: {
NODE_ENV: "test",
},
};
process.env = {
...process.env,
NODE_ENV: "test",
};
🤖 Prompt for AI Agents
In client/src/test/setup.js around lines 4 to 9, do not replace global.process
with a plain object because that removes Node's process prototype and existing
env vars; instead set the environment variable directly by assigning
process.env.NODE_ENV = "test" (or process.env.NODE_ENV ||= "test" if you want to
preserve an existing value) and remove the global.process reassignment so the
native process object and its APIs remain intact.

Copy link
Collaborator

@ajhollid ajhollid left a comment

Choose a reason for hiding this comment

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

@gorkem-bwl can we remove the tests and related dependencies from this PR? We haven't set up a front end testing regime set up yet so anything added now is probably going to be pulled out eventually anyway. Thanks!

@gorkem-bwl gorkem-bwl marked this pull request as draft October 15, 2025 18:36
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