fix(workflow): keep Limit and Offset when changing the Search Records object (#23423)

Fixes the second bug reported in #23387.

## Problem

In the Search Records action, changing the Object silently reset `Limit`
to `1`. A user who had set `Limit = 100` and then switched object (or
switched away and back) ended up with a step that returns exactly one
arbitrary record, with no indication beyond a small `1` in the side
panel.

Reproduced on `main` against a local instance, checking the persisted
draft version:

```json
{ "limit": 1, "offset": 0, "objectName": "person" }
```

`handleOptionClick` rebuilt the entire form as `{ objectNameSingular,
limit: 1, offset: 0 }`, discarding whatever the user had entered. `1` is
the server-side default for a newly created `FIND_RECORDS` step, so this
was effectively a revert-to-creation-default on every object change.

## Change

Carry `limit` and `offset` over instead of hardcoding them. `filter` and
`orderBy` are still dropped by omission, which is correct: they
reference fields of the previous object.

## Test

Added `KeepsLimitAndOffsetWhenObjectChanges` to the existing story file.
It switches the object and asserts `onActionUpdate` receives `{
objectName: 'company', limit: 100, offset: 20 }`.

Confirmed the test is not vacuous: reverting the fix makes it fail with
exactly the reported symptom (`limit: 100 -> 1`, `offset: 20 -> 0`).

## Not addressed here

The headline bug in #23387 (filters made only of value-less operators
never persisting) does not reproduce on `main`. I ran the reporter's
steps with `Is in past` OR `Is today (UTC)` and both rules plus the `OR`
group were written to the draft version correctly. Persistence hangs off
`useUpsertRecordFilter`, which fires the advanced-filter `onUpdate` on
every upsert, so operand changes save just as value changes do. The
reporter is on ~v2.18.x and did not re-test on a recent release.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23423?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:
Thomas Trompette
2026-07-28 14:32:16 +02:00
committed by GitHub
parent 11447bd96f
commit 4c904aa44c
2 changed files with 55 additions and 3 deletions
@@ -1,7 +1,7 @@
import { type WorkflowFindRecordsAction } from '@/workflow/types/Workflow';
import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { expect, fn, screen, userEvent, waitFor, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@@ -65,6 +65,58 @@ export const Default: Story = {
},
};
const onActionUpdateMock = fn();
export const KeepsLimitAndOffsetWhenObjectChanges: Story = {
args: {
action: {
...DEFAULT_ACTION,
settings: {
...DEFAULT_ACTION.settings,
input: {
objectName: 'person',
limit: 100,
offset: 20,
},
},
},
actionOptions: {
onActionUpdate: onActionUpdateMock,
},
},
play: async ({ canvasElement }) => {
onActionUpdateMock.mockClear();
const canvas = within(canvasElement);
await userEvent.click(await canvas.findByText('People'));
await userEvent.type(
await screen.findByPlaceholderText('Search'),
'Companies',
);
await userEvent.click(await screen.findByText('Companies'));
await waitFor(
() => {
expect(onActionUpdateMock).toHaveBeenCalledWith(
expect.objectContaining({
settings: expect.objectContaining({
input: expect.objectContaining({
objectName: 'company',
limit: 100,
offset: 20,
}),
}),
}),
);
},
{ timeout: 3000 },
);
},
};
export const DisabledWithEmptyValues: Story = {
args: {
actionOptions: {
@@ -178,8 +178,8 @@ export const WorkflowEditActionFindRecords = ({
const newFormData: FindRecordsFormData = {
objectNameSingular: value,
limit: 1,
offset: 0,
limit: formData.limit,
offset: formData.offset,
};
setFormData(newFormData);