From 6bc36635eb881519a40cf94049abc59d5bc55992 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Wed, 10 Jul 2024 19:43:52 +0200 Subject: [PATCH] Fixed various bugs in activity creation (#6208) - Fixed activity creation in cache - Fixed activity creation in DB, where the relation target was disappearing after creation - Added an option to match root query filter in creation optimistic effect to avoid adding the newly created record in every mounted query in Apollo cache on the same object (which was causing notes to be duplicated on every object in the cache) - Fixed tab list scope id - Fixed various browser console warnings --- .../activities/hooks/useCreateActivityInDB.ts | 83 ++++++++++++++++--- .../timeline/components/Timeline.tsx | 72 ---------------- .../components/TimelineCreateButtonGroup.tsx | 28 ++----- .../components/TimelineActivities.tsx | 2 +- .../triggerCreateRecordsOptimisticEffect.ts | 36 ++++++-- .../hooks/useCreateManyRecords.ts | 4 + .../object-record/hooks/useCreateOneRecord.ts | 7 +- .../RecordDetailRelationRecordsList.tsx | 6 +- .../components/RecordTableBodyDroppable.tsx | 6 +- .../components/RecordTableHeader.tsx | 12 +-- .../components/ShowPageRightContainer.tsx | 6 +- 11 files changed, 135 insertions(+), 127 deletions(-) delete mode 100644 packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx diff --git a/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts b/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts index 961514ede05e..470e9fda030e 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts @@ -3,35 +3,98 @@ import { isNonEmptyArray } from '@sniptt/guards'; import { CREATE_ONE_ACTIVITY_OPERATION_SIGNATURE } from '@/activities/graphql/operation-signatures/CreateOneActivityOperationSignature'; import { ActivityForEditor } from '@/activities/types/ActivityForEditor'; import { ActivityTarget } from '@/activities/types/ActivityTarget'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords'; +import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useApolloClient } from '@apollo/client'; + +import { useRecoilCallback } from 'recoil'; +import { capitalize } from '~/utils/string/capitalize'; export const useCreateActivityInDB = () => { const { createOneRecord: createOneActivity } = useCreateOneRecord({ objectNameSingular: CREATE_ONE_ACTIVITY_OPERATION_SIGNATURE.objectNameSingular, recordGqlFields: CREATE_ONE_ACTIVITY_OPERATION_SIGNATURE.fields, + shouldMatchRootQueryFilter: true, }); const { createManyRecords: createManyActivityTargets } = useCreateManyRecords({ objectNameSingular: CoreObjectNameSingular.ActivityTarget, - skipPostOptmisticEffect: true, + shouldMatchRootQueryFilter: true, + }); + + const { objectMetadataItems } = useObjectMetadataItems(); + + const { objectMetadataItem: objectMetadataItemActivityTarget } = + useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.ActivityTarget, }); - const createActivityInDB = async (activityToCreate: ActivityForEditor) => { - await createOneActivity?.({ - ...activityToCreate, - updatedAt: new Date().toISOString(), + const { objectMetadataItem: objectMetadataItemActivity } = + useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.Activity, }); - const activityTargetsToCreate = activityToCreate.activityTargets ?? []; + const cache = useApolloClient().cache; - if (isNonEmptyArray(activityTargetsToCreate)) { - await createManyActivityTargets(activityTargetsToCreate); - } - }; + const createActivityInDB = useRecoilCallback( + ({ set }) => + async (activityToCreate: ActivityForEditor) => { + const createdActivity = await createOneActivity?.({ + ...activityToCreate, + updatedAt: new Date().toISOString(), + }); + + const activityTargetsToCreate = activityToCreate.activityTargets ?? []; + + if (isNonEmptyArray(activityTargetsToCreate)) { + await createManyActivityTargets(activityTargetsToCreate); + } + + const activityTargetsConnection = getRecordConnectionFromRecords({ + objectMetadataItems, + objectMetadataItem: objectMetadataItemActivityTarget, + records: activityTargetsToCreate.map((activityTarget) => ({ + ...activityTarget, + __typename: capitalize( + objectMetadataItemActivityTarget.nameSingular, + ), + })), + withPageInfo: false, + computeReferences: true, + isRootLevel: false, + }); + + modifyRecordFromCache({ + recordId: createdActivity.id, + cache, + fieldModifiers: { + activityTargets: () => activityTargetsConnection, + }, + objectMetadataItem: objectMetadataItemActivity, + }); + + set(recordStoreFamilyState(createdActivity.id), { + ...createdActivity, + activityTargets: activityTargetsToCreate, + }); + }, + [ + cache, + createManyActivityTargets, + createOneActivity, + objectMetadataItemActivity, + objectMetadataItemActivityTarget, + objectMetadataItems, + ], + ); return { createActivityInDB, diff --git a/packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx b/packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx deleted file mode 100644 index a6b2fd9b07f5..000000000000 --- a/packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; - -import { SkeletonLoader } from '@/activities/components/SkeletonLoader'; -import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup'; -import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState'; -import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; -import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder'; -import { - AnimatedPlaceholderEmptyContainer, - AnimatedPlaceholderEmptySubTitle, - AnimatedPlaceholderEmptyTextContainer, - AnimatedPlaceholderEmptyTitle, - EMPTY_PLACEHOLDER_TRANSITION_PROPS, -} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; -import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; - -import { TimelineItemsContainer } from './TimelineItemsContainer'; - -const StyledMainContainer = styled.div` - align-items: flex-start; - align-self: stretch; - border-top: ${({ theme }) => - useIsMobile() ? `1px solid ${theme.border.color.medium}` : 'none'}; - display: flex; - flex-direction: column; - height: 100%; - - justify-content: center; -`; - -export const Timeline = ({ - targetableObject, - loading, -}: { - targetableObject: ActivityTargetableObject; - loading: boolean; -}) => { - const timelineActivitiesForGroup = useRecoilValue( - timelineActivitiesForGroupState, - ); - - if (loading) { - return ; - } - - if (timelineActivitiesForGroup.length === 0) { - return ( - - - - - Add your first Activity - - - There are no activities associated with this record.{' '} - - - - - ); - } - - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/activities/timeline/components/TimelineCreateButtonGroup.tsx b/packages/twenty-front/src/modules/activities/timeline/components/TimelineCreateButtonGroup.tsx index 21c0c5713014..a4ca491496d0 100644 --- a/packages/twenty-front/src/modules/activities/timeline/components/TimelineCreateButtonGroup.tsx +++ b/packages/twenty-front/src/modules/activities/timeline/components/TimelineCreateButtonGroup.tsx @@ -1,44 +1,30 @@ import { useSetRecoilState } from 'recoil'; import { IconCheckbox, IconNotes, IconPaperclip } from 'twenty-ui'; -import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; -import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { Button } from '@/ui/input/button/components/Button'; import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup'; import { TAB_LIST_COMPONENT_ID } from '@/ui/layout/show-page/components/ShowPageRightContainer'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; -export const TimelineCreateButtonGroup = ({ - targetableObject, -}: { - targetableObject: ActivityTargetableObject; -}) => { +export const TimelineCreateButtonGroup = () => { const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID); const setActiveTabId = useSetRecoilState(activeTabIdState); - const openCreateActivity = useOpenCreateActivityDrawer(); - return (