# Introduction
Defining very first basis of the twenty-cli e2e testing env.
Dynamically generating tests cases based on a list of applications names
that will be matched to stored twenty-apps and run install delete and
reinstall with their configuration on the same instance
We could use a glob pattern with a specific e2e configuration in every
apps but right now overkill
## Notes
- We should define typescript path aliasing to ease import devxp
- parse the config using a zod object
## Some vision on test granularity
Right now we only check that the server sent back success or failure on
below operation. In the future the synchronize will return a report of
what has been installed per entity exactly. We will be able to snapshot
everything in order to detect regressions
We should also be testing the cli directly for init and other stuff in
the end, we could get some inspiration from what's done in preconstruct
e2e tests with an on heap virtual file system
## Conclusion
Any suggestions are more than welcomed !
close https://github.com/twentyhq/core-team-issues/issues/1721
## Release 1.8.0
This release introduces three major workflow enhancements:
### Workflow Iterator Node
- Ability to loop through items in workflows
- Process multiple records sequentially
- Perform actions on each item in a collection
### Workflow Bulk Select
- Select multiple records for manual trigger nodes
- Pass several records to workflow execution
- Works seamlessly with the new iterator node
### Workflow Search Node Limit
- Customize search result limit above 1
- Retrieve multiple records in a single search
- Enhanced compatibility with iterator node for processing results
---
Changelog file: `packages/twenty-website/src/content/releases/1.8.0.mdx`
Release date: October 16, 2025
🎯 Merge Settings Relation and Morph Relation Forms followup
https://github.com/twentyhq/twenty/pull/15062
Here we fixed some little comments that could have be done earlier in
the main PR
🎯 Merge Settings Relation and Morph Relation Forms
In the settings, we unify morph and relation into a single form.
The form now automatically creates
- a `RELATION` field when 1 destination object is selected
- or a `MORPH_RELATION` field when 2+ objects are selected.
Better UI labels (showing object name for single selection, "X Objects"
for multiple), proper field editing controls on destination objects, and
capitalized field labels.
We still make sure the isMorphRelationEnabled feature flag prevents
current users from accessing this feature
Fixes https://github.com/twentyhq/core-team-issues/issues/1589
## Goal
- inferDeletionFromMissingEntities is now a map instead of a single
bool, allowing us to parameterise it based on the entity we want to
compare
- Adding index creation/update when field isUnique is set to true and
index deletion when isUnique is false (for now)
close https://github.com/twentyhq/core-team-issues/issues/1346
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.
This PR makes some hooks/functions/components more generic. I untied
them from dashboards as they will be needed for any page layout type.
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
### Summary
Split BAR into VERTICAL_BAR and HORIZONTAL_BAR as separate chart types.
### The Problem
Initially wanted a simple vertical/horizontal toggle for bar charts, but
ran into a GraphQL union type
constraint: union types can't have the same field name with different
nullability.
- Vertical bars need groupByFieldMetadataIdX as required (categories on
X)
- Horizontal bars need groupByFieldMetadataIdY as required (categories
on Y)
- GraphQL schema generation fails with this setup
### The Solution
Use semantic primaryAxis and secondaryAxis naming that's
orientation-agnostic:
- primaryAxisGroupByFieldMetadataId = main grouping field (e.g.,
"Company Name")
- secondaryAxisGroupByFieldMetadataId = optional secondary grouping
(e.g., "Stage")
These fields have consistent meaning regardless of orientation. The
visual mapping happens at the UI layer:
- Vertical bars: primary data renders on X-axis, secondary on Y-axis
- Horizontal bars: primary data renders on Y-axis, secondary on X-axis
Both chart types share the same DTO structure with consistent
nullability.
### What Changed
- Split GraphType.BAR → VERTICAL_BAR | HORIZONTAL_BAR
- Renamed fields: primaryAxisGroupByFieldMetadataId,
secondaryAxisGroupByFieldMetadataId (+ subfield
variants)
- useChartSettingsValues(): Maps semantic fields to setting values (no
swapping)
- getBarChartSettings(): Dynamically arranges settings panel based on
orientation
- transformGroupByDataToBarChartData(): Maps semantic fields to Nivo's
layout prop
video QA
https://github.com/user-attachments/assets/479061b5-712e-4ca6-9858-95273d1f16c1
- Removed a few visuals that are not necessary in the end (and not used
yet)
- Replaced visuals that were added yesterday with a new version,
matching the expected format
Closes#14726
### Added
- `trashRetentionDays` field to workspace entity (default: 14 days)
- Automated trash cleanup using BullMQ jobs
- Daily cron (00:10 UTC) that enqueues cleanup jobs for all active
workspaces
- Per-workspace limit: 100k records deleted per day
- Calendar-based retention: records deleted on day X are cleaned up X+14
days later (at midnight UTC boundaries)
### Architecture
- **Cron (WorkspaceTrashCleanupCronJob):** Runs daily, enqueues jobs in
parallel for all workspaces
- **Job (WorkspaceTrashCleanupJob):** Processes individual workspace
cleanup
- **Service (WorkspaceTrashCleanupService):** Discovers tables with
`deletedAt`, deletes old records with quota enforcement
- **Command:** `npx nx run twenty-server:command
cron:workspace:cleanup-trash` to register the cron
### Testing
- Unit tests for service with 100% coverage of public API
- Tested quota enforcement, error handling, and edge cases
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
## Problem
When trying to signup on localhost:3001 with a new random email, users
were getting an error message saying 'User already exists', even though
they were new users.
## Root Cause
The `signUpWithoutWorkspace` method in `sign-in-up.service.ts` had
inverted logic when checking if a user exists. It was using
`findUserByEmailOrThrow` which:
- **Returns the user** if found
- **Throws the provided error** if NOT found
This caused the opposite behavior:
- ❌ **New user (doesn't exist)**: Threw 'User already exists' error
- ❌ **Existing user**: Continued to create duplicate user
## Solution
Changed to use `findUserByEmail` and explicitly check if the user exists
before throwing the appropriate error:
```typescript
const existingUser = await this.userService.findUserByEmail(newUserParams.email);
if (existingUser) {
throw new AuthException(
'User already exist',
AuthExceptionCode.USER_ALREADY_EXIST,
{ userFriendlyMessage: msg`User already exists` },
);
}
```
This matches the correct pattern already used in `signUpInWorkspace`
(line 431-441 in auth.resolver.ts).
## Changes
- Fixed inverted logic in `signUpWithoutWorkspace` method
- Now correctly validates that user does NOT exist before creating new
user
- Matches the pattern used in `signUpInWorkspace`
## Testing
The fix corrects the logic so that:
- ✅ New users can sign up successfully
- ✅ Existing users get the correct 'User already exists' error
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
This PR determines the minimal setup required to render dashboards
without errors while extracting them from the record show page. The
ultimate goal is to create a `PageLayoutRenderer` component that takes
any page layout configuration and correctly renders dashboards or record
pages.
Currently, the `DashboardCard` component renders itself the
`PageLayoutRenderer` component. The next step is to reverse the flow of
control and make `PageLayoutRenderer` take a configuration and decide
whether it should render a dashboard or something else.
This PR takes into consideration two comments left by Charles on [the
first PR scaffolding the
refactor](https://github.com/twentyhq/twenty/pull/15021): renaming
`targetRecord` to `targetRecordIdentifier` and adding tests to
`evaluateTabVisibility()`.
## Demo to assert this PR doesn't break everything
https://github.com/user-attachments/assets/b5b99cf3-08fa-43d3-8da1-79018bc63641
# Introduction
Adding view-group to core engine v2
Following https://github.com/twentyhq/twenty/pull/15010 ( same pattern )
## What's done
- Created flat-view-group
- flat view group runner
- flat view group builder
- create view group service v2 and input transpilers
- refactor the existing view group resolver to fix standard (
BREAKING_CHANGE on graphql api update especially ) REST stays the same
- refactored the front to consume the mutations autogenerated
close https://github.com/twentyhq/core-team-issues/issues/1665
To avoid huge workflows to block the worker, we will enqueue a new job
every 20 steps.
This could be more than 20 if there are branches but I think this is
fine, the goal is only to have a limit set.
Also cleaning a bit the code to mark running steps as failed when
workflow fails.
I tested it on a huge workflow:
https://github.com/user-attachments/assets/d7b8e345-d1a1-4467-96fd-92117b500120
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.
# Introduction
After updating a `viewField` in a view the frontend receives a missmatch
metadata version.
That's because the `flatFieldMetadata` needs to be invalidated on a
viewField addition as it contains its primary key in its cache.
Before we would be checking updated flat entity maps, meaning that on a
view field update the flat field metadata maps would also get updated,
but we also invalidate the old v1 cache at the same time. Resuling in a
metadata version missmatch that's not really relevant for the gql schema
integrity
Now we only check if a object or field actions has been processed, if
yes increment the metadata version.
We should deco-relate the v1 object and fields cache from the metadata
version that should only serve as a "Please refresh browser state
because gql schema has mutated"
- Added readableFields for visible record field selector to take
permission into account
- Force refresh of table virtualization when updating view fields
Closes https://github.com/twentyhq/core-team-issues/issues/1563.
We now have two inter-group orderBy criteria: one on the aggregate
values, the other on the dimension values.
Ex: I am grouping companies by city and querying the average number of
employees for each group. I can either order the groups by the city
name, or by the average number of employees, or by one then the other.
I could actually also order groups by an aggregated value that I did not
ask for (ex: average ARR), but I cannot order groups by a field value I
did not group records by (ex: country).
[See discord
discussion](https://discord.com/channels/1130383047699738754/1425438213555753050/1425438223097921659)
An example of query variables:
```
{
"groupBy": [
{
"createdAt": {
"granularity": "QUARTER_OF_THE_YEAR"
}
},
{"city": true}
],
"orderBy": [
{
"aggregate": {
"avgEmployees": "DescNullsLast"
}
},
{
"aggregate": {
"percentyEmptyEmployees": "DescNullsLast"
}
},
{
"city": "AscNullsLast"
},
{
"createdAt": {
"orderBy": "AscNullsLast",
"granularity": "QUARTER_OF_THE_YEAR",
}
}
]
}
```
The aggregate orderBy criteria had already been implemented, but I
updated the implementation to add an "aggregate" key to prefix them, in
order to avoid confusion between the two + for the schema generation not
to break (otherwise we would have an issue when generating
OrderByWithGroupByInput if a user has created a field that has the same
name as an aggregate field, such as avgEmployees).
# Introduction
This PR introduces a huge type refactor that will leverage dynamic intra
entity optimistic flat maps update in the future and also a more
granular cache invalidation enhancing performances
close https://github.com/twentyhq/core-team-issues/issues/1717
close https://github.com/twentyhq/core-team-issues/issues/1716
close https://github.com/twentyhq/core-team-issues/issues/1643
## What's done
### Comparators centralization
Comparator is now done through global configuration as const for each
metadata names
Thanks to
Note:
Definition of standard is evolving, standard is now scoped to an app.
Meaning that a manifest should be able to update its own standards
objects but on other app standards ones ? Each synchronizable entities
will have a standardOverrides ?
## Typing refactor
### `AllFlatEntityTypesByMetadataName`
**Single source of truth for the complete type ecosystem**, mapping each
metadata name to its entity types, flat entities, and migration actions:
```typescript
export type AllFlatEntityTypesByMetadataName = {
fieldMetadata: {
actions: { created: CreateFieldAction; updated: UpdateFieldAction; deleted: DeleteFieldAction; };
flatEntity: FlatFieldMetadata;
entity: FieldMetadataEntity;
};
objectMetadata: { /* ... */ };
// ... all 10 metadata types
};
```
### `ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS`
**Explicitly declares database relationships** between entities with
compile-time validation:
```typescript
export const ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS = {
viewField: {
view: 'viewId',
fieldMetadata: 'fieldMetadataId',
},
cronTrigger: {
serverlessFunction: 'serverlessFunctionId',
},
// ... all relations
} as const satisfies MetadataNameAndRelations;
```
### `ALL_FLAT_ENTITY_CONFIGURATION`
**Centralizes comparison and serialization logic** for each metadata
type:
```typescript
export const ALL_FLAT_ENTITY_CONFIGURATION = {
fieldMetadata: {
propertiesToCompare: ['name', 'type', 'label', 'defaultValue', /* ... */],
propertiesToStringify: ['options', 'settings', 'defaultValue'],
},
objectMetadata: {
propertiesToCompare: ['nameSingular', 'namePlural', 'isActive', /* ... */],
propertiesToStringify: [],
},
// ... all metadata types
} as const satisfies AllFlatEntityConfiguration;
```
## Combined Impact
These three configurations work together to create a **strongly-typed,
centrally-managed metadata system**:
1. **`AllFlatEntityTypesByMetadataName`** defines *what exists*
2. **`ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS`** defines *how they
relate*
3. **`ALL_FLAT_ENTITY_CONFIGURATION`** defines *how to compare and
serialize them*
**Result:** Builders and validators become thin wrappers around
type-safe, configuration-driven logic instead of containing scattered,
error-prone manual implementations.
## What's next
### StandardOverrides standardization
Every metadata entity can be a standard one for a workspace if it's an
installed app, which means it might not expose the whole entity api to
be editable through an import dynamically
The standard overrides logic should not be applied to Fields and Objects
but to every entities
At the moment we have a logic of `EDITABLE_PROPERTIES` through the api,
and also `STANDARD_OVERRIDEDABLE_PROPERTIES`
This should be configuration centered like `propertiesToCompare` and
`propertiesToStringify`.
Scoping this PR to two last for the moment. As update dispatch to
standardOverrides could be considered as a side effect prefer waiting to
start the side effect refactor
### Granular Optimistic deprecation
With this new grain at runtime we will be able to add a flat entity and
dispatch its addition to related flat maps, so we don't have to describe
an optimistic method for each flat entity operations
See `addFlatEntityToFlatEntityAndRelatedEntityMapsOrThrow`
Note: Still in wip and included in this PR but about to create a new one
to integrate these utils and remove existing methods
### ValidateBuildAndRun dynamic args typed defintion
We should restrain the devxp to send expected flat maps entity as at
least from to or dependency as we now have the grain both a type lvl and
runtime to do so
It should not be possible in the devxp to forgot adding the views to the
v2 builder when passing the view field anymore ( that would lead to
permanent validation error in view field integrity checks )
## Conclusion
Thanks for reading and reviewing !
Any suggestions are more than welcomed ! ( same as for questions too ! )
Made the command more explicit for idempotency.
- Drop index explicitly (even though PostgreSQL does it automatically
when a column is dropped, but it might be good practice to account for
any unforeseen failures).
- Drop column.
- Create column.
- Create index (if we reach this point, column has already been created
and no error was thrown).
Figured we do not need extra queries to check ObjectMetadata for the
existence of person table on a workspace - we can use the IF EXISTS
syntax instead.
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
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>
**Fixes #15036**
Changes:
Centered Webhook URL: The webhook URL is now properly centered within
its table cell.
Conditional Scroll Arrows: The small arrows for horizontal scrolling now
only appear when the webhook URL is longer than the width of the cell.
This prevents the arrows from being displayed for shorter URLs.
Files changed:
1.
`packages/twenty-front/src/modules/settings/developers/components/SettingsWebhooksTable.tsx`
The grid-template-columns of the StyledTableRow component was changed
from a fixed-width layout (444px 68px) to a flexible layout (1fr auto).
This allows the first column, which contains the webhook URL, to grow
and shrink dynamically based on the available space, which is crucial
for proper centering and handling of different URL lengths.
2.
`packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookTableRow.tsx`
The overflow property of the StyledUrlTableCell component was changed
from hidden to auto. This ensures that the horizontal scrollbar (the
"small arrows") is only displayed when the content of the cell (the
webhook URL) overflows the cell's width.The redundant
StyledApisFieldTableRow component was removed to simplify the code, as
its styles were being overridden by the parent component.
How to Test
1. Log in to the application.
2. Go to Settings > API & Webhooks.
4. Add a new webhook with a short URL (e.g., http://localhost:3000).
5. Expected Behavior: The URL should be centered within the table cell,
and no scroll arrows should be visible.
6. Add another webhook with a long URL.
7. Expected Behavior: The URL should be centered, and horizontal scroll
arrows should be visible, allowing you
to scroll to see the entire URL.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>