Closes https://github.com/twentyhq/core-team-issues/issues/605
Actually settingsPermissions checks were already implemented, but we had
no tests on them.
In the ticket we had mentioned
_TO DO: in pemissions.service we should stop calling
userRoleService.getRolesByUserWorkspaces and call
getRoleIdForUserWorkspace instead which relies on the cache._
But actually roleId is not enough for settings permissions because we
don't store them in the cache (unlien object records permissions - which
I think we had forgotten about when adding that TODO.), so we will still
need to make a db call to load the role's settingsPermissions. I think
it's better to make just one db call to get the role and
settingsPermissions from userWorkspaceId (as currently) than to make one
redis call to get roleId for userWorksapce then one db call to get role
and its settingsPermissions).
# Summary
Enhanced the Google OAuth flow to better handle missing permissions and
improved user experience by redirecting to settings/account page.
## Changes
- Added new google-apis-scopes.ts service for better scope management
- Updated Google APIs auth controller for better flow control
- New tests for this logic
## User request
From @bonapara email test and need to better handle user flow during the
connect email flow
Before :
<img width="574" alt="Screenshot 2025-05-28 at 17 58 59"
src="https://github.com/user-attachments/assets/fd54625b-e211-4b2f-b76a-48bcb08b5222"
/>
After :
<img width="1143" alt="Screenshot 2025-05-28 at 16 29 05"
src="https://github.com/user-attachments/assets/8f3d1f2c-9e02-4d25-b949-fe2b20f048f4"
/>
## Reference :
For google specialities, I added this link in the `export const
getGoogleApisOauthScopes` in order to keep that in mind
https://developers.google.com/identity/protocols/oauth2/scopes
Fixes https://github.com/twentyhq/twenty/issues/12337
When importing emails, matched companies are added, but no event is
triggered. Which means that workflows are not triggered. Adding the
event.
To test:
- create a workflow that listens to company creation
- import emails
- make sure workflow has been triggered
I am seeing an issue where this migrations fails because the
`metadata._typeorm_migrations` table does not exist.
```pgsql
copy _typeorm_migrations from metadata to core
query failed: SELECT * FROM metadata._typeorm_migrations ORDER BY id ASC
error: error: relation "metadata._typeorm_migrations" does not exist
[Nest] 430 - 06/01/2025, 10:22:35 PM ERROR [CopyTypeormMigrationsCommand] Failed to copy migrations: relation "metadata._typeorm_migrations" does not exist
[Nest] 430 - 06/01/2025, 10:22:35 PM ERROR [CopyTypeormMigrationsCommand] undefined
[Nest] 430 - 06/01/2025, 10:22:35 PM ERROR [DatabaseMigrationService] Error running database migrations:
[Nest] 430 - 06/01/2025, 10:22:35 PM ERROR [DatabaseMigrationService] QueryFailedError: relation "metadata._typeorm_migrations" does not exist
[Nest] 430 - 06/01/2025, 10:22:35 PM ERROR [UpgradeCommand] Command failed
[Nest] 430 - 06/01/2025, 10:22:35 PM ERROR [UpgradeCommand] undefined
[Nest] 430 - 06/01/2025, 10:22:35 PM LOG [UpgradeCommand] Command completed!
[Nest] 430 - 06/01/2025, 10:22:35 PM ERROR [QueryFailedError] relation "metadata._typeorm_migrations" does not exist
```
I _think_ this table is not meant to exist anymore - which means that
anyone who is onboarding into the project will run into an issue unless
we handle the case where the table doesn't exist.
We need to handle both the existing case and the non existing case to
support people who _do_ have metadata._typeorm_migrations` to migrate.
Closes https://github.com/twentyhq/core-team-issues/issues/748
In the frame of the work on permissions we
- remove all raw queries possible to use repositories instead
- forbid usage workspaceDataSource.executeRawQueries()
- restrict usage of workspaceDataSource.query() to force developers to
pass on shouldBypassPermissionChecks to use it.
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Creating manual chunk was a bad idea, we should always solve lazy
loading problem at the source instance.
Setting a 4.5MB for the index bundle size, CI will fail if we go above.
There is still a lot of room for optimizations!
- More agressive lazy loading (e.g. xyflow and tiptap are still loaded
in index!)
- Add a prefetch mechanism
- Add stronger CI checks to make sure libraries we've set asides are not
added back
- Fix AllIcons component with does not work as intended (loaded on
initial load)
# Implementation Details
- Added support for 5 operators: `contains`, `containsAny`,
`containsAll`, `matches`, and `fuzzy`
- Works on any field of type `TS_VECTOR`
- Added PostgreSQL `pg_trgm` extension for fuzzy search functionality.
The extension provides the `similarity()` function needed for text
similarity searches.
- Not implemented in GraphQL
## Tradeoffs & Decisions
1. **Fuzzy Search Performance**: Using `pg_trgm` for fuzzy search is
more accurate but slower than simple text matching. We might want to add
a similarity threshold parameter in the future to control the tradeoff
between accuracy and performance.
2. **Operator Naming**: Chose `contains`/`containsAny`/`containsAll` to
be consistent with existing filter operators, though they might be less
intuitive than `search`/`searchAny`/`searchAll`.
## Demo
https://github.com/user-attachments/assets/790fc3ed-a188-4b49-864f-996a37481d99
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Changes the default behavior for settings navigation items to stay
active when navigating to sub-pages.
**Problem:**
- Navigation items like "Data Model" and "Webhooks" were not staying
highlighted when navigating to detail pages
- This was because `matchSubPages` defaulted to requiring exact path
matches
**Solution:**
- Updated logic to make sub-page matching the default behavior (`end:
item.matchSubPages === false`)
- Only "Accounts" explicitly sets `matchSubPages: false` for its custom
sub-item navigation
- Removed redundant `matchSubPages: true` declarations throughout the
codebase
**URL Changes:** -- checked with @Bonapara
- `/settings/workspace` → `/settings/general`
- `/settings/workspace-members` → `/settings/members`
- `/settings/api-keys` → `/settings/apis`
- `/settings/developers/webhooks` → `/settings/webhooks`
before:
https://github.com/user-attachments/assets/56b94a49-9c31-4bb5-9875-ec24f4bc4d1e
after:
https://github.com/user-attachments/assets/38742599-c045-44d1-8020-56f3eacca779
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Context :
Plan choice [on pricing page on website](https://twenty.com/pricing)
should redirect you the right plan on app /plan-required page (after
sign in), thanks to query parameters and BillingCheckoutSessionState
sync.
With email verification, an other session starts at CTA click in
verification email. Initial BillingCheckoutSessionState is lost and user
can't submit to the plan he choose.
Solution :
Pass a nextPath query parameter in email verification link
To test :
- Modify .env to add IS_BILLING_ENABLED (+ reset db + sync billing) +
IS_EMAIL_VERIFICATION_REQUIRED
- Start test from this page
http://app.localhost:3001/welcome?billingCheckoutSession={%22plan%22:%22ENTERPRISE%22,%22interval%22:%22Year%22,%22requirePaymentMethod%22:true}
- After verification, check you arrive on /plan-required page with
Enterprise plan on a yearly interval (default is Pro/monthly).
closes https://github.com/twentyhq/twenty/issues/12288
Backend part of https://github.com/twentyhq/core-team-issues/issues/928
- Add fields to database event settings
- If not set, match all automated triggers with the right event name
- If set, event needs at least one updated field listened to be treated
# Introduction
Diff description: ~500 tests and +500 additions
close https://github.com/twentyhq/core-team-issues/issues/731
## What has been done here
In a nutshell on a field metadata type ( `SELECT MULTI_SELECT` ) update,
we will be browsing all `ViewFilters` in a post hook searching for some
referencing related updated `fieldMetadata` select. In order to update
or delete the `viewFilter` depending on the associated mutations.
## How to test:
- Add FieldMetadata `SELECT | MULTI_SELECT` to an existing or a new
`objectMetadata`
- Create a filtered view on created `fieldMetadata` with any options you
would like
- Remove some options ( in the best of the world some that are selected
by the filter ) from the `fieldMetadata` settings page
- Go back to the filtered view, removed or updated options should have
been hydrated in the `displayValue` and the filtered data should make
sense
## All filtered options are deleted edge case
If an update implies that a viewFilter does not have any existing
related options anymore, then we remove the viewFilter
## Testing
```sh
PASS test/integration/metadata/suites/field-metadata/update-one-field-metadata-related-record.integration-spec.ts (27 s)
update-one-field-metadata-related-record
SELECT
✓ should delete related view filter if all select field options got deleted (2799 ms)
✓ should update related multi selected options view filter (1244 ms)
✓ should update related solo selected option view filter (1235 ms)
✓ should handle partial deletion of selected options in view filter (1210 ms)
✓ should handle reordering of options while maintaining view filter values (1487 ms)
✓ should handle no changes update of options while maintaining existing view filter values (1174 ms)
✓ should handle adding new options while maintaining existing view filter (1174 ms)
✓ should update display value with options label if less than 3 options are selected (1249 ms)
✓ should throw error if view filter value is not a stringified JSON array (1300 ms)
MULTI_SELECT
✓ should delete related view filter if all select field options got deleted (1127 ms)
✓ should update related multi selected options view filter (1215 ms)
✓ should update related solo selected option view filter (1404 ms)
✓ should handle partial deletion of selected options in view filter (1936 ms)
✓ should handle reordering of options while maintaining view filter values (1261 ms)
✓ should handle no changes update of options while maintaining existing view filter values (1831 ms)
✓ should handle adding new options while maintaining existing view filter (1610 ms)
✓ should update display value with options label if less than 3 options are selected (1889 ms)
✓ should throw error if view filter value is not a stringified JSON array (1365 ms)
Test Suites: 1 passed, 1 total
Tests: 18 passed, 18 total
Snapshots: 18 passed, 18 total
Time: 27.039 s
```
## Out of scope
- We should handle ViewFilter validation when extracting its definition
from the metadata
https://github.com/twentyhq/core-team-issues/issues/1009
## Concerns
- Are we able through the api to update an RATING fieldMetadata ? ( if
yes than that's an issue and we should handle RATING the same way than
for SELECT and MULTI_SELECT )
- It's not possible to group a view from a MULTI_SELECT field
The above points create a double nor a triple "lecture" to the post hook
effect:
- ViewGroup -> only SELECT
- VIewFilter -> only SELECT || MULTI_SELECT
- Rating nothing
I think we should determine the scope of all of that
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
### Primary Changes: Dynamic Driver Configuration
Refactors FileStorageService and EmailSenderService to support dynamic
driver configuration changes at runtime without requiring application
restarts.
**Key Architectural Change**: Instead of conditionally registering
drivers at build time based on configuration, we now **register all
possible drivers eagerly** and select the appropriate one at runtime.
### What Changed:
- **Before**: Modules conditionally registered only the configured
driver (e.g., only S3Driver if STORAGE_TYPE=S3)
- **After**: All drivers (LocalDriver, S3Driver, SmtpDriver,
LoggerDriver) are registered at startup
- **Runtime Selection**: Services dynamically choose and instantiate the
correct driver based on current configuration
### Secondary Fix: Integration Test Log Cleanup
Addresses ConfigStorageService error logs appearing in integration test
output by using injected LoggerService for consistent log handling.
# extracting domain emails
Added new test cases covering weird but valid email formats (plus
addressing, subdomains, international domains, etc.) to identify
potential failures in the current implementation.
Two tests with quoted local parts containing @ symbols or quotes are
marked as skipped since they're expected to fail with the current simple
string splitting approach. They are too exotic IMO, we should throw
errors.
## Next
We will monitor errors related to this and update accordingly later on.
### Note
technically, quotes are possible in RFC see
[here](https://stackoverflow.com/questions/4816424/are-single-quotes-legal-in-the-name-part-of-an-email-address)
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Various fixes from fast follows
- Sort roles by alphabetical order
- Change some tooltips
- During role creation, role should have all permissions enabled by
default
- Changed Permission icons design and refactored duplicating logic in a
dedicated component
- Changed "Revoked by" design
- Display role icon in default role picker
- Workspace member avatar was missing in role list and member picker
- Set "seeded" member role as editable for new workspaces
- Various css fixes
## Context
- Introduced objectPermissions in currentUserWorkspace which uses role
permissions from cache so we can fetch granular permissions from the API
- Refactored cached role permissions to map permissions with object
metadata id instead of object metadata name singular to be more flexible
New Cache
<img width="574" alt="Screenshot 2025-05-27 at 11 59 06"
src="https://github.com/user-attachments/assets/1a090134-1b8a-4681-a630-29f1472178bd"
/>
GQL
<img width="977" alt="Screenshot 2025-05-27 at 11 58 53"
src="https://github.com/user-attachments/assets/3b9a82b0-6019-4a25-a6e2-a9e0fb4bb8a0"
/>
Next steps: Use the updated API in the FE to fetch granular permissions
and update useHasObjectReadOnlyPermission hook
I encountered a bug where I was missing permissions while calling
searchResolver because the repository from
`twentyORMManager.getRepository` was missing permissions itself.
The repository was returned from the cached repositories map using a
repository key feature the roleId, the rolesVersion and
featureFlagMapVersion.
I was not able to reproduce but this error should not go unnoticed: we
always expect to find objectPermissions for every roleId in the
datasource now.
I was not able to understand what happened for now but I think throwing
the error will help keeping an eye on it
# Gmail OAuth authentication flow issues
### TLDR
This error is not an error and therefore should be treated as a simple
redirect with a snackbar.
### More details
Fixing incomplete OAuth token exchange processes and improving error
handling for empty Gmail inboxes.
The changes include modifications to OAuth guards, to ensure that if a
user clicks "cancel" instead of completing the authentication workflow
if fails
## Before:
Redirection from `/settings/accounts` to `app.twenty.com` with an
`UNAUTHORIZED` error
## After :
<img width="948" alt="Screenshot 2025-05-26 at 18 04 37"
src="https://github.com/user-attachments/assets/62c8721e-c2b3-4e3d-ad0b-e4059dfb7a98"
/>
Fixes https://github.com/twentyhq/twenty/issues/11895
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
For database event triggers, we remove the before / after logic. We go
directly with the properties
<img width="211" alt="Capture d’écran 2025-05-27 à 11 40 36"
src="https://github.com/user-attachments/assets/a05bd3c1-104b-477b-be52-d56846ce7e63"
/>
To achieve this without changing the shape of events, we need to handle
keys using dots, such:
```
'properties.after.name': {
icon: 'IconBuildingSkyscraper',
type: FieldMetadataType.TEXT,
label: 'Name',
value: 'My text',
isLeaf: true,
},
```
This PR:
- adds logic to handle the case where the key has dot included
- adds tests
Changes for performance improvement.
The primary improvements include replacing GraphQL queries with
REST-based client configuration fetching and making the client config
non render-blocking
We should capture graphQL exceptions thrown in the FE in Sentry.
All the more so as we have just cleaned back-end errors in sentry,
preventing 4xx errors from being wrongfully sent to sentry.
Those 4xx errors should, except for `Unauthenticated` and `Forbidden`
errors (for now - this list can evolve), trigger a sentry FE error, as
we are not suppose to let users of the product interface trigger queries
that will fail with 4xx errors (for instance a malformed input).
We still miss an efficient way to group those errors together in sentry.
It could be the message but the message may be different for each user
if it contains user-specific data, and we don't always have control on
the message.
This can be done later as we iterate on improving sentry
Workflow statuses are often broken. I did not figured out why yet. But I
see two causes that can be fixed:
- statuses calculation are really complicated today, just to spare a
call to the database
- job is not indempotent, it is using the combination of the previous
statuses + the update to calculate the new statuses. Which means that
once broken, next updates will be broken as well
Instead, we now:
- fetch workflow versions
- get the statuses from these.
It simplifies the code and make the job indempotent.
This PR has several objectives:
- Ignore invalid and empty links in the frontend
- Ignore empty links when creating or updating a link field in the
backend
- Throw an error when trying to create or update a link field with an
invalid link
The logic is mostly the same in the frontend and the backend: we take
the initial primaryLink and the secondaryLinks, we discard all the empty
links (with `url === '' || url === null`), and the primaryLink becomes
the first remaining link.
## Frontend
There are three parts in the frontend where we have to remove the empty
links:
- LinksDisplay
- LinksFieldInput
- isFieldValueEmpty; used in RecordInlineCell
## Backend
I put the logic in
`packages/twenty-server/src/engine/core-modules/record-transformer/services/record-input-transformer.service.ts`
as it's used by the REST API, the GraphQL API, and by Create Record and
Update Record actions in the workflows.
# Introduction
Big diff a lot of tests and snapshots ( real diff < 500+ )
close https://github.com/twentyhq/twenty/issues/12117
close https://github.com/twentyhq/twenty/issues/12133
## What has been done here
Implemented a strong integration coverage on both fieldmetadata`SELECT`
`UPDATE` and `CREATE`.
Implemented server side validation for the options `value` `label` `id`
and collision issue with also `position`
We could improve:
- Position validation
- DefaultValue validation
## Update
```ts
PASS test/integration/metadata/suites/field-metadata/update-one-field-metadata-select.integration-spec.ts (41.054 s)
Field metadata select update tests group
✓ Update should succeed with provided option id (2565 ms)
✓ Update should succeed with valid default value (1469 ms)
✓ Update should succeed with various options id (1257 ms)
✓ Update should succeed without option id (1286 ms)
✓ Update should trim option values (1366 ms)
✓ Update should succeed with default value and no options (1122 ms)
✓ Update should fail with unknown default value and no options (1075 ms)
✓ Update should fail with only white spaces id (1195 ms)
✓ Update should fail with empty string id (1058 ms)
✓ Update should fail with null id (1066 ms)
✓ Update should fail with not a string id (1098 ms)
✓ Update should fail with too long id (1373 ms)
✓ Update should fail with only white spaces label (1034 ms)
✓ Update should fail with empty string label (1057 ms)
✓ Update should fail with null label (1100 ms)
✓ Update should fail with not a string label (1144 ms)
✓ Update should fail with too long label (1273 ms)
✓ Update should fail with only white spaces value (1385 ms)
✓ Update should fail with empty string value (1035 ms)
✓ Update should fail with null value (1068 ms)
✓ Update should fail with not a string value (1021 ms)
✓ Update should fail with too long value (1134 ms)
✓ Update should fail with invalid option id (1137 ms)
✓ Update should fail with empty options (1238 ms)
✓ Update should fail with invalid option value format (1104 ms)
✓ Update should fail with comma in option label (1004 ms)
✓ Update should fail with duplicated option values (1015 ms)
✓ Update should fail with duplicated option ids (1079 ms)
✓ Update should fail with duplicated option positions (1266 ms)
✓ Update should fail with duplicated trimmed option values (1220 ms)
✓ Update should fail with undefined option label (1029 ms)
✓ Update should fail with an invalid default value (1142 ms)
✓ Update should fail with an unknown default value (1081 ms)
✓ Update should fail with undefined option value (1086 ms)
Test Suites: 1 passed, 1 total
Tests: 34 passed, 34 total
Snapshots: 28 passed, 28 total
Time: 41.079 s
```
## Create
```ts
PASS test/integration/metadata/suites/field-metadata/create-one-field-metadata-select.integration-spec.ts (38.292 s)
Field metadata select creation tests group
✓ Create should succeed with provided option id (2096 ms)
✓ Create should succeed with valid default value (1316 ms)
✓ Create should succeed with various options id (1113 ms)
✓ Create should succeed without option id (1378 ms)
✓ Create should trim option values (1296 ms)
✓ Create should fail with only white spaces id (1000 ms)
✓ Create should fail with empty string id (1325 ms)
✓ Create should fail with null id (1060 ms)
✓ Create should fail with not a string id (1142 ms)
✓ Create should fail with too long id (1321 ms)
✓ Create should fail with only white spaces label (999 ms)
✓ Create should fail with empty string label (1163 ms)
✓ Create should fail with null label (1198 ms)
✓ Create should fail with not a string label (1678 ms)
✓ Create should fail with too long label (1527 ms)
✓ Create should fail with only white spaces value (1200 ms)
✓ Create should fail with empty string value (1102 ms)
✓ Create should fail with null value (1037 ms)
✓ Create should fail with not a string value (1462 ms)
✓ Create should fail with too long value (896 ms)
✓ Create should fail with invalid option id (997 ms)
✓ Create should fail with empty options (1058 ms)
✓ Create should fail with invalid option value format (1190 ms)
✓ Create should fail with comma in option label (1142 ms)
✓ Create should fail with duplicated option values (872 ms)
✓ Create should fail with duplicated option ids (860 ms)
✓ Create should fail with duplicated option positions (1002 ms)
✓ Create should fail with duplicated trimmed option values (1336 ms)
✓ Create should fail with undefined option label (754 ms)
✓ Create should fail with an invalid default value (696 ms)
✓ Create should fail with an unknown default value (678 ms)
✓ Create should fail with undefined option value (699 ms)
✓ Create should fail with null options (720 ms)
✓ Create should fail with undefined options (686 ms)
Test Suites: 1 passed, 1 total
Tests: 34 passed, 34 total
Snapshots: 29 passed, 29 total
Time: 38.314 s
```
## Conclusion
As always any suggestions are welcomed ! Please let me know
## Discussion about validation governance
### Front
Front side will be dealing with zod validations schema that he will
handle and maintain by himself
### Back validation instances
- Validation hold through DTO declarations ( run by yoga through the
resolvers )
- Server programmatic validation and exceptions handling ( run through
the services )
For this refactor/fix we decided to stick to the current implementation
only touching the `Server programmatic validation and exceptions
handling` we will handle validation centralization when we will onboard
the `nestjs-query` deprecation/integration refactor.
### Vision
In the best of the world we could think of an intermediary model that
will handle and take responsibility of the validation decorators that
would be run programmatically through the service, Yoga would still
consume it ? then we would need to have enough grain in the service to
know the input has already validated
## Notes
Introduced zod back side in order to handle very atomic and primitive
validation