Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
155 commits
Select commit Hold shift + click to select a range
6c0666d
Add navigation menu item to ALL_METADATA_NAME constant
abdulrahmancodes Jan 18, 2026
7c84ffe
Add NavigationMenuItemEntity to manage navigation menu items in the c…
abdulrahmancodes Jan 18, 2026
c6ae7a8
Refactor import statements in NavigationMenuItemEntity for consistency
abdulrahmancodes Jan 18, 2026
b35a407
Add flat navigation menu item types and constants
abdulrahmancodes Jan 19, 2026
bf7e682
Add migration for NavigationMenuItem entity
abdulrahmancodes Jan 19, 2026
81af80b
Add navigation menu item constants and types
abdulrahmancodes Jan 19, 2026
1333770
Add FlatNavigationMenuItem module and services
abdulrahmancodes Jan 19, 2026
b303cf2
Add navigation menu item migration and validation services
abdulrahmancodes Jan 19, 2026
83a0a74
Enhance workspace migration orchestrator with navigation menu item su…
abdulrahmancodes Jan 19, 2026
90355f2
Add navigation menu item module and utilities
abdulrahmancodes Jan 19, 2026
15b0c90
Add navigation menu item support in optimistically apply utilities
abdulrahmancodes Jan 19, 2026
ba8670f
Add NavigationMenuItem type to GraphQL schema
abdulrahmancodes Jan 19, 2026
66fa866
Add navigation menu item mutations and query support in GraphQL schema
abdulrahmancodes Jan 19, 2026
b684f4f
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-item
abdulrahmancodes Jan 19, 2026
3db4724
feat: add NavigationMenuItemEntity to metadata constants
abdulrahmancodes Jan 19, 2026
cb5237a
refactor: improve code formatting and structure in navigation menu it…
abdulrahmancodes Jan 19, 2026
03b7d6c
fix: update index names in NavigationMenuItemEntity migration and ent…
abdulrahmancodes Jan 19, 2026
a76e956
feat: add integration tests for navigation menu item operations
abdulrahmancodes Jan 19, 2026
4745b87
fix: update favoriteFolderId type to allow null and enhance test cove…
abdulrahmancodes Jan 19, 2026
df91c24
fix: correct formatting in navigation menu item test and utility files
abdulrahmancodes Jan 19, 2026
52a68af
fix: improve error message formatting in createNavigationMenuItem uti…
abdulrahmancodes Jan 19, 2026
0905dd3
fix: reorder imports in failing navigation menu item update test
abdulrahmancodes Jan 19, 2026
a664bf5
fix: reorder imports in delete-navigation-menu-item utility for consi…
abdulrahmancodes Jan 19, 2026
518afaf
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-item
abdulrahmancodes Jan 19, 2026
ae51b87
fix: reorder imports and add QueryNavigationMenuItemArgs type in grap…
abdulrahmancodes Jan 19, 2026
86284bf
test: update snapshots for metadata-related utility tests
abdulrahmancodes Jan 19, 2026
ef9a683
refactor: update validation for navigation menu item inputs
abdulrahmancodes Jan 19, 2026
66a0b0a
feat: enhance validation for navigation menu item position
abdulrahmancodes Jan 19, 2026
24d9f44
fix: update snapshot for failing navigation menu item deletion test
abdulrahmancodes Jan 19, 2026
1398ef5
fix: normalize null checks for workspace member and favorite folder I…
abdulrahmancodes Jan 19, 2026
c4949c2
feat: add navigation menu item GraphQL path to codegen metadata
abdulrahmancodes Jan 20, 2026
5f6a869
feat: implement CRUD operations for navigation menu items in GraphQL
abdulrahmancodes Jan 20, 2026
278fcc9
refactor: migrate favorite deletion to navigation menu item hook
abdulrahmancodes Jan 20, 2026
95fa8ac
feat: add IS_NAVIGATION_MENU_ITEM_ENABLED feature flag
abdulrahmancodes Jan 20, 2026
65f35bf
feat: integrate navigation menu item creation into favorites function…
abdulrahmancodes Jan 20, 2026
3a608c6
refactor: simplify conditional check in useCreateFavorite hook
abdulrahmancodes Jan 20, 2026
a805396
feat: enhance favorite deletion logic with feature flag support
abdulrahmancodes Jan 20, 2026
2cf3287
feat: add hook for updating navigation menu items
abdulrahmancodes Jan 20, 2026
fa90a00
feat: enhance favorite drag-and-drop functionality with navigation me…
abdulrahmancodes Jan 20, 2026
b8a8a15
feat: integrate navigation menu items into favorites data handling
abdulrahmancodes Jan 20, 2026
028721e
feat: add utility functions for transforming navigation menu items to…
abdulrahmancodes Jan 20, 2026
b368c7b
fix: update GraphQL field type for position in navigation menu item i…
abdulrahmancodes Jan 20, 2026
9e13d9a
fix: enhance validation for navigation menu item position checks
abdulrahmancodes Jan 20, 2026
703d111
fix: update GraphQL input types for navigation menu item position
abdulrahmancodes Jan 20, 2026
b014f21
refactor: rename favoriteFolderId to folderId in navigation menu item…
abdulrahmancodes Jan 20, 2026
3d651ab
refactor: rename forWorkspaceMemberId to userWorkspaceId in navigatio…
abdulrahmancodes Jan 20, 2026
3002f63
refactor: update foreign key constraints in navigation menu item migr…
abdulrahmancodes Jan 20, 2026
efee320
refactor: streamline userWorkspaceId assignment in navigation menu it…
abdulrahmancodes Jan 20, 2026
247f161
feat: add userWorkspace relation to navigation menu item metadata
abdulrahmancodes Jan 20, 2026
11492b1
refactor: enhance navigation menu item creation test with userWorkspa…
abdulrahmancodes Jan 20, 2026
eb212e3
feat: add folder relation to navigation menu item entity
abdulrahmancodes Jan 20, 2026
3a15657
refactor: add foreign key constraint for folderId in navigation menu …
abdulrahmancodes Jan 20, 2026
27243da
feat: implement access control for navigation menu items
abdulrahmancodes Jan 20, 2026
0deb17b
refactor: enhance navigation menu item tests with folder creation and…
abdulrahmancodes Jan 20, 2026
cbbf547
feat: enhance navigation menu item retrieval with userWorkspaceId fil…
abdulrahmancodes Jan 20, 2026
45dc76f
fix: update snapshots to reflect changes in navigationMenuItem metadata
abdulrahmancodes Jan 20, 2026
87524bb
refactor: improve navigation menu item creation tests with folder cle…
abdulrahmancodes Jan 20, 2026
dab3a0b
refactor: streamline navigation menu item update logic
abdulrahmancodes Jan 20, 2026
bb456fd
refactor: remove unnecessary cleanup comment in navigation menu item …
abdulrahmancodes Jan 20, 2026
f03b7ce
refactor: improve navigation menu item creation test with current use…
abdulrahmancodes Jan 20, 2026
4675c13
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 20, 2026
17d2e83
refactor: update navigation menu item fields for consistency
abdulrahmancodes Jan 20, 2026
055e82b
test: add IS_NAVIGATION_MENU_ITEM_ENABLED flag to workspace entity ma…
abdulrahmancodes Jan 20, 2026
562b7ba
refactor: update navigation menu item fields for improved clarity
abdulrahmancodes Jan 20, 2026
d33e3ca
refactor: normalize userWorkspaceId in navigation menu item creation
abdulrahmancodes Jan 20, 2026
9c5f15d
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 20, 2026
bfedb6c
refactor: restructure navigation menu item components for improved or…
abdulrahmancodes Jan 20, 2026
0ef5938
feat: add name property to navigation menu item and update related logic
abdulrahmancodes Jan 20, 2026
bc37aa3
refactor: update validation for name property in navigation menu item…
abdulrahmancodes Jan 20, 2026
cc9d6de
feat: add name property to navigationMenuItem entity in migration
abdulrahmancodes Jan 20, 2026
21f6c89
refactor: remove unnecessary validation for name property in navigati…
abdulrahmancodes Jan 20, 2026
ef49898
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-item
abdulrahmancodes Jan 20, 2026
39dd071
refactor: format imports in update-navigation-menu-item.input.ts
abdulrahmancodes Jan 20, 2026
22686b7
refactor: update GraphQL field definitions for name property in navig…
abdulrahmancodes Jan 20, 2026
483b363
refactor: update GraphQL field definitions for optional properties in…
abdulrahmancodes Jan 20, 2026
e4b276d
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 20, 2026
062c62a
feat: implement navigation menu item folder management features
abdulrahmancodes Jan 20, 2026
32b9c70
refactor: remove navigation menu item dependencies from favorites hooks
abdulrahmancodes Jan 20, 2026
77bd11c
refactor: improve validation logic for navigation menu item creation
abdulrahmancodes Jan 20, 2026
1450f97
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 20, 2026
4c15420
fix: update error messages for navigation menu item creation validation
abdulrahmancodes Jan 20, 2026
da88b0a
feat: enforce folder name requirement in navigation menu item creation
abdulrahmancodes Jan 21, 2026
ab262f7
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 21, 2026
3b9e591
feat: add conditional navigation menu item creation to favorites action
abdulrahmancodes Jan 21, 2026
b0ceb4d
refactor: optimize filtering and sorting logic for navigation menu items
abdulrahmancodes Jan 21, 2026
1cf4b1a
chore: update GraphQL types for navigation menu items
abdulrahmancodes Jan 21, 2026
4c7d28b
feat: integrate feature flag for navigation menu item visibility in p…
abdulrahmancodes Jan 21, 2026
da48c69
feat: implement navigation menu item creation logic in standard appli…
abdulrahmancodes Jan 21, 2026
3b10e18
feat: enhance NavigationDrawerOpenedSection to support navigation men…
abdulrahmancodes Jan 21, 2026
4bd6f4d
refactor: improve navigation menu items filtering logic in useNavigat…
abdulrahmancodes Jan 21, 2026
3f91e4f
feat: add orphan navigation menu items support
abdulrahmancodes Jan 21, 2026
5ee380e
fix: ensure async handling in navigation menu item drag and drop logic
abdulrahmancodes Jan 21, 2026
b2005e4
refactor: migrate favorite drag and drop logic to utilize new utility…
abdulrahmancodes Jan 21, 2026
51a1034
refactor: clean up imports and improve component structure in navigat…
abdulrahmancodes Jan 21, 2026
2e0bc0a
fix: restore missing import for Droppable in NavigationMenuItemDroppa…
abdulrahmancodes Jan 21, 2026
86497a5
feat: add viewId property to navigation menu item inputs and entities
abdulrahmancodes Jan 21, 2026
3d2601e
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-item
abdulrahmancodes Jan 21, 2026
dfe09f9
refactor: replace SyncableEntityRequired with SyncableEntity in Navig…
abdulrahmancodes Jan 21, 2026
3ad265f
feat: add viewId column and indexing to navigationMenuItem table
abdulrahmancodes Jan 21, 2026
564081e
feat: include flatViewMaps in workspace migration orchestrator
abdulrahmancodes Jan 21, 2026
22d2b1f
feat: add check constraint to NavigationMenuItemEntity
abdulrahmancodes Jan 21, 2026
92c7bb2
feat: update navigation menu item input structure to include ID
abdulrahmancodes Jan 21, 2026
6f7a1a7
feat: enhance navigation menu item validation with circular dependenc…
abdulrahmancodes Jan 21, 2026
a705582
feat: introduce maximum depth validation for navigation menu items
abdulrahmancodes Jan 21, 2026
366068f
feat: enhance flat navigation menu item management with new utilities…
abdulrahmancodes Jan 21, 2026
cb7bd80
fix: streamline existing items filtering in navigation menu item utility
abdulrahmancodes Jan 21, 2026
7d9500d
refactor: improve navigation menu item validation logic
abdulrahmancodes Jan 21, 2026
7c6e31a
fix: update foreign key constraint for navigation menu item entity
abdulrahmancodes Jan 21, 2026
9fe5f6d
refactor: simplify navigation menu item validation logic
abdulrahmancodes Jan 21, 2026
e828d08
feat: add 'viewId' to flat navigation menu item editable properties
abdulrahmancodes Jan 21, 2026
e917a6b
refactor: enhance validation logic in FlatNavigationMenuItemValidator…
abdulrahmancodes Jan 21, 2026
2256f81
refactor: improve flat navigation menu item replacement logic
abdulrahmancodes Jan 21, 2026
72d3813
feat: add check constraint for target record and object metadata in n…
abdulrahmancodes Jan 21, 2026
88b0db2
feat: add 'viewId' to navigation menu item and input types
abdulrahmancodes Jan 21, 2026
1f07d89
fix: update snapshot tests for navigation menu item validation errors
abdulrahmancodes Jan 21, 2026
8538c20
test: add snapshots for navigation menu item circular dependency vali…
abdulrahmancodes Jan 21, 2026
0f2f9d6
refactor: remove 'viewId' from editable properties and update navigat…
abdulrahmancodes Jan 21, 2026
8a49a42
refactor: simplify existing items retrieval in navigation menu item u…
abdulrahmancodes Jan 21, 2026
124504a
refactor: enhance delete action handling for flat navigation menu items
abdulrahmancodes Jan 21, 2026
cc78ce1
refactor: enhance navigation menu item validation logic
abdulrahmancodes Jan 21, 2026
0a55d17
refactor: remove 'viewId' from UpdateNavigationMenuItemInput type
abdulrahmancodes Jan 21, 2026
70e98ef
refactor: streamline delete action handling for navigation menu items
abdulrahmancodes Jan 21, 2026
5544e88
Revert "refactor: enhance delete action handling for flat navigation …
abdulrahmancodes Jan 21, 2026
edd6476
Revert "refactor: streamline delete action handling for navigation me…
abdulrahmancodes Jan 21, 2026
2527607
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-item
abdulrahmancodes Jan 22, 2026
2d19fee
refactor: enhance delete action handling for navigation menu items
abdulrahmancodes Jan 22, 2026
d63b40a
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 22, 2026
4028faf
refactor: update navigation menu item mutation input type
abdulrahmancodes Jan 22, 2026
c97d38b
refactor: reorganize imports and update feature flag enumeration
abdulrahmancodes Jan 22, 2026
e4f2ff3
fix: update navigation menu item validation error messages
abdulrahmancodes Jan 22, 2026
0406706
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 22, 2026
6f7a47a
feat: add viewId to navigation menu item types and update related hooks
abdulrahmancodes Jan 22, 2026
1810b15
fix: refine navigation menu item validation error reporting
abdulrahmancodes Jan 22, 2026
f99bad1
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 22, 2026
e2c6fee
fix: enhance navigation menu item validation error messages
abdulrahmancodes Jan 22, 2026
3bf440b
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 22, 2026
d150006
fix: correct total error count in navigation menu item validation sna…
abdulrahmancodes Jan 22, 2026
5106a69
Merge branch 'feat/migrate-favorite-to-navigation-menu-item' into fea…
abdulrahmancodes Jan 22, 2026
5f9972d
feat: implement NavigationMenuItemSkeletonLoader for improved loading…
abdulrahmancodes Jan 22, 2026
4dc3759
fix: ensure favoriteFolderId is always set during drag and drop updates
abdulrahmancodes Jan 22, 2026
988eaf3
feat: integrate prefetched navigation menu items into workspace navig…
abdulrahmancodes Jan 22, 2026
d381313
refactor: simplify createFavorite and deleteFavorite functions
abdulrahmancodes Jan 22, 2026
f9c7071
refactor: simplify filtering logic in usePrefetchedFavoritesData hook
abdulrahmancodes Jan 22, 2026
92e7860
refactor: remove unnecessary whitespace in usePrefetchedFavoritesData…
abdulrahmancodes Jan 22, 2026
b0ba052
refactor: improve filtering logic in usePrefetchedNavigationMenuItems…
abdulrahmancodes Jan 22, 2026
869afa6
test: add unit tests for navigation menu item utilities
abdulrahmancodes Jan 22, 2026
065353c
test: add unit tests for favorites utilities
abdulrahmancodes Jan 22, 2026
f8e8e07
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-ite…
abdulrahmancodes Jan 22, 2026
9737877
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-ite…
abdulrahmancodes Jan 22, 2026
201617b
feat: enhance navigation menu item with target record support
abdulrahmancodes Jan 22, 2026
ae0d724
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-ite…
abdulrahmancodes Jan 22, 2026
bf28dff
Merge branch 'main' into feat/migrate-favorite-to-navigation-menu-ite…
abdulrahmancodes Jan 22, 2026
08bcfb8
Add new enum values to FileFolder in GraphQL schema
abdulrahmancodes Jan 22, 2026
5cbb46f
Refactor navigation menu item GraphQL fragments and queries
abdulrahmancodes Jan 22, 2026
cbda6be
Refactor navigation menu item GraphQL schema and related utilities
abdulrahmancodes Jan 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/twenty-front/codegen-metadata.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = {
'./src/modules/workflow/**/graphql/**/*.{ts,tsx}',
'./src/modules/analytics/graphql/**/*.{ts,tsx}',
'./src/modules/object-metadata/graphql/**/*.{ts,tsx}',
'./src/modules/navigation-menu-item/graphql/**/*.{ts,tsx}',
'./src/modules/attachments/graphql/**/*.{ts,tsx}',
'./src/modules/file/graphql/**/*.{ts,tsx}',
'./src/modules/onboarding/graphql/**/*.{ts,tsx}',
Expand Down
239 changes: 239 additions & 0 deletions packages/twenty-front/src/generated-metadata/graphql.ts

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions packages/twenty-front/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,7 @@ export enum FeatureFlagKey {
IS_FILES_FIELD_ENABLED = 'IS_FILES_FIELD_ENABLED',
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
IS_NAVIGATION_MENU_ITEM_ENABLED = 'IS_NAVIGATION_MENU_ITEM_ENABLED',
IS_PAGE_LAYOUT_ENABLED = 'IS_PAGE_LAYOUT_ENABLED',
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED',
Expand Down Expand Up @@ -3055,6 +3056,7 @@ export type NavigationMenuItem = {
position: Scalars['Float'];
targetObjectMetadataId?: Maybe<Scalars['UUID']>;
targetRecordId?: Maybe<Scalars['UUID']>;
targetRecordIdentifier?: Maybe<RecordIdentifier>;
updatedAt: Scalars['DateTime'];
userWorkspaceId?: Maybe<Scalars['UUID']>;
viewId?: Maybe<Scalars['UUID']>;
Expand Down Expand Up @@ -3878,6 +3880,13 @@ export type RatioAggregateConfig = {
optionValue: Scalars['String'];
};

export type RecordIdentifier = {
__typename?: 'RecordIdentifier';
id: Scalars['UUID'];
imageIdentifier?: Maybe<Scalars['String']>;
labelIdentifier: Scalars['String'];
};

export type Relation = {
__typename?: 'Relation';
sourceFieldMetadata: Field;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@ import { Action } from '@/action-menu/actions/components/Action';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { useCreateNavigationMenuItem } from '@/navigation-menu-item/hooks/useCreateNavigationMenuItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated/graphql';

export const AddToFavoritesSingleRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();

const recordId = useSelectedRecordIdOrThrow();

const isNavigationMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
);

const { createFavorite } = useCreateFavorite();
const { createNavigationMenuItem } = useCreateNavigationMenuItem();

const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));

Expand All @@ -20,7 +28,11 @@ export const AddToFavoritesSingleRecordAction = () => {
return;
}

createFavorite(selectedRecord, objectMetadataItem.nameSingular);
if (isNavigationMenuItemEnabled) {
createNavigationMenuItem(selectedRecord, objectMetadataItem.nameSingular);
} else {
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
}
};

return <Action onClick={handleClick} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FOLDER_DROPPABLE_IDS } from '@/ui/layout/draggable-list/utils/folderDroppableIds';

export const FAVORITE_DROPPABLE_IDS = {
ORPHAN_FAVORITES: 'orphan-favorites',
FOLDER_PREFIX: 'folder-',
FOLDER_HEADER_PREFIX: 'folder-header-',
};
FOLDER_PREFIX: FOLDER_DROPPABLE_IDS.FOLDER_PREFIX,
FOLDER_HEADER_PREFIX: FOLDER_DROPPABLE_IDS.FOLDER_HEADER_PREFIX,
} as const;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds';
import { useSortedFavorites } from '@/favorites/hooks/useSortedFavorites';
import { openFavoriteFolderIdsState } from '@/favorites/states/openFavoriteFolderIdsState';
import { calculateNewPosition } from '@/favorites/utils/calculateNewPosition';
import { validateAndExtractFolderId } from '@/favorites/utils/validateAndExtractFolderId';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition';
import { validateAndExtractFolderId } from '@/ui/layout/draggable-list/utils/validateAndExtractFolderId';
import { type OnDragEndResponder } from '@hello-pangea/dnd';
import { useSetRecoilState } from 'recoil';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
Expand Down Expand Up @@ -43,10 +43,14 @@ export const useHandleFavoriteDragAndDrop = () => {
const draggedFavorite = favorites.find((f) => f.id === draggableId);
if (!draggedFavorite) return;

const destinationFolderId = validateAndExtractFolderId(
destination.droppableId,
);
const sourceFolderId = validateAndExtractFolderId(source.droppableId);
const destinationFolderId = validateAndExtractFolderId({
droppableId: destination.droppableId,
orphanDroppableId: FAVORITE_DROPPABLE_IDS.ORPHAN_FAVORITES,
});
const sourceFolderId = validateAndExtractFolderId({
droppableId: source.droppableId,
orphanDroppableId: FAVORITE_DROPPABLE_IDS.ORPHAN_FAVORITES,
});

if (
destination.droppableId.startsWith(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getObjectMetadataNamePluralFromViewId } from '@/favorites/utils/getObjectMetadataNamePluralFromViewId';
import { type View } from '@/views/types/View';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';

describe('getObjectMetadataNamePluralFromViewId', () => {
it('should return namePlural and view for matching objectMetadataId', () => {
const view: Pick<View, 'id' | 'name' | 'objectMetadataId'> = {
id: 'view-id',
name: 'All People',
objectMetadataId:
generatedMockObjectMetadataItems[0]?.id ?? 'metadata-id',
};

const result = getObjectMetadataNamePluralFromViewId(
view,
generatedMockObjectMetadataItems,
);

expect(result.namePlural).toBeDefined();
expect(result.view).toEqual(view);
});

it('should throw error when objectMetadataItem is not found', () => {
const view: Pick<View, 'id' | 'name' | 'objectMetadataId'> = {
id: 'view-id',
name: 'All People',
objectMetadataId: 'non-existent-id',
};

expect(() => {
getObjectMetadataNamePluralFromViewId(
view,
generatedMockObjectMetadataItems,
);
}).toThrow('Object metadata item not found for id non-existent-id');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import { type Favorite } from '@/favorites/types/Favorite';
import { sortFavorites } from '@/favorites/utils/sortFavorites';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
import { type View } from '@/views/types/View';
import { FieldMetadataType } from '~/generated-metadata/graphql';

jest.mock('@/favorites/utils/getObjectMetadataNamePluralFromViewId', () => ({
getObjectMetadataNamePluralFromViewId: jest.fn(
(
view: Pick<View, 'id' | 'name' | 'objectMetadataId'>,
items: ObjectMetadataItem[],
) => {
const item = items.find((item) => item.id === view.objectMetadataId);
return { namePlural: item?.namePlural ?? 'items' };
},
),
}));

jest.mock('twenty-shared/utils', () => {
const actual = jest.requireActual('twenty-shared/utils');
return {
...actual,
getAppPath: jest.fn((path, params, query) => {
const basePath = `/app/objects/${params.objectNamePlural}`;
const viewId = query?.viewId;
if (viewId !== undefined && viewId !== null) {
return `${basePath}?viewId=${viewId}`;
}
return basePath;
}),
};
});

describe('sortFavorites', () => {
const mockView: Pick<View, 'id' | 'name' | 'objectMetadataId' | 'icon'> = {
id: 'view-id',
name: 'All People',
objectMetadataId: 'metadata-id',
icon: 'IconUser',
};

const mockObjectMetadataItem: ObjectMetadataItem = {
id: 'metadata-id',
nameSingular: 'person',
namePlural: 'people',
} as ObjectMetadataItem;

const mockObjectRecord: ObjectRecord = {
__typename: 'ObjectRecord',
id: 'record-id',
name: 'John Doe',
} as ObjectRecord;

const mockObjectRecordIdentifier: ObjectRecordIdentifier = {
id: 'record-id',
name: 'John Doe',
avatarUrl: 'https://example.com/avatar.jpg',
avatarType: 'rounded',
linkToShowPage: '/app/objects/people/record-id',
};

const mockRelationField: FieldMetadataItem = {
name: 'person',
type: FieldMetadataType.RELATION,
relation: {
targetObjectMetadata: {
nameSingular: 'person',
},
},
} as FieldMetadataItem;

const getObjectRecordIdentifierByNameSingular = jest.fn(
(
record: ObjectRecord,
objectNameSingular: string,
): ObjectRecordIdentifier => {
if (objectNameSingular === 'person') {
return mockObjectRecordIdentifier;
}
return {
id: record.id,
name: 'Unknown',
};
},
);

beforeEach(() => {
jest.clearAllMocks();
});

it('should process favorite with viewId', () => {
const favorite = {
id: 'favorite-id',
viewId: 'view-id',
position: 1,
} as unknown as Favorite;

const result = sortFavorites(
[favorite],
[],
getObjectRecordIdentifierByNameSingular,
true,
[mockView],
[mockObjectMetadataItem],
);

expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
id: 'favorite-id',
objectNameSingular: 'view',
Icon: 'IconUser',
});
});

it('should handle favorite with viewId when view is not found', () => {
const favorite = {
id: 'favorite-id',
viewId: 'non-existent-view-id',
position: 1,
} as unknown as Favorite;

const result = sortFavorites(
[favorite],
[],
getObjectRecordIdentifierByNameSingular,
true,
[],
[mockObjectMetadataItem],
);

expect(result).toHaveLength(1);
expect(result[0].objectNameSingular).toBe('view');
});

it('should process favorite with relation field', () => {
const favorite = {
id: 'favorite-id',
person: mockObjectRecord,
position: 2,
} as unknown as Favorite;

const result = sortFavorites(
[favorite],
[mockRelationField],
getObjectRecordIdentifierByNameSingular,
true,
[],
[],
);

expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
id: 'favorite-id',
objectNameSingular: 'person',
labelIdentifier: 'John Doe',
});
});

it('should return empty link when hasLinkToShowPage is false', () => {
const favorite = {
id: 'favorite-id',
person: mockObjectRecord,
position: 2,
} as unknown as Favorite;

const result = sortFavorites(
[favorite],
[mockRelationField],
getObjectRecordIdentifierByNameSingular,
false,
[],
[],
);

expect(result).toHaveLength(1);
expect(result[0].link).toBe('');
});

it('should sort favorites by position', () => {
const favorites = [
{
id: 'favorite-3',
viewId: 'view-id',
position: 3,
},
{
id: 'favorite-1',
viewId: 'view-id',
position: 1,
},
{
id: 'favorite-2',
viewId: 'view-id',
position: 2,
},
] as unknown as Favorite[];

const result = sortFavorites(
favorites,
[],
getObjectRecordIdentifierByNameSingular,
true,
[mockView],
[mockObjectMetadataItem],
);

expect(result).toHaveLength(3);
expect(result[0].id).toBe('favorite-1');
expect(result[1].id).toBe('favorite-2');
expect(result[2].id).toBe('favorite-3');
});

it('should filter out favorites with no viewId and no relation fields', () => {
const favorite = {
id: 'favorite-id',
position: 1,
} as unknown as Favorite;

const result = sortFavorites(
[favorite],
[],
getObjectRecordIdentifierByNameSingular,
true,
[],
[],
);

expect(result).toHaveLength(0);
});
});
Loading