# Introduction
While importing records encountering missing expected fields when
writting a fragment from apollo cache
## Updates
### 1/ `createdBy` Default value
When inserting in cache in create single or many we will now make
optimistic behavior on the createdBy value
### 2/ `createRecordInCache` dynamically create `recordGrqlFields`
When creating an entry in cache, we will now dynamically generate fields
to be written in the fragment instead of expecting all of them. As by
nature record could be partial
### 3/ Strictly typed `RecordGqlFields`
# Conclusion
closes#9927
Introduce isCustomDomainEnabled field in Workspace entity to manage
custom domain activation. Update related services, types, and logic to
validate and toggle the custom domain's status dynamically based on its
current state. This ensures accurate domain configurations are reflected
across the system.
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This PR essentially removes the usage of filterDefinition.type, by
replacing it with fieldMetadataItem.type derivation. Thus allowing to
completely remove filterDefinition later on.
In computeFilterRecordGqlOperationFilter, emptyOperationFilter is now
returned before going into the big switch case. This avoids repeating
the same exact call to getEmptyRecordGqlOperationFilter for each type.
Fixed some tests that need
getJestMetadataAndApolloMocksAndActionMenuWrapper to have record filters
properly working with the new implementation. We'll probably want to
refactor the record context store, record index context, etc.
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
# Introduction
At the moment when updating any record cache occurence, we will build a
fragment that will expect all of the object metadata item fields to be
provided.
Which result in the following traces: ( in the video companies aren't
fetch with companyId and other missing fields )
https://github.com/user-attachments/assets/56eab7c1-8f01-45ff-8f5d-78737b788b92
By definition as we're using graphql we might not request every record's
fields each time we wanna consume them.
In this way we will now dynamically compute or expect depending on the
CRUD operation specific fields to be written in the cache, and not all
of them
Tested all optimistic and failure management use cases
## Covering cache
Added coverage only for the `deleteOne` and `deleteMany` hooks, it cover
only the record record cache and not its relations hydratation ( for the
moment )
## Why not closing #9927
Unless I'm mistaken everything done here have fixed the same logs/traces
issue for updates and deletion but not creation.
Which means we still need to investigate the mass upload from import and
prefillRecord behavior
In a nutshell: went over each `updateRecordFromCache` calls, still need
to do all `createRecordInCache` calls
related to #9927
## Conlusion
Sorry for the big PR should have ejected into a specific one for the
`MinimalRecord` refactor
Will also continue covering others hooks later in my week as for the
`deleteOne`
As always any suggestions are welcomed !
More progress on translations:
- Migrate from translations.io to crowdin
- Optimize performance and robustness
- Set workspaceMember/user locale upon signup
# Content
- Introduce the `workspaceUrls` property. It contains two
sub-properties: `customUrl, subdomainUrl`. These endpoints are used to
access the workspace. Even if the `workspaceUrls` is invalid for
multiple reasons, the `subdomainUrl` remains valid.
- Introduce `ResolveField` workspaceEndpoints to avoid unnecessary URL
computation on the frontend part.
- Add a `forceSubdomainUrl` to avoid custom URL using a query parameter
- Do not render a source handle for the leaf nodes
- Upgrade the `@xyflow/react` library
| Before | After |
|--------|--------|
| 
| 
|
## Other options considered
React Flow exposes a hook to get the connections of the current node. I
tried to use this hook – which makes things way simpler – but I couldn't
find a way to make it work in Storybook. I had two options: 1. Set up
React Flow to render the nodes properly, 2. Mock the hook in Storybook.
The first option was hard to achieve as the `<Reactflow />` component
renders a whole flow, and it doesn't play well with the idea of
rendering a single node in a story.
The second option seemed overkill as mocking modules with Storybook is
not straightforward. See
https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.
I chose to keep the initial version of my code, written before I spot a
function simplifying the code. We can give it a look another time.
# Introduction
Added the `RecordAction` destroy multiple record
## Repro
Select multiples `deletedRecords`, you should be able to see the
`Destroy` pinned CTA ( iso short label with the destroy one ), open
control panel and fin new CTA `Permanently delete records`
https://github.com/user-attachments/assets/31ee8738-9d61-4dec-9a1f-41bb6785e018
## TODO
- [ ] Gain granularity within tests to assert the action should be
registered only when filtering by deleted
## Conclusion
Closes https://github.com/twentyhq/core-team-issues/issues/110
Refresh of `objectmetadataitems` was not happening fast enough. Page was
breaking when enabling the feature flag. Instead of not storing worklow
objects in state, we will use the feature flag to block on read. This
way we avoid race conditions
<img width="1511" alt="Capture d’écran 2025-02-04 à 14 11 56"
src="https://github.com/user-attachments/assets/912cc59a-f422-48ab-84b7-7fdd7bbc35c1"
/>
The global record filter refactor will derive everything at runtime from
objectMetadataItemsState, thus removing the need for a filter definition
concept.
Here we don't yet remove available filter definition usage but we
replace the available filter definitions states, we now derive the same
value from objectMetadataItemsState.
This will allow us to progressively remove the usage of the concept of
filter definition, at the end it will then be easy to just remove from
the codebase because nothing will use it anymore.
# Introduction
Avoid having multiple `isDefined` definition across our pacakges
Also avoid importing `isDefined` from `twenty-ui` which exposes a huge
barrel for a such little util function
## In a nutshell
Removed own `isDefined.ts` definition from `twenty-ui` `twenty-front`
and `twenty-server` to move it to `twenty-shared`.
Updated imports for each packages, and added explicit dependencies to
`twenty-shared` if not already in place
Related PR https://github.com/twentyhq/twenty/pull/9941
Fixes
https://discord.com/channels/1130383047699738754/1333822806504247467
In this PR:
- Make the workflow step title input readonly when the visualizer is in
readonly mode
- Make all the fields of the Update Record and Delete Record readonly
when the visualizer is in readonly mode
- Create stories for the Create Record, Updated Record and Delete Record
actions; I'm checking for the default mode and several variants of the
disabled mode
- Set up mocks for the workflows and use them in msw handlers
Follow up:
- We use `readonly` and `disabled` alternatively; these are two
different states when talking about a HTML `<input />` element. I think
we should settle on a single word.
- Refactor the `<WorkflowSingleRecordPicker />` component to behave as
other selects
| Current component | Should look like |
|--------|--------|
| 
| 
|
The property name "hasValidEntrepriseKey" was corrected to
"hasValidEnterpriseKey" across multiple files for consistency and
accuracy. This ensures proper alignment with naming conventions and
avoids potential issues in usage or understanding.
Fixes#9827
Also uncovered a conflict with `@objectType('Relation')` and
`@objectType('relation)`
I don't want to address it in this PR so I will create a followup issue
when we close this but I think there's a confusion between
Relation/RelationMetadata, it's unclear what is what
---------
Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
This PR implements a first real use case, now currentRecordFilters
component state acts as the global record filter reference.
It is set by the view initially and can be reset to view filters state
at any point.
This new state is also modified by two new upsertRecordFilter /
removeRecordFilter hooks that will be drop-in replacement of the actual
upsertCombinedViewFilter and removeCombinediewFilter hooks.
This PR implements the logic to manipulate record filters but only reads
it to make the table find many request, all other features are still
relying on the old view filter implementation.
Advanced filters are ignored because they are hidden and because this
effort is made precisely to allow the completion of the advanced filters
feature.
# Introduction
By initially fixing this Fixes#9381, discovered other behavior that
have been fix.
Overall we encountered a bug that corrupts a workspace and make the
browser + api crash
This issue https://github.com/twentyhq/core-team-issues/issues/25
suggests a refactor that has final save button instead of auto-save
## `labelIdentifierFieldMetadataId` form default value
The default value resulted in being undefined, resulting in react hook
form `labelIdentifierFieldMetadataId` is required field error.
### Fix
Setting default value fallback to `null` as field is `nullable`
## `SettingsDataModelObjectSettingsFormCard` never triggers form
Unless I'm mistaken in production touching any fields within
`SettingsDataModelObjectSettingsFormCard` would never trigger form
submission until you also modify `SettingsDataModelObjectAboutForm`
fields
### Fix
Provide and apply `onblur` that triggers the form on both
`SettingsDataModelObjectSettingsFormCard` inputs
## Wrong default `labelIdentifierFieldMetadataItem` on first page render
When landing on the page for the first time, if a custom
`labelIdentifierFieldMetadataItem` has been set it won't be computed
within the `PreviewCard`.
Occurs when `labelIdentifierFieldMetadataId` form default value is
undefined, due to `any` injection.
### Fix
In the `getLabelIdentifierFieldMetadataItem` check the
`labelIdentifierFieldMetadataIdFormValue` definition, if undefined
fallback to current `objectMetadata` identifier
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Billing portal is created in settings/billing page even if subscription
is canceled, causing server internal error. -> Skip back end request
Bonus : display settings/billing page with disabled button even if
subscription is canceled
---------
Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
Refers #8128
Changes Introduced:
- Added i18n configuration.
- Added a feature flag for localization.
- Enabled language switching based on the flag.
---------
Co-authored-by: Félix Malfait <felix@twenty.com>
In this PR:
- migrate WorkspaceActivationStatus to twenty-shared (and update case to
make FE and BE consistent)
- introduce isWorkspaceActiveOrSuspended in twenty-shared
- refactor the code to use it (when we fetch data on the FE, we want to
keep SUSPENDED workspace working + when we sync workspaces we want it
too)
### Context
- Update /plan-required page to let users get free trial without credit
card plan
- Update usePageChangeEffectNavigateLocation to redirect paused and
canceled subscription (suspended workspace) to /settings/billing page
### To do
- [x] Update usePageChangeEffectNavigateLocation test
- [x] Update ChooseYourPlan sb test
closes#9520
---------
Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
Closestwentyhq/twenty#8240
This PR introduces email verification for non-Microsoft/Google Emails:
## Email Verification SignInUp Flow:
https://github.com/user-attachments/assets/740e9714-5413-4fd8-b02e-ace728ea47ef
The email verification link is sent as part of the
`SignInUpStep.EmailVerification`. The email verification token
validation is handled on a separate page (`AppPath.VerifyEmail`). A
verification email resend can be triggered from both pages.
## Email Verification Flow Screenshots (In Order):



## Sent Email Details (Subject & Template):


### Successful Email Verification Redirect:

### Unsuccessful Email Verification (invalid token, invalid email, token
expired, user does not exist, etc.):

### Force Sign In When Email Not Verified:

# TODOs:
## Sign Up Process
- [x] Introduce server-level environment variable
IS_EMAIL_VERIFICATION_REQUIRED (defaults to false)
- [x] Ensure users joining an existing workspace through an invite are
not required to validate their email
- [x] Generate an email verification token
- [x] Store the token in appToken
- [x] Send email containing the verification link
- [x] Create new email template for email verification
- [x] Create a frontend page to handle verification requests
## Sign In Process
- [x] After verifying user credentials, check if user's email is
verified and prompt to to verify
- [x] Show an option to resend the verification email
## Database
- [x] Rename the `emailVerified` colum on `user` to to `isEmailVerified`
for consistency
## During Deployment
- [x] Run a script/sql query to set `isEmailVerified` to `true` for all
users with a Google/Microsoft email and all users that show an
indication of a valid subscription (e.g. linked credit card)
- I have created a draft migration file below that shows one possible
approach to implementing this change:
```typescript
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateEmailVerifiedForActiveUsers1733318043628
implements MigrationInterface
{
name = 'UpdateEmailVerifiedForActiveUsers1733318043628';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE core."user_email_verified_backup" AS
SELECT id, email, "isEmailVerified"
FROM core."user"
WHERE "deletedAt" IS NULL;
`);
await queryRunner.query(`
-- Update isEmailVerified for users who have been part of workspaces with active subscriptions
UPDATE core."user" u
SET "isEmailVerified" = true
WHERE EXISTS (
-- Check if user has been part of a workspace through userWorkspace table
SELECT 1
FROM core."userWorkspace" uw
JOIN core."workspace" w ON uw."workspaceId" = w.id
WHERE uw."userId" = u.id
-- Check for valid subscription indicators
AND (
w."activationStatus" = 'ACTIVE'
-- Add any other subscription-related conditions here
)
)
AND u."deletedAt" IS NULL;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
UPDATE core."user" u
SET "isEmailVerified" = b."isEmailVerified"
FROM core."user_email_verified_backup" b
WHERE u.id = b.id;
`);
await queryRunner.query(`DROP TABLE core."user_email_verified_backup";`);
}
}
```
---------
Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Eliminated all references to `isSSOEnabled` across the frontend,
backend, and configuration files. This change simplifies the codebase by
removing unnecessary feature flag checks, associated logic, and
environment variables. The SSO feature remains available without
reliance on this flag.
There is a problem of hotkey scope not being passed to the relation
picker used for single record select fields.
Fixed it where we open a single record select.
Replaced user-based parameterization with workspace-focused logic across
seed scripts, mocks, and billing services. Removed redundant `user`
references and standardized to `workspace` to align with updated
business rules. Adjusted mock data and tests to reflect these changes.
Fix https://github.com/twentyhq/twenty/issues/9295
Closes#9260
- Refactored multiple record actions and no selection record actions to
use config file
- Simplified actions registration logic
- Updated tests
The DX is not great when you need to do a lot of database
resets/command.
Should we disable Typescript validation to speed things up? With this
and caching database:reset takes 1min instead of 2 on my machine.
See also: https://github.com/typeorm/typeorm/issues/4136
And #9291 / #9293
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
- Added a new Seeder service to help with custom object seeds
- Added RichTextFieldInput to edit a rich text field directly on the
table, but deactivated it for now.
## Summary
- [x] Remove defaultWorkspace in user
- [x] Remove all occurrence of defaultWorkspace and defaultWorkspaceId
- [x] Improve activate workspace flow
- [x] Improve security on social login
- [x] Add `ImpersonateGuard`
- [x] Allow to use impersonation with couple `User/Workspace`
- [x] Prevent unexpected reload on activate workspace
- [x] Scope login token with workspaceId
Fix https://github.com/twentyhq/twenty/issues/9033#event-15714863042