refactor(filters): pass fieldMetadataItems array to dispatcher (#20737)

## Summary

Alternative to #20717. Same goal (clean up the filter dispatcher API
after #20670) but smaller and follows the codebase's "pass data, not
behavior" style.

The dispatcher takes a `fieldMetadataItems: FieldShared[]` array
directly instead of a `findFieldMetadataItemById: (id) => FieldShared |
undefined` callback. The util builds the id lookup internally — once per
call, used for both source-field and relation-target-field lookups. No
new types, no separate hydration step.

## What changes

**`twenty-shared`**
- `computeRecordGqlOperationFilter` /
`turnRecordFilterIntoRecordGqlOperationFilter` /
`turnRecordFilterGroupsIntoGqlOperationFilter`: replace
`findFieldMetadataItemById` param with `fieldMetadataItems` /
`fieldMetadataItemById` (internal Map).
- Remove the exported `FindFieldMetadataItemById` type.
- `turnAnyFieldFilterIntoRecordGqlFilter`: rename its internal
`fieldById` Map for consistency.
- Tests updated to pass arrays.

**Frontend (15 call sites)**
- Switch from `fieldMetadataItemByIdMapSelector` to
`flattenedFieldMetadataItemsSelector`.
- Pass `fieldMetadataItems: flattenedFieldMetadataItems` to the
dispatcher.
- `useFindManyRecordsSelectedInContextStore` keeps the Map selector
because it still does a per-filter lookup for the soft-delete check.

**Server (5 call sites)**
- Pass
`Object.values(flatFieldMetadataMaps.byUniversalIdentifier).filter(isDefined)`.

## Why this over #20717

#20717 moves resolution into a separate hydration step + introduces a
`HydratedRecordFilter` type. The bug that #20717 originally surfaced was
Sentry catching 4 critical runtime errors during review
(`fieldMetadataItemByIdMap` declared but not passed). The added type and
the explicit hydration boundary are extra surface area for not much
benefit — the existing API was a callback wrapping a Map at every call
site, and the natural simplification is to just pass the Map (or its
array) directly.

Net diff: **196 insertions, 203 deletions** (~7 lines net removed). 32
files.

## Test plan
- [x] Shared filter unit tests pass (461 tests)
- [x] Frontend filter/context-store tests pass (13 tests)
- [x] Frontend typecheck passes
- [x] Server typecheck passes
- [x] Lint passes (frontend + server)
- [ ] Integration tests on #20670 still pass — workflow find-records +
chart-data with relation-traversal filter still work end-to-end through
the new array param
This commit is contained in:
Charles Bochet
2026-05-19 22:49:53 +02:00
committed by GitHub
parent 265d2edc83
commit 1a9f786e42
32 changed files with 194 additions and 203 deletions
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import {
computeRecordGqlOperationFilter,
isDefined,
isRecordFilterValueValid,
resolveInput,
} from 'twenty-shared/utils';
@@ -9,7 +10,6 @@ import {
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import {
WorkflowStepExecutorException,
@@ -79,11 +79,9 @@ export class FindRecordsWorkflowAction implements WorkflowAction {
workflowActionInput.filter?.recordFilters &&
workflowActionInput.filter?.recordFilterGroups
? computeRecordGqlOperationFilter({
findFieldMetadataItemById: (id) =>
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: id,
flatEntityMaps: flatFieldMetadataMaps,
}),
fieldMetadataItems: Object.values(
flatFieldMetadataMaps.byUniversalIdentifier,
).filter(isDefined),
recordFilters: workflowActionInput.filter.recordFilters,
recordFilterGroups: workflowActionInput.filter.recordFilterGroups,
filterValueDependencies: {