Skip to content

Conversation

@JivusAyrus
Copy link
Member

@JivusAyrus JivusAyrus commented Dec 2, 2025

Summary by CodeRabbit

  • New Features

    • Per-filter selection validation to block invalid picks and preserve input/state.
    • URL-length safety checks with toast feedback; current URL length exposed with a warning tooltip.
  • Bug Fixes

    • Detects and clears preloaded filter state that exceeds limits on load, notifying the user.
  • UI

    • Simplified faceted-filter UI (removed "Select All"); selection and removal behavior made more consistent.

✏️ Tip: You can customize this high-level summary in your review settings.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/cosmo-docs.
  • I have read the Contributors Guide.
@coderabbitai
Copy link

coderabbitai bot commented Dec 2, 2025

Walkthrough

Adds validateSelection hooks to faceted and analytics filters, refactors faceted filter selection to non‑mutative patterns, and implements URL-length and filter-size validation with toast/tooltip feedback and utilities to compute and enforce URL limits.

Changes

Cohort / File(s) Summary
Faceted filter API & behavior
studio/src/components/analytics/filters.tsx, studio/src/components/analytics/data-table-faceted-filter.tsx
Added validateSelection?: (value: string[]) => boolean to filter types and DataTableFilterCommands. Refactored faceted filter to derive state from selectedOptions (useMemo), remove mutative Set usage and local filteredOptions, gate add/select/apply actions with validateSelection (abort on failure), remove "Select All" logic, forward validateSelection, and treat removals as always allowed.
Analytics filters, metrics & URL utilities
studio/src/components/analytics/metrics.tsx
Added MAX_URL_LENGTH, MAX_FILTER_SIZE, and utilities: calculateByteSize, checkFilterLimits, calculateUrlLength, wouldExceedUrlLimit. Enhanced useSelectedFilters / useMetricsFilters to validate preloaded filterState, deep-copy selections, maintain selectedFiltersRef, build filtersListWithValidation, expose isUrlLimitReached and currentUrlLength, and emit toasts when limits are exceeded.
Filter application flow & UI integration
studio/src/components/analytics/data-table.tsx, studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/analytics/index.tsx
On load and on columnFilters changes, detect additions vs removals and block additions that would exceed URL limits (show toast). Replace plain filter lists with validation-wrapped lists for UI, surface current URL-length state, render a warning tooltip when limit reached, and adjust toolbar layout to include the tooltip.
Repository metadata
manifest_file, package.json
Updated manifest/package files (changes summarized in diff metadata; specifics not detailed).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review byte-size and URL-serialization logic in calculateByteSize / calculateUrlLength.
  • Validate wouldExceedUrlLimit / checkFilterLimits against router.query shapes and edge cases (null/undefined, shallow routing).
  • Inspect non‑mutative refactor in data-table-faceted-filter.tsx for stale-closure or UI parity issues.
  • Verify routing side effects (router.replace) and toast usage do not produce repeated redirects or UX regressions.
  • Confirm validateSelection forwarding and that removal paths intentionally bypass validation without causing inconsistent state.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing a URL length limit (10k characters/10kb) for analytics pages.
✨ Finishing touches
  • 📝 Generate docstrings

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

@github-actions github-actions bot added the studio label Dec 2, 2025
@JivusAyrus JivusAyrus marked this pull request as ready for review December 2, 2025 13:34
@JivusAyrus JivusAyrus changed the title fix: limit the url of analytics pages to a max of 10k characters fix: limit the url of analytics pages to a max of 10k characters or 10kb Dec 2, 2025
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

🧹 Nitpick comments (4)
studio/src/components/analytics/data-table.tsx (1)

344-388: Potential stale closure with router and toast.

The validateSelection function captures router and toast in its closure, but these are not included in the useMemo dependency array. While router from Next.js is typically stable and toast from a hook is usually stable, this pattern can lead to subtle bugs if these references change.

Consider using useCallback for the validation logic or ensuring the dependencies are properly tracked.

 const filtersListWithValidation = useMemo(() => { return filtersList.map((filter) => { return { ...filter, // ALWAYS validate selection to check if NEW addition would exceed the limit validateSelection: (value: string[]) => { // Build the new filter state to check URL length const newSelectedFilters = [...selectedFilters]; const index = newSelectedFilters.findIndex((f) => f.id === filter.id); if (index >= 0) { newSelectedFilters[index] = { id: filter.id, value: value }; } else { newSelectedFilters.push({ id: filter.id, value: value }); } let stringifiedFilters; try { stringifiedFilters = JSON.stringify(newSelectedFilters); } catch { stringifiedFilters = "[]"; } const { exceeded, reason } = checkFilterLimits( router, stringifiedFilters, ); if (exceeded) { toast({ title: "Filter limit reached", description: `${reason}. Please remove some filters before adding new ones.`, }); return false; // Validation failed - prevents onSelect from being called } return true; // Validation passed - allows onSelect to be called }, onSelect: filter.onSelect, // Use original onSelect without wrapping }; }); - // Only filtersList and selectedFilters should trigger recalculation - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filtersList, selectedFilters]); + }, [filtersList, selectedFilters, router, toast]);
studio/src/components/analytics/data-table-faceted-filter.tsx (1)

225-248: Consider adding validation to range filter updates for consistency.

Unlike text and list selection paths, updateRangeFilters doesn't invoke validateSelection before calling onSelect. While range filters produce predictable small data (2 values), adding validation would maintain consistency across all filter types.

 const updateRangeFilters = ({ rangeValue, }: { rangeValue: { start: number; end: number }; }) => { setRange({ start: rangeValue.start, end: rangeValue.end, }); // Create new filter values without mutating selectedValues const filterValues = [ JSON.stringify({ label: (rangeValue.start * 10 ** 9).toString(), value: (rangeValue.start * 10 ** 9).toString(), operator: 4, }), JSON.stringify({ label: (rangeValue.end * 10 ** 9).toString(), value: (rangeValue.end * 10 ** 9).toString(), operator: 5, }), ]; + + // Validate before applying (for consistency, though range data is small) + if (validateSelection && !validateSelection(filterValues)) { + return; + } + onSelect?.(filterValues); };
studio/src/components/analytics/metrics.tsx (2)

377-410: Stale closure risk with router and toast in useMemo.

Similar to the data-table implementation, the validateSelection function captures router and toast in its closure, but these aren't in the dependency array. While a ref is correctly used for selectedFilters to avoid stale closures, the same pattern could be applied for router if needed.

The current implementation should work in practice since Next.js router and toast hook typically return stable references, but consider adding them to the dependency array for correctness.

 const filtersListWithValidation = useMemo(() => { return filtersList.map((filter) => { return { ...filter, validateSelection: (value: string[]) => { const currentSelectedFilters = selectedFiltersRef.current; if ( wouldExceedUrlLimit( router, currentSelectedFilters, filter.id, value, ) ) { toast({ title: "Filter limit reached", description: `Maximum URL length of ${MAX_URL_LENGTH.toLocaleString()} characters reached. Please remove some filters before adding new ones.`, }); return false; } return true; }, onSelect: filter.onSelect, }; }); - // Only filtersList should trigger recalculation - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filtersList]); + }, [filtersList, router, toast]);

311-340: Consider extracting common filter update logic.

The onSelect handler logic for updating filters (deep copy, find/update, stringify, applyNewParams) is duplicated between this hook and the data-table's onColumnFiltersChange. While the contexts differ, extracting a utility function could reduce duplication.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9be1a0e and d083d4d.

📒 Files selected for processing (5)
  • studio/src/components/analytics/data-table-faceted-filter.tsx (12 hunks)
  • studio/src/components/analytics/data-table.tsx (7 hunks)
  • studio/src/components/analytics/filters.tsx (1 hunks)
  • studio/src/components/analytics/metrics.tsx (9 hunks)
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/analytics/index.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
studio/src/components/analytics/data-table.tsx (3)
studio/src/components/analytics/metrics.tsx (3)
  • checkFilterLimits (83-105)
  • calculateUrlLength (113-171)
  • MAX_URL_LENGTH (69-69)
studio/src/components/analytics/getDataTableFilters.ts (1)
  • getDataTableFilters (25-52)
studio/src/components/analytics/filters.tsx (1)
  • AnalyticsFilters (27-35)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/analytics/index.tsx (1)
studio/src/components/analytics/metrics.tsx (2)
  • useMetricsFilters (284-418)
  • LatencyDistributionCard (1105-1217)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (15)
studio/src/components/analytics/filters.tsx (1)

14-14: LGTM!

The validateSelection callback is a clean addition to the interface with proper optional typing and clear documentation of the return value semantics.

studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/analytics/index.tsx (3)

122-134: LGTM!

The URL limit warning tooltip provides clear, actionable feedback to users. The conditional rendering and destructive icon styling appropriately communicate the constraint.


225-228: LGTM!

Minor formatting adjustment for better readability.


48-48: The AnalyticsViewResultFilter import is actually used in this file. It appears on line 61 as a type annotation for the filters prop in the OverviewToolbar component definition (filters?: AnalyticsViewResultFilter[];). The import is necessary and should be kept.

Likely an incorrect or invalid review comment.

studio/src/components/analytics/data-table.tsx (3)

171-199: LGTM with minor observation.

The initial URL validation on load is a good safety net for malicious/corrupted URLs. The shallow routing with router.replace handles the reset cleanly.


224-268: LGTM!

The filter validation logic correctly distinguishes between removal and addition operations, only validating additions. The early return pattern effectively prevents limit-exceeding filters from being applied.


469-483: LGTM!

The URL limit warning tooltip provides consistent UX with the analytics index page, showing a clear warning when the limit is reached.

studio/src/components/analytics/data-table-faceted-filter.tsx (3)

189-195: LGTM!

The memoized selectedValues Set provides efficient O(1) lookups while keeping selectedOptions as the source of truth for building filter arrays. Good separation of concerns.


268-285: LGTM!

Good UX decision to preserve the input value when validation fails, allowing users to see what they attempted to add.


419-438: LGTM!

The validation logic correctly gates additions while allowing deselections (removals) to proceed without validation.

studio/src/components/analytics/metrics.tsx (5)

69-77: LGTM!

Clean utility implementations. The Blob API is a reliable cross-browser method for calculating UTF-8 byte size, and the constants are well-documented.


83-171: LGTM!

The checkFilterLimits and calculateUrlLength utilities are well-implemented with:

  • Proper SSR handling for window.location.origin
  • Correct query parameter merging semantics
  • URL encoding matching Next.js behavior for arrays

177-215: LGTM!

The wouldExceedUrlLimit helper correctly:

  • Creates a deep copy to avoid mutating the original filters
  • Short-circuits for removal operations (always allowed)
  • Reuses checkFilterLimits for consistent validation

252-279: LGTM!

The URL validation effect provides a safety net for manually manipulated or corrupted URLs. The dependency array comment correctly explains the intentional exclusions.


605-605: LGTM!

Minor formatting change for props destructuring.

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

🧹 Nitpick comments (2)
studio/src/components/analytics/metrics.tsx (2)

122-135: Simplify the param merging logic.

Lines 128-133 add existing values to mergedQuery only to override them immediately in the next loop (lines 138-142). This is confusing and inefficient.

Apply this diff to simplify:

 // First, copy existing params that aren't being replaced or removed for (const [key, value] of Object.entries(router.query)) { - if (!newParams.hasOwnProperty(key)) { + if (!(key in newParams)) { // Keep existing param if not in newParams if (value !== undefined && value !== null) { mergedQuery[key] = value as string | string[]; } - } else if (newParams[key] !== null) { - // Keep existing param if newParams[key] is not null (will be overridden below) - if (value !== undefined && value !== null) { - mergedQuery[key] = value as string | string[]; - } } // If newParams[key] is null, we skip it (removes the param) }

The simplified version skips existing values that will be replaced by new params, making the logic clearer and slightly more efficient.


375-408: Consider memoizing filtersList for better performance.

The filtersListWithValidation useMemo depends on filtersList, which is recalculated on every render (lines 306-354). This causes filtersListWithValidation to also recalculate on every render, even when the underlying data hasn't changed.

Consider wrapping filtersList calculation in a useMemo:

const filtersList = useMemo(() => { return (filters ?? []).map((filter) => { return { ...filter, id: filter.columnName, onSelect: (value) => { // ... existing logic ... }, selectedOptions: // ... existing logic ... options: // ... existing logic ... } as AnalyticsFilter; }); }, [filters, selectedFilters, applyNewParams]);

This would prevent unnecessary recalculations of filtersListWithValidation and improve performance, though the current implementation is functionally correct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d083d4d and dec7445.

📒 Files selected for processing (1)
  • studio/src/components/analytics/metrics.tsx (9 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
🔇 Additional comments (7)
studio/src/components/analytics/metrics.tsx (7)

69-70: LGTM! Well-chosen limits.

The 10k character URL limit and 10KB filter size provide reasonable safeguards. This conservative limit ensures compatibility across different browsers and prevents performance issues with overly complex filter states.


72-77: LGTM!

The Blob API approach correctly calculates UTF-8 byte size and properly handles multi-byte characters. Since this is only called from client-side hooks, there are no SSR concerns.


79-105: LGTM!

The function correctly validates both URL length and filter size limits, provides clear user-facing error messages, and returns a well-structured result.


173-215: LGTM! Solid validation logic.

The function correctly handles filter removal, uses deep copies to prevent mutation, gracefully handles JSON stringification errors, and leverages the shared checkFilterLimits utility for consistent validation.


252-279: LGTM! Proper URL validation on load.

The effect correctly validates filters loaded from the URL (e.g., from bookmarks or manual manipulation) and resets them if limits are exceeded. The eslint-disable is appropriate here because router and toast are stable references, and including them in dependencies would not provide value.


371-382: LGTM! Excellent use of ref to avoid stale closures.

The ref pattern correctly ensures that validateSelection always accesses the latest selectedFilters value, preventing stale closure issues. This is a well-implemented React pattern.


603-603: Minor cleanup.

Formatting change with no functional impact.

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

🧹 Nitpick comments (1)
studio/src/components/analytics/data-table-faceted-filter.tsx (1)

419-434: Minor inconsistency: validation on removal differs between paths.

The text input removal (lines 311-319) explicitly skips validation with the comment "Removal doesn't need validation (always allowed)", but this command item handler validates even when removing (when isSelected is true and result is non-empty).

Since removal always reduces URL size, validation will always pass, making this harmless but inconsistent. Consider aligning the behavior:

- // Validate BEFORE calling onSelect to prevent optimistic UI updates if ( - filterValues.length > 0 && + !isSelected && // Only validate additions, not removals + filterValues.length > 0 && validateSelection && !validateSelection(filterValues) ) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dec7445 and e1fe3d6.

📒 Files selected for processing (1)
  • studio/src/components/analytics/data-table-faceted-filter.tsx (12 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
🔇 Additional comments (6)
studio/src/components/analytics/data-table-faceted-filter.tsx (6)

40-40: LGTM - Clear validation callback signature.

The optional validateSelection prop with the inline comment clarifying the return semantics is well-designed.


189-195: LGTM - Good memoization for lookup efficiency.

The useMemo correctly creates a derived Set for O(1) lookups while keeping selectedOptions as the source of truth for building new filter arrays. The inline comments clearly document this pattern.


234-247: Range filters bypass validation - verify this is intentional.

The updateRangeFilters function does not invoke validateSelection, unlike the text input and command item paths. This appears intentional since range filters have a fixed structure (2 values), but for consistency, consider adding validation here as well or documenting why it's excluded.


268-285: LGTM - Correct validation before state update.

The validation gate properly prevents onSelect from being called when the limit is exceeded. Preserving the input on failure is good UX, allowing users to see what they tried to add.


466-481: LGTM - Clean conditional rendering for clear button.

The simplified structure correctly shows the clear selection button only when there are selections, using the memoized selectedValues for the size check.


493-546: LGTM - Validation prop correctly forwarded.

The validateSelection prop is properly destructured and forwarded to DataTableFilterCommands.

Note: Line 499 creates a non-memoized Set, unlike the memoized version in DataTableFilterCommands. This is acceptable here since it's only used for size checks and badge display, not for heavy operations.

…clarifying comments in DataTableFilterCommands
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

🧹 Nitpick comments (1)
studio/src/components/analytics/data-table-faceted-filter.tsx (1)

225-248: Range slider updates bypass validateSelection — confirm or align with other flows

In updateRangeFilters for CustomOptions.Range, you build the two JSON range entries and call onSelect(filterValues) directly, without going through validateSelection, whereas text and default option flows always validate before committing. If validateSelection is your only guard for URL length / payload size, range changes currently won’t be checked.

If you want full consistency and central enforcement, consider routing range updates through validateSelection as well, e.g.:

 const updateRangeFilters = ({ rangeValue, }: { rangeValue: { start: number; end: number }; }) => { - setRange({ - start: rangeValue.start, - end: rangeValue.end, - }); - // Create new filter values without mutating selectedValues - const filterValues = [ + const nextFilterValues = [ JSON.stringify({ label: (rangeValue.start * 10 ** 9).toString(), value: (rangeValue.start * 10 ** 9).toString(), operator: 4, }), JSON.stringify({ label: (rangeValue.end * 10 ** 9).toString(), value: (rangeValue.end * 10 ** 9).toString(), operator: 5, }), ]; - onSelect?.(filterValues); + if (validateSelection && !validateSelection(nextFilterValues)) { + return; + } + setRange({ + start: rangeValue.start, + end: rangeValue.end, + }); + onSelect?.(nextFilterValues); };

This keeps the range UI and filter state aligned while still letting validateSelection block over‑long URLs when necessary. If you intentionally want range updates exempt from validation (because they’re always a fixed, small payload), adding a short comment to that effect here would help future maintainers.

Please confirm against your URL-length reproduction case that range-only or “large selection + range change” scenarios can’t still breach the 10k/10kb limit, or adjust validateSelection as above.

Also applies to: 350-373

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1fe3d6 and 5aaec97.

📒 Files selected for processing (1)
  • studio/src/components/analytics/data-table-faceted-filter.tsx (11 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
🔇 Additional comments (1)
studio/src/components/analytics/data-table-faceted-filter.tsx (1)

22-22: validateSelection wiring and non‑mutating selection updates look solid

The new validateSelection?: (value: string[]) => boolean prop is threaded correctly from DataTableFacetedFilter down into DataTableFilterCommands, and it’s only invoked on additive/toggle flows where a non‑empty filterValues array is being applied. Using selectedOptions as the single source of truth and a memoized, read‑only Set for display/dedup (including the text custom input and command list toggles) avoids local mutations and keeps the UI consistent with the parent state. I don’t see correctness issues in these paths; the behavior matches the intended “validate before committing, but allow clears/removals unconditionally” semantics.

Please verify via your analytics URL tests that validateSelection is indeed pure and only depends on the incoming value: string[] plus its closure (e.g., filter id and other active filters), and that it’s not relying on being called for clear/removal flows.

Also applies to: 36-45, 181-188, 268-289, 317-323, 425-440, 472-487, 496-504, 549-557

Copy link
Contributor

@StarpTech StarpTech left a comment

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3 participants