feat(workflow): variable pickers for Search Records limit, offset and date filters (#23696)
## Summary <img width="491" height="390" alt="Capture d’écran 2026-08-03 à 11 30 40" src="https://github.com/user-attachments/assets/6db59b8a-3e8e-41b8-80b3-c736e56b7f7f" /> Adds workflow variable pickers to the **Search Records** action for fields that previously only accepted static values: - **Limit** and **Offset** number inputs now expose the `WorkflowVariablePicker`, so they can be bound to a variable from a previous step. The stored value can be a standalone variable string; the backend coerces the resolved value back to a number. - **Date filters** using the `Is before` (`IS_BEFORE`) and `Is after or equal` (`IS_AFTER`) operands now expose the variable picker in the advanced filter side panel (previously disabled for all date filters). The backend already resolves these inputs via `resolveInput`; the only backend change is a small numeric coercion of the resolved limit/offset. ## Changes - `WorkflowEditActionFindRecords.tsx` — pass `WorkflowVariablePicker` to the Limit/Offset inputs; make `onChange` and form state variable-aware (`number | string`). - `AdvancedFilterSidePanelValueFormInput.tsx` — enable the date `VariablePicker` only for `IS_BEFORE` / `IS_AFTER`. - `useGetRecordFilterDisplayValue.ts` — return the raw variable for a standalone `{{variable}}` value so date filters don't crash `Temporal.*.from`. - `find-records-action-settings-schema.ts` — allow a string (variable) for `limit` / `offset`. - `find-records.workflow-action.ts` — coerce resolved `limit` / `offset` to numbers before querying. ## Testing Built a workflow locally (Manual trigger → Code step returning `{ limit: 2, offset: 1, sinceDate }` → Search Records) with all three fields bound to those variables. The run completed successfully; the Search Records step returned exactly 2 records (limit applied) filtered by `createdAt >= sinceDate`, confirming the backend resolves each variable. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23696?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+49
-13
@@ -33,6 +33,8 @@ import { WorkflowFindRecordsFilters } from '@/workflow/workflow-steps/workflow-a
|
||||
import { WorkflowFindRecordsFiltersEffect } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsFiltersEffect';
|
||||
import { WorkflowFindRecordsSorts } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts';
|
||||
import { WorkflowObjectDropdownContent } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent';
|
||||
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
@@ -65,8 +67,8 @@ type FindRecordsFormData = {
|
||||
objectNameSingular: string;
|
||||
filter?: FindRecordsActionFilter;
|
||||
orderBy?: FindRecordsActionOrderBy;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
limit?: number | string;
|
||||
offset?: number | string;
|
||||
};
|
||||
|
||||
export type FindRecordsActionFilter = {
|
||||
@@ -96,12 +98,16 @@ export const WorkflowEditActionFindRecords = ({
|
||||
|
||||
const [formData, setFormData] = useState<FindRecordsFormData>(() => ({
|
||||
objectNameSingular: action.settings.input.objectName,
|
||||
limit:
|
||||
isNumber(action.settings.input.limit) &&
|
||||
action.settings.input.limit > QUERY_MAX_RECORDS
|
||||
? QUERY_MAX_RECORDS
|
||||
: (action.settings.input.limit ?? 1),
|
||||
offset: Math.max(0, Math.floor(action.settings.input.offset ?? 0)),
|
||||
limit: isNumber(action.settings.input.limit)
|
||||
? Math.min(action.settings.input.limit, QUERY_MAX_RECORDS)
|
||||
: isStandaloneVariableString(action.settings.input.limit)
|
||||
? action.settings.input.limit
|
||||
: 1,
|
||||
offset: isNumber(action.settings.input.offset)
|
||||
? Math.max(0, Math.floor(action.settings.input.offset))
|
||||
: isStandaloneVariableString(action.settings.input.offset)
|
||||
? action.settings.input.offset
|
||||
: 0,
|
||||
filter: action.settings.input.filter as FindRecordsActionFilter,
|
||||
orderBy: action.settings.input.orderBy as FindRecordsActionOrderBy,
|
||||
}));
|
||||
@@ -155,7 +161,9 @@ export const WorkflowEditActionFindRecords = ({
|
||||
input: {
|
||||
objectName: updatedObjectName,
|
||||
limit: updatedLimit ?? 1,
|
||||
offset: Math.max(0, Math.floor(updatedOffset ?? 0)),
|
||||
offset: isNumber(updatedOffset)
|
||||
? Math.max(0, Math.floor(updatedOffset))
|
||||
: (updatedOffset ?? 0),
|
||||
filter: updatedFilter,
|
||||
orderBy: updatedOrderBy as Record<string, any[]> | undefined,
|
||||
},
|
||||
@@ -312,12 +320,26 @@ export const WorkflowEditActionFindRecords = ({
|
||||
readonly={isFormDisabled}
|
||||
hint={t`This action can return up to ${maxRecordsFormatted} records.`}
|
||||
error={limitError}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
onChange={(limit) => {
|
||||
if (isFormDisabled === true || !isNumber(limit)) {
|
||||
if (isFormDisabled === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedLimit = Math.floor(limit);
|
||||
if (isStandaloneVariableString(limit)) {
|
||||
setLimitError(undefined);
|
||||
|
||||
const newFormData: FindRecordsFormData = {
|
||||
...formData,
|
||||
limit,
|
||||
};
|
||||
|
||||
setFormData(newFormData);
|
||||
saveAction(newFormData);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedLimit = isNumber(limit) ? Math.floor(limit) : 1;
|
||||
|
||||
if (normalizedLimit <= 0) {
|
||||
setLimitError(t`Limit must be greater than 0.`);
|
||||
@@ -350,12 +372,26 @@ export const WorkflowEditActionFindRecords = ({
|
||||
readonly={isFormDisabled}
|
||||
hint={t`Number of records to skip. Combine with Limit to page through results.`}
|
||||
error={offsetError}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
onChange={(offset) => {
|
||||
if (isFormDisabled === true || !isNumber(offset)) {
|
||||
if (isFormDisabled === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedOffset = Math.floor(offset);
|
||||
if (isStandaloneVariableString(offset)) {
|
||||
setOffsetError(undefined);
|
||||
|
||||
const newFormData: FindRecordsFormData = {
|
||||
...formData,
|
||||
offset,
|
||||
};
|
||||
|
||||
setFormData(newFormData);
|
||||
saveAction(newFormData);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedOffset = isNumber(offset) ? Math.floor(offset) : 0;
|
||||
|
||||
if (normalizedOffset < 0) {
|
||||
setOffsetError(t`Offset cannot be negative.`);
|
||||
|
||||
Reference in New Issue
Block a user