- update publishOneServerlessFunction so it does not break the workflow
if failing
- update upgrade documentation to update `docker-compose.yml` when
mutated
Fixes https://github.com/twentyhq/twenty/issues/13058
In this PR
- introduction of fieldPermission entity
- addition of upsertFieldPermission in role resolver
- computing of permissions taking fieldPermission into account. In order
to limit what is stored in Redis we only store fields restrictions. For
instance for objectMetadata with id XXX with a restriction on field with
id YYY we store:
`"XXX":{"canRead":true,"canUpdate":false,"canSoftDelete":false,"canDestroy":false,"restrictedFields":{"YYY":{"canRead":false,"canUpdate":null}}}`
---------
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
## Expected behavior
Described behavior regarding: (update | create) x (custom | standard) x
(icon, label, name, isSynced)
**Custom:**
- Field RELATION create: name, label, isSynced, icon should be editable
- Field RELATION update: name should not, icon label, isSynced should
- For other fields, icon, label, name, isSynced should be editable at
field creation | update
To simplify: Field RELATION name should not be editable at update
**Standards**
- Field: create does not makes sense
- Field: name should not, icon label, isSynced should (this will end up
in overrides)
To simplify, no Field RELATION edge case, name should not be editable at
update
**Note:** the FE logic is quite different as the UI is hiding some
details behind the syncWithLabel. See my comments and TODO there
## What I've tested:
(update | create) x (custom | standard) x (icon, label, name, isSynced,
description)
This PR is raised to close the issue #13044
But there are some doubts that needs to be approved.
If the fields are not custom then we were saving the changes in
**standardOverrides** obj in which only three fields are
overridableFields **label, icon & description** can be updated for a
field.
You can see this in _before-update-one-field.hook.ts_ on line 85
```ts
const overridableFields = ['label', 'icon', 'description'];
```
If the field to be updated are from these three we are putting this in a
**standardOverrides** obj and passing it
However in our _field-metadata.service.ts_ file. We have **updateOne**
function inside it we have wrote a condition if **isCustom** is false
then the purpose was to build the updatableFields from the
**standardOverrides** obj that we got in **fieldMetadataInput** but
there was an error in it. As you can see below
```ts
const updatableFieldInput =
existingFieldMetadata.isCustom === false
? this.buildUpdatableStandardFieldInput(
fieldMetadataInput,
existingFieldMetadata,
)
: fieldMetadataInput;
```
However, the issue was that we were placing the entire
**standardOverrides** object inside **updatableFieldInput** again —
instead of merging its individual fields (label, icon, description)
directly into the update payload.
This PR fixes that by correctly applying the overrides into the
top-level object.
Please refer to the file changes for the full context.
But the thing i don't know.
[ ] - Is this the correct expected flow??
[ ]- Will this change break anything elsewhere?
I still have doubts on these two. Let me know if I missed something.
---------
Co-authored-by: Jagss24 <btwitsjagannat12@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
Currently, when a server query or mutation from the front-end fails, the
error message defined server-side is displayed in a snackbar in the
front-end.
These error messages usually contain technical details that don't belong
to the user interface, such as "ObjectMetadataCollection not found" or
"invalid ENUM value for ...".
**BE**
In addition to the original error message that is still needed (for the
request response, debugging, sentry monitoring etc.), we add a
`displayedErrorMessage` that will be used in the snackbars. It's only
relevant to add it for the messages that will reach the FE (ie. not in
jobs or in rest api for instance) and if it can help the user sort out /
fix things (ie. we do add displayedErrorMessage for "Cannot create
multiple draft versions for the same workflow" or "Cannot delete
[field], please update the label identifier field first", but not
"Object metadata does not exist"), even if in practice in the FE users
should not be able to perform an action that will not work (ie should
not be able to save creation of multiple draft versions of the same
workflows).
**FE**
To ease the usage we replaced enqueueSnackBar with enqueueErrorSnackBar
and enqueueSuccessSnackBar with an api that only requires to pass on the
error.
If no displayedErrorMessage is specified then the default error message
is `An error occured.`
Modifying the data-model can sometimes fail in the middle of your
operation, due to the way we handle both metadata update and schema
migration separately, a field can be created while the associated column
creation failed (same for object/table and such). This is also an issue
because WorkspaceMigrations are then stored as FAILED can never really
recovered by themselves so the schema is broken and we can't update the
models anymore.
This PR adds a executeMigrationFromPendingMigrationsWithinTransaction
method where we can (and must) pass a queryRunner executing a
transaction, which should come from the metadata services so that if
anything during metadata update OR schema update fails, it rolls back
everything (this also mean a workspaceMigration should never stay in a
failed state now).
This also fixes some issues with migration not running in the correct
order due to having the same timestamp and having to do some weird logic
to fix that.
This is a first step and fix before working on a much more reliable
solution in the upcoming weeks where we will refactor the way we
interact with the data model.
---------
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This PR introduces a significant enhancement to the role-based
permission system by extending it to support AI agents, enabling them to
perform database operations based on assigned permissions.
## Key Changes
### 1. Database Schema Migration
- **Table Rename**: `userWorkspaceRole` → `roleTargets` to better
reflect its expanded purpose
- **New Column**: Added `agentId` (UUID, nullable) to support AI agent
role assignments
- **Constraint Updates**:
- Made `userWorkspaceId` nullable to accommodate agent-only role
assignments
- Added check constraint `CHK_role_targets_either_agent_or_user`
ensuring either `agentId` OR `userWorkspaceId` is set (not both)
### 2. Entity & Service Layer Updates
- **RoleTargetsEntity**: Updated with new `agentId` field and constraint
validation
- **AgentRoleService**: New service for managing agent role assignments
with validation
- **AgentService**: Enhanced to include role information when retrieving
agents
- **RoleResolver**: Added GraphQL mutations for `assignRoleToAgent` and
`removeRoleFromAgent`
### 3. AI Agent CRUD Operations
- **Permission-Based Tool Generation**: AI agents now receive database
tools based on their assigned role permissions
- **Dynamic Tool Creation**: The `AgentToolService` generates CRUD tools
(`create_*`, `find_*`, `update_*`, `soft_delete_*`, `destroy_*`) for
each object based on role permissions
- **Granular Permissions**: Supports both global role permissions
(`canReadAllObjectRecords`) and object-specific permissions
(`canReadObjectRecords`)
### 4. Frontend Integration
- **Role Assignment UI**: Added hooks and components for
assigning/removing roles from agents
## Demo
https://github.com/user-attachments/assets/41732267-742e-416c-b423-b687c2614c82
---------
Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Guillim <guillim@users.noreply.github.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Marie <51697796+ijreilly@users.noreply.github.com>
Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: Baptiste Devessier <baptiste@devessier.fr>
Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: prastoin <paul@twenty.com>
Co-authored-by: Vicky Wang <157669812+vickywxng@users.noreply.github.com>
Co-authored-by: Vicky Wang <vw92@cornell.edu>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
Context :
- IndexFieldMetadata was no longer available on 'objects' gql query
([since this PR](https://github.com/twentyhq/twenty/pull/12785)). Then,
unicity checks on import do not work anymore.
Fix :
- Add a dataloader logic in indexFieldMetadata
- Add extra check in unicity hook on import
# Introduction
Following https://github.com/twentyhq/twenty/pull/12852
Discovered that:
- `relationCreationPayload` does not seem to be validated through the
input decorators
```ts
// TODO @prastoin implement validation for this with validate nested and dedicated class instance
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })
relationCreationPayload?: {
targetObjectMetadataId: string;
targetFieldLabel: string;
targetFieldIcon: string;
type: RelationType;
};
```
- Sending an unknown `targetObjectMetadataId` generates an
`internal_server_error` `500` @guillim on the go
## Coverage
```ts
PASS test/integration/metadata/suites/object-metadata/failing-field-metadata-relation-creation.integration-spec.ts
Field metadata relation creation should fail
✓ relation when targetFieldLabel is empty (109 ms)
✓ relation when targetFieldLabel exceeds maximum length (100 ms)
✓ relation when targetObjectMetadataId is unknown (97 ms)
✓ relation when targetFieldLabel contains only whitespace (103 ms)
✓ relation when targetFieldLabel conflicts with an existing field on target object metadata id (108 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 5 passed, 5 total
Time: 2.629 s, estimated 3 s
```
Better catching label input
- there were absolutely no check on label when creating the target field
while doing a relation : we crearted these checks here.
- We keep the label quite open to special char as discussed with Felix.
so mostly checking length of label.
- We check that label does not already exists on the targetted object
- making sure the Target fieldinput label is checked before we create
it. The previous checks are not enough since the label goes through
anoteher merthod before going in the database
- validate-metadata-name-is-camel-case.utils.ts : making sure we can use
this error message for metadata name and for target label
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: prastoin <paul@twenty.com>
In this PR:
- add query hashKey to ObjectMetadataItems query graphql cache to avoid
caching outdated queries
- improve performance by removing ResolveField at FieldLevel and adding
this at resolver level
In this PR:
## Improve recompute metadata cache performance. We are aiming for
~100ms
Deleting relationMetadata table and FKs pointing on it
Fetching indexMetadata and indexFieldMetadata in a separate query as
typeorm is suboptimizing
## Remove caching lock
As recomputing the metadata cache is lighter, we try to stop preventing
multiple concurrent computations. This also simplifies interfaces
## Introduce self recovery mecanisms to recompute cache automatically if
corrupted
Aka getFreshObjectMetadataMaps
## custom object resolver performance improvement: 1sec to 200ms
Double check queries and indexes used while creating a custom object
Remove the queries to db to use the cached objectMetadataMap
## reduce objectMetadataMaps to 500kb
<img width="222" alt="image"
src="https://github.com/user-attachments/assets/2370dc80-49b6-4b63-8d5e-30c5ebdaa062"
/>
We used to stored 3 fieldMetadataMaps (byId, byName, byJoinColumnName).
While this is great for devXP, this is not great for performances.
Using the same mecanisme as for objectMetadataMap: we only keep byIdMap
and introduce two otherMaps to idByName, idByJoinColumnName to make the
bridge
## Add dataloader on IndexMetadata (aka indexMetadataList in the API)
## Improve field resolver performances too
## Deprecate ClientConfig
We were using a global ValidationPipe in main.ts. This is an issue as
@Controllers should return HttpExecption and @Resolvers should return
GraphqlErrors
Removing the global pipe and creating a ResolverValidationPipe able to
generate GraphqlError. We also need to handle the exception in a filter
to avoid nest to think it's unhandled and make it flow to logs
Next step:
- it would be nice to have both @UsePipes(ResolverValidationPipe) +
@UseFilters(GraphqlValidationExceptionFilter) come together. This should
be possible if we create a @GraphQLResolver annotation
- Fix an issue where custom object were seeded with 2 views, and with
the wrong icon
- ACME becomes YCombinator
- Allow 2 workspaces to have different metadata seeded
- Add many seeds for messages
- Add many seeds for calendar events
- Randomize createdBy for person and companies
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
# Introduction
close https://github.com/twentyhq/twenty/issues/12343
Adding a transform step for any field phone in order to infer country
code and calling code from the number if they're provided
## Edges cases
```ts
RecordTransformerExceptionCode.INVALID_PHONE_NUMBER:
RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE:
RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE:
RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE:
RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE:
RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE:
RecordTransformerExceptionCode.INVALID_URL:
```
## Coverage
Note: Will handle REST api integration testing pivot and UPDATE
operation later in the afternoon, critical bug appeared that I prefer
handling before improving this PR coverage, also would be too many
updates
Note2: Haven't fuzzed all of the string inputs, would seem overkill for
such a use case, to be debated
```ts
PASS test/integration/metadata/suites/field-metadata/phone/create-one-field-metadata-phone.integration-spec.ts (23.609 s)
Phone field metadata tests suite
✓ It should succeed create primary phone field (1397 ms)
✓ It should succeed create primary phone field with number and other information (930 ms)
✓ It should succeed create primary phone field with full international format and other information (893 ms)
✓ It should succeed create primary phone field with full international and infer other information from it but not the countryCode as its shared (825 ms)
✓ It should succeed create primary phone field with full international and infer other information from it (818 ms)
✓ It should succeed create primary phone field with empty payload (827 ms)
✓ It should succeed create additional phone field with number and other information (894 ms)
✓ It should succeed create additional phone field with full international format and other information (1024 ms)
✓ It should succeed create additional phone field with full international and infer other information from it but not the countryCode as its shared (808 ms)
✓ It should succeed create additional phone field with full international and infer other information from it (751 ms)
✓ It should succeed create additional phone field with empty payload (739 ms)
✓ It should fail to create primary phone field without country or calling code at all (776 ms)
✓ It should fail to create primary phone field with invalid country code (782 ms)
✓ It should fail to create primary phone field with invalid calling code (858 ms)
✓ It should fail to create primary phone field with conflicting country code and calling code (872 ms)
✓ It should fail to create primary phone field with invalid phone number format (1489 ms)
✓ It should fail to create primary phone field with conflicting phone number country code (1425 ms)
✓ It should fail to create primary phone field with conflicting phone number calling code (1553 ms)
✓ It should fail to create primary phone field without country or calling code at all (814 ms)
✓ It should fail to create primary phone field with invalid country code (813 ms)
✓ It should fail to create primary phone field with invalid calling code (742 ms)
✓ It should fail to create primary phone field with conflicting country code and calling code (783 ms)
✓ It should fail to create primary phone field with invalid phone number format (731 ms)
✓ It should fail to create primary phone field with conflicting phone number country code (947 ms)
✓ It should fail to create primary phone field with conflicting phone number calling code (822 ms)
Test Suites: 1 passed, 1 total
Tests: 25 passed, 25 total
Snapshots: 14 passed, 14 total
Time: 23.627 s
```
## Goal
We have identified that sync-metadata (which is called during new
workspace initialization) is slow mainly because of workspaceMigration
application (migration-runner module). This is due to the fact that we
use typeORM API to perform schema changes, which often query the
existing schema. As querying the existing schema is costly (especially
with ~1M existing columns) and as we already have what we need described
as metadata, we will use raw SQL directly. This should divide the
workspace initialization time by x2.
## How
This PR can be read in two commits:
1. Extract functions tied to column migrations in a separate service
(`workspace-migration-column.service`) + deprecate COMMENT column
migration type which is not useful since we are not using pg-graphql
anymore
2. Re-work `workspace-migration-column.service` to make it clearer + use
raw SQL
## Result
Before:
<img width="1367" alt="image"
src="https://github.com/user-attachments/assets/e730df7b-db7f-4433-9ce5-52841b010990"
/>
After:
<img width="1367" alt="image"
src="https://github.com/user-attachments/assets/72d2c2b1-2475-4541-a3d5-50b70824a2e4"
/>
## Manual Testing
- Sync-metadata OK
- Workspace init OK
Closes https://github.com/twentyhq/core-team-issues/issues/868
We should not allow to grant any writing permission (update, soft
delete, delete) on an object or at role-level without the reading
permission at the same level.
This has been implemented in the front-end at role level, and is yet to
be done at object level (@Weiko)
We need to use twentyORMManager and not twentyORMGlobalManager in rest
api base handler, because we don't want to bypass permissions using
`shouldBypassPermissions` parameter (which we would have to do to use
twentyORMGlobalManager).
ScopedWorkspaceContextFactory was not adapted to rest api requests which
form differs from graphql request.
Update the default set of system fields for custom objects, to ensure
position is not nullabel and has a default value to 0
Steps to reproduce :
create a custom object,
send a POST request with body ```{position:null}```
the record should be created
After the change,
an error will be thrown
<img width="754" alt="Screenshot 2025-06-13 at 17 16 56"
src="https://github.com/user-attachments/assets/d40931f7-16cc-4b68-8dbb-deb0fa292be5"
/>
# Introduction
In a nutshell this PR introduces a `workspaceMemberEntity` to
`workspaceMemberDto` transpilation which was not done but commented as
`// TODO` across the `user resolver`.
Also passed on the `Roles` and `UserWorkspacePermissions` transpilation
We now also compute the roles for the `workspaceMember` resolver ( not
only the `workspaceMembers` )
Some refactor
In the following days about to create a PR that introduces integration
testing on the user resolver
## Conclusion
As always any suggestions are more than welcomed ! Please let me know !
## Misc
Following https://github.com/twentyhq/twenty/pull/11914
closing https://github.com/twentyhq/core-team-issues/issues/1011
If permissionsV2 feature flag is toggled, we should recompute the
permissions.
We decided to make each WorkspaceXxCacheService Xx-specific (feature
flag, permissions...), so we are not recomputing permission cache from
workspaceFeatureFlagCacheService where feature flags are recomputed,
even if that would be a lower level than FeatureFlagService. This allows
to avoid complex circuclar dependency and keeps a clear purpose for each
service.
In this PR
- Determine object record permissions on workflows objects (workflow,
workflowVersion, workflowRun) base on settings permissions @Weiko
- Add Workflow permission guards on workflow resolvers @thomtrp . **Any
method within a resolver that has the SettingsPermission Guard is only
callable by a apiKey or a user that has the permission** (so not by
external parties).
- Add checks bypass in workflow services since 1) for actions gated by
settings permissions, the gate should be done at resolver level, so it
will have been done before the call to the service 2) some service
methods may be called by workflowTriggerController which is callable by
external parties without permissions (ex:
workflowCommonWorkspaceService.getWorkflowVersionOrFail). This is
something we may want to change in the future (still to discuss), by
removing the guard at resolver-level and relying on
shouldBypassPermissionChecks at getRepository and made in a way that we
only bypass for external parties.
- Add checks bypass for actions performed by workflows since they should
not be restricted in our current vision
- Add tests
# What
Fully deprecate old relations because we have one bug tied to it and it
make the codebase complex
# How I've made this PR:
1. remove metadata datasource (we only keep 'core') => this was causing
extra complexity in the refactor + flaky reset
2. merge dev and demo datasets => as I needed to update the tests which
is very painful, I don't want to do it twice
3. remove all code tied to RELATION_METADATA /
relation-metadata.resolver, or anything tied to the old relation system
4. Remove ONE_TO_ONE and MANY_TO_MANY that are not supported
5. fix impacts on the different areas : see functional testing below
# Functional testing
## Functional testing from the front-end:
1. Database Reset ✅
2. Sign In ✅
3. Workspace sign-up ✅
5. Browsing table / kanban / show ✅
6. Assigning a record in a one to many / in a many to one ✅
7. Deleting a record involved in a relation ✅ => broken but not tied to
this PR
8. "Add new" from relation picker ✅ => broken but not tied to this PR
9. Creating a Task / Note, Updating a Task / Note relations, Deleting a
Task / Note (from table, show page, right drawer) ✅ => broken but not
tied to this PR
10. creating a relation from settings (custom / standard x oneToMany /
manyToOne) ✅
11. updating a relation from settings should not be possible ✅
12. deleting a relation from settings (custom / standard x oneToMany /
manyToOne) ✅
13. Make sure timeline activity still work (relation were involved
there), espacially with Task / Note => to be double checked ✅ => Cannot
convert undefined or null to object
14. Workspace deletion / User deletion ✅
15. CSV Import should keep working ✅
16. Permissions: I have tested without permissions V2 as it's still hard
to test v2 work and it's not in prod yet ✅
17. Workflows global test ✅
## From the API:
1. Review open-api documentation (REST) ✅
2. Make sure REST Api are still able to fetch relations ==> won't do, we
have a coupling Get/Update/Create there, this requires refactoring
3. Make sure REST Api is still able to update / remove relation => won't
do same
## Automated tests
1. lint + typescript ✅
2. front unit tests: ✅
3. server unit tests 2 ✅
4. front stories: ✅
5. server integration: ✅
6. chromatic check : expected 0
7. e2e check : expected no more that current failures
## Remove // Todos
1. All are captured by functional tests above, nothing additional to do
## (Un)related regressions
1. Table loading state is not working anymore, we see the empty state
before table content
2. Filtering by Creator Tim Ap return empty results
3. Not possible to add Tasks / Notes / Files from show page
# Result
## New seeds that can be easily extended
<img width="1920" alt="image"
src="https://github.com/user-attachments/assets/d290d130-2a5f-44e6-b419-7e42a89eec4b"
/>
## -5k lines of code
## No more 'metadata' dataSource (we only have 'core)
## No more relationMetadata (I haven't drop the table yet it's not
referenced in the code anymore)
## We are ready to fix the 6 months lag between current API results and
our mocked tests
## No more bug on relation creation / deletion
---------
Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
In this PR
1. adding tests on relations and nested relations to make sure that if
any permission is missing, the query fails
2. adding tests on objectRecord permissions to make sure that
permissions granted or restricted by objectPermissions take precedence
on the role's allObjectRecords permissions
We must separate the concept of hydratation which happens at the request
level (take the token and pass auth/user context), from the concept of
authorization which happens at the query/endpoint/mutation level.
Previously, hydratation exemption happened at the operation name level
which is not correct because the operation name is meaningless and
optional. Still this gave an impression of security by enforcing a
blacklist. So in this PR we introduce linting rule that aim to achieve a
similar behavior, now every api method has to have a guard. That way if
and endpoint is not protected by AuthUserGuard or AuthWorspaceGuard,
then it has to be stated explicitly next to its code.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
BlocknoteJS requires an ESM module where our server is CJS, this forced
us to pin the server-util version, which led us to force the resolution
of several packages, leading to bugs downstream.
From Node 22.12 Node supports requiring ESM modules (available from Node
22.0 with a flag). So I upgrade the module.
I picked Node 22 and not Node 23 or Node 24 because 22 is the LTS and we
don't plan to change node versions frequently.
If you remain on Node 18, things should still mostly work, except if you
edit a Rich Text field.
I also starting changing the default runtime for Serverless Functions
which isn't directly related. This means new serverless functions will
be created on Node 22, but we will still need another PR to migrate
existing serverless functions before September (end of support by AWS).
(In this PR I also remove the upgrade commands from 0.43 since they rely
on Blocknote and I didn't want to have to deal with this)
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
- Fix: AvatarURL signedPath for workspace members were not consistent
when queried multiple times and it was causing the frontend to wrongly
interpret this as a change in the deepEqual condition
- Use SaveAndCancel button to be consistent with data model page
- When applying all object permission changes, a "smarter" logic applies
and removes all permissions if read is unchecked for example
- Hide settings permissions when Settings All Access is toggled