This big PR implements table virtualization with an offset paging,
allowing a way more fluid UX.
It is a v1 that should be improved in the future with partial data
loading and optimization of the browser display performance of a row.
But with this PR we have the solid enough technical foundation, both
frontend and backend, to get to a smooth table UX.
Fixes and improvements after first successful round of development
(needed to have main clean) :
- [x] Delete should refresh virtualized portion only and reset all table
- [x] Fix add new : top and bottom
- [x] Table empty shouldn’t show when first loading
- [x] Fix d&d
- [x] Fix sorts
- [x] Fix drag when scrolling after a full virtual page (it throws an
error)
- [x] Si update mais qu’on a un sort ou filter, alors il faut trigger le
refresh
- [x] Reset scroll position between tables
- [x] Reset scroll shadows between tables
- [x] Setup d&n for virtual list :
https://github.com/hello-pangea/dnd/blob/main/docs/patterns/virtual-lists.md
- [x] Full table re-render when entering edit mode
- [x] Clean code and prepare for merge
Fixes https://github.com/twentyhq/core-team-issues/issues/1613 that
contains other bugs to be fixed before merge
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
# Introduction
Preparing view-filter and view-group introduction in v2 core engine
Moving view from `core-modules` to `metadata-modules`
## What happened
### Created dedicated modules for each view entity:
- ViewFieldModule
- ViewFilterModule
- ViewFilterGroupModule
- ViewGroupModule
- ViewSortModule
### Each module is now completely independent with its own:
- Controller
- Resolver
- Service
- Entity
### Created dedicated abstraction metadata module folder for:
- flat-view-field
- flat-view
### Dependencies
- Eleminated circular dep on ViewModule to all others ones
- Granular import not importing the whole viewModule anymore everywhere
close https://github.com/twentyhq/core-team-issues/issues/1703
closes https://github.com/twentyhq/core-team-issues/issues/1418
Tested :
- findOne on Rest and Gql
### Vision
#### Common
- Common is kind of renamed Gql base resolver
- Common handles args (filter, values, ...) validation
- Common accepts depth or raw gql selected fields to compute
selectedFields
- Common is directly called by each CommonQueries (findOne, ...)
service, which extend CommonBaseQuery service
- Common sequence :
| - Parse & Validate args (args-handlers, to create)
| - Build query (query-parsers : currently in gql-query-parsers, to
move)
| - Execute query
| - Fetch relation + format
#### Rest
- Simple parsing (without metadata validation)
- Calling Common API
- Simple rest response formatting
#### Gql
- Calling Common API
Following https://github.com/twentyhq/twenty/pull/14869
Dynamically clearing the cache, as one entry was forgotten
It seems like a permission test deletes a role that's expected to be
existing in following tests, as now cache is getting invalidated tests
are failing
Seems to be related to
https://discord.com/channels/1130383047699738754/1423768505911869460
## Singleton local cache key collision
When set for the first time caches local keys looks like
`workspaceId:undefined", singleton is shared between several cache
instances
If the given key is undefined it will read on other entity cache entry.
Updates of the hello-world application
- remove the old hello-world application
- adds an object "postCard"
- adds a serverlessFunction `create-new-post-card` that calls the twenty
api to create a new postCard record
- add a route trigger /post-card/create?recipient=John
## 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
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
## Context
We are experiencing bad performance on Twenty. One of the root cause
hypothesis is that computing `currentUser.currentWorkspace.views` is CPU
consuming. Without views, the GetCurrentUser response is ~1000 lines.
With its ~10000 lines.
As graphql is going through all fields recursively this can be quite
heavy on CPU. We had a similar issues on ObjectMetadataItems 2 years ago
and came with storing the response in redis.
Note: I thought there was also a cache in RAM but this is not the case,
so to invalidate the cache we can just empty redis.
## How
- Extract getting all views from GetCurrentUser and update frontend to
perform both queries
- Add views to cached graphql operations
- invalidate the cache manually on view or related core entities update
/ create / delete / destroy
## Tests
I have tested a lot on v1
Following https://github.com/twentyhq/twenty/pull/14762
In this PR
- moved logic around any field filter formatting to twenty-shared
- added any field filter to the filters applied to groupBy when viewId
is defined
- added integration test
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.)
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
Closes
https://github.com/twentyhq/core-team-issues/issues/248?issue=twentyhq%7Ccore-team-issues%7C1543
In this PR
- Implementation of groupBy resolver, without pagination, without viewId
parameter
- introduction of new GqlInputTypeDefinitionKind `OrderByWithGroupBy`
which differs from `OrderBy` as it should use aggregations as order by
conditions (e.g.: OrderByWithGroupBy should use avgEmployees and not
employees which does not make sense)
- Minor refacto of all orderBy expressions to use orderBy signature with
`OrderByCondition` which is usable for both groupBy queries and
non-groupBy queries. groupBy queries differ because their orderBy
conditions are based on aggregatedFields (such as avgEmployees) for
which we can't use the same orderBy signature
<img width="728" height="216" alt="Capture d’écran 2025-09-24 à 15 07
14"
src="https://github.com/user-attachments/assets/aa5c0875-cf38-4d4b-be70-f4d64da77a41"
/>
- Minor refacto to add table name in aggregates expressions
`(AVG("employees") -> AVG("company"."employees")` to keep having the
table names in the orderBy expression as we already had. Since we use
common utils for orderBy and aggregates on findMany this change also
impacts findMany queries with aggregates. It also impacts field
permission check where we extract the name of the selected fields to
check permission on, so I also updated that (see
`extractColumnNamesFromAggregateExpression`)
Fixes:
- relation one to many cannot be set (we have deprecated limit: keyword
in graphql at it was not use, we should only use first or last)
- https://github.com/twentyhq/twenty/issues/14556
Tested :
- workspace needs to flush cache after activating feature flag
- workspace does not need to flush cache if ff false and coming from
previous version (before gql schema gen updates)
## ✨ Add accent-insensitive search functionality
### 🎯 Overview
Implements accent-insensitive search across all searchable fields in
Twenty CRM.
Users can now search for "jose" to find "José", "muller" to find
"Müller", "cafe" to find "café", etc.
### 🔍 Problem
Twenty's search functionality was accent-sensitive, requiring users to
type exact accented characters to find records.
This created a poor user experience, especially for international names
and content.
### 💡 Solution
Added PostgreSQL `unaccent` extension with a custom immutable wrapper
function to enable accent-insensitive full-text search across all
searchable field types.
### 📋 Changes Made
**Modified Files:**
- `packages/twenty-server/scripts/setup-db.ts`
-
`packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts`
-
`packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts`
### 🗄️ Database Setup (`setup-db.ts`)
```sql
-- Added unaccent extension
CREATE EXTENSION IF NOT EXISTS "unaccent";
-- Created immutable wrapper function
CREATE OR REPLACE FUNCTION unaccent_immutable(text) RETURNS text AS $$
SELECT public.unaccent($1)
$$ LANGUAGE sql IMMUTABLE;
```
### 🔍 Search Vector Generation
(`get-ts-vector-column-expression.util.ts`)
Applied `public.unaccent_immutable()` to all searchable field types:
- TEXT fields (job titles, names, etc.)
- FULL_NAME fields (first/last names)
- EMAILS fields (both email address and domain)
- ADDRESS fields
- LINKS fields
- RICH_TEXT and RICH_TEXT_V2 fields
### 🔎 Query Processing (`compute-where-condition-parts.ts`)
Enhanced search queries to use `public.unaccent_immutable()` for both:
- Full-text search (`@@` operator with `to_tsquery`)
- Pattern matching (`ILIKE` operator)
### 🧠 Technical Rationale: Why the Wrapper Function?
**The Challenge:**
PostgreSQL's built-in `unaccent()` is marked as **STABLE**, but
`GENERATED ALWAYS AS` expressions (used for search vector columns)
require **IMMUTABLE** functions.
**The Solution:**
Created an IMMUTABLE wrapper function that calls the underlying
`unaccent()` function:
- ✅ Satisfies PostgreSQL's immutability requirements for generated
columns
- ✅ Maintains the exact same functionality as the original `unaccent()`
- ✅ Uses fully qualified `public.unaccent_immutable()` to ensure
function resolution from workspace schemas
**Alternative Approaches Considered:**
- ❌ Modifying `search_path`: would affect workspace isolation
- ❌ Computing unaccent at query time: would hurt performance
- ❌ Using triggers: would complicate data consistency
### 🎯 Impact
For **Person** records, accent-insensitive search now works on:
- Name (first/last name): `"jose garcia"` finds `"José García"`
- Email: `"jose@cafe.com"` finds `"josé@café.com"`
- Job Title: `"manager"` finds `"Managér"` or `"Gerente de Café"`
Applies to all searchable standard objects:
- Companies, People, Opportunities, Notes, Tasks, etc.
- Any custom fields of searchable types (TEXT, EMAILS, etc.)
### ✅ Testing
- Database reset completes successfully
- Workspace seeding works without errors
- Search vectors generate with unaccent functionality
- All searchable field types properly handle accented characters
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
We decided to send 4xx errors to sentry from the FE, except for
ForbiddenErrors.
In some cases, we also need not to send to sentry some 4xx errors, like
user input error, because the error stemming from the user input is
unpredictable (ex: duplicate entry for a record with unicity
constraints) and thus acceptable.
Let's add a isExpected extension on UserInputErrors to introduce a
specific behaviour for them (do not send to sentry).
Closes https://github.com/twentyhq/core-team-issues/issues/1417
- The pre-hook creates a page-layout and links it to the dashboard
- Added `position`, `createdBy`, `attachments`, `searchVector`,
`favorites` to the dashboard entity
- Updated the view seed
- Updated the page layout services to work within a transaction
## Context
Views and some other objects "leak" in the core API, this should be
fixed but in the meantime they fail if they use dataloaders because it's
not provided in the core config
```
[
TypeError: Cannot read properties of undefined (reading 'objectMetadataLoader')
]
```
in view.resolver using dataloaders to compute the name field
How to test: query views from /graphql instead of /metadata
Note: we could have a dedicated dataloader service for each gql server in the future
This Pr continues the extensibility journey
- adds a `core.databaseEventTrigger` table
- add a oneToMany relation between `core.serverlessFunction` and
`core.databaseEventTrigger`
- add a job `CallDatabaseEventTriggerJobsJob` triggered by
`EntityEventsToDbListener` triggering `serverlessFunction` based on the
`core.databaseEventTrigger.settings.eventName`
- add a new `trigger-queue` to carry this job
- renamed `DatabaseEventTriggerListener` into
`WorkflowDatabaseEventTriggerListener`
Parallel code path to read and write core views when
IS_CORE_VIEW_ENABLED.
Migrated view key to an enum.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
# Morph Relation needs some work on the core API
Especially on the Morph types
<img width="682" height="394" alt="Screenshot 2025-07-31 at 12 19 40"
src="https://github.com/user-attachments/assets/8b185ae3-3fe7-4886-97b1-ec143bb1b0f4"
/>
## Focus Areas:
- GraphQL schema generation
- Query parsing and field selection
- Morph relation field support
- Input type definition preparation