Skip to content

Refactor column filters#3825

Open
flomillot wants to merge 7 commits intomainfrom
refactor-columns-filters
Open

Refactor column filters#3825
flomillot wants to merge 7 commits intomainfrom
refactor-columns-filters

Conversation

@flomillot
Copy link
Contributor

@flomillot flomillot commented Mar 20, 2026

PR Summary

Simplifying and securing column filters state updates :

  • Unified all column filters into state.tableFilters.columnsFilters as Record<string, Record<string, FilterConfig[]>>, keyed by TableType then by tab ID
  • Stop updating the same things multiple times in different callbacks, data flow for results computation and spreadsheet is update backend database -> notification -> update redux state -> useEffect -> update hook state (no backend for logs)
  • Simplified pagination reset directly in the reducer when filters change using Immer direct mutation (.page = 0) instead of creating new objects via dispatch actions and callbacks
  • handleChangeComparator now consistently uses debouncedUpdateFilter for all branches instead of mixing direct and debounced calls, also fixed a bug by keeping the debounce when unmounting
  • Stop updating AG Grid filters when it's not necessary (not used anymore for computation result and already did in a use effect for spreadsheet)
  • Global code cleaning

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Consolidates column filters into state.tableFilters.columnsFilters keyed by TableType and tab; removes useFilterSelector and GridApi-based update callbacks; adds selectors getColumnFiltersFromState/getColumnFiltersFromStore; introduces persistence functions (persistSpreadsheetColumnFilters, persistComputationColumnFilters) and replaces old actions with updateColumnFiltersAction(TableType, tab, filters) across components and reducer.

Changes

Cohort / File(s) Summary
Redux State & Selectors
src/redux/actions.ts, src/redux/reducer.ts, src/redux/reducer.type.ts, src/redux/selectors/filter-selectors.ts, src/redux/selectors/filter-store-selectors.ts
Removed spreadsheet/logs-specific actions and store fields; introduced unified state.tableFilters.columnsFilters[TableType][tab]; added getColumnFiltersFromState and getColumnFiltersFromStore; updated reducer init/handlers and types to use FilterConfig[] and reset pagination for certain computation types.
Filter-related Types & Utils
src/types/custom-aggrid-types.ts, src/utils/store-sort-filter-fields.ts
Removed updateFilterCallback from FilterParams; removed SPREADSHEET_STORE_FIELD and LOGS_STORE_FIELD exports.
Filter Hooks & Persistence
src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts, src/components/results/common/column-filter/update-computation-columns-filters.ts, src/components/results/common/column-filter/use-computation-column-filters.ts, src/components/results/common/use-ag-grid-initial-column-filters.ts, src/components/spreadsheet-view/columns/persist-spreadsheet-column-filters.ts
Replaced old hook with useCustomAggridColumnFilter(colId, params) (removed GridApi and update callback); replaced update side effects to call persistSpreadsheetColumnFilters / persistComputationColumnFilters with onError handling; selectors now read filter arrays directly (removed .columns).
Filter Components
src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-autocomplete-filter.tsx, .../custom-aggrid-boolean-filter.tsx, .../custom-aggrid-duration-filter.tsx, .../custom-aggrid-filter.tsx, .../hooks/use-custom-aggrid-comparator-filter.ts
Updated components to use useCustomAggridColumnFilter (no api param); adjusted destructuring for selectedFilterData, comparators, and change handlers; removed reliance on update callback param.
Spreadsheet Views & Dialogs
src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-utils.ts, src/components/spreadsheet-view/columns/column-creation-dialog.tsx, src/components/spreadsheet-view/columns/common-column-definitions.ts, src/components/spreadsheet-view/spreadsheet/.../spreadsheet-content.tsx, src/components/spreadsheet-view/.../use-filtered-row-counter.tsx, src/components/spreadsheet-view/.../save/...
Replaced addFilterForNewSpreadsheet with updateColumnFiltersAction(TableType.Spreadsheet, ...); added persistSpreadsheetColumnFilters; switched selectors to getColumnFiltersFromState(..., TableType.Spreadsheet, ...); changed processed tablesFilters typing to Record<string, FilterConfig[]>.
Computation Results & Exports
src/components/results/* (shortcircuit, sensitivity-analysis, pccmin, securityanalysis, dynamicsimulation, loadflow, etc.), src/components/results/common/*, src/components/results/pccmin/*
Removed GridApi-based updateFilterCallback wiring and goToFirstPage prop threading; selectors now read tableFilters.columnsFilters[...] (no .columns); export buttons fetch filters at export time via getColumnFiltersFromStore; replaced updateComputationColumnsFilters with persistComputationColumnFilters.
App Integration & Runners
src/components/app.jsx, src/components/run-button-container.jsx, src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx
Replaced usages of addFilterForNewSpreadsheet / setLogsFilter with updateColumnFiltersAction(TableType.*, tab, filters) and added TableType imports.
Removed Hook Implementation
src/hooks/use-filter-selector.ts
Deleted useFilterSelector hook (selector + dispatch helper); callers migrated to direct selectors or store helpers.
Service Signature Update
src/services/study/sensitivity-analysis.ts
Relaxed exportSensitivityResultsAsCsv parameter to accept `filters: FilterConfig[]

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Component
participant Hook as useCustomAggridColumnFilter
participant Persist as persistSpreadsheetColumnFilters / persistComputationColumnFilters
participant Dispatcher as updateColumnFiltersAction
participant Reducer
Component->>Hook: user changes filter value (colId, params)
Hook->>Persist: call persist...(studyUuid, tabUuid, filters, colDef, onError)
Persist->>Dispatcher: dispatch updateColumnFiltersAction(TableType, tab, filters)
Dispatcher->>Reducer: reducer handles UPDATE_COLUMN_FILTERS
Reducer-->>Component: state.tableFilters.columnsFilters updated

mermaid
sequenceDiagram
participant ExportBtn
participant StoreSelector as getColumnFiltersFromStore
participant Service as export...AsCsv
ExportBtn->>StoreSelector: onExport -> get filters for TableType/tab
StoreSelector-->>ExportBtn: FilterConfig[] | undefined
ExportBtn->>Service: call export...AsCsv(filters, ...)
Service-->>ExportBtn: CSV response / download or error

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Refactor column filters' directly summarizes the main change in the changeset—a comprehensive refactoring of how column filters are managed and stored in Redux state.
Description check ✅ Passed The pull request description provides a detailed and well-structured summary of the refactoring changes, covering unified filter state structure, simplified data flow, pagination reset improvements, and debouncing fixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

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

🧹 Nitpick comments (1)
src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts (1)

107-119: Excluding updateFilter from debounced callback dependencies is a known pattern but fragile.

The comment at lines 107-111 correctly explains the rationale: including updateFilter would recreate the debounced function on every filter change, canceling pending updates. However, this relies on the assumption that other dependencies of updateFilter (like filters, studyUuid, type, tab, colId, colDef) remain stable.

The comment at line 111 acknowledges this: "PS: it only works because most of the props never change..."

This is acceptable for now, but consider using useRef to store the latest updateFilter callback if stability becomes an issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts`
around lines 107 - 119, The debouncedUpdateFilter currently closes over an
unstable updateFilter which is excluded from deps; instead capture the latest
updateFilter in a ref and have the debounced callback call ref.current(data) so
the debounced function can safely remain stable (deps only [debounceMs]);
specifically create a ref (e.g., updateFilterRef) and assign updateFilter to
updateFilterRef.current whenever updateFilter changes, then in the
useCallback/debounce body call updateFilterRef.current(data) and keep
isEditingRef.current = false as before to avoid recreating debouncedUpdateFilter
on every render.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/results/securityanalysis/security-analysis-result-tab.tsx`:
- Line 233: The column filter changes from useComputationColumnFilters are not
triggering pagination reset; add a useEffect in the component that watches the
filters returned by useComputationColumnFilters and calls
resetPaginationIfNKResults when filters change (mirroring how
useComputationGlobalFilters passes a callback), or alternatively update
useComputationColumnFilters to accept an optional onChange callback (like
useComputationGlobalFilters) and invoke that callback on filter updates;
reference useComputationColumnFilters, filters, useEffect,
resetPaginationIfNKResults, and useComputationGlobalFilters to locate where to
add the effect or hook change.

In `@src/redux/reducer.ts`:
- Around line 1600-1628: The UPDATE_COLUMN_FILTERS reducer currently resets
pagination for all tabs in a given analysis type; modify the switch cases inside
builder.addCase(UPDATE_COLUMN_FILTERS, ...) to only reset the pagination for the
specific tab given by action.filterSubType (not all tabs). For each case
(TableType.SecurityAnalysis, TableType.SensitivityAnalysis,
TableType.ShortcircuitAnalysis, TableType.PccMin) replace the loop over *_TABS
with a single assignment to
state[SECURITY_ANALYSIS_PAGINATION_STORE_FIELD][filterSubType].page = 0 (and the
corresponding pagination store field for each case), ensuring you reference the
action's filterSubType variable from UpdateColumnFiltersAction when locating the
tab to reset. Ensure the existing columnsFilters initialization
(state.tableFilters.columnsFilters[filterType] ??= {}) and the assignment
state.tableFilters.columnsFilters[filterType][filterSubType] = filters remain
unchanged.

In `@src/redux/reducer.type.ts`:
- Around line 136-138: Persisted column filters changed shape (previously nested
object wrapping the array) and must be normalized on rehydrate: add a
migration/normalization step that walks the TableFiltersState.columnsFilters
entries, detects legacy values (e.g., an object like { filters: FilterConfig[] }
or similar wrapper) and replaces them with the bare FilterConfig[] expected by
the new code; implement this in the state rehydration path (or a helper like
normalizePersistedTableFilters called from reducer initialization) so
save-spreadsheet-collection-dialog.tsx and use-filtered-row-counter.tsx can
assume columnsFilters contains FilterConfig[] directly.

In `@src/redux/selectors/filter-selectors.ts`:
- Around line 11-16: The selector getColumnFiltersFromState currently always
returns an array (possibly empty) which breaks callers expecting a nullable
value; restore the original "no filters" contract by returning undefined when no
column filters exist instead of an empty array. Update getColumnFiltersFromState
to return state.tableFilters.columnsFilters?.[filterType]?.[filterTab] (no
nullish coalescing) so callers that check for truthiness/optional chaining keep
working, and ensure the function signature still returns FilterConfig[] |
undefined if needed.

---

Nitpick comments:
In
`@src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts`:
- Around line 107-119: The debouncedUpdateFilter currently closes over an
unstable updateFilter which is excluded from deps; instead capture the latest
updateFilter in a ref and have the debounced callback call ref.current(data) so
the debounced function can safely remain stable (deps only [debounceMs]);
specifically create a ref (e.g., updateFilterRef) and assign updateFilter to
updateFilterRef.current whenever updateFilter changes, then in the
useCallback/debounce body call updateFilterRef.current(data) and keep
isEditingRef.current = false as before to avoid recreating debouncedUpdateFilter
on every render.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6f4d408-1012-4dc6-b07c-ed47ca773d59

📥 Commits

Reviewing files that changed from the base of the PR and between 602ba8e and 86bcec4.

📒 Files selected for processing (44)
  • src/components/app.jsx
  • src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-autocomplete-filter.tsx
  • src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-boolean-filter.tsx
  • src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-duration-filter.tsx
  • src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-filter.tsx
  • src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts
  • src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-comparator-filter.ts
  • src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx
  • src/components/report-viewer/log-table.tsx
  • src/components/results/common/column-filter/update-computation-columns-filters.ts
  • src/components/results/common/column-filter/use-computation-column-filters.ts
  • src/components/results/common/use-ag-grid-initial-column-filters.ts
  • src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx
  • src/components/results/loadflow/load-flow-result-utils.ts
  • src/components/results/pccmin/pcc-min-export-button.tsx
  • src/components/results/pccmin/pcc-min-result-table.tsx
  • src/components/results/pccmin/pcc-min-result.tsx
  • src/components/results/pccmin/pcc-min-result.type.ts
  • src/components/results/securityanalysis/security-analysis-result-tab.tsx
  • src/components/results/securityanalysis/security-analysis-result-utils.ts
  • src/components/results/securityanalysis/use-security-analysis-column-defs.tsx
  • src/components/results/sensitivity-analysis/paged-sensitivity-analysis-result.tsx
  • src/components/results/sensitivity-analysis/sensitivity-analysis-export-button.tsx
  • src/components/results/sensitivity-analysis/sensitivity-analysis-result.tsx
  • src/components/results/shortcircuit/shortcircuit-analysis-export-button.tsx
  • src/components/results/shortcircuit/shortcircuit-analysis-result-table.tsx
  • src/components/results/shortcircuit/shortcircuit-analysis-result.tsx
  • src/components/run-button-container.jsx
  • src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-utils.ts
  • src/components/spreadsheet-view/columns/column-creation-dialog.tsx
  • src/components/spreadsheet-view/columns/common-column-definitions.ts
  • src/components/spreadsheet-view/columns/persist-spreadsheet-column-filters.ts
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/row-counter/use-filtered-row-counter.tsx
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-collection-dialog.tsx
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-dialog.tsx
  • src/hooks/use-filter-selector.ts
  • src/redux/actions.ts
  • src/redux/reducer.ts
  • src/redux/reducer.type.ts
  • src/redux/selectors/filter-selectors.ts
  • src/redux/selectors/filter-store-selectors.ts
  • src/types/custom-aggrid-types.ts
  • src/utils/store-sort-filter-fields.ts
💤 Files with no reviewable changes (6)
  • src/components/results/shortcircuit/shortcircuit-analysis-result.tsx
  • src/components/results/sensitivity-analysis/paged-sensitivity-analysis-result.tsx
  • src/types/custom-aggrid-types.ts
  • src/components/results/pccmin/pcc-min-result.tsx
  • src/hooks/use-filter-selector.ts
  • src/utils/store-sort-filter-fields.ts

Comment on lines 1600 to 1628
builder.addCase(UPDATE_COLUMN_FILTERS, (state, action: UpdateColumnFiltersAction) => {
const { filterType, filterSubType, filters } = action;
state.tableFilters.columnsFilters[filterType] ??= {};
state.tableFilters.columnsFilters[filterType][filterSubType] ??= {
columns: [],
};
state.tableFilters.columnsFilters[filterType][filterSubType].columns = filters;
state.tableFilters.columnsFilters[filterType][filterSubType] = filters;

// Reset pagination to page 0 when column filters change
switch (filterType) {
case TableType.SecurityAnalysis:
SECURITY_ANALYSIS_TABS.forEach((tab) => {
state[SECURITY_ANALYSIS_PAGINATION_STORE_FIELD][tab].page = 0;
});
break;
case TableType.SensitivityAnalysis:
SENSITIVITY_ANALYSIS_TABS.forEach((tab) => {
state[SENSITIVITY_ANALYSIS_PAGINATION_STORE_FIELD][tab].page = 0;
});
break;
case TableType.ShortcircuitAnalysis:
SHORTCIRCUIT_ANALYSIS_TABS.forEach((tab) => {
state[SHORTCIRCUIT_ANALYSIS_PAGINATION_STORE_FIELD][tab].page = 0;
});
break;
case TableType.PccMin:
PCCMIN_ANALYSIS_TABS.forEach((tab) => {
state[PCCMIN_ANALYSIS_PAGINATION_STORE_FIELD][tab].page = 0;
});
break;
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Is resetting pagination for all tabs when filtering one tab a common UX pattern in data grids?

💡 Result:

No, resetting pagination for all tabs when filtering one tab is not a common UX pattern in data grids. In tabbed data grids, each tab typically maintains independent pagination, filtering, and sorting state to preserve user context and avoid disorienting switches between tabs. Sources confirm independent state management is standard: - AG Grid multi-tab implementations use separate grid instances per tab, each managing its own pagination and filters independently (23). - Tabulator persists state per table instance, implying separate handling for multiple tables/tabs (16,18). - Shadcn and Filament table tabs treat tabs as pre-configured filters on a single table, not separate grids with cross-tab resets (19,22). - MUI X and other grids provide per-grid pagination reset props/options for filter/sort within one grid, not across multiple (1,2,17). General UX consensus for filters/sorts is to reset pagination to page 1 within the affected grid/tab only, as the page contents change (3,4,5). Cross-tab resets would frustrate users navigating between tabs, as evidenced by no documentation or examples recommending it; instead, state persistence per tab is emphasized (23). Developers implement separate state per tab to maintain user expectations (23).

Citations:


🏁 Script executed:

# First, let's look at the context around the UPDATE_COLUMN_FILTERS reducer
# to understand if there are any comments explaining the behavior
cd src/redux && head -n 1650 reducer.ts | tail -n 100

Repository: gridsuite/gridstudy-app

Length of output: 4470


🏁 Script executed:

# Search for any tests related to UPDATE_COLUMN_FILTERS or pagination reset
fd -e test.ts -e spec.ts -e test.tsx -e spec.tsx | xargs grep -l "UPDATE_COLUMN_FILTERS\|pagination.*reset" 2>/dev/null | head -5

Repository: gridsuite/gridstudy-app

Length of output: 49


🏁 Script executed:

# Search for any documentation or comments about this design decision
rg -i "pagination.*reset.*all.*tab|all.*tab.*pagination" --type ts --type tsx

Repository: gridsuite/gridstudy-app

Length of output: 94


🏁 Script executed:

# Check if there's any conditional logic elsewhere that might explain why all tabs reset
rg "filterSubType" src/redux/reducer.ts | head -20

Repository: gridsuite/gridstudy-app

Length of output: 209


Pagination reset should target only the affected tab, not all tabs in the analysis type.

The pagination reset resets page 0 for all tabs within an analysis type when a filter changes on any single tab. For example, filtering on the "N" tab resets pagination for both "N" and "N-K" tabs in Security Analysis. This is not standard UX practice in data grids—industry consensus is to either maintain independent pagination per tab or reset pagination only for the affected tab.

Since the action already provides filterSubType, use it to reset pagination only for the specific tab that had its filters changed:

case TableType.SecurityAnalysis:
    state[SECURITY_ANALYSIS_PAGINATION_STORE_FIELD][filterSubType].page = 0;
    break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/redux/reducer.ts` around lines 1600 - 1628, The UPDATE_COLUMN_FILTERS
reducer currently resets pagination for all tabs in a given analysis type;
modify the switch cases inside builder.addCase(UPDATE_COLUMN_FILTERS, ...) to
only reset the pagination for the specific tab given by action.filterSubType
(not all tabs). For each case (TableType.SecurityAnalysis,
TableType.SensitivityAnalysis, TableType.ShortcircuitAnalysis, TableType.PccMin)
replace the loop over *_TABS with a single assignment to
state[SECURITY_ANALYSIS_PAGINATION_STORE_FIELD][filterSubType].page = 0 (and the
corresponding pagination store field for each case), ensuring you reference the
action's filterSubType variable from UpdateColumnFiltersAction when locating the
tab to reset. Ensure the existing columnsFilters initialization
(state.tableFilters.columnsFilters[filterType] ??= {}) and the assignment
state.tableFilters.columnsFilters[filterType][filterSubType] = filters remain
unchanged.

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/services/study/sensitivity-analysis.ts (1)

69-69: Consider applying the same pattern to fetchSensitivityAnalysisResult for consistency.

The fetchSensitivityAnalysisResult function uses optional chaining on line 80 (filters?.length) but declares filters as required (FilterConfig[]). For consistency with the updated exportSensitivityResultsAsCsv, consider updating this parameter type as well if callers may pass undefined.

♻️ Suggested change for consistency
 export function fetchSensitivityAnalysisResult(
     studyUuid: UUID,
     currentNodeUuid: UUID,
     currentRootNetworkUuid: UUID,
     selector: any,
-    filters: FilterConfig[],
+    filters: FilterConfig[] | undefined,
     globalFilters: GlobalFilters | undefined
 ): Promise<SensitivityResult | null> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/study/sensitivity-analysis.ts` at line 69,
fetchSensitivityAnalysisResult declares filters as required FilterConfig[] but
its code uses optional chaining (filters?.length); update the function signature
to accept filters?: FilterConfig[] (or FilterConfig[] | undefined) to match
exportSensitivityResultsAsCsv and callers that may pass undefined, and then
ensure any internal logic (in fetchSensitivityAnalysisResult and its callers)
handles the undefined case consistently with exportSensitivityResultsAsCsv.
src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts (1)

179-193: Consider syncing tolerance from external filter changes.

When syncing from Redux store, only value and type are synced from filterObject. If filterObject.tolerance exists and differs from local state, subsequent calls to handleChangeComparator will use stale tolerance values (lines 148, 155, 164).

♻️ Proposed fix to sync tolerance
         if (filterObject) {
             setSelectedFilterData(filterObject.value);
             setSelectedFilterComparator(filterObject.type ?? '');
+            setTolerance(filterObject.tolerance);
         } else {
             setSelectedFilterData(undefined);
+            setTolerance(undefined);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts`
around lines 179 - 193, The effect in use-custom-aggrid-column-filter's
useEffect currently syncs only filterObject.value and filterObject.type but
ignores filterObject.tolerance, which can leave local tolerance stale when
external filters change; update the useEffect (the block that reads
filters?.find by colId) to also call the local setter for tolerance (the same
state used by handleChangeComparator), i.e.
setSelectedTolerance(filterObject.tolerance) when present and clear it
(setSelectedTolerance(undefined) or appropriate default) when no filterObject
exists, so handleChangeComparator sees the up-to-date tolerance value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts`:
- Around line 107-119: The debounced function created in
use-custom-aggrid-column-filter (debouncedUpdateFilter via useCallback +
debounce) lacks cleanup and can fire after unmount; add a useEffect that depends
on debouncedUpdateFilter and returns a cleanup function which calls
debouncedUpdateFilter.clear() (or the appropriate cancel/clear method returned
by debounce) to cancel any pending calls on unmount, keeping the existing
dependency strategy that excludes updateFilter and still referencing debounceMs,
updateFilter, isEditingRef, and debouncedUpdateFilter to locate the code to
modify.

In `@src/components/report-viewer/log-table.tsx`:
- Around line 108-110: The selector fallback currently uses a new array literal
(?? []) which creates a new reference each render and causes the filters
returned by useSelector (where you call getColumnFiltersFromState with
TableType.Logs and reportType) to change unnecessarily and re-trigger the
onFiltersChanged effect; fix this by defining a single module-level constant
like EMPTY_FILTERS: FilterConfig[] = [] (outside the component) and use that
constant as the fallback in the useSelector call so the reference stays stable
when the store value is undefined.

---

Nitpick comments:
In
`@src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts`:
- Around line 179-193: The effect in use-custom-aggrid-column-filter's useEffect
currently syncs only filterObject.value and filterObject.type but ignores
filterObject.tolerance, which can leave local tolerance stale when external
filters change; update the useEffect (the block that reads filters?.find by
colId) to also call the local setter for tolerance (the same state used by
handleChangeComparator), i.e. setSelectedTolerance(filterObject.tolerance) when
present and clear it (setSelectedTolerance(undefined) or appropriate default)
when no filterObject exists, so handleChangeComparator sees the up-to-date
tolerance value.

In `@src/services/study/sensitivity-analysis.ts`:
- Line 69: fetchSensitivityAnalysisResult declares filters as required
FilterConfig[] but its code uses optional chaining (filters?.length); update the
function signature to accept filters?: FilterConfig[] (or FilterConfig[] |
undefined) to match exportSensitivityResultsAsCsv and callers that may pass
undefined, and then ensure any internal logic (in fetchSensitivityAnalysisResult
and its callers) handles the undefined case consistently with
exportSensitivityResultsAsCsv.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 467d4eab-07c7-4b4a-93b1-88b941a0370e

📥 Commits

Reviewing files that changed from the base of the PR and between 86bcec4 and 0d456a8.

📒 Files selected for processing (7)
  • src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts
  • src/components/report-viewer/log-table.tsx
  • src/components/spreadsheet-view/columns/column-creation-dialog.tsx
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx
  • src/redux/selectors/filter-selectors.ts
  • src/redux/selectors/filter-store-selectors.ts
  • src/services/study/sensitivity-analysis.ts
✅ Files skipped from review due to trivial changes (1)
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/redux/selectors/filter-selectors.ts
  • src/redux/selectors/filter-store-selectors.ts
  • src/components/spreadsheet-view/columns/column-creation-dialog.tsx

Comment on lines 107 to 119
// We intentionally exclude `updateFilter` from dependencies.
// `updateFilter` depends on `filters`, which changes on every filter update.
// Including it would recreate the debounced function on each change,
// canceling any pending debounced call and preventing updates from being sent.
// PS: it only works because most of the props never change...
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedUpdateFilter = useCallback(
debounce((data) => {
updateFilter(colId, data);
updateFilter(data);
isEditingRef.current = false;
}, debounceMs),
[colId, debounceMs]
[debounceMs]
);
Copy link

@coderabbitai coderabbitai bot Mar 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing cleanup for debounced function on unmount.

The PR summary mentions "preserved debounce behavior on unmount (bug fix)", but the debounced function created here has no cleanup. If the component unmounts while a debounced call is pending, it will still execute, potentially causing:

  • State updates on unmounted components (React warning)
  • Unnecessary API calls after navigation away

MUI's debounce returns a function with a .clear() method that should be called on unmount.

🛡️ Proposed fix to add cleanup
     // eslint-disable-next-line react-hooks/exhaustive-deps
     const debouncedUpdateFilter = useCallback(
         debounce((data) => {
             updateFilter(data);
             isEditingRef.current = false;
         }, debounceMs),
         [debounceMs]
     );
+
+    // Cleanup debounced function on unmount to prevent stale calls
+    useEffect(() => {
+        return () => {
+            debouncedUpdateFilter.clear();
+        };
+    }, [debouncedUpdateFilter]);
📝 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
// We intentionally exclude `updateFilter` from dependencies.
// `updateFilter` depends on `filters`, which changes on every filter update.
// Including it would recreate the debounced function on each change,
// canceling any pending debounced call and preventing updates from being sent.
// PS: it only works because most of the props never change...
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedUpdateFilter = useCallback(
debounce((data) => {
updateFilter(colId, data);
updateFilter(data);
isEditingRef.current = false;
}, debounceMs),
[colId, debounceMs]
[debounceMs]
);
// We intentionally exclude `updateFilter` from dependencies.
// `updateFilter` depends on `filters`, which changes on every filter update.
// Including it would recreate the debounced function on each change,
// canceling any pending debounced call and preventing updates from being sent.
// PS: it only works because most of the props never change...
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedUpdateFilter = useCallback(
debounce((data) => {
updateFilter(data);
isEditingRef.current = false;
}, debounceMs),
[debounceMs]
);
// Cleanup debounced function on unmount to prevent stale calls
useEffect(() => {
return () => {
debouncedUpdateFilter.clear();
};
}, [debouncedUpdateFilter]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts`
around lines 107 - 119, The debounced function created in
use-custom-aggrid-column-filter (debouncedUpdateFilter via useCallback +
debounce) lacks cleanup and can fire after unmount; add a useEffect that depends
on debouncedUpdateFilter and returns a cleanup function which calls
debouncedUpdateFilter.clear() (or the appropriate cancel/clear method returned
by debounce) to cancel any pending calls on unmount, keeping the existing
dependency strategy that excludes updateFilter and still referencing debounceMs,
updateFilter, isEditingRef, and debouncedUpdateFilter to locate the code to
modify.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, and it's on purpose.

Copy link

Choose a reason for hiding this comment

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

@flomillot, understood! That makes sense — letting the debounced call complete after unmount ensures that a pending filter change isn't silently dropped when the user navigates away. Canceling on unmount would have been the actual data-loss bug. Thanks for the clarification!


✏️ Learnings added
Learnt from: flomillot
Repo: gridsuite/gridstudy-app PR: 3825
File: src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts:107-119
Timestamp: 2026-03-20T15:27:35.661Z
Learning: In `src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts`, the `debouncedUpdateFilter` (created via `useCallback` + MUI `debounce`) intentionally has **no** `.clear()` cleanup on unmount. This is by design so that any pending filter persistence call (to the backend) is not lost when the component unmounts (e.g., user navigates away after editing a filter). Adding a cleanup `useEffect` that calls `debouncedUpdateFilter.clear()` would silently drop the last filter update and should NOT be suggested.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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.

🧹 Nitpick comments (1)
src/components/spreadsheet-view/columns/column-creation-dialog.tsx (1)

299-303: Consider the implications of fire-and-forget filter persistence.

The persistFilters call is not awaited, meaning the column update proceeds regardless of whether filter persistence succeeds. If filter persistence fails (network error, etc.) but the column update succeeds, there could be a temporary inconsistency where stale filters reference the old column ID.

Given the PR's stated data flow (backend → notification → Redux), this should eventually self-correct via backend notifications. If this is the intended behavior, consider adding a brief comment noting that consistency is restored via the notification mechanism.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/spreadsheet-view/columns/column-creation-dialog.tsx` around
lines 299 - 303, persistFilters is currently invoked fire-and-forget when
updating an existing column (see existingColumn, isUpdate, filters and the call
persistFilters(studyUuid, updatedFilters)); either await the persistFilters call
and handle errors (e.g., try/catch and surface/log failures) to avoid stale
filters if persistence fails, or if the design intentionally relies on
backend→notification→Redux to restore consistency, add a concise comment above
the persistFilters call explaining that eventual consistency is expected and why
the call is not awaited.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/spreadsheet-view/columns/column-creation-dialog.tsx`:
- Around line 299-303: persistFilters is currently invoked fire-and-forget when
updating an existing column (see existingColumn, isUpdate, filters and the call
persistFilters(studyUuid, updatedFilters)); either await the persistFilters call
and handle errors (e.g., try/catch and surface/log failures) to avoid stale
filters if persistence fails, or if the design intentionally relies on
backend→notification→Redux to restore consistency, add a concise comment above
the persistFilters call explaining that eventual consistency is expected and why
the call is not awaited.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b871c205-c542-4a74-aea1-60203bdc649b

📥 Commits

Reviewing files that changed from the base of the PR and between 0d456a8 and 92e0a36.

📒 Files selected for processing (4)
  • src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts
  • src/components/report-viewer/log-table.tsx
  • src/components/spreadsheet-view/columns/column-creation-dialog.tsx
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/report-viewer/log-table.tsx
  • src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx
  • src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts

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.

1 participant