ressolve #12205
This PR fixes the issue where the record in the command menu was
reopening when clicking the same field again.
https://github.com/user-attachments/assets/52da7b3f-4704-4a9c-8fc4-29534568b0c0
- Added recordId to cells so it can be accessed when
useListenClickOutside is triggered, and compared the previous recordId
with the new one to prevent closing the command menu for the same
record.
- When the field is clicked, we compare the lastRecordId with the new
recordId inside the openRecordInCommandMenu function to avoid reopening
the menu unnecessarily.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Fixes#10177
Modified `usePersistField` to check for deep equality between the value
to persist and the current record store value before sending an update
query.
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)
This PR fixes a edge case where the user tries to create a non-advanced
filter that already exists in advanced filters, from the table header
drodpown.
This was because the hook that handles the creation was checking for
duplicate filters but without discerning between advanced and
non-advanced, and we want to be able to create non-advanced filters no
matter what we have in advanced filters.
Fixes https://github.com/twentyhq/twenty/issues/12316
This PR fixes an edge case where we couldn't apply a filter on the field
metadata item that is used by a kanban.
As this has already been fixed for tables with groups, this PR just uses
the same technique.
Fixes https://github.com/twentyhq/twenty/issues/12311
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Small optimization for faster loading (gaining ~80ms - average time of a
click)
It might seem a little over-engineered but there are a lot of edge cases
and I couldn't find a simpler solution
I also tried to tackle Link Chips but it's more complex so this will be
for another PR
# 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>
This PR refactors all the dropdown content wrapping mechanism across the
entire app.
It refactors the internals of the `Dropdown` component and introduces a
new generic `DropdownContent` component that is a generic wrapper used
for each dropdown.
## Why this PR ?
Because we’ve been experiencing continuous regressions for months on the
dropdown content width, with weird scrolling behaviors in some and not
in others, and every time a solution was found for a particular set of
dropdowns, it broke another set of dropdowns, which wasn’t noticed
because doing the QA of all dropdowns of the app is very difficult for
fixing an apparently small bug.
## Don’t we already have a `DropdownMenu` component ?
Indeed, this new `DropdownContent` is almost like `DropdownMenu` and
took inspiration from it but `DropdownContent` acts as a generic content
container that sets the width of the whole dropdown, whether we have a
menu or not.
## Why don’t we put it directly in Dropdown internals ?
Because the Dropdown component is using a complex logic with floating-ui
middleware to compute its position and size, and for this logic to work
correctly, it cannot be responsible for the “wanted” width of its
content, because the children components, which the dropdown is not
aware of, can request different widths after the dropdown has been
mounted.
A good example with multiple use cases inside the same dropdown can be
found in `AdvancedFilterDropdownFilterInput`
Thus, it is the responsibility of the content of the dropdown to
determine the width it wants to have.
## What is the difference with DropdownMenuItemsContainer ?
We can have multiple `DropdownMenuItemsContainer` in a dropdown,
alongside other components like `DropdownMenuSeparator` or
`DropdownMenuHeader`, and each of those components behaves differently
regarding to its width, paddings, etc. Therefore it is logical that the
`DropdownMenuItemsContainer` cannot be responsible for the whole
dropdown content width, and trying to do so has been the cause of many
regressions for months.
Now `DropdownMenuItemsContainer` is taking a width of `auto` by default,
which is the best to adapt to a parent which has a defined width.
## How do I set the width of my dropdown now ?
By passing a pixel width to the props `widthInPixels` of
`DropdownContent`, which only accepts numbers to avoid any confusion
with `auto` , `100%` or `160px` and other specific width variables.
The `dropdownWidth` props has been removed from `<Dropdown>` to avoid
any confusion.
Also the `DropdownMenuItemsContainer` is now using `auto` as its default
width to fill the available space inside `DropdownContent` .
It is highly recommended to use the enum `GenericDropdownContentWidt` to
define your width.
## Where to use this new `DropdownContent` component ?
There are two main use cases.
If the dropdown content is defined directly inline in the Dropdown
props, then it is recommended to use it here too.
On the other hand if the dropdown content is abstracted in another
component, it’s recommended to use this new component alongside the
others components like `DropdownMenuItemsContainer`.
A good rule of thumb is to place `DropdownContent` where
`DropdownMenuItemsContainer`, `DropdownMenuSearchInput`, etc. are
placed.
## What if I have a custom width ?
Just define a constant like `ICON_PICKER_DROPDOWN_CONTENT_WIDTH` and use
it with the props `widthInPixels` .
Otherwise there’s a `GenericDropdownContentWidth` enum. The default
value being `GenericDropdownContentWidth.Medium` (or 200px), which most
dropdowns use.
## QA
Component | Comment
-- | --
AttachmentDropdown | Fixed overflowing (thanks to DropdownContent)
RecordIndexActionMenuDropdown |
CommandMenuActionMenuDropdown |
SupportDropdown | Fixed overflowing (thanks to DropdownContent)
MessageThreadSubscribersDropdownButton | Removed because unused
FavoriteFolderNavigationDrawerItemDropdown | Set width at Narrow
FavoriteFolderPicker |
ViewPickerOptionDropdown |
PageFavoriteFolderDropdown | Removed because unused
AdvancedFilterAddFilterRuleSelect |
AdvancedFilterAddFilterRuleSelect |
AdvancedFilterFieldSelectMenu |
AdvancedFilterRecordFilterGroupOptionsDropdown |
AdvancedFilterRecordFilterOperanceSelect | Set width at Narrow
AdvancedFilterLogicalOperatorDropdown | Set width at Narrow
AdvancedFilterRecordFilterOptionsDropdown |
AdvancedFilterRootRecordFilterGroup | Fixed broken horizontal scrolling
behavior
AdvancedFilterSubFieldSelectMenu |
AdvancedFilterDropdownFilterInput |
ObjectFilterDropdownBooleanSelect |
ObjectFilterDropdownCountrySelect | Fixed broken menu items container
ObjectFilterDropdownCurrencySelect | Set width to Large
ObjectFilterDropdownFilterInput |
ObjectFilterDropdownOperandDropdown | Fixed width that was not fixed
ObjectFilterDropdownFilterInput | Fixed width that wasn’t the same for
EditableFilterChip
ObjectFilterDropdownOperandSelect | Refactored
ObjectOptionsDropdownRecordGroupFieldsContent | Added missing separator
ObjectOptionDropdownFieldsContent |
ObjectOptionsDropdownHiddenFieldsContent |
ObjectOptionsDropdownLayoutContent |
ObjectOptionsDropdownLayoutOpenInContent |
ObjectOptionsDropdownMenuContent |
ObjectOptionsDropdownRecordGroupFieldsContent |
ObjectOptionsDropdownRecordGroupsContent |
ObjectOptionsDropdownRecordGroupSortContent |
ObjectOptionsDropdownHiddenRecordGroupsContent | Removed unnecessary
DropdownMenuItemsContainer
RecordBoardColumnHeaderAggregateDropdown | Fixed overflowing (thanks to
DropdownContent)
RecordBoardColumnHeaderAggregateDropdownFieldsContent | Fixed
overflowing (thanks to DropdownContent)
RecordBoardColumnHeaderAggregateDropdownMenuContent | Fixed overflowing
(thanks to DropdownContent)
RecordBoardColumnHeaderAggregateDropdownOptionsContent | Fixed
overflowing (thanks to DropdownContent)
MultiItemFieldInput | Fixed overflowing (thanks to DropdownContent)
MultiItemFieldMenuItem |
MultipleRecordPicker | Fixed overflowing (thanks to DropdownContent)
SingleRecordPicker |
RecordTableColumnAggregateDropdownSubmenuContent |
RecordTableColumnAggregateFooterMenuContent |
RecordTableColumnHeadDropdownMenu | Fixed overflowing (thanks to
DropdownContent)
RecordTableHeaderPlusButtonContent |
MultipleSelectDropdown | Broken width fixed
ObjectSortDropdownButton |
RecordDetailRelationRecordsListItem |
ConfigVariableDatabaseInput |
ConfigVariableOptionsDropdownContent |
SettingsObjectFieldActiveActionDropdown | Fixed overflowing (thanks to
DropdownContent)
SettingsObjectFieldDisabledActionDropdown | Set width at Narrow
SettingsObjectSummaryCard | Removed because unused
SettingsDataModelFieldSelectFormOptionRow |
SettingsDataModelNewFieldBreadcrumbDropdown |
SettingsObjectInactiveMenuDropDown |
SettingsRoleAssignementWorkspaceMemberPickerDropdown |
SettingsRolePermissionObjectLevelObjectPickerDropdownContent |
SettingsSecurityApprovedAccessDomainRowDropdownMenu | Couldn’t test
SettingsSecuritySSORowDropdownMenu | Couldn’t test
SettingsAccountsRowDropdownMenu | Fixed overflowing (thanks to
DropdownContent)
SettingsIntegrationDatabaseConnectionSummaryCard | Couldn’t test
SettingsServerlessFunctionTablEnvironmentVariableTableRow | Deactivated
scope
MatchColumnSelectFieldSelectDropdownContent | Removed now unnecessary
width on DropdownMenuItemsContainer
MatchColumnSelectSubFieldSelectDropdownContent |
SubMatchingSelectInput |
CurrencyPickerDropdownSelect |
IconPicker | Fixed overflowing (thanks to DropdownContent)
PhoneCountryPickerDropdownSelect |
Select | Refactored to drilldown wanted width of content, in this case
it’s intended
ExpandedListDropdown |
ShowPageAddButton | Removed because unused
MultiWorkspaceDropdownDefaultComponent |
MultiWorkspaceDropdownThemesComponent |
MultiWorkspaceDropdownWorkspacesListComponent |
AdvancedFilterDropdownButton |
EditableFilterChip |
EditableFilterDropdownButton |
UpdateViewButtonGroup |
ViewBarFilterDropdown |
ViewBarFilterDropdownFieldSelectMenu |
ViewPickerContentCreateMode |
ViewPickerContentEditMode |
ViewPickerListContent |
WorkflowEditTriggerDatabaseEventForm |
WorkflowVariablesDropdownFieldItems |
WorkflowVariablesDropdownObjectItems |
WorkflowVariablesDropdownWorkflowStepItems |
CommandMenuContextChipGroups |
RecordBoardColumnDropdownMenu |
MultiSelectInput |
SelectInput |
CustomSlashMenu |
DropdownMenu | Removed and replaced by DropdownContent
OverlayContainer and around |
<!-- notionvc: 1e23bdb8-2dda-4f8d-a64d-ecc829a768a2 -->
## Miscellaneous
Side notes :
- The `Select` component is now wrapping the `DropdownContent` because
it computes a dynamic width.
- The advanced filter dropdown has been fixed, it was broken when
resizing the window horizontally, we couldn’t scroll. This specific edge
case was taken into account when refactoring the whole dropdown content
system
- As discussed with Nitin, data-select-disable will probably be removed
entirely, so I let it as is, because right now it is not used by the
refactored d&d selection.
- Duplicate separators under DropdownMenuHeader have been removed.
Fixes : https://github.com/twentyhq/twenty/issues/12327
Fixes : https://github.com/twentyhq/core-team-issues/issues/951
# 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>
## 🛠️ What this PR fixes
Fixes#12268
This PR fixes the UI behavior where the "Set as Primary" button was
incorrectly shown for emails or phones that are already marked as
primary. Instead, users now see a bookmark icon indicating the entry as
primary.
## 🎥 Demo
The attached video demonstrates the updated UI where the "Set as
Primary" button is hidden for primary contacts or emails and replaced by
a bookmark icon.
https://github.com/user-attachments/assets/9afcc818-fbb4-4e7c-8fa2-093fdc7d8a26
---------
Co-authored-by: Davinder Kumar <davinder.kumar@intverse.io>
Co-authored-by: Devessier <baptiste@devessier.fr>
This PR changes the way we do automatching in the import feature.
It uses [Fuse.js](https://www.fusejs.io/) to do a fuzzy text search on
fields and sub-fields.
The labels of sub-fields are now derived from the common config constant
we have for sub-fields.
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.
We previously used classnames to exclude elements from the click outside
listener.
With this PR we can now use `data-click-outside-id` instead of
`classNames` to target the elements we want to exclude from the click
outside listener.
We can also add `data-globally-prevent-click-outside` to a component to
globally prevent triggering click outside listeners for other
components. This attribute is especially useful for confirmation modals
and snackbar items.
Fixes#11785:
https://github.com/user-attachments/assets/318baa7e-0f82-4e3a-a447-bf981328462d
Fixes https://github.com/twentyhq/twenty/issues/11566 + translates dates
- Date display bug: We had an issue with date (not date time) display
depending on the timezone the user had selected. The date is stored in
the db as yyyy-mm-dd, eg. 2025-05-01 for **May 1st, 2025**. When
returned this date is formatted in a UTC DateTime at midnight, so
2025-05-1 00:00:00. Then when displaying the date we were converting
this date using the timeZone, so 2025-04-30 17:00:00, thus displaying
**April 30th, 2025**. The fix chosen is that we should not take into
account the timezone for date (not date time!) displays as we always
want to show the same date.
- Date translation: dates were not translated, not in their default
display (_May 1st, 2025_) nor in their relative display (_about a month
ago_). The lib we use for date formatting, date-fns, offers a
translation option with pre-built `Locale`s from their lib.
Unfortunately and surprisingly we cannot just use directly string locale
codes (like `fr-FR`), cf [open issue on
date-fns](https://github.com/date-fns/date-fns/issues/3660). A util was
introduced to offset this by dynamically importing the right date-fns
Locale based on the locale code.
# Introduce focus stack to handle hotkeys
This PR introduces a focus stack to track the order in which the
elements are focused:
- Each focused element has a unique focus id
- When an element is focused, it is pushed on top of the stack
- When an element loses focus, we remove it from the stack
This focus stack is then used to determine which hotkeys are available.
The previous implementation lead to many regressions because of race
conditions, of wrong order of open and close operations and by
overwriting previous states. This implementation should be way more
robust than the previous one.
The new api can be incrementally implemented since it preserves
backwards compatibility by writing to the old hotkey scopes states.
For now, it has been implemented on the modal components.
To test this PR, verify that the shortcuts still work correctly,
especially for the modal components.
This pull request introduces changes to improve handling of nullable
values in link-related data structures and simplifies field value
generation logic. Key updates include adjustments to type definitions,
utility functions, and component logic to support `null` values for
links, along with the removal of the `generateDefaultFieldValue`
function in favor of `generateEmptyFieldValue`.
There will be a few more follow-up Pull Requests.
---
Closes https://github.com/twentyhq/twenty/issues/11844
This PR adds back and fixes the story for RelationFromManyFieldDisplay,
which was broken due to the removal of use-context-selector state.
The story was also setting unnecessary states, we now only keep one
state set in the recoil state.
This PR removes use-context-selector completely, so that any bug
associated with state synchronization between recoil and
use-context-selector disappears.
There might be a slight performance decrease on the table, but since we
have already improved the average performance per line by a lot, and
that the performance bottleneck right now is the fetch more logic and
the windowing solution we use, it is not relevant.
Also the DX has become so hindered by this parallel state logic recently
(think [cache
invalidation](https://martinfowler.com/bliki/TwoHardThings.html)), that
the main benefit we gain from this removal is the DX improvement.
Fixes https://github.com/twentyhq/twenty/issues/12123
Fixes https://github.com/twentyhq/twenty/issues/12109
Fixes https://github.com/twentyhq/twenty/issues/12131
All instances of RecordDetailRelationRecordsListItem are sharing the
same DELETE_RELATION_MODAL_ID, this PR makes the modal ID unique for
each item.