Skip to content

Conversation

Junjiequan
Copy link
Member

@Junjiequan Junjiequan commented Oct 16, 2025

Description

Short description of the pull request

Motivation

Background on use case, changes needed

Fixes:

Please provide a list of the fixes implemented in this PR

  • Items added

Changes:

Please provide a list of the changes implemented by this PR

  • changes made

Tests included

  • Included for each change/fix?
  • Passing? (Merge will not be approved unless this is checked)

Documentation

  • swagger documentation updated [required]
  • official documentation updated [nice-to-have]

official documentation info

If you have updated the official documentation, please provide PR # and URL of the pages where the updates are included

Backend version

  • Does it require a specific version of the backend
  • which version of the backend is required:

Summary by Sourcery

Implement complete checkbox filter support by centralizing filter configurations in the store, introducing new filter actions and selectors, refactoring filter components to consume appConfig and Redux state, and updating shared filter utilities and templates.

New Features:

  • Add addProposalFilterAction, removeProposalFilterAction, and clearProposalsFiltersAction with corresponding reducer logic for proposals filters
  • Introduce selectFilterByKey and selectProposalsfacetCountsWithInstrumentNameByKey selectors to fetch individual filter state and facet counts

Enhancements:

  • Refactor ProposalSideFilterComponent and DatasetsFilterComponent to load filter settings from appConfig via store and remove static inputs
  • Enhance MultiSelectFilterComponent to emit structured value objects, maintain label mappings, and add getLabel helper
  • Update shared-filter component to handle checkbox filters and streamline filtering logic

Tests:

  • Add unit tests for new filter actions and reducer behaviors (multiSelect and checkbox)
  • Update actions and effects specs to align with the revised fetchFacetCountsAction signature and filter action props

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • ProposalSideFilterComponent is subscribing to filterConfigs$ but never unsubscribing—implement ngOnDestroy to clear subscriptions and avoid memory leaks.
  • There’s a lot of duplicated add/remove filter dispatch logic across the proposals and datasets components—consider extracting that into a shared helper or service to DRY up the code.
  • The TODO about restructuring externalSettings hints at a breaking migration; please define a clear migration plan so user settings aren’t disrupted when updating the backend schema.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- ProposalSideFilterComponent is subscribing to filterConfigs$ but never unsubscribing—implement ngOnDestroy to clear subscriptions and avoid memory leaks.
- There’s a lot of duplicated add/remove filter dispatch logic across the proposals and datasets components—consider extracting that into a shared helper or service to DRY up the code.
- The TODO about restructuring externalSettings hints at a breaking migration; please define a clear migration plan so user settings aren’t disrupted when updating the backend schema.

## Individual Comments

### Comment 1
<location> `src/app/proposals/proposal-filters/side-bar-filter/proposal-side-filter.component.ts:31` </location>
<code_context>
   standalone: false,
 })
 export class ProposalSideFilterComponent implements OnInit {
+  private subscriptions: Subscription[] = [];
   appConfig = this.appConfigService.getConfig();
   activeFilters: Record<string, string[] | DateRange> = {};
</code_context>

<issue_to_address>
**issue (bug_risk):** Consider unsubscribing from subscriptions in ngOnDestroy to prevent memory leaks.

Currently, subscriptions are added in ngOnInit but not cleaned up in ngOnDestroy. Please implement ngOnDestroy to unsubscribe from all subscriptions and prevent memory leaks.
</issue_to_address>

### Comment 2
<location> `src/app/proposals/proposal-filters/side-bar-filter/proposal-side-filter.component.ts:97-101` </location>
<code_context>
+            }
+          });
+
+          this.router.navigate([], {
+            queryParams: {
+              searchQuery: JSON.stringify(searchQuery),
+            },
+            queryParamsHandling: "merge",
+          });
+        }
</code_context>

<issue_to_address>
**suggestion (performance):** Navigating with updated queryParams on every filterConfigs$ emission may cause excessive route changes.

Frequent emissions from filterConfigs$ may trigger unnecessary navigations and impact performance. Debounce or only navigate when queryParams actually change.

Suggested implementation:

```typescript
          // Debounce navigation and only navigate if queryParams actually change
          if (!this._previousSearchQuery || JSON.stringify(this._previousSearchQuery) !== JSON.stringify(searchQuery)) {
            clearTimeout(this._debounceNavTimeout);
            this._debounceNavTimeout = setTimeout(() => {
              this.router.navigate([], {
                queryParams: {
                  searchQuery: JSON.stringify(searchQuery),
                },
                queryParamsHandling: "merge",
              });
              this._previousSearchQuery = { ...searchQuery };
            }, 300); // 300ms debounce, adjust as needed
          }
        }
      }),

```

You will need to add the following class properties to your component:

```typescript
// At the top of your component class
private _previousSearchQuery: any = null;
private _debounceNavTimeout: any;
```

If your component is destroyed, clear the timeout in ngOnDestroy:

```typescript
ngOnDestroy() {
  clearTimeout(this._debounceNavTimeout);
}
```
</issue_to_address>

### Comment 3
<location> `src/app/shared/modules/filters/multiselect-filter.component.html:8` </location>
<code_context>
       *ngFor="let item of currentFilter$ | async"
       [removable]="true"
       (removed)="itemRemoved(item)"
-      >{{ item || "No item" }}
+      >{{ getLabel(item) || "No item" }}
       <mat-icon matChipRemove>cancel</mat-icon>
     </mat-chip-row>
</code_context>

<issue_to_address>
**issue (bug_risk):** getLabel usage assumes item is an id, but item may be an object.

Consider normalizing item to a string id before calling getLabel, or modify getLabel to accept objects as input.
</issue_to_address>

### Comment 4
<location> `src/app/state-management/reducers/proposals.reducer.ts:161-166` </location>
<code_context>
+      const filters = {
+        ...state.proposalFilters.fields,
+      };
+      if (filterType === "multiSelect") {
+        const newValue = (state.proposalFilters.fields[key] || [])
+          .concat(value)
+          .filter((val, i, self) => self.indexOf(val) === i); // Unique
+
+        filters[key] = newValue;
+      } else {
+        filters[key] = value;
</code_context>

<issue_to_address>
**issue:** MultiSelect filter addition does not handle case where value is already an array.

Flatten the concatenated array to prevent nested arrays and ensure filters[key] remains a flat array of strings.
</issue_to_address>

### Comment 5
<location> `src/app/proposals/proposal-filters/side-bar-filter/proposal-side-filter.component.ts:112` </location>
<code_context>
   }

   setFilter(filterKey: string, value: string[]) {
+    // Text filter type is not supported for proposal side panel filters
+    // This is to seperate the logic of side filter panel and top text search box
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the filter update logic into a single helper method to centralize action dispatching and reduce code duplication.

You have three very similar code-paths (checkbox, date, multi-select) all doing:

 1. mutate `activeFilters`  
 2. dispatch one of two actions  
 3. optionally call `applyFilters()`

We can collapse them into one helper.  

```ts
type FilterType = 'checkbox' | 'multiSelect' | 'text';

private updateFilter(
  key: string,
  value: any,
  filterType: FilterType,
  remove: boolean = false
) {
  const hasValue = Array.isArray(value)
    ? value.length > 0
    : !!(value?.begin || value?.end);

  if (hasValue && !remove) {
    this.activeFilters[key] = value;
    this.store.dispatch(addProposalFilterAction({ key, value, filterType }));
  } else {
    delete this.activeFilters[key];
    this.store.dispatch(removeProposalFilterAction({ key, filterType }));
  }

  if (this.appConfig.checkBoxFilterClickTrigger) {
    this.applyFilters();
  }
}
```

Then your three methods become one-liners:

```ts
setFilter(key: string, values: string[]) {
  this.updateFilter(key, values, 'checkbox');
}

setDateFilter(key: string, range: DateRange) {
  this.updateFilter(key, range, 'text');
}

selectionChange({ event, key, value }: MultiSelectFilterValue) {
  const current = (this.activeFilters[key] as string[]) || [];
  const newVals = event === 'add'
    ? [...current, value._id]
    : current.filter(v => v !== value._id);

  this.updateFilter(key, newVals, 'multiSelect', event === 'remove');
}
```

— this removes the two helper methods (`addMultiSelectFilter…`, `removeMultiSelectFilter…`), centralizes dispatch logic, and flattens out the branching.  

Also remember to unsubscribe your `this.subscriptions` in `ngOnDestroy`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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