diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx index 15b40d17f5fe..aa8f2461208d 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx @@ -7,7 +7,6 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; @@ -128,10 +127,8 @@ describe('useActivityTargetObjectRecords', () => { objectMetadataItemsState, ); - const { activityTargetObjectRecords } = useActivityTargetObjectRecords( - task, - CoreObjectNameSingular.Task, - ); + const { activityTargetObjectRecords } = + useActivityTargetObjectRecords(task); return { activityTargetObjectRecords, diff --git a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts index 2fe34d8c2f5e..4b8edec3a2a3 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts @@ -1,4 +1,3 @@ -import { useApolloClient } from '@apollo/client'; import { useRecoilValue } from 'recoil'; import { Nullable } from 'twenty-ui'; @@ -7,46 +6,37 @@ import { Note } from '@/activities/types/Note'; import { NoteTarget } from '@/activities/types/NoteTarget'; import { Task } from '@/activities/types/Task'; import { TaskTarget } from '@/activities/types/TaskTarget'; -import { getJoinObjectNameSingular } from '@/activities/utils/getJoinObjectNameSingular'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { isDefined } from '~/utils/isDefined'; export const useActivityTargetObjectRecords = ( - activity: Task | Note, - objectNameSingular: CoreObjectNameSingular, + activity?: Task | Note, + activityTargets?: NoteTarget[] | TaskTarget[], ) => { const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const activityTargets = - 'noteTargets' in activity && activity.noteTargets + if (!isDefined(activity) && !isDefined(activityTargets)) { + return { activityTargetObjectRecords: [] }; + } + + const targets = activityTargets + ? activityTargets + : activity && 'noteTargets' in activity && activity.noteTargets ? activity.noteTargets - : 'taskTargets' in activity && activity.taskTargets + : activity && 'taskTargets' in activity && activity.taskTargets ? activity.taskTargets : []; - const getRecordFromCache = useGetRecordFromCache({ - objectNameSingular: getJoinObjectNameSingular(objectNameSingular), - }); - - const apolloClient = useApolloClient(); - - const activityTargetObjectRecords = activityTargets + const activityTargetObjectRecords = targets .map>((activityTarget) => { - const activityTargetFromCache = getRecordFromCache< - NoteTarget | TaskTarget - >(activityTarget.id, apolloClient.cache); - - if (!isDefined(activityTargetFromCache)) { - throw new Error( - `Cannot find activity target ${activityTarget.id} in cache, this shouldn't happen.`, - ); + if (!isDefined(activityTarget)) { + throw new Error(`Cannot find activity target`); } const correspondingObjectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => - isDefined(activityTargetFromCache[objectMetadataItem.nameSingular]) && + isDefined(activityTarget[objectMetadataItem.nameSingular]) && ![CoreObjectNameSingular.Note, CoreObjectNameSingular.Task].includes( objectMetadataItem.nameSingular as CoreObjectNameSingular, ), @@ -57,7 +47,7 @@ export const useActivityTargetObjectRecords = ( } const targetObjectRecord = - activityTargetFromCache[correspondingObjectMetadataItem.nameSingular]; + activityTarget[correspondingObjectMetadataItem.nameSingular]; if (!targetObjectRecord) { throw new Error( @@ -66,7 +56,7 @@ export const useActivityTargetObjectRecords = ( } return { - activityTarget: activityTargetFromCache ?? activityTarget, + activityTarget, targetObject: targetObjectRecord ?? undefined, targetObjectMetadataItem: correspondingObjectMetadataItem, }; diff --git a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx index 60494d98bd20..9f56a3c0ceef 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx +++ b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx @@ -35,10 +35,8 @@ export const ActivityTargetsInlineCell = ({ readonly, activityObjectNameSingular, }: ActivityTargetsInlineCellProps) => { - const { activityTargetObjectRecords } = useActivityTargetObjectRecords( - activity, - activityObjectNameSingular, - ); + const { activityTargetObjectRecords } = + useActivityTargetObjectRecords(activity); const { closeInlineCell } = useInlineCell(); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldValue.ts b/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldValue.ts index d63ff4c20758..001cf4ecb06b 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldValue.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/getLabelIdentifierFieldValue.ts @@ -16,14 +16,6 @@ export const getLabelIdentifierFieldValue = ( return `${record.name?.firstName ?? ''} ${record.name?.lastName ?? ''}`; } - if (objectNameSingular === CoreObjectNameSingular.NoteTarget) { - return record.note?.title ?? ''; - } - - if (objectNameSingular === CoreObjectNameSingular.TaskTarget) { - return record.task?.title ?? ''; - } - if (isDefined(labelIdentifierFieldMetadataItem?.name)) { return String(record[labelIdentifierFieldMetadataItem.name]); } diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx index adfaea988958..086fe6b33677 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -1,3 +1,7 @@ +import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; +import { NoteTarget } from '@/activities/types/NoteTarget'; +import { TaskTarget } from '@/activities/types/TaskTarget'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { RecordChip } from '@/object-record/components/RecordChip'; import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus'; import { useRelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRelationFromManyFieldDisplay'; @@ -7,24 +11,74 @@ export const RelationFromManyFieldDisplay = () => { const { fieldValue, fieldDefinition } = useRelationFromManyFieldDisplay(); const { isFocused } = useFieldFocus(); + const { fieldName, objectMetadataNameSingular } = fieldDefinition.metadata; + const relationObjectNameSingular = fieldDefinition?.metadata.relationObjectMetadataNameSingular; + const { activityTargetObjectRecords } = useActivityTargetObjectRecords( + undefined, + fieldValue as NoteTarget[] | TaskTarget[], + ); + if (!fieldValue || !relationObjectNameSingular) { return null; } - return ( - - {fieldValue.map((record) => { - return ( + const isRelationFromActivityTargets = + (fieldName === 'noteTargets' && + objectMetadataNameSingular === CoreObjectNameSingular.Note) || + (fieldName === 'taskTargets' && + objectMetadataNameSingular === CoreObjectNameSingular.Task); + + const isRelationFromManyActivities = + (fieldName === 'noteTargets' && + objectMetadataNameSingular !== CoreObjectNameSingular.Note) || + (fieldName === 'taskTargets' && + objectMetadataNameSingular !== CoreObjectNameSingular.Task); + + if (isRelationFromManyActivities) { + const objectNameSingular = + fieldName === 'noteTargets' + ? CoreObjectNameSingular.Note + : CoreObjectNameSingular.Task; + + const relationFieldName = fieldName === 'noteTargets' ? 'note' : 'task'; + + return ( + + {fieldValue.map((record) => ( + + ))} + + ); + } else if (isRelationFromActivityTargets) { + return ( + + {activityTargetObjectRecords.map((record) => ( + + ))} + + ); + } else { + return ( + + {fieldValue.map((record) => ( - ); - })} - - ); + ))} + + ); + } }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationFromManyFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationFromManyFieldDisplay.perf.stories.tsx index 96054d36a118..2c6b1977a80e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationFromManyFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationFromManyFieldDisplay.perf.stories.tsx @@ -1,5 +1,5 @@ -import { useEffect } from 'react'; import { Meta, StoryObj } from '@storybook/react'; +import { useEffect } from 'react'; import { useSetRecoilState } from 'recoil'; import { ComponentDecorator } from 'twenty-ui'; @@ -94,9 +94,10 @@ type Story = StoryObj; export const Default: Story = {}; +// TODO: optimize this component once we have morph many export const Performance = getProfilingStory({ componentName: 'RelationFromManyFieldDisplay', - averageThresholdInMs: 0.5, + averageThresholdInMs: 1, numberOfRuns: 20, numberOfTestsPerRun: 100, }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts index b9b3e12e8d5b..6e9661a120a2 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts @@ -1,7 +1,10 @@ import { useRecoilValue } from 'recoil'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getObjectMetadataIdentifierFields } from '@/object-metadata/utils/getObjectMetadataIdentifierFields'; +import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { isDefined } from '~/utils/isDefined'; @@ -27,6 +30,16 @@ export const useRecordTableRecordGqlFields = ({ identifierQueryFields[imageIdentifierFieldMetadataItem.name] = true; } + const { objectMetadataItem: noteTargetObjectMetadataItem } = + useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.NoteTarget, + }); + + const { objectMetadataItem: taskTargetObjectMetadataItem } = + useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.TaskTarget, + }); + const recordGqlFields: Record = { id: true, ...Object.fromEntries( @@ -34,18 +47,12 @@ export const useRecordTableRecordGqlFields = ({ ), ...identifierQueryFields, position: true, - noteTargets: { - note: { - id: true, - title: true, - }, - }, - taskTargets: { - task: { - id: true, - title: true, - }, - }, + noteTargets: generateDepthOneRecordGqlFields({ + objectMetadataItem: noteTargetObjectMetadataItem, + }), + taskTargets: generateDepthOneRecordGqlFields({ + objectMetadataItem: taskTargetObjectMetadataItem, + }), }; return recordGqlFields; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts index 658c15cce0dc..97354aa1f2ff 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts @@ -30,7 +30,7 @@ export const notesAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[ - NOTE_STANDARD_FIELD_IDS.body + NOTE_STANDARD_FIELD_IDS.noteTargets ], position: 1, isVisible: true, @@ -39,7 +39,7 @@ export const notesAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[ - NOTE_STANDARD_FIELD_IDS.createdBy + NOTE_STANDARD_FIELD_IDS.body ], position: 2, isVisible: true, @@ -48,12 +48,21 @@ export const notesAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[ - BASE_OBJECT_STANDARD_FIELD_IDS.createdAt + NOTE_STANDARD_FIELD_IDS.createdBy ], position: 3, isVisible: true, size: 150, }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[ + BASE_OBJECT_STANDARD_FIELD_IDS.createdAt + ], + position: 4, + isVisible: true, + size: 150, + }, /* TODO: Add later, since we don't have real-time it probably doesn't work well? { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts index dc6f5fe5ce25..3d9a0ffadf71 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts @@ -49,7 +49,7 @@ export const tasksAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[ - TASK_STANDARD_FIELD_IDS.createdBy + TASK_STANDARD_FIELD_IDS.taskTargets ], position: 3, isVisible: true, @@ -58,7 +58,7 @@ export const tasksAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[ - TASK_STANDARD_FIELD_IDS.dueAt + TASK_STANDARD_FIELD_IDS.createdBy ], position: 4, isVisible: true, @@ -67,7 +67,7 @@ export const tasksAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[ - TASK_STANDARD_FIELD_IDS.assignee + TASK_STANDARD_FIELD_IDS.dueAt ], position: 5, isVisible: true, @@ -76,7 +76,7 @@ export const tasksAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[ - TASK_STANDARD_FIELD_IDS.body + TASK_STANDARD_FIELD_IDS.assignee ], position: 6, isVisible: true, @@ -85,12 +85,21 @@ export const tasksAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[ - BASE_OBJECT_STANDARD_FIELD_IDS.createdAt + TASK_STANDARD_FIELD_IDS.body ], position: 7, isVisible: true, size: 150, }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[ + BASE_OBJECT_STANDARD_FIELD_IDS.createdAt + ], + position: 8, + isVisible: true, + size: 150, + }, /* TODO: Add later, since we don't have real-time it probably doesn't work well? { diff --git a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts index a8aa5b9342c4..e6de81454b3f 100644 --- a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts +++ b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts @@ -78,15 +78,14 @@ export class NoteWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceRelation({ standardId: NOTE_STANDARD_FIELD_IDS.noteTargets, - label: 'Targets', + label: 'Relations', description: 'Note targets', - icon: 'IconCheckbox', + icon: 'IconArrowUpRight', type: RelationMetadataType.ONE_TO_MANY, inverseSideTarget: () => NoteTargetWorkspaceEntity, onDelete: RelationOnDeleteAction.SET_NULL, }) @WorkspaceIsNullable() - @WorkspaceIsSystem() noteTargets: Relation; @WorkspaceRelation({ diff --git a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts index d087d69c6fbd..7ace998603ce 100644 --- a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts +++ b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts @@ -116,15 +116,14 @@ export class TaskWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceRelation({ standardId: TASK_STANDARD_FIELD_IDS.taskTargets, - label: 'Targets', + label: 'Relations', description: 'Task targets', - icon: 'IconCheckbox', + icon: 'IconArrowUpRight', type: RelationMetadataType.ONE_TO_MANY, inverseSideTarget: () => TaskTargetWorkspaceEntity, onDelete: RelationOnDeleteAction.SET_NULL, }) @WorkspaceIsNullable() - @WorkspaceIsSystem() taskTargets: Relation; @WorkspaceRelation({ diff --git a/packages/twenty-server/src/modules/view/services/view.service.ts b/packages/twenty-server/src/modules/view/services/view.service.ts index ed34f8e81dc4..e889db41d112 100644 --- a/packages/twenty-server/src/modules/view/services/view.service.ts +++ b/packages/twenty-server/src/modules/view/services/view.service.ts @@ -23,7 +23,7 @@ export class ViewService { fieldId: string; viewsIds: string[]; positions?: { - [key: string]: number; + [viewId: string]: number; }[]; size?: number; }) {