Commit Graph
518 Commits
Author SHA1 Message Date
Baptiste DevessierandGitHub 9ec0a7b969 fix: use default trigger name if step name isn't defined (#15676)
## Before


https://github.com/user-attachments/assets/68427ca7-6523-4acc-bec3-7d98f3ae7840

## After


https://github.com/user-attachments/assets/8a998a52-29fc-450e-a92e-1cadc78e3856
2025-11-06 15:48:37 +01:00
Thomas TrompetteandGitHub e4ae792225 Allow rich text v2 in workflows (#15674)
Rich text V2 is supported and has probably been removed by mistake

Fix https://github.com/twentyhq/twenty/issues/15660
2025-11-06 14:33:06 +00:00
Thomas TrompetteandGitHub d1330a4a8c Improve workflow performances (#15528)
- on draft creation, do not fetch the full version. Avoid the fetch of
steps and trigger
- on activation, we were performing 8 queries/mutations synchronously +
2 additional for automated triggers. I refacto the call it it gets
reduced to 4 queries/mutations + 2 additional for automated triggers
2025-11-03 17:23:01 +01:00
afeb505eed [Breaking Change] Implement reliable date picker utils to handle all timezone combinations (#15377)
This PR implements the necessary tools to have `react-datepicker`
calendar and our date picker components work reliably no matter the
timezone difference between the user execution environment and the user
application timezone.

Fixes https://github.com/twentyhq/core-team-issues/issues/1781

This PR won't cover everything needed to have Twenty handle timezone
properly, here is the follow-up issue :
https://github.com/twentyhq/core-team-issues/issues/1807

# Features in this PR

This PR brings a lot of features that have to be merged together.

- DATE field type is now handled as string only, because it shouldn't
involve timezone nor the JS Date object at all, since it is a day like a
birthday date, and not an absolute point in time.
- DATE_TIME field wasn't properly handled when the user settings
timezone was different from the system one
- A timezone abbreviation suffix has been added to most DATE_TIME
display component, only when the timezone is different from the system
one in the settings.
- A lot of bugs, small features and improvements have been made here :
https://github.com/twentyhq/core-team-issues/issues/1781

# Handling of timezones

## Essential concepts

This topic is so complex and easy to misunderstand that it is necessary
to define the precise terms and concepts first. It resembles character
encoding and should be treated with the same care.

- Wall-clock time : the time expressed in the timezone of a user, it is
distinct from the absolute point in time it points to, much like a
pointer being a different value than the value that it points to.
- Absolute time : a point in time, regardless of the timezone, it is an
objective point in time, of course it has to be expressed in a given
timezone, because we have to talk about when it is located in time
between humans, but it is in fact distinct from any wall clock time, it
exists in itself without any clock running on earth. However, by
convention the low-level way to store an absolute point in time is in
UTC, which is a timezone, because there is no way to store an absolute
point in time without a referential, much like a point in space cannot
be stored without a referential.
- DST : Daylight Save Time, makes the timezone shift in a specific
period every year in a given timezone, to make better use of longer days
for various reasons, not all timezones have DST. DST can be 1 hour or 30
min, 45 min, which makes computation difficult.
- UTC : It is NOT an “absolute timezone”, it is the wall-clock time at
0° longitude without DST, which is an arbitrary and shared human
convention. UTC is often used as the standard reference wall-clock time
for talking about absolute point in time without having to do timezone
and DST arithmetic. PostgreSQL stores everything in UTC by convention,
but outputs everything in the server’s SESSION TIMEZONE.

## How should an absolute point in time be stored ?

Since an absolute point in time is essentially distinct from its
timezone it could be stored in an absolute way, but in practice it is
impossible to store an absolute point in time without a referential. We
have to say that a rocket launched at X given time, in UTC, EST, CET,
etc. And of course, someone in China will say that it launched at 10:30,
while in San Francisco it will have launched at 19:30, but it is THE
SAME absolute point in time.

Let’s take a related example in computer science with character
encoding. If a text is stored without the associated encoding table, the
correct meaning associated to the bits stored in memory can be lost
forever. It can become impossible for a program to guess what encoding
table should be used for a given text stored as bits, thus the glitches
that appeared a lot back in the early days of internet and document
processing.

The same can happen with date time storing, if we don’t have the
timezone associated with the absolute point in time, the information of
when it absolutely happened is lost.

It is NOT necessary to store an absolute point in time in UTC, it is
more of a standard and practical wall-clock time to be associated with
an absolute point in time. But an absolute point in time MUST be store
with a timezone, with its time referential, otherwise the information of
when it absolutely happened is lost.

For example, it is easier to pass around a date as a string in UTC, like
`2024-01-02T00:00:00Z` because it allows front-end and back-end code to
“talk” in the same standard and DST-free wall-clock time, BUT it is not
necessary. Because we have date libraries that operate on the standard
ISO timezone tables, we can talk in different timezone and let the
libraries handle the conversion internally.

It is false to say that UTC is an absolute timezone or an absolute point
in time, it is just the standard, conventional time referential, because
one can perfectly store every absolute points in time in UTC+10 with a
complex DST table and have the exactly correct absolute points in time,
without any loss of information, without having any UTC+0 dates
involved.

Thus storing an absolute point in time without a timezone associated,
for example with `timestamp` PostgreSQL data type, is equivalent to
storing a wall-clock time and then throwing away voluntarily the
information that allows to know when it absolutely happened, which is a
voluntary data-loss if the code that stores and retrieves those
wall-clock points in time don’t store the associated timezone somewhere.

This is why we use `timestamptz` type in PostgreSQL, so that we make
sure that the correct absolute point in time is stored at the exact time
we send it to PostgreSQL server, no matter the front-end, back-end and
SQL server's timezone differences.

## The JavaScript Date object

The native JavaScript Date object is now officially considered legacy
([source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)),
the Date object stores an absolute point in time BUT it forces the
storage to use its execution environment timezone, and one CANNOT modify
this timezone, this is a legacy behavior.

To obtain the desired result and store an absolute point in time with an
arbitrary timezone there are several options :

- The new Temporal API that is the successor of the legacy Date object.
- Moment / Luxon / @date-fns/tz that expose objects that allow to use
any timezone to store an absolute point in time.

## How PostgreSQL stores absolute point in times

PostgreSQL stores absolute points in time internally in UTC
([source](https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-INPUT-TIME-STAMPS)),
but the output date is expressed in the server’s session timezone
([source](https://www.postgresql.org/docs/current/sql-set.html)) which
can be different from UTC.

Example with the object companies in Twenty seed database, on a local
instance, with a new “datetime” custom column :

<img width="374" height="554" alt="image"
src="https://github.com/user-attachments/assets/4394cb43-d97e-4479-801d-ca068f800e39"
/>

<img width="516" height="524" alt="image"
src="https://github.com/user-attachments/assets/b652f36a-d2e2-47a4-8950-647ca688cbbd"
/>

## Why can’t I just use the JavaScript native Date object with some
manual logic ?

Because the JavaScript Date object does not allow to change its internal
timezone, the libraries that are based on it will behave on the
execution environment timezone, thus leading to bugs that appear only on
the computers of users in a timezone but not for other in another
timezone.

In our case the `react-datepicker` library forces to use the `Date`
object, thus forcing the calendar to behave in the execution environment
system timezone, which causes a lot of problems when we decide to
display the Twenty application DATE_TIME values in another timezone than
the user system one, the bugs that appear will be of the off-by-one date
class, for example clicking on 23 will select 24, thus creating an
unreliable feature for some system / application timezone combinations.

A solution could be to manually compute the difference of minutes
between the application user and the system timezones, but that’s not
reliable because of DST which makes this computation unreliable when DST
are applied at different period of the year for the two timezones.

## Why can’t I compute the timezone difference manually ?

Because of DST, the work to compute the timezone difference reliably,
not just for the usual happy path, is equivalent to developing the
internal mechanism of a date timezone library, which is equivalent to
use a library that handles timezones.

## Using `@date-fns/tz` to solve this problem

We could have used `luxon` but it has a heavy bundle size, so instead we
rely here on `@date-fns/tz` (~1kB) which gives us a `TZDate` object that
allows to use any given timezone to store an absolute point-in-time.

The solution here is to trick `react-datepicker` by shifting a Date
object by the difference of timezone between the user application
timezone and the system timezone.

Let’s take a concerte example.

System timezone : Midway,  ⇒ UTC-11:00, has no DST.

User application timezone : Auckland, NZ ⇒ UTC+13:00, has a DST.

We’ll take the NZ daylight time, so that will make a timezone difference
of 24 hours !

Let’s take an error-prone date : `2025-01-01T00:00:00` . This date is
usually a good test-case because it can generate three classes of bugs :
off-by-one day bugs, off-by-one month bugs and off-by-one year bugs, at
the same time.

Here is the absolute point in time we take expressed in the different
wall-clock time points we manipulate


Case | In system timezone ⇒ UTC-11 | In UTC | In user application
timezone ⇒ UTC+13
-- | -- | -- | --
Original date | `2024-12-31T00:00:00-11:00` | `2024-12-31T11:00:00Z` |
`2025-01-01T00:00:00+13:00`
Date shifted for react-datepicker | `2025-01-01T00:00:00-11:00` |
`2025-01-01T11:00:00Z` | `2025-01-02T00:00:00+13:00`

We can see with this table that we have the number part of the date that
is the same (`2025-01-01T00:00:00`) but with a different timezone to
“trick” `react-datepicker` and have it display the correct day in its
calendar.

You can find the code in the hooks
`useTurnPointInTimeIntoReactDatePickerShiftedDate` and
`useTurnReactDatePickerShiftedDateBackIntoPointInTime` that contain the
logic that produces the above table internally.

## Miscellaneous

Removed FormDateFieldInput and FormDateTimeFieldInput stories as they do
not behave the same depending of the execution environment and it would
be easier to put them back after having refactored FormDateFieldInput
and FormDateTimeFieldInput

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-11-01 18:31:24 +01:00
Baptiste DevessierandGitHub 5f0d24798a Support workflows in record page layouts (#15471)
https://github.com/user-attachments/assets/275a02a3-7054-45d1-9423-75bb5223a8ba
2025-10-31 18:45:58 +01:00
Thomas TrompetteandGitHub 71e7063fc4 Move http node to backend (#15424)
Fixes https://github.com/twentyhq/twenty/issues/14491

Also adding an url validator.
2025-10-31 17:46:58 +01:00
Thomas TrompetteandGitHub 5f13e6fcf4 Put delete button on step footer + add unique fields on upsert action (#15497)
<img width="491" height="823" alt="Capture d’écran 2025-10-31 à 14 54
38"
src="https://github.com/user-attachments/assets/8058a6a3-8a6c-4cdd-93d9-2af71208dc5a"
/>
2025-10-31 16:39:45 +00:00
Baptiste DevessierandGitHub 51fdaf2731 Some fixes for colors (#15479)
Please note the background colors of the tags and of the nodes

## Before

<img width="3456" height="2161" alt="CleanShot 2025-10-30 at 16 03
21@2x"
src="https://github.com/user-attachments/assets/09cbd1bd-26e2-4e05-88bc-f7d96de8a004"
/>

## After

<img width="3456" height="2160" alt="CleanShot 2025-10-30 at 17 03
25@2x"
src="https://github.com/user-attachments/assets/dc4fe113-1489-4476-a8b6-4cb446addcd5"
/>
2025-10-30 16:23:22 +00:00
Thomas TrompetteandGitHub a82b74c1ff Make upsert action body common with create record instead of update record (#15442)
We do not want the fields to update multiselect for upsert record
action. We want all available fields displayed by default.
This makes upsert record action closer to create record than update
record.

This PR: 
- deletes WorkflowUpdateRecordBody that was common between update and
upsert and put back content into update
- creates WorkflowCreateRecordBody that is now common between create and
upsert
- simplifies shouldDisplayFormField

Before - using fields to update as update record action
<img width="546" height="823" alt="Capture d’écran 2025-10-30 à 10 00
24"
src="https://github.com/user-attachments/assets/9206bc8b-75c2-40fa-a8de-e708b6b2cd05"
/>

After - displaying all fields as create record action
<img width="546" height="823" alt="Capture d’écran 2025-10-30 à 10 00
04"
src="https://github.com/user-attachments/assets/87141a47-946f-4604-be55-f4c21ff4a3d8"
/>
2025-10-30 13:36:16 +01:00
Raphaël BosiandGitHub 198bf5a333 Complete color refactoring (#15414)
# Complete color refactoring

Closes https://github.com/twentyhq/core-team-issues/issues/1779

- Updated all colors to use Radix colors with P3 color space allowing
for brighter colors
- Created our own gray scale interpolated on Radix's scale to have the
same values for grays as the old ones in the app
- Introduced dark and light colors as well as there transparent versions
- Added many new colors from radix that can be used in the tags or in
the graphs
- Updated multiple color utilities to match new behaviors
- Changed the computation of Avatar colors to return only colors from
the theme (before it was random hsl)

These changes allow the user to use new colors in tags or charts, the
colors are brighter and with better contrast. We have a full range of
color variations from 1 to 12 where before we only had 4 adaptative
colors.
All these changes will allow us to develop custom themes for the user
soon, where users can choose their accent colors, background colors and
there contrast.
2025-10-29 18:08:51 +00:00
Thomas TrompetteGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
da0e5ba342 Support primitive types in filters (#15402)
When using primitive types such as array, number and boolean, we display
a text field in filters because fieldmetadataId is empty. We should
instead support these as we would do for our own fields.

Adding also a fix for https://github.com/twentyhq/twenty/issues/15282

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-10-28 14:13:10 +00:00
Paul RastoinandGitHub 503a5029da Refactor twenty-front metadata api services for v2 (#15360)
# Introduction
Please first review this PR initial base
https://github.com/twentyhq/twenty/pull/15358
In a nutshell refactored the frontend fetchers to display v2 errors
format smoothly
Please note that the v2 now finished the whole validation and does fail
fast anymore ( summary is hardcoded for the moment )
```json
[
  {
    "extensions": {
      "code": "BAD_USER_INPUT",
      "errors": {
        "cronTrigger": [],
        "databaseEventTrigger": [],
        "fieldMetadata": [
          {
            "errors": [
              {
                "code": "INVALID_FIELD_INPUT",
                "message": "Default value should be as quoted string",
                "value": "",
              },
              {
                "code": "INVALID_FIELD_INPUT",
                "message": "Default value "" must be one of the option values",
                "value": "",
              },
            ],
            "flatEntityMinimalInformation": {
              "id": Any<String>,
              "name": "testField",
              "objectMetadataId": Any<String>,
            },
            "status": "fail",
            "type": "create_field",
          },
        ],
        "index": [],
        "objectMetadata": [],
        "routeTrigger": [],
        "serverlessFunction": [],
        "view": [],
        "viewField": [],
        "viewFilter": [],
        "viewGroup": [],
      },
      "message": "Validation failed for 0 object(s) and 0 field(s)",
      "summary": {
        "invalidCronTrigger": 0,
        "invalidDatabaseEventTrigger": 0,
        "invalidFieldMetadata": 0,
        "invalidIndex": 0,
        "invalidObjectMetadata": 0,
        "invalidRouteTrigger": 0,
        "invalidServerlessFunction": 0,
        "invalidView": 0,
        "invalidViewField": 0,
        "invalidViewFilter": 0,
        "invalidViewGroup": 0,
        "totalErrors": 0,
      },
      "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
    },
    "message": "Multiple validation errors occurred while creating fields",
    "name": "GraphQLError",
  },
]
```

## What's done
- `usePersistView` tool ( CRUD )
- renamed `usePersistViewX` tools accordingly ( no more records or core
)
- Now catching a lot of before unhandled exceptions
- refactored each services to handle their own exception handlers and
return either the response or the error within a discriminated union
record

## Result
### Primary entity error 
When performing an metadata operation on a given metadata, if validation
errors occurs we will display each of them in a toast
Here while creating an object metadata.
<img width="700" height="327" alt="image"
src="https://github.com/user-attachments/assets/0c33d13c-c66c-4749-af36-b253abd3449b"
/>

### Related entity error
Still while creating an object
<img width="700" height="327" alt="image"
src="https://github.com/user-attachments/assets/52607788-c4e9-470c-ac8c-23437345ee5c"
/>

### Translated
<img width="700" height="327" alt="image"
src="https://github.com/user-attachments/assets/a7198c20-ae82-47a6-910c-761de9594672"
/>


## Conclusion
This PR is an extract of https://github.com/twentyhq/twenty/pull/15331
close https://github.com/twentyhq/core-team-issues/issues/1776
## Notes
- Not refactor around triggers services as they're not consumed directly
by any frontend services
2025-10-28 10:50:41 +01:00
Thomas TrompetteandGitHub be08233fa4 Add sort section in find records action (#15340)
https://github.com/user-attachments/assets/020f3e97-316b-41db-b95d-7b938a7f82cf
2025-10-28 09:54:47 +01:00
Thomas TrompetteandGitHub 4effa5351c Allow to stop running workflow (#15270)
https://github.com/user-attachments/assets/599154e4-8743-471b-b05a-721b635bcf4e

On stoppage:
- if no running steps, mark pending as failed and end the workflow
- if running steps, set as stopping and exit. Going to the next step, as
the workflow is not running anymore, it will naturally stop
2025-10-23 13:01:15 +00:00
4b846a42a2 feat: Attachement for Send Email workflow node (#15044)
## Description

- This PR addresses the one issue out of
https://github.com/twentyhq/core-team-issues/issues/1685
-  Added backend support for workflow node to support attachement
- updated send email schema, core utility 
- added workflowattachmentRow and workflowsendEmailAttachment file to
handle file attachment in email workflow
- updated Google and Microsoft to use MailComposer which unifies with
SMTP provider and improves mail structure

## Visual Appearance



https://github.com/user-attachments/assets/16478569-0a83-417e-a85e-70e41fe83343

---------

Co-authored-by: martmull <martmull@hotmail.fr>
2025-10-22 16:45:28 +00:00
Thomas TrompetteandGitHub c04b3722b3 Use array fields in filter steps (#15256)
Fixes https://github.com/twentyhq/twenty/issues/15247

Before - arrays not available
<img width="595" height="403" alt="Capture d’écran 2025-10-22 à 14 32
22"
src="https://github.com/user-attachments/assets/6c7a5fe3-5002-4b06-8ff3-af42f26d0dae"
/>

After
<img width="595" height="403" alt="Capture d’écran 2025-10-22 à 14 32
03"
src="https://github.com/user-attachments/assets/2e00c4f0-9e57-4bd9-94de-2caa0493446d"
/>
2025-10-22 15:01:00 +00:00
32558673c6 feat: Implement AI Router for Dynamic Agent Selection (#15227)
Adds intelligent routing system that automatically selects the best
agent for user queries based on conversation context.

### Changes:
- Added `routerModel` column to workspace table for configurable router
LLM selection
- Implemented `RouterService` with conversation history analysis and
agent matching logic
- Created router settings UI in AI Settings page with model dropdown
- Removed agent-specific thread associations - threads are now
agent-agnostic
- Added real-time routing status notification in chat UI with shimmer
effect
- Removed automatic default assistant agent creation
- Renamed GraphQL operations from agent-specific to generic (e.g.,
`agentChatThreads` → `chatThreads`)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-10-22 15:02:41 +02:00
8369168abe feat: display specific action types in workflow side panel tooltips (#15013)
## What I've Implemented

I've added tooltips that show the original action type when you hover
over workflow step icons in the side panel. Now even if you rename an
action to something custom, you can still see what type of action it
actually is by hovering over the icon.

## The Problem This Solves

Before this change, once you renamed a workflow action (like changing
"Create Record" to "Add New Customer"), there was no way to tell what
the original action type was. This made it really confusing when
collaborating with others or when coming back to your own workflows
after some time - you couldn't tell if an action was a "Create Record"
or "Update Record" or something else.

## What Changed

Updated action type labels: Instead of showing just "Action" for all
record operations, the system now shows specific types like "Create
Record", "Update Record", "Delete Record", etc.

Added hover tooltips: When you hover over the action icons in the
workflow side panel, a tooltip appears showing the original action type,
even if you've renamed the step title.

## Before vs After

Before: All actions showed "Action" in the side panel, making it
impossible to distinguish between different types after renaming.

After: Each action shows its specific type in tooltips, so you always
know what you're working with.

This should make workflow management much clearer, especially when
multiple people are collaborating on the same workflow!

Resolves #14878

## Demo Video

See it in action:
https://www.loom.com/share/c0e0ec24e4524d0685b841e9ceb011d0?t=65&sid=dc05e58a-8869-4969-aa67-9772242fe697

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2025-10-22 10:20:49 +00:00
Thomas TrompetteandGitHub bf3c3fc5a5 Workflow command menu fixes (#15234)
- Move trash button to command menu footer
<img width="132" height="102" alt="Capture d’écran 2025-10-21 à 18 12
19"
src="https://github.com/user-attachments/assets/ad6a9374-a28f-4498-b8f3-ca576981693c"
/>

- Add footer to triggers + on missing steps
- Catch step body errors so the user can still delete the step when an
error happens
<img width="529" height="419" alt="Capture d’écran 2025-10-21 à 18 13
17"
src="https://github.com/user-attachments/assets/0ac07511-f4ad-40c4-98f1-afb53c0f7a89"
/>
2025-10-22 10:17:06 +02:00
27f50c4f4e feat: add-create-update-record in workflow (#14654)
## Description

- this PR focuses on issue
https://github.com/twentyhq/core-team-issues/issues/1476
- Added upsert action


## Visual Appearance

<img width="1792" height="1041" alt="Screenshot 2025-10-03 at 12 57
58 PM"
src="https://github.com/user-attachments/assets/57afb96c-d4b3-4a87-95f0-11ac4bd61dd8"
/>




<img width="1792" height="1031" alt="Screenshot 2025-10-03 at 12 57
48 PM"
src="https://github.com/user-attachments/assets/9032d4c2-f0d2-46f1-8682-a7e5c280a303"
/>

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2025-10-21 15:48:25 +02:00
23d8bbbf92 Refactored WorkflowSendEmailBody to FormAdvancedTextFieldInput (#15157)
Step 1 towards fixing issue #14976 

Refactored WorkflowSendEmailBody to FormAdvancedTextFieldInput For its
reusability across application

- `useEmailEditor` is transformed to `useAdvancedTextEditor`: A
reuseable hook for managing the text editor state and functionality.
- `WorkflowSendEmailBody` is transformed to
`FormAdvancedTextFieldEditor`: A wrapper component for the advanced text
editor.
- `WorkflowSendEmailBody` is transformed to
`FormAdvancedTextFieldInput`: A component integrating the advanced text
editor into forms.
- `ImageBubbleMenu`, `LinkBubbleMenu`, and `TextBubbleMenu`: Contextual
menus for image, link, and text formatting options. are moved to
ui/components for access in FormAdvancedTextFieldInput

Additionally, the email action workflow component was updated to utilize
the new reuseable text editor, enhancing the user experience for
composing emails.

This update also includes storybook entries for the new components to
facilitate testing and documentation.

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-10-20 17:56:49 +02:00
4e5783eaf4 feat: workflow delay action (Pause - Wait/Sleep/Delay) (#14915)
## Description

- This PR focuses on issue
https://github.com/orgs/twentyhq/projects/1/views/33?pane=issue&itemId=93150683&issue=twentyhq%7Ccore-team-issues%7C20
- added Workflow delay as a Flow action
- for V1 added Type 1: Resume at a specific date or time


## Visual Appearance
<img width="1792" height="1038" alt="Screenshot 2025-10-09 at 5 46
18 PM"
src="https://github.com/user-attachments/assets/e62980e9-59c7-4e5a-b8ec-1e848a462d3f"
/>

<img width="1792" height="1037" alt="Screenshot 2025-10-09 at 5 46
35 PM"
src="https://github.com/user-attachments/assets/7c3f4e39-ab0a-40ed-97a8-4f0cdb86f295"
/>

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2025-10-20 15:15:55 +02:00
Thomas TrompetteandGitHub 91b54e7e68 Remove iterator feature flag (#15165)
As title
2025-10-17 09:19:39 +00:00
ce045db15a Analyze Context of Issue #1586 Using GitHub MCP (#15058)
# Implement "Tidy Up" Action for Workflow Diagram

**Task Link:** https://twill.ai/twentyhq/ENG/tasks/6

## Summary

This PR adds a "Tidy Up" action to the workflow diagram interface,
allowing users to automatically organize and clean up the layout of
workflow nodes and connections.

## Changes Made

- **Added new workflow action**: Created
`TidyUpWorkflowSingleRecordAction` component to provide a UI action for
tidying up workflow diagrams
- **Refactored tidy-up logic**: Extracted core tidy-up functionality
into a reusable `useTidyUp` hook, separating layout logic from workflow
version persistence
- **Updated action menu configuration**: Integrated the new tidy-up
action into `WorkflowActionsConfig` with proper permissions and keyboard
shortcuts
- **Enhanced right-click menu**: Updated
`WorkflowDiagramRightClickCommandMenu` to use the refactored tidy-up
hook
- **Improved hook architecture**: Simplified `useTidyUpWorkflowVersion`
to delegate layout calculations to the new `useTidyUp` hook

## Key Features

- Users can now trigger workflow diagram tidy-up from the action menu
- Consistent tidy-up behavior across both right-click menu and action
menu interfaces
- Better separation of concerns between layout calculation and data
persistence

<details>
<summary>📸 Screenshots</summary>

Playwright test screenshots captured during development:

### command-menu-with-tidy-up-workflow.png


![command-menu-with-tidy-up-workflow.png](https://storage.googleapis.com/twill-screenshots-twill-469020/screenshots/1760356112795-command-menu-with-tidy-up-workflow.png)

### tidy-up-workflow-command-menu.png


![tidy-up-workflow-command-menu.png](https://storage.googleapis.com/twill-screenshots-twill-469020/screenshots/1760356113168-tidy-up-workflow-command-menu.png)

### tidy-up-workflow-error-toast.png


![tidy-up-workflow-error-toast.png](https://storage.googleapis.com/twill-screenshots-twill-469020/screenshots/1760356113407-tidy-up-workflow-error-toast.png)

</details>

---------

Co-authored-by: Twill <agent@twill.ai>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2025-10-16 16:59:48 +00:00
Thomas TrompetteandGitHub 0d0cce7bb1 Iterators design updates (#15146)
A few fixes :
- add a counter of iteration for nodes that have been run
<img width="601" height="538" alt="Capture d’écran 2025-10-16 à 17 50
07"
src="https://github.com/user-attachments/assets/de9d56ed-3057-4a67-b1d7-b421f644d11d"
/>

- if a node has fail, we should end the workflow even if a step is still
running
- fix position for add node button in iterator loop :
Before


https://github.com/user-attachments/assets/eafeda64-977f-415d-bab4-b7d0c44406b0

After


https://github.com/user-attachments/assets/6ff221a3-b287-45f6-977f-b127f9998692
2025-10-16 16:32:47 +00:00
Thomas TrompetteandGitHub d3f3f991a5 Infer array current item schema (#15115)
This PR allows to infer the schema of the current item of an iterator
step:
- iterator step receive a variable
- added an util that navigate to the array in schema -
navigateOutputSchemaProperty
- use the array value in schema to generate a new schema - used the
existing getFunctionOutputSchema that I renamed and moved to
twenty-shared

Also cleaned a bit the existing schema for AI.

Before


https://github.com/user-attachments/assets/9767fc89-3524-4bfb-b1ab-8abe92084767

After


https://github.com/user-attachments/assets/3650c1d2-14f2-44f9-b10c-e649fe04128d
2025-10-15 16:15:08 +00:00
martmullandGitHub b16ab1b7c9 1518 extensibility front add an application section in settings (#15056)
Protected by IS_APPLICATION_ENABLED featureFlag

Add `Application` section in settings

<img width="301" height="137" alt="image"
src="https://github.com/user-attachments/assets/ee53bdd2-36f6-45c6-8646-17b1e08abf00"
/>


A `settings/applications` route listing all installed applications

<img width="661" height="428" alt="image"
src="https://github.com/user-attachments/assets/69d534c4-4e9e-452a-a3d9-ded0223bb457"
/>

Introduce a new Tag for application managed items

<img width="885" height="759" alt="image"
src="https://github.com/user-attachments/assets/19767be5-61e5-4bd2-a51d-54ed9bfb1923"
/>



A `settings/applications/<application_id>` details setting page listing
all objects, serverlessFunctions and agents created by the application:

<img width="917" height="778" alt="image"
src="https://github.com/user-attachments/assets/7fc056a6-1d73-4242-b2eb-6f8955d8597d"
/>

A `settings/applications/<application_id>/<serverless_function_id>`

<img width="905" height="652" alt="image"
src="https://github.com/user-attachments/assets/56ca0021-26bf-42cb-9abf-34879f16050a"
/>

Add trigger tab in serverless function details (readonly for now)

<img width="899" height="724" alt="image"
src="https://github.com/user-attachments/assets/5eeefa35-f2a4-4fd8-a640-7b5c5891f226"
/>

Set object, serverless and agent setting detail pages readonly for
managed items
<img width="1075" height="859" alt="image"
src="https://github.com/user-attachments/assets/57c73d69-4980-47a2-b752-8dc5ab494530"
/>
<img width="648" height="582" alt="image"
src="https://github.com/user-attachments/assets/5ad5f3f7-3bc3-4e40-870a-4981c6492524"
/>
<img width="982" height="692" alt="image"
src="https://github.com/user-attachments/assets/7ad756c4-5d33-4a0a-9eb8-416c040362b9"
/>
<img width="1077" height="647" alt="image"
src="https://github.com/user-attachments/assets/e086b9f5-4062-4d10-82a9-4023de3cad3f"
/>
2025-10-15 17:13:29 +02:00
Lucas BordeauandGitHub 7f8ec35b5d Added dashboard chart advanced filter components (#15095)
This PR adds the components for advanced filters on chart settings.

It also refactors what's in common between this new command menu page
and the workflow advanced filters.
2025-10-15 16:06:15 +02:00
Thomas TrompetteandGitHub 5f1878c96f Simple design for empty node (#15087)
I did an acceptable design for empty node and iterators for release:
- use array field for iterator node. Added an util to stringify arrays
for backward compatibily
- remove icon for empty node
- allow to select a node on empty node selection


https://github.com/user-attachments/assets/b00037a8-aa1d-4784-b973-05973649b46e
2025-10-14 16:58:53 +02:00
Thomas TrompetteandGitHub 650a037f67 Prevent losing form data when workflow is not defined (#15055)
When updating an active version, we :
- create a draft
- update the draft

Issue is that after the creation, workflow is shortly null. So the
component is re-rendered and the form data are lost.
Removing the check on the null allow to keep the form data.
2025-10-13 17:25:08 +02:00
7b84db4708 Only fetch steps and trigger for the current workflow version (#15003)
Avoid fetching full steps and trigger for versions that are not the
current version. Because those won't be used anyway. Better for
performances.

Only difficulty was for the `createDraft` mutation. I needed to return
the full created version so I can store it in cache and use it as new
`currentVersion`. Otherwise the current version is considered as
incomplete for a short time, since workflow is fetched separately from
the current version.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-12 18:06:12 +02:00
Félix MalfaitandGitHub 938debd83e Refactor record layouts for backend-driven configuration (#15021)
## Refactor: Prepare frontend record layouts for backend-driven
configuration

This PR simplifies and prepares the frontend record layout system for
eventual migration to backend-driven layouts, aligning the architecture
with the existing PageLayout system used for dashboards.

### Key Changes

**Architecture Improvements:**
- Created unified LayoutRenderingContext that works for both record
pages (with targetRecord) and dashboards (standalone)
- Replaced prop drilling with context-based data flow - cards access
targetRecord and isInRightDrawer via context hooks
- Introduced useTargetRecord() helper hook that provides type-safe
access to the current record
- Created generic CardRenderer component that handles configuration
injection and context guards uniformly

**Configuration System:**
- Converted all tab icons from React components to JSON-serializable
strings (e.g., Icon: IconCheckbox → icon: 'IconCheckbox')
- Added configuration field to cards, matching the widget configuration
pattern on the backend
- Created CardConfiguration types system similar to WidgetConfiguration
on the backend
- Moved widget-specific props (like showDuplicatesSection) into
configuration objects

**Code Organization:**
- Extracted visibility evaluation logic into reusable
evaluateTabVisibility() utility
- Organized layouts into dedicated files (one per object:
base-record-layout.ts, company-record-layout.ts, etc.)
- Renamed components to match their purpose: Notes → NotesCard,
Attachments → FilesCard, etc.
- Consolidated card rendering from registry object to direct
getCardComponent() function

**API Alignment:**
- Made ifNoReadPermissionObject an explicit part of TabVisibilityConfig
(follows if* naming convention)
- Removed redundant targetObjectNameSingular from tab-level (now derived
from visibility config)
- Card API now mirrors Widget API (both use type, configuration,
accessed via context)
2025-10-10 16:26:17 +02:00
Charles BochetandGitHub 37473175a0 Reduce relation loading overload on FE graphql queries (#14991)
## Problem

With Twenty usage growing, we are facing challenges on server side to
respond to the demand. The current bottleneck we are facing is server
CPU.
While investigating CPU performances, we figured out that loading all
relation fields was the biggest issue.

### About graphql query response size

**Example:** on `People Index` Table page:
we query: `person.company` and we query **all non relation fields** on
company relation field.
`{ person { company { id, name, domainName, employees, address ... } }`
 
 
However we only need **company.id, company.name and company.domainName**
to be able to render the Company Chip in the company column (RELATION)
in the table.
`{ person { company { id, name, domainName } }`

We initially assumed that the querying all non relation fields was not
an issue because it was not adding additional load on database (which is
also partially true: if a field is containing a huge json this will add
load on postgres as data needs to be transfered)
This assumption is wrong on CPU side:
- when loading one to many relations (company.people), this quickly ads
up and we end up with response of 20kB quite quickly, even without
adding any custom field on person.
- even when loading a many to one relation, if the user is storing big
data in a given field (note.body for instance, or workflowRun.state),
this starts also being an issue.
- Worst case scenario are starting to happen: on a production workspace
with 20 active workflows. Loading workflow table while displaying
workflow runs commands (20 workflows x 60 workflowRuns x
workflowRun.state which is a big JSON) result in a response of... 125MB.

### Why is it an issue?

When yoga (graphql engine) parses a response to send it back in server
response, it's using `JSON.parse `(or stringify depending on the case).
Parsing data is CPU instensive (as you need to validate, transform) and
this is more or less `O(n)`.
This means returning 125MB is very intense on the CPU and will likely
use 100% of the CPU for ~1sec in our production servers.

NodeJs (nodeV8) is single threaded. It's able to process multiple
requests in parallel but to do that, it will cut them in "microTasks"
and process each microTasks (that can belong to different requests) one
after the other. Exactly like a single threaded CPU would allocate some
time to a process and then to the next one, etc.

This means that this big response request will actually block the other
request.
As a result, all requests are slow and our infrastructure is multi
tenant so some customers are impacting others.

This is even a bigger issue when our health checks start to fail and
containers are starting being considered as unhealthy and killed by the
orchestrator


## How to fix this

1. On Front end side, we should only query what we need. In this PR, I'm
forcing the relations to only query: `id`, `labelIdentifier`,
`imageIdentifier`
2. (later) as we are API first, we also need to do something to protect
the servers from this side too. To do that, Graphql APIs can associate a
cost to each graphql field (maybe 1 for a test fixed, 2 for a JSON
field, 10 for relations, etc...) and we can throttle based on that.

## Changes in this PR

It's mainly about refactor the tooling to generate `RecordGqlFields`
(`generateDepthOneGqlFields`). this tooling is used to generate the list
of fields we want to query. Most of the time we want to query a record
with one level of nesting.
1) Refactor to remove duplicate code => `generateDepthOne` become
`generateDepth` and can handle both `depth = 0 | 1`

2) Introduce `generateDepthRecordGqlFieldsFromFields.ts` which is the
base and can give you a list of gqlFields based on FieldMetadataItems

3) Introduce `generateDepthRecordGqlFieldsFromObject` which is a
shortcut for an object
4) Introduce `generateDepthRecordGqlFieldsFromRecords` which is the
intersection between `generateDepthRecordGqlFieldsFromObject` and a
given record (useful for cache tooling)
5) Replace all usages + introduce a hook
`useGenerateDepthRecordGqlFieldsFromObject` to ease usage
2025-10-10 14:31:12 +02:00
Baptiste DevessierandGitHub e382730a39 Accept any json value in result and error (#14954)
Some users might throw JS values that aren't strings.

<img width="3456" height="2160" alt="CleanShot 2025-10-07 at 17 48
36@2x"
src="https://github.com/user-attachments/assets/b02f886c-1c8e-4186-954b-6d0e025a6fcf"
/>

Errors seen previously:

<img width="1308" height="840" alt="image"
src="https://github.com/user-attachments/assets/1d7e4947-8f6f-44fb-aefb-c7d105712204"
/>
2025-10-07 18:25:51 +02:00
f60817a1e1 Morph many to one picker (#14155)
This PR follows the multiSelect PR merged previously. It will enable
morph relation Many to One to be handled from the table, using a
singleSelect picker

Main point : I decided to change the singleSelect API to take an array
of **objectMetadataName** instead of only one to deal with both our
usecases.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-07 17:25:11 +02:00
Charles BochetandGitHub 0fd6085b53 Prevent querying too much on worklow index page (#14916)
## Performance short term fix

Done in this PR:
workflowVersions and workflowRuns are heavy object. I'm preventing
loading their data when they are loaded as relations. This will reduce
the work on backend

## Long term fix

Todo:
We should make sure workflow data is ligher
2025-10-06 23:53:58 +02:00
6c4ae1ae33 Ability to navigate/scroll the workflow canvas with sliding two fingers (#14804)
**Issue**: https://github.com/twentyhq/core-team-issues/issues/1587

### Changes Made
- Modified scroll event handling to detect and limit two-finger scroll
gestures to the workflow canvas component
- Improved user experience by preventing accidental navigation or
zooming outside the intended canvas area

---------

Co-authored-by: Twill <agent@twill.ai>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-10-06 13:44:23 +02:00
Charles BochetandGitHub c5cdf45f85 Improve performances (#14869)
## Improvements

- Add logs to all gql operations and rest calls to help debug CPU issues
on the backend. These are temporary and should be removed
- Remove nested relations from workflowVersions load (used to add manual
triggers in the side bar). On some workspaces this call result in a
response of 4MB which is heavy on CPU
- Investigated Redis Usage ==> made a few improvements, we are should
still migrate to the new cache service once available
- investigated db calls in messaging / calendar fetch list + workflow
enqueue run cron jobs. Everything seems to be properly batched
2025-10-03 17:22:03 +02:00
Raphaël BosiandGitHub d10ab5f67c Chart editor - Part 1 (#14820)
This PR is the first part of the creation of the Chart editor.


https://github.com/user-attachments/assets/8b0af8ea-be41-4506-84cb-e40f5521cbf0

Done:
- Bar chart settings (except filters)
- Line chart settings (except filters)

In progress:
- Pie chart settings
- Number chart settings
- Gauge chart settings

Left to do:
- Loosen the backend validation to allow the user to save a partial
configuration, validate the graph configuration in the frontend and
display a error friendly message in the graph if the config is not
completed yet
- Implement the filter edition
- Finish the other graph types settings
2025-10-03 10:25:45 +02:00
WeikoandGitHub 0b60aa4249 Add custom routes to migration v2 (#14846)
## Context
Add routes to migration V2
- Resolvers
- Service v2
- Builder
- Validator
- Action runner

Next PR: Add to twenty-cli to sync routes with serverless
2025-10-03 00:20:21 +02:00
MarieandGitHub aac1314f5b [Fix] Handle deprecated viewFilterOperand in workflows run & on interface (#14837)
In this PR https://github.com/twentyhq/twenty/pull/14785 we deprecated
viewFilterOperand (from camelCase to capital snakeCase values), but we
had not migrated the existing workflow steps values. Hence they cannot
be executed!

Let's fix that by dealing with both operands, new and deprecated, to
mitigate the issue. Then we will add a command to migrate the values.
2025-10-02 13:08:24 +02:00
Thomas TrompetteandGitHub 3fa12bb9fc Search record schema migration breaks following workflow steps (#14833)
If iterator loop feature flag is not enabled, `all` field in undefined
in search record schema. We need to handle that case properly.
2025-10-02 11:04:38 +02:00
martmullandGitHub 6fdd7894a1 Add serverless function in twenty-cli (#14819)
- add serverlessFunction schema in twenty-cli
- add trigger schema in twenty-cli
- update serverless function code save and get
- sync serverless function
2025-10-01 22:11:03 +02:00
ae4a93c993 feat: add create or update workflow trigger (#14708)
### Description

This PR focuses on
https://github.com/twentyhq/core-team-issues/issues/1476

- introduces the handling of upserted events in the workflow trigger
system


## Visual Appearance
<img width="1792" height="1039" alt="Screenshot 2025-09-21 at 11 40
28 PM"
src="https://github.com/user-attachments/assets/d926b566-f7d3-412b-b1ab-ed2064425f3d"
/>

<img width="501" height="1025" alt="Screenshot 2025-09-21 at 11 40
39 PM"
src="https://github.com/user-attachments/assets/13c3031a-0762-4c80-8df1-fa74ca70a540"
/>


### Changes
- Added `UPSERTED` action to the `DatabaseEventAction` enum.
- Updated the `WorkflowEditTriggerDatabaseEventForm` to support upserted
events.

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2025-10-01 15:17:05 +02:00
Baptiste DevessierandGitHub a68aa303aa Hide iteration switcher for steps following iterators (#14810)
## Before


https://github.com/user-attachments/assets/a8eca1aa-b2b6-4fdb-8848-65c8235a330c

## After


https://github.com/user-attachments/assets/84b7e969-423b-4f0e-8f53-524bfb11e2ca
2025-10-01 14:44:11 +02:00
Baptiste DevessierandGitHub b3153535a6 Fix step output for iterators (#14796)
It also prevents the app from crashing when opening the output of the
trigger node.

## Before

<img width="3738" height="2442" alt="CleanShot 2025-09-30 at 19 44
39@2x"
src="https://github.com/user-attachments/assets/39fa81b1-2c25-402e-9030-770054870f70"
/>

## After


https://github.com/user-attachments/assets/b7585690-dd3b-4875-9c3e-18b60b3acc3d
2025-10-01 14:15:07 +02:00
MarieandGitHub 3927b36406 [View migration] Clean ViewFilterOperand (#14785)
In the BE we have stored filter operand as IS_EMPTY, IS_NOT_EMPTY, etc. 
For some reason in the FE we were manipulating IsEmpty, IsNotEmpty, etc.
(maybe because they were used before in Views before they were moved to
core)

So we were converting the operands in the FE from IS to Is
(convertViewFilterOperandFromCore) to read and manipulate viewFilters,
and then back to BE version to send mutations etc., from Is to IS
(convertViewFilterOperandToCore).
The migration is now over, so we can remove and simplify that code. 
(In the 1-5:migrate-views-to-core command we still do the migration from
Is format to IS format.)
2025-09-30 17:12:03 +02:00
Baptiste DevessierandGitHub 15975a2cfc Visualize workflow run step input (#14784)
https://github.com/user-attachments/assets/8358a252-0407-4925-a923-7b1d6c7404b0
2025-09-30 17:09:57 +02:00
Thomas TrompetteandGitHub b7cdf9183a Search record action - allow to search more than 1 record (#14769)
https://github.com/user-attachments/assets/426da6af-6c6b-4d49-9034-0aa041cb331f
2025-09-30 12:07:25 +02:00
MarieandGitHub da3c3d2b9d [GroupBy] Add views filters to groupBy query (#14762)
Closes https://github.com/twentyhq/core-team-issues/issues/1560

If viewId is defined in a groupBy query, we want to apply all filters of
the view to the query.
This required to move a lot of code from twenty-front to twenty-shared
to convert the filters as stored in the db into graphql filters,
applying the right combinations between filters etc., which was
previously only done in the FE.

This PR does not handle any field filters, it will be done in a later pr
2025-09-30 09:58:55 +02:00