ebfaca5b3d8e2178ea9ab32e82751184a160c032
30 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c97d872b9f |
[BREAKING_CHANGE_VIEW_SORT] Refactor view sort to v2 (#17609)
Fixes https://github.com/twentyhq/core-team-issues/issues/2187 --------- Co-authored-by: prastoin <paul@twenty.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> |
||
|
|
7512b9f9bb | Load view field groups (#18010) | ||
|
|
b4e924b671 |
Sync views and navigation items (#18003)
Both objects are necessary to fully enjoy objects within applications <img width="770" height="311" alt="Capture d’écran 2026-02-17 à 15 19 43" src="https://github.com/user-attachments/assets/48c51fa4-63f4-45b2-a40a-df73f3aa79be" /> |
||
|
|
6aca1dd013 |
Introducing view field group syncable entity (#17867)
## Context Introduces a new viewFieldGroup entity that allows grouping view fields into sections (e.g. "General", "Additional", "Other") within a view. The page layout fields widget needs a way to organize fields into sections. Today, views have no concept of field grouping. This PR introduces the viewFieldGroup entity which sits between a view and its viewFields, enabling section-based organization. <img width="401" height="724" alt="Layout - V2 (customize visibility)" src="https://github.com/user-attachments/assets/6376e2ab-44db-42bf-9d2c-758f56f6b548" /> --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
8c951d3623 |
Migrate Views-xxx Index Field Object Skill to be fully universal ( all actions and metadata runner and builder ) + all metadata update actions runner (#17687)
# What this PR does Overall naming `universal` versus `flat` is not always the most updated and so on Will make a big cleaning tour after I've finished the whole migration Migrating all `view` and ( filter fields etc ) `field` `object` `index` to the universal pattern on all `services`, `builder` and `runner` levels ## Universal and flat optimistic tooling `addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow` and its delete counterpart maintain the consistency of `UniversalFlatEntityMaps` when an entity is created or removed. Beyond inserting/removing the entity from its own maps, they walk through `ALL_UNIVERSAL_METADATA_RELATIONS` to update the **aggregator arrays** on related parent entities — the add appends the new entity's `universalIdentifier` to the parent's aggregator (e.g. a new viewField's identifier gets appended to its parent view's `viewFieldUniversalIdentifiers`), and the delete filters it out. This keeps the maps in sync so that diff computations and relation lookups remain accurate throughout the migration building process. ## ALL_UNIVERSAL_METADATA_RELATIONS `ALL_UNIVERSAL_METADATA_RELATIONS` is the universal counterpart of `ALL_METADATA_RELATIONS`. It maps each metadata entity to its many-to-one and one-to-many relations using universal foreign keys (`*UniversalIdentifier`) instead of database IDs (`*Id`). This allows migration actions to reference related entities in a workspace-agnostic way. Relations that are workspace-specific (e.g. `workspace`, `dataSource`, `userWorkspace`) are set to `null` and skipped during resolution. ## `workspaceMigrationCreateIdEnrichment` Reserved to API metadata ( will be able to validate at app installation lvl ) - Workspace migration `create` actions now carry an optional `id` (and `fieldIdByUniversalIdentifier` for object/field actions) so that caller-provided IDs flow through the entire build-validate-run pipeline. - New `enrichCreateWorkspaceMigrationActionsWithIds` utility resolves `universalIdentifier → id` mappings after the builder runs and injects them into the migration actions before the runner persists entities. - Runner action handlers use the provided IDs instead of generating new UUIDs, enabling deterministic entity creation for synchronization workflows. ## `resolveUniversalUpdateRelationIdentifiersToIds` `resolveUniversalUpdateRelationIdentifiersToIds` converts universal identifiers (workspace-agnostic, stable keys) in a migration update payload into concrete database UUIDs, so the update can be applied to a specific workspace. It iterates over the many-to-one relations defined in `ALL_UNIVERSAL_METADATA_RELATIONS` for the given entity type, replaces each `*UniversalIdentifier` property with its corresponding `*Id` by looking up the target entity in `allFlatEntityMaps`, and throws if a non-null identifier can't be resolved. Used by all `update` action handlers in `transpileUniversalActionToFlatAction`, avoiding duplicated resolution logic across handlers. ## What this PR does not - Migrating twenty-standard declaration to universal - Migrating all the inputs transpilers to universal - Migrating all metadata to be fully universal ( we still need to de-scope the type of all of them and refactor their validator very close ) --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
3dc5b162c7 |
Spread in parent and requires FlatEntity.__universal (#17753)
# Introduction Requiring the spreaded `__universal` record that aggregates all the universal identifier ( relations fk and aggregators ) of an entity to its root It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be finalized because if we don't we would have to migrated all related entities at once in order for them to always have the universal properties ## `resolveEntityRelationUniversalIdentifiers` Introduced `resolveEntityRelationUniversalIdentifiers` a centralized utility that resolves foreign key IDs to universal identifiers using ALL_METADATA_RELATIONS metadata. It provides strict typing for both input (foreign keys) and output (universal identifiers), with nullability dynamically inferred from entity relation types. Strictly and dynamically typed for both output and input To do so added a new type and const/runtime grain to ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from the entity relation property types. And fixed incorrectly typed typeorm entities ### Usage ```ts const { availabilityObjectMetadataUniversalIdentifier, frontComponentUniversalIdentifier, } = resolveEntityRelationUniversalIdentifiers({ metadataName: 'commandMenuItem', foreignKeyValues: { availabilityObjectMetadataId: createCommandMenuItemInput.availabilityObjectMetadataId, frontComponentId: createCommandMenuItemInput.frontComponentId, }, flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps }, }); ``` |
||
|
|
476bdf764c |
Refactor flat entity maps to be universal oriented (#17665)
# Introduction
In preparation of the workspace agnostic builder, we're migrating
`FlatEntityMaps` to be universal identifier oriented and based
As in the builder context there're won't be any ids at all
Please also note that the FlatEntity is a UniversalFlatEntity superset
From
```ts
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
export type FlatEntityMaps<T extends SyncableFlatEntity> = {
byId: Partial<Record<string, T>>;
idByUniversalIdentifier: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>;
};
```
To
```ts
export type FlatEntityMaps<
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
> = {
byUniversalIdentifier: Partial<Record<string, T>>;
universalIdentifierById: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>; // this might make more sense to be migrated to universalIdentifiersByApplicationUniversalIdentifier but it's the main topic of this PR
};
```
## Low level maps tools
Had to refactor find | create | delete | replace | find-many | get-sub
tools ( through mutations and or throw equivalent )
|
||
|
|
fe9d6f34ff |
[REQUIRES_FULL_CACHE_FLUSH_WHEN_RELEASED] Refactor FlatEntity to be UniversalFlatEntity superset (#17452)
# Introduction
In this PR we're refactoring the `FlatEntity` type to become a superset
of the `UniversalFlatEntity`.
Right now we're storing all the extra properties in `__universal`
property, at some point it might just be sibling to other entity and we
might rely on the `propertiesToCompare` constants and TypeScript
allowing passing a superset type into a smaller subset type
## FromTo utils
The entity to flat entity method now computes the universal information,
standardized a typing and pattern to do
## Example
Also strictly type
```ts
"bbb019ea-6205-498c-aea5-67bc53bce8a9": {
"workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationId": "d01b010d-b984-465b-b40b-370e954e5188",
"id": "bbb019ea-6205-498c-aea5-67bc53bce8a9",
"pageLayoutTabId": "791a512f-169f-4209-b731-aa86716668c6",
"title": "Deals by Company",
"type": "GRAPH",
"objectMetadataId": "9e14efea-df5b-4c0e-aba9-cfe455f32397",
"gridPosition": { "row": 0, "column": 6, "rowSpan": 6, "columnSpan": 6 },
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"groupByFieldMetadataId": "6673ff18-63d2-47a1-8f85-2b9b09ca27a5",
"aggregateFieldMetadataId": "8d64ee41-5dd4-4de6-945a-7c0c18399715"
},
"createdAt": "2026-01-28T14:08:52.140Z",
"updatedAt": "2026-01-28T14:08:52.140Z",
"deletedAt": null,
"__universal": {
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationUniversalIdentifier": "20202020-64aa-4b6f-b003-9c74b97cee20",
"pageLayoutTabUniversalIdentifier": "20202020-d011-4d11-8d11-da5ab0a01001",
"objectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
"gridPosition": {
"row": 0,
"column": 6,
"rowSpan": 6,
"columnSpan": 6
},
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"aggregateFieldMetadataUniversalIdentifier": "20202020-d01a-4131-8a31-f123456789ab",
"groupByFieldMetadataUniversalIdentifier": "20202020-cbac-457e-b565-adece5fc815f"
}
}
},
```
|
||
|
|
4c93ab5259 |
Introduce UniversalFlatEntityFrom (#17367)
# Introduction
Creating a `UniversalFlatEntityFrom` that strips out all the relation
and foreignKey properties in order to replace them with
`UniversalIdentifier` suffix
This data type will be major for the workspace migration workspace
agnostic refactor
## Chore
- renamed `flat-entity.type` to `flat-entity-from.type.ts` ( more
accurate to exported module )
- create static test type over the field metadata entity on quite
complex utils as both coverage and documentation
## Example
Here's an example of a `UniversalFlatEntityFrom<FieldMetadataEntity>`
```ts
const universalFlatFieldMetadata: UniversalFlatFieldMetadata<FieldMetadataType.RELATION> = {
// Base properties (from FieldMetadataEntity, excluding relations and applicationId)
universalIdentifier: '550e8400-e29b-41d4-a716-446655440001',
applicationUniversalIdentifier: '5800681c-088e-4e2b-9fc3-bcf6e8ec2051',
type: FieldMetadataType.RELATION,
name: 'firstName',
label: 'First Name',
defaultValue: null,
description: 'The first name of the person',
icon: 'IconUser',
standardOverrides: null,
options: null,
settings: {
relationType: RelationType.ONE_TO_MANY,
},
isCustom: false,
isActive: true,
isSystem: false,
isUIReadOnly: false,
isNullable: true,
isUnique: false,
isLabelSyncedWithName: true,
morphId: null,
// Date properties cast to string
createdAt: '2024-01-15T10:30:00.000Z',
updatedAt: '2024-01-15T10:30:00.000Z',
// ManyToOne relation universal identifiers (from FieldMetadataEntity relations)
relationTargetFieldMetadataUniversalIdentifier:
'550e8400-e29b-41d4-a716-446655440012',
relationTargetObjectMetadataUniversalIdentifier:
'550e8400-e29b-41d4-a716-446655440013',
// Join column universal identifiers (foreignKey -> universalIdentifier)
objectMetadataUniversalIdentifier: '550e8400-e29b-41d4-a716-446655440010',
// OneToMany relation universal identifiers (array of related entity identifiers)
viewFieldUniversalIdentifiers: [
'550e8400-e29b-41d4-a716-446655440020',
'550e8400-e29b-41d4-a716-446655440021',
],
viewFilterUniversalIdentifiers: ['550e8400-e29b-41d4-a716-446655440030'],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
};
```
## Settings
Will hop on the settings typing next. Might not be dynamic but
declarative though
|
||
|
|
6aa43b68f7 |
Identification cleanup (#17301)
# Introduction following https://github.com/twentyhq/twenty/pull/17279 As we've finally identified all the syncable metadata entities, which means they're expected to have non nullable applicationId and universalIdentifier at pg_level we can remove previous retro comp universalIdentifier fallbacking and update the dto too ~~This needs IdentifyRemainingEntitiesMetadataCommand and MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand to be run~~ ```ts [Nest] 197 - 01/21/2026, 3:08:35 PM LOG [MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand] Successfully run MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand ``` |
||
|
|
942d2fef83 |
Remove sync-metadata and IS_WORKSPACE_CREATION_V2_ENABLED feature flag (#16997)
# Introduction Followup of https://github.com/twentyhq/twenty/pull/17001#pullrequestreview-3638508738 close https://github.com/twentyhq/core-team-issues/issues/1910 We've completely decom the `sync-metadata` in production. We're now then removing its implementation in favor of the v2. ## TODO: - [x] Remove sync-metadata implem and commands - [x] Remove workspace decorators - [x] Type each deprecated field to deprecated on their workspaceEntity - [x] Remove the `workspace-sync-metadata` folder entirely - [x] remove workspace migration - [x] workspace migration removal migration - [x] remove the `v2` references from workspace manager file names - [x] remove the `v2` references from workspace manager modules - [ ] Double check impact on translation file path updates ## Note - Removed the gate logic - Remains some service v2 naming, serverless needs to be migrated on v2 fully - Removed workspaceMigration service app health consumption, making it always returning up ( no more down ) cc @FelixMalfait ( quite obsolete health check now, will require complete refactor once we introduce inter app dependency etc ) |
||
|
|
42c9ae1ebc |
Centralize metadata relations constant + simplification (#16901)
# Introduction As we introduced a new grain on relation extraction thanks to low level `SyncableEntity` and `WorkspaceRelatedEntity` we're able to strictly typesafe extract metadata entity The new constant centralizes both many to one and one to many constants metadata entity constants in a more strictly typesafe way. Remains only the flatEntityForeignKey aggregator which has to be chosen manually across all available targeted flat entity ids properties |
||
|
|
98a9ae2a0e |
Migrate view filter group to v2 (#16876)
## Introduction On the side hanlded PR with a // agents on another repo Made several iterations to fix behavior and direction Find below auto-generated PR description closes https://github.com/twentyhq/core-team-issues/issues/2037 Created generic tooling for entity circular dep checking, will be useful for permissions validation too @Weiko ## Migrate `viewFilterGroup` entity to v2 flat architecture ### Summary Migrates the `viewFilterGroup` entity from v1 to the v2 flat entity architecture, following the established patterns for other v2 entities like `viewFilter`, `view`, and `viewField`. ### Changes **Types & Constants** - Added `FlatViewFilterGroup` and `FlatViewFilterGroupMaps` types - Added editable properties constant for `viewFilterGroup` - Registered `viewFilterGroup` in `ALL_METADATA_NAME`, `ALL_METADATA_RELATION_PROPERTIES`, `ALL_METADATA_MANY_TO_ONE_RELATIONS`, and related constants **Cache Service** - Created `WorkspaceFlatViewFilterGroupMapCacheService` with proper relation loading for `viewFilters` and `childViewFilterGroups` - Updated `WorkspaceFlatViewMapCacheService` to load `viewFilterGroups` relation **Builder & Validator** - Created `WorkspaceMigrationV2ViewFilterGroupActionsBuilderService` - Created `FlatViewFilterGroupValidatorService` with creation, update, and deletion validation - Integrated validation into the orchestrator service (runs before `viewFilter` validation) **Action Handlers** - Created create, update, and delete action handlers for `viewFilterGroup` **Service Migration** - Rewrote `ViewFilterGroupService` to use v2 migration pattern with `WorkspaceMigrationValidateBuildAndRunService` - Created utility functions for transforming DTOs to flat entities **Database Migration** - Added migration to make `parentViewFilterGroupId` foreign key deferrable (handles self-referential parent/child insertions) **ViewFilter Integration** - Added `viewFilterGroupId` validation in `FlatViewFilterValidatorService` - Updated `viewFilter` many-to-one relations to include `viewFilterGroup` **Tests** - Added integration tests for successful creation, update, deletion, and destruction - Added failing test cases for non-existent entities and invalid references - Added failing test for `viewFilter` creation with non-existent `viewFilterGroupId` ### Breaking Changes None - existing API contracts are preserved. |
||
|
|
e3ffdb0c2b |
[BREAKING_CHANGE_NESTED_WORKSPACE]Refactor FlatEntity typing in aim of introducing UniversalFlatEntity (#16701)
# Introduction
Added a `WorkspaceRelated` and `AllNonWorkspaceRelatedEntity` to
simplify the `FlatEntityFrom` that now do not expect a string literal to
omit and itself builds the related many to one entities foreign key
aggregators
We now have the type grain over relation to syncable or just workspace
related entities
Added a migrations that sets the fk on missing entities
## Next
In upcoming PR we will be able to introduce such below type
```ts
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/remove-suffix.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';
export type UniversalFlatEntityFrom<TEntity extends SyncableEntity> = Omit<
TEntity,
| `${ExtractEntityManyToOneEntityRelationProperties<TEntity> & string}Id`
| ExtractEntityRelatedEntityProperties<TEntity>
| 'application'
| 'workspaceId'
| 'applicationId'
| keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
> &
CastRecordTypeOrmDatePropertiesToString<TEntity> & {
[P in ExtractEntityManyToOneEntityRelationProperties<TEntity> &
string as `${RemoveSuffix<P, 's'>}UniversalIdentifier`]: string;
} & {
[P in ExtractEntityOneToManyEntityRelationProperties<
TEntity,
SyncableEntity
> &
string as `${RemoveSuffix<P, 's'>}UniversalIdentifiers`]: string[];
};
```
|
||
|
|
1839f8e946 |
Fix: Persist hide empty groups setting on backend (BREAKING: deploy server first) (#16385)
Closes #13754 and [core team issue #414](https://github.com/twentyhq/core-team-issues/issues/414) |
||
|
|
a18203934c |
Fix flat entity maps date serialization (#16420)
Changes: - as we store date in redis as serialized, let's make all flatEntity dates as string. This requires changing FlatEntity types and making sure that entity are converted to flatEntity and flatEntity to dtos |
||
|
|
7ce22d5c7e |
breaking (soft) - Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (2/3) (#16277)
Should be merged once https://github.com/twentyhq/twenty/pull/16206 has been released + command run to prod In this PR - Remove usage of viewGroup.fieldMetadataId, both in BE and FE states. - But we still need to properly populate it until we fully remove viewGroup.fieldMetadataId from db and ORM entity (upcoming 3rd PR out of 3). fieldMetadataId was removed from CoreViewGroup type and CreateViewGroupInput and is determined BE-side based on the associated view's mainGroupByFieldMetadataId. **I expect this means a downtime on viewGroup creation, until both FE and BE are deployed and cache is flushed.** This seems acceptable to me as it only regards viewGroup creation. - this information is replaced by view.mainGroupByFieldMetadataID - Handle view group creation, update and deletion in the BE as a side-effect of a view creation, update or deletion. Optimistic effects are still used - Add validation at view creation or update regarding mainGroupByFieldMetadata Left to do in 3rd PR - Remove viewGroup.fieldMetadataId from db and ORM entity - Restore feature allowing to update an existing grouped view's group by field (already OK on BE side but need to rebuild FE optimistic) |
||
|
|
59672e3e34 |
Migrate agent v2 (#16214)
# Introduction Closes https://github.com/twentyhq/core-team-issues/issues/1980 In this PR we migrate the agent from v1 to v2. ## New FlatRoleTargetByAgentIdMaps Derivated the `flatRoleTargetMaps` to be building a `flatRoleTargetByAgentIdMaps` to ease retrieving a roleId to associate to an agent ## Coverage Added strong coverage on both failing and successful CRU agents operations --------- Co-authored-by: Weiko <corentin@twenty.com> |
||
|
|
77409b6eb2 |
[Requires "warm" cache flush (no immediate downtime before flush)] Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (1/3) (#16206)
In this PR (1/3) - introduce view.mainGroupByFieldMetadataId as the new reference determining which fieldMetadataId is used in a grouped view, in order to deprecate viewGroup.fieldMetadataId which creates inconsistencies. view.mainGroupByFieldMetadataId is now filled at every view creation, though not in use yet. - Introduce a command to backfill view.mainGroupByFieldMetadataId for existing views + delete all viewGroup.fieldMetadataId with a fieldMetadataId that is not view.mainGroupByFieldMetadataId. (It should concern 37 active workspaces) - Temporarily disable the option to change a grouped view's fieldMetadataId as for now it creates inconsistencies. This feature can be reintroduced when we have done the full migration. In a next PR - (2/3) use view.mainGroupByFieldMetadataId instead of viewGroup.fieldMetadataId. In FE we may keep viewGroup.fieldMetadataId as a state (TBD). View groups will now be created / deleted as a side effect of view's mainGroupByFieldMetadataId update. - (3/3) remove viewGroup.fieldMetadataId --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
1eb2e44058 |
Refactor workspace cache service (#16208)
## Context We've recently introduced a new workspace cache service which now acts as a cache access and local storage for all workspace related data, deprecating the individual specific services. - Better performance through multiple caching/fetching strategies - Consistent data access patterns across the codebase - Reduced redis queries through MGET/MSET/PIPELINE with multiple cache keys |
||
|
|
f9ab09c404 |
Metadata api create entity in workspace custom app (#15911)
# Introduction Cleaner and fewer scope version of https://github.com/twentyhq/twenty/pull/15745 ( removed sync-metadata hack through, too ambitious migration and upgrade ) Please note that this PR won't have any interaction with the existing sync-metadata Which mean that the sync metadata does not update the standard entities applicationId and universalIdentifier, and it won't we will deprecate it on favor of a workspace migration aka twenty-standard app installation ## API Metadata Any operation going through the api metadata nows automatically scope the related entity to the workspace custom application instance. ( optionally passing an applicationId to allow current hacky implem of app sync service ) We need to either ignore the tests or remove the cli status check from the blocking status badges for a PR to be merged ## New workspace Already handled in previous https://github.com/twentyhq/twenty/pull/15625, when a workspace is created it gets created a twenty standard and custom workspace instance All his views and permissions will be prefilled to the its twenty standard app instance with a specific universalIdentifier ## New universalIdentifier At the contrary as before with standardIds, universalIdentifier are unique for a given workspace This means that createdAt field of both object company and opportunity will have a unique universalIdentifier whereas they share the same standardId ## FlatApplication Introduced the flatApplication and cache. Will migrate existing `MetadataName` to be `SyncableMetadataName` in a following PR ## What's next Next we will describe a twenty standard app configuration as json that will be used to generate a workspace migration that will be run instead of the sync metadata, in a nutshell we aim to deprecated the sync metadata So we can standardize any entity to have a non nullable applicationId and universalIdentifier ## Upgrade command Introduced an upgrade command that will create a custom workspace instance for any workspace that do not have one in order to align with the new behavior when creating a new workspace |
||
|
|
cff17db6cb |
Enhance role-check system with stricter checks (#15392)
## Overview This PR strengthens our permission system by introducing more granular role-based access control across the platform. ## Changes ### New Permissions Added - **Applications** - Control who can install and manage applications - **Layouts** - Control who can customize page layouts and UI structure - **AI** - Control access to AI features and agents - **Upload File** - Separate permission for file uploads - **Download File** - Separate permission for file downloads (frontend visibility) ### Security Enhancements - Implemented whitelist-based validation for workspace field updates - Added explicit permission guards to core entity resolvers - Enhanced ESLint rule to enforce permission checks on all mutations - Created `CustomPermissionGuard` and `NoPermissionGuard` for better code documentation ### Affected Components - Core entity resolvers: webhooks, files, domains, applications, layouts, postgres credentials - Workspace update mutations now use whitelist validation - Settings UI updated with new permission controls ### Developer Experience - ESLint now catches missing permission guards during development - Explicit guard markers make permission requirements clear in code review - Comprehensive test coverage for new permission logic ## Testing - ✅ All TypeScript type checks pass - ✅ ESLint validation passes - ✅ New permission guards properly enforced - ✅ Frontend UI displays new permissions correctly ## Migration Notes Existing workspaces will need to assign the new permissions to roles as needed. By default, all new permissions are set to `false` for non-admin roles. |
||
|
|
d640b93096 |
Improve v2 and cache invalidation perfs (#15467)
# Introduction Log are debug logs of `packages/twenty-server/test/integration/metadata/suites/object-metadata/create-delete-and-create-object-metadata-v2.integration-spec.ts`run ten times in a row on clean db reset ## Next Will improve cache computation to lighter invalidation. RelationLoad `query` does not seem to work with typeorm so I'll continue the custom integration i've started in https://github.com/twentyhq/twenty/tree/optimize-cache-read-v2 ## Integration tests duration Significant test duration improvement too ### Before <img width="2632" height="1402" alt="image" src="https://github.com/user-attachments/assets/2f1f0ccf-44de-4856-bfe1-4f45a351763a" /> ### After <img width="2632" height="1402" alt="image" src="https://github.com/user-attachments/assets/4bc9e6db-3046-48b9-b903-1464053936c4" /> ## What's next - The legacy cache invalidation removal - Factorizing redis calls in only one operation ## Autogenerated performance comparison ( including mutation refactor too ) [Before](https://gist.github.com/prastoin/3c1e21fa9e3b3ce4b0716902ff4a2dd6) [After](https://gist.github.com/prastoin/7bfddd14bfded2e4991a9378970a026d) The optimized implementation shows **dramatic performance improvements** across all metrics: - 🚀 **Cache Invalidation**: 156.3ms → 76.8ms (**50.9% faster**) - 🚀 **Builder Operations**: 21.3ms → 14.2ms (**33.3% faster**) - ⚡ **Consistency**: 16.4% more predictable performance --- ## 1. Overall Performance Summary | Component | Before (avg) | After (avg) | Best (After) | Worst (After) | Improvement | |-----------|-------------|-------------|--------------|---------------|-------------| | **Total Execution Time** | 180.2ms | 110.5ms | 52.3ms | 585.9ms | **38.7% faster** ⚡⚡ | | **Cache Invalidation** | 156.3ms | 76.8ms | 47.8ms | 285.1ms | **50.9% faster** ⚡⚡⚡ | | **Transaction Execution** | 22.4ms | 22.1ms | 0.99ms | 314.2ms | Similar | | **Initial Cache Retrieval** | 0.81ms | 2.08ms | 0.21ms | 8.24ms | Similar | | **Entity Builder (total)** | 21.3ms | 14.2ms | 0.36ms | 42.5ms | **33.3% faster** ⚡ | ### Total Execution Time Distribution #### Before (Legacy Sequential) ``` Time (ms) Count Percentage Visualization < 150 18 16% ████ 150-180 32 29% ███████ 180-210 35 32% ████████ 210-250 17 15% ████ 250-350 6 5% █ > 350 2 2% ▌ ``` #### After (Optimized Parallel) ``` Time (ms) Count Percentage Visualization < 70 28 31% ████████ 70-100 31 34% █████████ 100-150 18 20% █████ 150-200 8 9% ██ 200-300 4 4% █ > 300 2 2% ▌ ``` --- ## 2. Builder Performance Breakdown ### Field Metadata Builder | Operation | Before (avg) | After (avg) | Improvement | |-----------|-------------|-------------|-------------| | Matrix computation | 3.5ms | 3.4ms | Similar | | Creation validation | 15.2ms | 2.1ms | **86% faster** ⚡⚡⚡ | | Deletion validation | 0.08ms | 0.06ms | Similar | | Update validation | 1.3ms | 0.09ms | **93% faster** ⚡⚡⚡ | | Entity processing | 18.6ms | 11.8ms | **37% faster** ⚡ | | **Total validateAndBuild** | **21.3ms** | **14.2ms** | **33% faster** ⚡ | #### Performance Distribution ``` Before: ▁▂▄█████▆▄▂▁ (wide spread, 15-28ms range) After: ▁▁▃█████▃▁▁ (tight clustering, 10-18ms range) ``` ## 4. Cache Invalidation Performance Breakdown ### Cache Invalidation Summary | Metric | Before (Legacy) | After (Optimized) | Improvement | |--------|-----------------|-------------------|-------------| | **Best Time** | 131.965ms | 47.833ms | **63.7% faster** ⚡⚡⚡ | | **10th Percentile** | 140.2ms | 51.7ms | **63.1% faster** ⚡⚡⚡ | | **25th Percentile** | 146.1ms | 54.4ms | **62.7% faster** ⚡⚡⚡ | | **Median (50th)** | 155.1ms | 63.4ms | **59.1% faster** ⚡⚡⚡ | | **Average** | 156.3ms | 76.8ms | **50.9% faster** ⚡⚡⚡ | | **75th Percentile** | 160.2ms | 90.2ms | **43.7% faster** ⚡⚡ | | **90th Percentile** | 191.7ms | 100.2ms | **47.7% faster** ⚡⚡ | | **95th Percentile** | 235.6ms | 110.3ms | **53.2% faster** ⚡⚡⚡ | | **99th Percentile** | 278.2ms | 224.9ms | **19.1% faster** ⚡ | | **Worst Time** | 383.914ms | 285.102ms | **25.7% faster** ⚡ | ### Cache Invalidation Time Distribution #### Before (Legacy Sequential) ``` Time (ms) Count Percentage Visualization 130-140 3 3% ▊ 140-150 15 14% ████ 150-160 48 44% ███████████ 160-180 31 28% ███████ 180-220 8 7% ██ 220-280 3 3% ▊ > 280 2 2% ▌ ``` #### After (Optimized Parallel + Intersection) ``` Time (ms) Count Percentage Visualization < 50 3 3% ▊ 50-60 27 25% ███████ 60-70 25 23% ██████ 70-90 25 23% ██████ 90-100 15 14% ████ 100-120 8 7% ██ 120-150 3 3% ▊ > 150 4 4% █ ``` ## 6. Performance Consistency Analysis ### Standard Deviation & Variance | Metric | Before | After | Improvement | |--------|--------|-------|-------------| | **Cache Invalidation Std Dev** | 42.1ms | 35.2ms | **16.4% more consistent** ⚡ | | **Total Execution Std Dev** | 68.3ms | 89.1ms | Slightly more variable | | **Coefficient of Variation (Cache)** | 26.9% | 45.8% | More variance | | **Outliers (> 2σ)** | 5 cases | 3 cases | **40% fewer outliers** ⚡ | --- |
||
|
|
c5564d9bd0 |
[BREAKING CHANGE] refactor: Add Entity suffix to TypeORM entity classes (#15239)
## Summary This PR refactors all TypeORM entity classes in the Twenty codebase to include an 'Entity' suffix (e.g., User → UserEntity, Workspace → WorkspaceEntity) to improve code clarity and follow TypeORM naming conventions. ## Changes ### Entity Renaming - ✅ Renamed **57 core TypeORM entities** with 'Entity' suffix - ✅ Updated all related imports, decorators, and type references - ✅ Fixed Repository<T>, @InjectRepository(), and TypeOrmModule.forFeature() patterns - ✅ Fixed @ManyToOne/@OneToMany/@OneToOne decorator references ### Backward Compatibility - ✅ Preserved GraphQL schema names using @ObjectType('OriginalName') decorators - ✅ **No breaking changes** to GraphQL API - ✅ **No database migrations** required - ✅ File names unchanged (user.entity.ts remains as-is) ### Code Quality - ✅ Fixed **497 TypeScript errors** (82% reduction from 606 to 109) - ✅ **All linter checks passing** - ✅ Improved type safety across the codebase ## Entities Renamed ``` User → UserEntity Workspace → WorkspaceEntity ApiKey → ApiKeyEntity AppToken → AppTokenEntity UserWorkspace → UserWorkspaceEntity Webhook → WebhookEntity FeatureFlag → FeatureFlagEntity ApprovedAccessDomain → ApprovedAccessDomainEntity TwoFactorAuthenticationMethod → TwoFactorAuthenticationMethodEntity WorkspaceSSOIdentityProvider → WorkspaceSSOIdentityProviderEntity EmailingDomain → EmailingDomainEntity KeyValuePair → KeyValuePairEntity PublicDomain → PublicDomainEntity PostgresCredentials → PostgresCredentialsEntity ...and 43 more entities ``` ## Impact ### Files Changed - **400 files** modified - **2,575 insertions**, **2,191 deletions** ### Progress - ✅ **82% complete** (497/606 errors fixed) - ⚠️ **109 TypeScript errors** remain (18% of original) ## Remaining Work The 109 remaining TypeScript errors are primarily: 1. **Function signature mismatches** (~15 errors) - Test mocks with incorrect parameter counts 2. **Entity type mismatches** (~25 errors) - UserEntity vs UserWorkspaceEntity confusion 3. **Pre-existing issues** (~50 errors) - Null safety and DTO compatibility (unrelated to refactoring) 4. **Import type issues** (~10 errors) - Entities imported with 'import type' but used as values 5. **Minor decorator issues** (~9 errors) - onDelete property configurations These can be addressed in follow-up PRs without blocking this refactoring. ## Testing Checklist - [x] Linter passing - [ ] Unit tests should be run (CI will verify) - [ ] Integration tests should be run (CI will verify) - [ ] Manual testing recommended for critical user flows ## Breaking Changes **None** - This is a pure refactoring with full backward compatibility: - GraphQL API unchanged (uses original entity names) - Database schema unchanged - External APIs unchanged ## Notes - Created comprehensive `REFACTORING_STATUS.md` documenting the entire process - All temporary scripts have been cleaned up - Branch: `refactor/add-entity-suffix-to-typeorm-entities` ## Reviewers Please review especially: - Entity renaming patterns - GraphQL backward compatibility - Any areas where entity types are confused (UserEntity vs UserWorkspaceEntity) --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
45473218d3 |
Field deactivation side effect views calendar kanban viewFields (#15180)
# Introduction
Handling both:
- field deactivation side effect on view fields, view filters and views
- field deactivation side effect on view that targets it as
`kanbanAggregateFieldMetadataId`
- field deactivation side effect on view that targets it as
`calendarFieldMetadataId`
## Coverage
added coverage
```ts
PASS test/integration/metadata/suites/field-metadata/kanban-aggregate-field-deactivation-deletes-views.integration-spec.ts (13.132 s)
kanban-aggregate-field-deactivation-nullifies-kanban-properties
✓ should nullify kanban properties when field used as kanbanAggregateOperationFieldMetadataId is deactivated (3923 ms)
✓ should not modify views when field not used as kanbanAggregateOperationFieldMetadataId is deactivated (2958 ms)
✓ should nullify kanban properties on multiple views when they all use the same field as kanbanAggregateOperationFieldMetadataId (2542 ms)
✓ should nullify kanban properties when views have different aggregate operations on same field (3380 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 13.154 s
```
```ts
PASS test/integration/metadata/suites/field-metadata/view-group-field-deactivation-deletes-views.integration-spec.ts (12.639 s)
view-group-field-deactivation-deletes-views
✓ should delete view when field used in view group is deactivated (3469 ms)
✓ should not delete view when field not used in view group is deactivated (3109 ms)
✓ should delete multiple views when they all use the same field in view groups (2741 ms)
✓ should handle deactivation when view has multiple view groups with different fields (3008 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 12.664 s
```
```ts
PASS test/integration/metadata/suites/field-metadata/calendar-field-deactivation-deletes-views.integration-spec.ts (14.579 s)
calendar-field-deactivation-deletes-views
✓ should delete view when field used as calendarFieldMetadataId is deactivated (3388 ms)
✓ should not delete view when field not used as calendarFieldMetadataId is deactivated (2438 ms)
✓ should delete multiple views when they all use the same field as calendarFieldMetadataId (2635 ms)
✓ should handle deactivation when views have different calendar layouts on same field (3195 ms)
✓ should delete calendar view but not other view types when calendar field is deactivated (2682 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 14.601 s, estimated 15 s
```
## View soft deletion
We decided to remove the soft deletion grain on all the views, in this
PR context we've only removed soft deleted validation requirement on any
view entities
## Conclusion
close https://github.com/twentyhq/core-team-issues/issues/1754
|
||
|
|
cceeb6ed4d |
Add applicationId to syncableEntity and fix syncApp deletion (#15170)
## Context - All flatEntity should extend SyncableEntity - SyncableEntity should now have applicationId and application relation - Fix syncApp deletion, should now properly use migration v2 to delete syncable entities |
||
|
|
7d747c9876 |
[REQUIRES_CACHE_FLUSH][GQL_VIEW_GROUP_API_BREAKING_CHANGE] ViewGroup in v2 (#15052)
# 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 |
||
|
|
6188c72f74 |
Simplify and enhance v2 type devxp (#15032)
# 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 ! ) |
||
|
|
651ab184a7 |
[GQL_VIEW_FILTER_API_BREAKING_CHANGE][WHEN_RELEASED_REQUIRES_CACHE_FLUSH] ViewFilter migration to workspace migration v2 (#15010)
# Introduction Migrating `viewFilter` to v2 in order to migrate later the field update side effect on view to v2 too ## What's done - Created flat-view-filter - flat view filter runner - flat view filter builder - create view filter service v2 and input transpilers - refactor the existing view filter resolver to fix standard ( BREAKING_CHANGE on graphql api update especially ) REST stays the same - refactored the front to consume the mutations autogenerated ## New generic tools ### Compare two flat entity Introducing a new util to compare two flat entity, it's strictly typed and will be added to the generic builder in a following PR This will ease flat entity addition as won't required to create a specific abstraction for comparison Generic builder will expect specific constant: properties to compare and properties to stringify ### Transform flat entity for comparison Forked and refactor the initial existing method for flat entity business scope and type safety ## Coverage Migrated existing integration tests to fit new contract API This PR does not add strong coverage on validation exceptions Deadlines are too short close https://github.com/twentyhq/core-team-issues/issues/1666 |
||
|
|
59fbe35a8c |
Move view in metadata-modules/ and create atomic folder + module for each view entity (#14990)
# 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 |