fix(server): stop FIND_RECORDS from silently ignoring its filter (#23640)
## Problem
A FIND_RECORDS workflow step with a filter configured could silently
return every record (and thus the first row of the table) instead of
applying the filter. Ways to hit it:
- `recordFilters` set but `recordFilterGroups` omitted (e.g. an
API/agent caller, or any non-UI config).
- A grouped filter (carrying `recordFilterGroupId`) whose
`recordFilterGroups` is missing.
- A filter referencing an unknown `fieldMetadataId` or an unresolvable
relation (`turnRecordFilterIntoRecordGqlOperationFilter` returns
`undefined`, silently dropped).
- `gqlOperationFilter` set, which passed validation but was never read.
In every case the computed filter collapses to `{}`,
`FindRecordsService` returns all records ordered by `id ASC`, and
`records[0]` is the first row. This is fail-open: the step reports
success and returns wrong records rather than erroring.
## Fix
- Compute the filter whenever `recordFilters` is non-empty, defaulting
`recordFilterGroups` to `[]`. `computeRecordGqlOperationFilter` handles
ungrouped filters independently of groups.
- **Fail closed**: if `recordFilters` is non-empty but the computed
`gqlOperationFilter` is empty, throw `INVALID_STEP_INPUT` instead of
running an unfiltered query. This covers grouped-without-groups and
unknown-field/unresolvable-relation cases raised in review. An absent or
empty `recordFilters` still legitimately means "find all".
- Remove the unused `gqlOperationFilter` field from the find-records
input type and settings schema so it is no longer advertised as a filter
option (it has been dead since #16147, when the action moved to
computing the filter at runtime from `recordFilters`).
This commit is contained in:
+1
-5
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type ObjectRecordFilter,
|
||||
type ObjectRecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { type ObjectRecordProperties } from './object-record-properties.type';
|
||||
|
||||
@@ -30,7 +27,6 @@ export type FindRecordsInput = {
|
||||
recordFilterGroups?: any;
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
recordFilters?: any;
|
||||
gqlOperationFilter?: Partial<ObjectRecordFilter>[];
|
||||
};
|
||||
orderBy?: {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
|
||||
+24
-14
@@ -3,6 +3,8 @@ import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
computeRecordGqlOperationFilter,
|
||||
isDefined,
|
||||
isEmptyObject,
|
||||
isNonEmptyArray,
|
||||
isRecordFilterValueValid,
|
||||
resolveInput,
|
||||
} from 'twenty-shared/utils';
|
||||
@@ -75,20 +77,28 @@ export class FindRecordsWorkflowAction implements WorkflowAction {
|
||||
}
|
||||
}
|
||||
|
||||
const gqlOperationFilter =
|
||||
workflowActionInput.filter?.recordFilters &&
|
||||
workflowActionInput.filter?.recordFilterGroups
|
||||
? computeRecordGqlOperationFilter({
|
||||
fieldMetadataItems: Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
).filter(isDefined),
|
||||
recordFilters: workflowActionInput.filter.recordFilters,
|
||||
recordFilterGroups: workflowActionInput.filter.recordFilterGroups,
|
||||
filterValueDependencies: {
|
||||
timeZone: 'UTC',
|
||||
},
|
||||
})
|
||||
: {};
|
||||
const recordFilters = workflowActionInput.filter?.recordFilters;
|
||||
|
||||
const gqlOperationFilter = isDefined(recordFilters)
|
||||
? computeRecordGqlOperationFilter({
|
||||
fieldMetadataItems: Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
).filter(isDefined),
|
||||
recordFilters,
|
||||
recordFilterGroups:
|
||||
workflowActionInput.filter?.recordFilterGroups ?? [],
|
||||
filterValueDependencies: {
|
||||
timeZone: 'UTC',
|
||||
},
|
||||
})
|
||||
: {};
|
||||
|
||||
if (isNonEmptyArray(recordFilters) && isEmptyObject(gqlOperationFilter)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Filter could not be resolved to a valid query. Check that filtered fields exist and that grouped filters include their recordFilterGroups.',
|
||||
WorkflowStepExecutorExceptionCode.INVALID_STEP_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const toolOutput = await this.findRecordsService.execute({
|
||||
objectName: workflowActionInput.objectName,
|
||||
|
||||
@@ -11,7 +11,6 @@ export const workflowFindRecordsActionSettingsSchema =
|
||||
.object({
|
||||
recordFilterGroups: z.array(z.any()).optional(),
|
||||
recordFilters: z.array(z.any()).optional(),
|
||||
gqlOperationFilter: z.any().optional().nullable(),
|
||||
})
|
||||
.optional(),
|
||||
orderBy: z
|
||||
|
||||
Reference in New Issue
Block a user