fix(filters): make filter dispatcher own relation-target resolution (#20670)

## Summary

Two relation-traversal bugs surfaced post-merge of #20533, both rooted
in the same architectural smell: the GraphQL filter dispatcher took a
flat `fields: FieldShared[]` array and silently dropped any filter whose
`relationTargetFieldMetadataId` wasn't in that array. Callers had to
remember to pre-augment the list with relation targets — and 16+ call
sites did not all know this.

This PR fixes both bugs and removes the smell.

### Bug 1 — Save as new view loses the relation target

`useCreateViewFromCurrentView` built the create-filter input without
`relationTargetFieldMetadataId`. The saved view's filter persisted
without the traversal — on reload the chip showed "Company contains
'air'" instead of "Company → Name contains 'air'". Discarded at save
time, not at read time.

Fix: include `relationTargetFieldMetadataId` in the create input.
(Commit 1.)

### Bug 2 — Workflow Search Records drops one-hop traversals

`FindRecordsWorkflowAction` built its fields list from
`flatObjectMetadata.fieldIds` only (source object's fields). The shared
dispatcher then couldn't resolve the relation target field on the
related object and silently dropped the filter — a configured "People
where Company → Name Contains 'Airbnb'" came through as `{ and: [] }`.

This was the same shape as bugs already fixed in 5 other call sites
(chart filters, view filters, record table, etc.). The pattern was:
caller forgets to augment fields → dispatcher silently drops the filter.

Fix (commit 2): change the dispatcher to take a
`findFieldMetadataItemById: (id) => FieldShared | undefined` resolver
callback. Both source-field and relation-target-field lookups go through
the same resolver, so callers no longer need to know about the
augmentation requirement. Frontend callers pass a workspace-wide
resolver built from `flattenedFieldMetadataItemsSelector`; server
callers wrap `findFlatEntityByIdInFlatEntityMaps` on
`flatFieldMetadataMaps`. In both cases relation-target lookups just
work, because the resolver can see fields on related objects.

## Why this matters

Before: "if you call the dispatcher, pre-augment your fields list with
relation targets, or filters get silently dropped." An invariant only
enforceable by code review, broken often enough to ship two user-visible
bugs in one week.

After: the dispatcher resolves field ids itself. There's no list to
forget to augment. The failure mode (filter silently dropped) becomes
structurally impossible at the dispatcher boundary.

Net diff: 240 insertions, 319 deletions. Removed
`augmentFieldsWithRelationTargets` (frontend) and the workflow
whack-a-mole code (server).

## Test plan
- [ ] Save view: create an advanced filter using a one-hop relation
traversal, click "Save as new view", reload, confirm the chip still
reads "Source → Target operator value"
- [ ] Workflow: configure a Search Records action with a
relation-traversal filter, run the workflow, confirm the filter is
actually applied
- [ ] Dashboard chart: configure a chart with a relation-traversal
filter, confirm the chart data respects it
- [ ] Record table, group-by, calendar, total count, footer aggregates:
all continue to work with both plain and relation-traversal filters
This commit is contained in:
Félix Malfait
2026-05-19 11:55:22 +02:00
committed by GitHub
parent a9634b027e
commit 291ce5ccdb
39 changed files with 824 additions and 419 deletions
@@ -27,7 +27,8 @@ describe('computeRecordGqlOperationFilter', () => {
];
const filter = computeRecordGqlOperationFilter({
fields: [companyIdField],
findFieldMetadataItemById: (id) =>
id === companyIdField.id ? companyIdField : undefined,
recordFilters,
recordFilterGroups: [],
filterValueDependencies: {
@@ -21,11 +21,14 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
},
];
const fieldById = new Map(fields.map((field) => [field.id, field]));
const findFieldMetadataItemById = (id: string) => fieldById.get(id);
it('should return undefined when group is not found', () => {
const result = turnRecordFilterGroupsIntoGqlOperationFilter({
filterValueDependencies: {},
filters: [],
fields,
findFieldMetadataItemById,
recordFilterGroups: [],
currentRecordFilterGroupId: 'nonexistent',
});
@@ -45,7 +48,7 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
recordFilterGroupId: 'group1',
},
],
fields,
findFieldMetadataItemById,
recordFilterGroups: [
{
id: 'group1',
@@ -70,7 +73,7 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
recordFilterGroupId: 'group1',
},
],
fields,
findFieldMetadataItemById,
recordFilterGroups: [
{
id: 'group1',
@@ -95,7 +98,7 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
recordFilterGroupId: 'subgroup1',
},
],
fields,
findFieldMetadataItemById,
recordFilterGroups: [
{
id: 'group1',
@@ -131,6 +131,9 @@ const fields = [
const filterValueDependencies = { timeZone: 'UTC' };
const fieldById = new Map(fields.map((field) => [field.id, field]));
const findFieldMetadataItemById = (id: string) => fieldById.get(id);
const makeFilter = (
fieldMetadataId: string,
operand: RecordFilterOperand,
@@ -156,7 +159,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'x',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toBeUndefined();
@@ -166,7 +169,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-text', RecordFilterOperand.CONTAINS, ''),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toBeUndefined();
@@ -181,7 +184,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'test',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ name: { ilike: '%test%' } });
@@ -195,7 +198,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.DOES_NOT_CONTAIN,
'test',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ not: { name: { ilike: '%test%' } } });
@@ -207,7 +210,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-number', RecordFilterOperand.IS, '42'),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ amount: { eq: 42 } });
@@ -217,7 +220,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-number', RecordFilterOperand.IS_NOT, '42'),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ not: { amount: { eq: 42 } } });
@@ -231,7 +234,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
'10',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ amount: { gte: 10 } });
@@ -245,7 +248,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.LESS_THAN_OR_EQUAL,
'100',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ amount: { lte: 100 } });
@@ -261,7 +264,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_AFTER,
'2024-03-15',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ createdAt: { gte: '2024-03-15' } });
@@ -275,7 +278,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_BEFORE,
'2024-03-15',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ createdAt: { lt: '2024-03-15' } });
@@ -289,7 +292,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS,
'2024-03-15',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ createdAt: { eq: '2024-03-15' } });
@@ -299,7 +302,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-date', RecordFilterOperand.IS_IN_PAST, ''),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('createdAt.lt');
@@ -313,7 +316,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_IN_FUTURE,
'',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('createdAt.gte');
@@ -323,7 +326,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-date', RecordFilterOperand.IS_TODAY, ''),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('createdAt.eq');
@@ -337,7 +340,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_RELATIVE,
'PAST_7_DAY',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('and');
@@ -353,7 +356,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_AFTER,
'2024-03-15T10:00:00Z',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('updatedAt.gte');
@@ -367,7 +370,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_BEFORE,
'2024-03-15T10:00:00Z',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('updatedAt.lt');
@@ -381,7 +384,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS,
'2024-03-15T10:00:00Z',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('and');
@@ -395,7 +398,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_IN_PAST,
'',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('updatedAt.lt');
@@ -409,7 +412,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_IN_FUTURE,
'',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('updatedAt.gt');
@@ -423,7 +426,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_TODAY,
'',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('and');
@@ -437,7 +440,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_RELATIVE,
`PAST_7_DAY;;UTC;;`,
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('and');
@@ -449,7 +452,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-rating', RecordFilterOperand.IS, '3'),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('rating.eq');
@@ -463,7 +466,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
'3',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('rating.in');
@@ -477,7 +480,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.LESS_THAN_OR_EQUAL,
'3',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('rating.in');
@@ -489,7 +492,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-bool', RecordFilterOperand.IS, 'true'),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ isActive: { eq: true } });
@@ -499,7 +502,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
const result = turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: makeFilter('f-bool', RecordFilterOperand.IS, 'false'),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ isActive: { eq: false } });
@@ -515,7 +518,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS,
'["ACTIVE","PENDING"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('status.in');
@@ -529,7 +532,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_NOT,
'["ACTIVE"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('not');
@@ -545,7 +548,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'["TAG1","TAG2"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('tags.containsAny');
@@ -559,7 +562,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.DOES_NOT_CONTAIN,
'["TAG1"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('or');
@@ -575,7 +578,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS,
'["550e8400-e29b-41d4-a716-446655440000"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('companyId.in');
@@ -589,7 +592,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS_NOT,
'["550e8400-e29b-41d4-a716-446655440000"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('or');
@@ -605,7 +608,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'test',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ metadata: { like: '%test%' } });
@@ -619,7 +622,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.DOES_NOT_CONTAIN,
'test',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ not: { metadata: { like: '%test%' } } });
@@ -635,7 +638,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'doc',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ attachments: { like: '%doc%' } });
@@ -651,7 +654,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.VECTOR_SEARCH,
'hello world',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ search: { search: 'hello world' } });
@@ -669,7 +672,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
'CURRENCY',
'amountMicros',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('revenue');
@@ -685,7 +688,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'John',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('or');
@@ -701,7 +704,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'Paris',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('or');
@@ -717,7 +720,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'api',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({
@@ -744,7 +747,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'xyz123',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({
@@ -769,7 +772,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.DOES_NOT_CONTAIN,
'api',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({
@@ -800,7 +803,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.DOES_NOT_CONTAIN,
'xyz123',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({
@@ -831,7 +834,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
'PHONES',
'primaryPhoneNumber',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toBeDefined();
@@ -849,7 +852,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
'EMAILS',
'primaryEmail',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toBeDefined();
@@ -867,7 +870,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
'LINKS',
'primaryLinkUrl',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toBeDefined();
@@ -883,7 +886,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.CONTAINS,
'["item1"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toBeDefined();
@@ -897,7 +900,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.DOES_NOT_CONTAIN,
'["item1"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('not');
@@ -913,7 +916,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS,
'["550e8400-e29b-41d4-a716-446655440000"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('recordId.in');
@@ -931,13 +934,13 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
...makeFilter('f-relation', RecordFilterOperand.CONTAINS, 'Acme'),
relationTargetFieldMetadataId: 'f-text',
} as RecordFilter,
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ company: { name: { ilike: '%Acme%' } } });
});
// If the target field is no longer in fieldMetadataItems (e.g. it was
// If the target field is no longer resolvable (e.g. it was
// deleted from the workspace), dropping the filter is the safe path —
// the alternative would silently interpret the text value as a UUID
// list against the relation FK.
@@ -948,7 +951,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
...makeFilter('f-relation', RecordFilterOperand.CONTAINS, 'Acme'),
relationTargetFieldMetadataId: 'nonexistent-target',
} as RecordFilter,
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toBeUndefined();
@@ -963,7 +966,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
...makeFilter('f-relation', RecordFilterOperand.IS_EMPTY, ''),
relationTargetFieldMetadataId: 'f-text',
} as RecordFilter,
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
// Without traversal, the RELATION case would have produced a filter
@@ -984,7 +987,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
...makeFilter('f-relation', RecordFilterOperand.IS, '["ACTIVE"]'),
relationTargetFieldMetadataId: 'f-select',
} as RecordFilter,
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toEqual({ company: { status: { in: ['ACTIVE'] } } });
@@ -1001,7 +1004,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
RecordFilterOperand.IS,
'["550e8400-e29b-41d4-a716-446655440000"]',
),
fieldMetadataItems: fields,
findFieldMetadataItemById,
});
expect(result).toHaveProperty('companyId.in');
@@ -1,5 +1,4 @@
import {
type PartialFieldMetadataItem,
type RecordFilterValueDependencies,
type RecordGqlOperationFilter,
} from '@/types';
@@ -8,17 +7,20 @@ import {
type RecordFilter,
type RecordFilterGroup,
} from '@/utils/filter/turnRecordFilterGroupIntoGqlOperationFilter';
import { turnRecordFilterIntoRecordGqlOperationFilter } from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
import {
type FindFieldMetadataItemById,
turnRecordFilterIntoRecordGqlOperationFilter,
} from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
import { isDefined } from '@/utils/validation/isDefined';
export const computeRecordGqlOperationFilter = ({
fields,
findFieldMetadataItemById,
recordFilters,
recordFilterGroups,
filterValueDependencies,
}: {
recordFilters: Omit<RecordFilter, 'id'>[];
fields: PartialFieldMetadataItem[];
findFieldMetadataItemById: FindFieldMetadataItemById;
recordFilterGroups: RecordFilterGroup[];
filterValueDependencies: RecordFilterValueDependencies;
}): RecordGqlOperationFilter => {
@@ -28,7 +30,7 @@ export const computeRecordGqlOperationFilter = ({
.map((regularFilter) => {
return turnRecordFilterIntoRecordGqlOperationFilter({
recordFilter: regularFilter,
fieldMetadataItems: fields,
findFieldMetadataItemById,
filterValueDependencies,
});
})
@@ -42,7 +44,7 @@ export const computeRecordGqlOperationFilter = ({
turnRecordFilterGroupsIntoGqlOperationFilter({
filterValueDependencies,
filters: recordFilters,
fields,
findFieldMetadataItemById,
recordFilterGroups,
currentRecordFilterGroupId: outermostFilterGroupId,
});
@@ -221,11 +221,13 @@ export const turnAnyFieldFilterIntoRecordGqlFilter = ({
}
}
const fieldById = new Map(fields.map((field) => [field.id, field]));
const baseRecordGqlOperationFilters = anyFieldRecordFilters
.map((recordFilter) =>
turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies: {},
fieldMetadataItems: fields,
findFieldMetadataItemById: (id) => fieldById.get(id),
recordFilter,
}),
)
@@ -1,7 +1,6 @@
import {
type CompositeFieldSubFieldName,
type FilterableAndTSVectorFieldType,
type PartialFieldMetadataItem,
RecordFilterGroupLogicalOperator,
type RecordFilterValueDependencies,
type RecordGqlOperationFilter,
@@ -9,7 +8,10 @@ import {
} from '@/types';
import { isDefined } from '@/utils';
import { turnRecordFilterIntoRecordGqlOperationFilter } from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
import {
type FindFieldMetadataItemById,
turnRecordFilterIntoRecordGqlOperationFilter,
} from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
export type RecordFilter = {
id: string;
@@ -31,13 +33,13 @@ export type RecordFilterGroup = {
export const turnRecordFilterGroupsIntoGqlOperationFilter = ({
filterValueDependencies,
filters,
fields,
findFieldMetadataItemById,
recordFilterGroups,
currentRecordFilterGroupId,
}: {
filterValueDependencies: RecordFilterValueDependencies;
filters: Omit<RecordFilter, 'id'>[];
fields: PartialFieldMetadataItem[];
findFieldMetadataItemById: FindFieldMetadataItemById;
recordFilterGroups: RecordFilterGroup[];
currentRecordFilterGroupId?: string;
}): RecordGqlOperationFilter | undefined => {
@@ -58,7 +60,7 @@ export const turnRecordFilterGroupsIntoGqlOperationFilter = ({
turnRecordFilterIntoRecordGqlOperationFilter({
filterValueDependencies,
recordFilter: recordFilter,
fieldMetadataItems: fields,
findFieldMetadataItemById,
}),
)
.filter(isDefined);
@@ -73,7 +75,7 @@ export const turnRecordFilterGroupsIntoGqlOperationFilter = ({
turnRecordFilterGroupsIntoGqlOperationFilter({
filterValueDependencies,
filters,
fields,
findFieldMetadataItemById,
recordFilterGroups,
currentRecordFilterGroupId: subRecordFilterGroup.id,
}),
@@ -62,21 +62,23 @@ type FieldShared = {
label: string;
};
export type FindFieldMetadataItemById = (id: string) => FieldShared | undefined;
type TurnRecordFilterIntoRecordGqlOperationFilterParams = {
filterValueDependencies: RecordFilterValueDependencies;
recordFilter: Omit<RecordFilter, 'id'>;
fieldMetadataItems: FieldShared[];
findFieldMetadataItemById: FindFieldMetadataItemById;
};
export const turnRecordFilterIntoRecordGqlOperationFilter = ({
recordFilter,
fieldMetadataItems,
findFieldMetadataItemById,
filterValueDependencies,
}: TurnRecordFilterIntoRecordGqlOperationFilterParams):
| RecordGqlOperationFilter
| undefined => {
const sourceFieldMetadataItem = fieldMetadataItems.find(
(field) => field.id === recordFilter.fieldMetadataId,
const sourceFieldMetadataItem = findFieldMetadataItemById(
recordFilter.fieldMetadataId,
);
if (!isDefined(sourceFieldMetadataItem)) {
@@ -91,8 +93,8 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
sourceFieldMetadataItem.type === FieldMetadataType.RELATION &&
isDefined(recordFilter.relationTargetFieldMetadataId)
) {
const targetFieldMetadataItem = fieldMetadataItems.find(
(field) => field.id === recordFilter.relationTargetFieldMetadataId,
const targetFieldMetadataItem = findFieldMetadataItemById(
recordFilter.relationTargetFieldMetadataId,
);
if (!isDefined(targetFieldMetadataItem)) {
@@ -104,6 +104,7 @@ export type {
RecordFilterGroup,
} from './filter/turnRecordFilterGroupIntoGqlOperationFilter';
export { turnRecordFilterGroupsIntoGqlOperationFilter } from './filter/turnRecordFilterGroupIntoGqlOperationFilter';
export type { FindFieldMetadataItemById } from './filter/turnRecordFilterIntoGqlOperationFilter';
export { turnRecordFilterIntoRecordGqlOperationFilter } from './filter/turnRecordFilterIntoGqlOperationFilter';
export { combineFilters } from './filter/utils/combineFilters';
export { convertViewFilterOperandToCoreOperand } from './filter/utils/convert-view-filter-operand-to-core-operand.util';