Feat/activity optimistic activities (#4009)
* Fix naming * Fixed cache.evict bug for relation target deletion * Fixed cascade delete activity targets * Working version * Fix * fix * WIP * Fixed optimistic effect target inline cell * Removed openCreateActivityDrawer v1 * Ok for timeline * Removed console.log * Fix update record optimistic effect * Refactored activity queries into useActivities for everything * Fixed bugs * Cleaned * Fix lint --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,120 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useActivityTargetsForTargetableObjects } from '@/activities/hooks/useActivityTargetsForTargetableObjects';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
export const useActivities = ({
|
||||
targetableObjects,
|
||||
activitiesFilters,
|
||||
activitiesOrderByVariables,
|
||||
skip,
|
||||
skipActivityTargets,
|
||||
}: {
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
activitiesFilters: ObjectRecordQueryFilter;
|
||||
activitiesOrderByVariables: OrderByField;
|
||||
skip?: boolean;
|
||||
skipActivityTargets?: boolean;
|
||||
}) => {
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
const { makeActivityWithoutConnection } = useActivityConnectionUtils();
|
||||
|
||||
const {
|
||||
activityTargets,
|
||||
loadingActivityTargets,
|
||||
initialized: initializedActivityTargets,
|
||||
} = useActivityTargetsForTargetableObjects({
|
||||
targetableObjects,
|
||||
skip: skipActivityTargets || skip,
|
||||
});
|
||||
|
||||
const activityIds = activityTargets
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString)
|
||||
.toSorted(sortByAscString);
|
||||
|
||||
const activityTargetsFound =
|
||||
initializedActivityTargets && isNonEmptyArray(activityTargets);
|
||||
|
||||
const filter: ObjectRecordQueryFilter = {
|
||||
id: activityTargetsFound
|
||||
? {
|
||||
in: activityIds,
|
||||
}
|
||||
: undefined,
|
||||
...activitiesFilters,
|
||||
};
|
||||
|
||||
const skipActivities =
|
||||
skip ||
|
||||
(!skipActivityTargets &&
|
||||
(!initializedActivityTargets || !activityTargetsFound));
|
||||
|
||||
const { records: activitiesWithConnection, loading: loadingActivities } =
|
||||
useFindManyRecords<Activity>({
|
||||
skip: skipActivities,
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
filter,
|
||||
orderBy: activitiesOrderByVariables,
|
||||
onCompleted: useRecoilCallback(
|
||||
({ set }) =>
|
||||
(data) => {
|
||||
if (!initialized) {
|
||||
setInitialized(true);
|
||||
}
|
||||
|
||||
const activities = getRecordsFromRecordConnection({
|
||||
recordConnection: data,
|
||||
});
|
||||
|
||||
for (const activity of activities) {
|
||||
set(recordStoreFamilyState(activity.id), activity);
|
||||
}
|
||||
},
|
||||
[initialized],
|
||||
),
|
||||
});
|
||||
|
||||
const loading = loadingActivities || loadingActivityTargets;
|
||||
|
||||
// TODO: fix connection in relation => automatically change to an array
|
||||
const activities = activitiesWithConnection
|
||||
?.map(makeActivityWithoutConnection as any)
|
||||
.map(({ activity }: any) => activity);
|
||||
|
||||
const noActivities =
|
||||
(!activityTargetsFound && !skipActivityTargets && initialized) ||
|
||||
(initialized && !loading && !isNonEmptyArray(activities));
|
||||
|
||||
useEffect(() => {
|
||||
if (skipActivities || noActivities) {
|
||||
setInitialized(true);
|
||||
}
|
||||
}, [
|
||||
activities,
|
||||
initialized,
|
||||
loading,
|
||||
noActivities,
|
||||
skipActivities,
|
||||
skipActivityTargets,
|
||||
]);
|
||||
|
||||
return {
|
||||
activities,
|
||||
loading,
|
||||
initialized,
|
||||
noActivities,
|
||||
};
|
||||
};
|
||||
@ -1,34 +1,26 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
|
||||
const QUERY_DEPTH_TO_GET_ACTIVITY_TARGET_RELATIONS = 3;
|
||||
|
||||
export const useActivityById = ({ activityId }: { activityId: string }) => {
|
||||
const setEntityFields = useSetRecoilState(recordStoreFamilyState(activityId));
|
||||
|
||||
const { makeActivityWithoutConnection } = useActivityConnectionUtils();
|
||||
|
||||
const { record: activityWithConnections } = useFindOneRecord({
|
||||
// TODO: fix connection in relation => automatically change to an array
|
||||
const { record: activityWithConnections, loading } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
objectRecordId: activityId,
|
||||
skip: !activityId,
|
||||
onCompleted: (activityWithConnections: any) => {
|
||||
const { activity } = makeActivityWithoutConnection(
|
||||
activityWithConnections,
|
||||
);
|
||||
|
||||
setEntityFields(activity);
|
||||
},
|
||||
depth: QUERY_DEPTH_TO_GET_ACTIVITY_TARGET_RELATIONS,
|
||||
});
|
||||
|
||||
const { activity } = makeActivityWithoutConnection(activityWithConnections);
|
||||
const { activity } = activityWithConnections
|
||||
? makeActivityWithoutConnection(activityWithConnections as any)
|
||||
: { activity: null };
|
||||
|
||||
return {
|
||||
activity,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActivityTargetObjectRecord } from '@/activities/types/ActivityTargetObject';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
@ -16,7 +17,7 @@ export const useActivityTargetObjectRecords = ({
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const { records: activityTargets, loading: loadingActivityTargets } =
|
||||
useFindManyRecords({
|
||||
useFindManyRecords<ActivityTarget>({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
skip: !isNonEmptyString(activityId),
|
||||
filter: {
|
||||
@ -27,7 +28,7 @@ export const useActivityTargetObjectRecords = ({
|
||||
});
|
||||
|
||||
const activityTargetObjectRecords = activityTargets
|
||||
.map<Nullable<ActivityTargetObjectRecord>>((activityTarget) => {
|
||||
.map<Nullable<ActivityTargetWithTargetRecord>>((activityTarget) => {
|
||||
const correspondingObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
isDefined(activityTarget[objectMetadataItem.nameSingular]) &&
|
||||
@ -39,8 +40,8 @@ export const useActivityTargetObjectRecords = ({
|
||||
}
|
||||
|
||||
return {
|
||||
activityTargetRecord: activityTarget,
|
||||
targetObjectRecord:
|
||||
activityTarget: activityTarget,
|
||||
targetObject:
|
||||
activityTarget[correspondingObjectMetadataItem.nameSingular],
|
||||
targetObjectMetadataItem: correspondingObjectMetadataItem,
|
||||
targetObjectNameSingular: correspondingObjectMetadataItem.nameSingular,
|
||||
|
||||
@ -22,6 +22,9 @@ export const useActivityTargetsForTargetableObject = ({
|
||||
|
||||
const skipRequest = !isNonEmptyString(targetableObjectId);
|
||||
|
||||
// TODO: We want to optimistically remove from this request
|
||||
// If we are on a show page and we remove the current show page object corresponding activity target
|
||||
// See also if we need to update useTimelineActivities
|
||||
const { records: activityTargets, loading: loadingActivityTargets } =
|
||||
useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetsFilter } from '@/activities/utils/getActivityTargetsFilter';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
|
||||
export const useActivityTargetsForTargetableObjects = ({
|
||||
targetableObjects,
|
||||
skip,
|
||||
}: {
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
skip?: boolean;
|
||||
}) => {
|
||||
const activityTargetsFilter = getActivityTargetsFilter({
|
||||
targetableObjects: targetableObjects,
|
||||
});
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
// TODO: We want to optimistically remove from this request
|
||||
// If we are on a show page and we remove the current show page object corresponding activity target
|
||||
// See also if we need to update useTimelineActivities
|
||||
const { records: activityTargets, loading: loadingActivityTargets } =
|
||||
useFindManyRecords({
|
||||
skip,
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
filter: activityTargetsFilter,
|
||||
onCompleted: () => {
|
||||
if (!initialized) {
|
||||
setInitialized(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
activityTargets: activityTargets as ActivityTarget[],
|
||||
loadingActivityTargets,
|
||||
initialized,
|
||||
};
|
||||
};
|
||||
@ -1,10 +1,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useAttachRelationInBothDirections } from '@/activities/hooks/useAttachRelationInBothDirections';
|
||||
import { useInjectIntoActivityTargetInlineCellCache } from '@/activities/inline-cell/hooks/useInjectIntoActivityTargetInlineCellCache';
|
||||
import { useInjectIntoTimelineActivitiesQueryAfterDrawerMount } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQueryAfterDrawerMount';
|
||||
import { Activity, ActivityType } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
@ -14,6 +12,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useCreateManyRecordsInCache } from '@/object-record/hooks/useCreateManyRecordsInCache';
|
||||
import { useCreateOneRecordInCache } from '@/object-record/hooks/useCreateOneRecordInCache';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
|
||||
export const useCreateActivityInCache = () => {
|
||||
const { createManyRecordsInCache: createManyActivityTargetsInCache } =
|
||||
@ -28,46 +27,36 @@ export const useCreateActivityInCache = () => {
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { record: workspaceMemberRecord } = useFindOneRecord({
|
||||
const { record: currentWorkspaceMemberRecord } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
objectRecordId: currentWorkspaceMember?.id,
|
||||
depth: 3,
|
||||
});
|
||||
|
||||
const { injectIntoTimelineActivitiesQueryAfterDrawerMount } =
|
||||
useInjectIntoTimelineActivitiesQueryAfterDrawerMount();
|
||||
|
||||
const { injectIntoActivityTargetInlineCellCache } =
|
||||
useInjectIntoActivityTargetInlineCellCache();
|
||||
|
||||
const {
|
||||
attachRelationInBothDirections:
|
||||
attachRelationSourceRecordToItsRelationTargetRecordsAndViceVersaInCache,
|
||||
} = useAttachRelationInBothDirections();
|
||||
const { attachRelationInBothDirections } =
|
||||
useAttachRelationInBothDirections();
|
||||
|
||||
const createActivityInCache = ({
|
||||
type,
|
||||
targetableObjects,
|
||||
timelineTargetableObject,
|
||||
assigneeId,
|
||||
customAssignee,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
timelineTargetableObject: ActivityTargetableObject;
|
||||
assigneeId?: string;
|
||||
customAssignee?: WorkspaceMember;
|
||||
}) => {
|
||||
const activityId = v4();
|
||||
|
||||
const createdActivityInCache = createOneActivityInCache({
|
||||
id: activityId,
|
||||
author: workspaceMemberRecord,
|
||||
authorId: workspaceMemberRecord?.id,
|
||||
assignee: !assigneeId ? workspaceMemberRecord : undefined,
|
||||
assigneeId:
|
||||
assigneeId ?? isNonEmptyString(workspaceMemberRecord?.id)
|
||||
? workspaceMemberRecord?.id
|
||||
: undefined,
|
||||
type: type,
|
||||
author: currentWorkspaceMemberRecord,
|
||||
authorId: currentWorkspaceMemberRecord?.id,
|
||||
assignee: customAssignee ?? currentWorkspaceMemberRecord,
|
||||
assigneeId: customAssignee?.id ?? currentWorkspaceMemberRecord?.id,
|
||||
type,
|
||||
});
|
||||
|
||||
const activityTargetsToCreate =
|
||||
@ -80,18 +69,12 @@ export const useCreateActivityInCache = () => {
|
||||
activityTargetsToCreate,
|
||||
);
|
||||
|
||||
injectIntoTimelineActivitiesQueryAfterDrawerMount({
|
||||
activityToInject: createdActivityInCache,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
timelineTargetableObject,
|
||||
});
|
||||
|
||||
injectIntoActivityTargetInlineCellCache({
|
||||
activityId,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
attachRelationSourceRecordToItsRelationTargetRecordsAndViceVersaInCache({
|
||||
attachRelationInBothDirections({
|
||||
sourceRecord: createdActivityInCache,
|
||||
fieldNameOnSourceRecord: 'activityTargets',
|
||||
sourceObjectNameSingular: CoreObjectNameSingular.Activity,
|
||||
|
||||
@ -0,0 +1,133 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetsFilter } from '@/activities/utils/getActivityTargetsFilter';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
// TODO: create a generic hook from this
|
||||
export const useInjectIntoActivitiesQuery = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache: overwriteFindManyActivitiesInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivityTargetsQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivitiesQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache:
|
||||
overwriteFindManyActivityTargetsQueryInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const injectActivitiesQueries = ({
|
||||
activityToInject,
|
||||
activityTargetsToInject,
|
||||
targetableObjects,
|
||||
activitiesFilters,
|
||||
activitiesOrderByVariables,
|
||||
}: {
|
||||
activityToInject: Activity;
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
activitiesFilters: ObjectRecordQueryFilter;
|
||||
activitiesOrderByVariables: OrderByField;
|
||||
}) => {
|
||||
const newActivity = {
|
||||
...activityToInject,
|
||||
__typename: 'Activity',
|
||||
};
|
||||
|
||||
const findManyActivitiyTargetsQueryFilter = getActivityTargetsFilter({
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const findManyActivitiyTargetsQueryVariables = {
|
||||
filter: findManyActivitiyTargetsQueryFilter,
|
||||
};
|
||||
|
||||
const existingActivityTargets = readFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: findManyActivitiyTargetsQueryVariables,
|
||||
});
|
||||
|
||||
const newActivityTargets = [
|
||||
...existingActivityTargets,
|
||||
...activityTargetsToInject,
|
||||
];
|
||||
|
||||
const existingActivityIds = existingActivityTargets
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const currentFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
id: {
|
||||
in: existingActivityIds.toSorted(sortByAscString),
|
||||
},
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
const existingActivities = readFindManyActivitiesQueryInCache({
|
||||
queryVariables: currentFindManyActivitiesQueryVariables,
|
||||
});
|
||||
|
||||
const nextActivityIds = [...existingActivityIds, newActivity.id];
|
||||
|
||||
const nextFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
id: {
|
||||
in: nextActivityIds.toSorted(sortByAscString),
|
||||
},
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
overwriteFindManyActivityTargetsQueryInCache({
|
||||
objectRecordsToOverwrite: newActivityTargets,
|
||||
queryVariables: findManyActivitiyTargetsQueryVariables,
|
||||
});
|
||||
|
||||
const newActivities = [newActivity, ...existingActivities];
|
||||
|
||||
overwriteFindManyActivitiesInCache({
|
||||
objectRecordsToOverwrite: newActivities,
|
||||
queryVariables: nextFindManyActivitiesQueryVariables,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectActivitiesQueries,
|
||||
};
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
@ -8,13 +10,26 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope
|
||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||
|
||||
export const useOpenActivityRightDrawer = () => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
const { openRightDrawer, isRightDrawerOpen, rightDrawerPage } =
|
||||
useRightDrawer();
|
||||
const [viewableActivityId, setViewableActivityId] = useRecoilState(
|
||||
viewableActivityIdState,
|
||||
);
|
||||
const [, setActivityInDrawer] = useRecoilState(activityInDrawerState);
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
return (activityId: string) => {
|
||||
return (activity: Activity) => {
|
||||
if (
|
||||
isRightDrawerOpen &&
|
||||
rightDrawerPage === RightDrawerPages.EditActivity &&
|
||||
viewableActivityId === activity.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableActivityId(activityId);
|
||||
setViewableActivityId(activity.id);
|
||||
setActivityInDrawer(activity);
|
||||
openRightDrawer(RightDrawerPages.EditActivity);
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,102 +1,69 @@
|
||||
import { useCallback } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { Activity, ActivityType } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useCreateActivityInCache } from '@/activities/hooks/useCreateActivityInCache';
|
||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
||||
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
|
||||
import { activityTargetableEntityArrayState } from '../states/activityTargetableEntityArrayState';
|
||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
import { flattenTargetableObjectsAndTheirRelatedTargetableObjects } from '../utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects';
|
||||
|
||||
export const useOpenCreateActivityDrawer = () => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const { createManyRecords: createManyActivityTargets } =
|
||||
useCreateManyRecords<ActivityTarget>({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
const { createOneRecord: createOneActivity } = useCreateOneRecord<Activity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { createActivityInCache } = useCreateActivityInCache();
|
||||
|
||||
const [, setActivityTargetableEntityArray] = useRecoilState(
|
||||
activityTargetableEntityArrayState,
|
||||
);
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
|
||||
return useCallback(
|
||||
async ({
|
||||
const setIsCreatingActivity = useSetRecoilState(isActivityInCreateModeState);
|
||||
|
||||
const setTemporaryActivityForEditor = useSetRecoilState(
|
||||
temporaryActivityForEditorState,
|
||||
);
|
||||
|
||||
const setActivityInDrawer = useSetRecoilState(activityInDrawerState);
|
||||
|
||||
const [, setIsUpsertingActivityInDB] = useRecoilState(
|
||||
isUpsertingActivityInDBState,
|
||||
);
|
||||
|
||||
const openCreateActivityDrawer = async ({
|
||||
type,
|
||||
targetableObjects,
|
||||
customAssignee,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
customAssignee?: WorkspaceMember;
|
||||
}) => {
|
||||
const { createdActivityInCache } = createActivityInCache({
|
||||
type,
|
||||
targetableObjects,
|
||||
assigneeId,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects?: ActivityTargetableObject[];
|
||||
assigneeId?: string;
|
||||
}) => {
|
||||
const flattenedTargetableObjects = targetableObjects
|
||||
? flattenTargetableObjectsAndTheirRelatedTargetableObjects(
|
||||
targetableObjects,
|
||||
)
|
||||
: [];
|
||||
customAssignee,
|
||||
});
|
||||
|
||||
const createdActivity = await createOneActivity?.({
|
||||
authorId: currentWorkspaceMember?.id,
|
||||
assigneeId:
|
||||
assigneeId ?? isNonEmptyString(currentWorkspaceMember?.id)
|
||||
? currentWorkspaceMember?.id
|
||||
: undefined,
|
||||
type: type,
|
||||
});
|
||||
setActivityInDrawer(createdActivityInCache);
|
||||
setTemporaryActivityForEditor(createdActivityInCache);
|
||||
setIsCreatingActivity(true);
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableActivityId(createdActivityInCache.id);
|
||||
setActivityTargetableEntityArray(targetableObjects ?? []);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
setIsUpsertingActivityInDB(false);
|
||||
};
|
||||
|
||||
if (!createdActivity) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activityTargetsToCreate = flattenedTargetableObjects.map(
|
||||
(targetableObject) => {
|
||||
const targetableObjectFieldIdName =
|
||||
getActivityTargetObjectFieldIdName({
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
return {
|
||||
[targetableObjectFieldIdName]: targetableObject.id,
|
||||
activityId: createdActivity.id,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
if (isNonEmptyArray(activityTargetsToCreate)) {
|
||||
await createManyActivityTargets(activityTargetsToCreate);
|
||||
}
|
||||
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableActivityId(createdActivity.id);
|
||||
setActivityTargetableEntityArray(targetableObjects ?? []);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
},
|
||||
[
|
||||
openRightDrawer,
|
||||
setActivityTargetableEntityArray,
|
||||
setHotkeyScope,
|
||||
setViewableActivityId,
|
||||
createOneActivity,
|
||||
createManyActivityTargets,
|
||||
currentWorkspaceMember,
|
||||
],
|
||||
);
|
||||
return openCreateActivityDrawer;
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
@ -8,8 +9,6 @@ import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from './useOpenCreateActivityDrawer';
|
||||
|
||||
export const useOpenCreateActivityDrawerForSelectedRowIds = (
|
||||
recordTableId: string,
|
||||
) => {
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useCreateActivityInCache } from '@/activities/hooks/useCreateActivityInCache';
|
||||
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
||||
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
|
||||
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
|
||||
export const useOpenCreateActivityDrawerV2 = () => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { createActivityInCache } = useCreateActivityInCache();
|
||||
|
||||
const [, setActivityTargetableEntityArray] = useRecoilState(
|
||||
activityTargetableEntityArrayState,
|
||||
);
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
|
||||
const setIsCreatingActivity = useSetRecoilState(isCreatingActivityState);
|
||||
|
||||
const setTemporaryActivityForEditor = useSetRecoilState(
|
||||
temporaryActivityForEditorState,
|
||||
);
|
||||
|
||||
const openCreateActivityDrawer = async ({
|
||||
type,
|
||||
targetableObjects,
|
||||
timelineTargetableObject,
|
||||
assigneeId,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
timelineTargetableObject: ActivityTargetableObject;
|
||||
assigneeId?: string;
|
||||
}) => {
|
||||
const { createdActivityInCache } = createActivityInCache({
|
||||
type,
|
||||
targetableObjects,
|
||||
timelineTargetableObject,
|
||||
assigneeId,
|
||||
});
|
||||
|
||||
setTemporaryActivityForEditor(createdActivityInCache);
|
||||
setIsCreatingActivity(true);
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableActivityId(createdActivityInCache.id);
|
||||
setActivityTargetableEntityArray(targetableObjects ?? []);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
};
|
||||
|
||||
return openCreateActivityDrawer;
|
||||
};
|
||||
@ -0,0 +1,134 @@
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetsFilter } from '@/activities/utils/getActivityTargetsFilter';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
export const useRemoveFromActivitiesQueries = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache: overwriteFindManyActivitiesInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivityTargetsQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivitiesQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache:
|
||||
overwriteFindManyActivityTargetsQueryInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const removeFromActivitiesQueries = ({
|
||||
activityIdToRemove,
|
||||
activityTargetsToRemove,
|
||||
targetableObjects,
|
||||
activitiesFilters,
|
||||
activitiesOrderByVariables,
|
||||
}: {
|
||||
activityIdToRemove: string;
|
||||
activityTargetsToRemove: ActivityTarget[];
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
activitiesFilters?: ObjectRecordQueryFilter;
|
||||
activitiesOrderByVariables?: OrderByField;
|
||||
}) => {
|
||||
const findManyActivitiyTargetsQueryFilter = getActivityTargetsFilter({
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const existingActivityTargetsForTargetableObject =
|
||||
readFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: findManyActivitiyTargetsQueryFilter,
|
||||
});
|
||||
|
||||
const newActivityTargetsForTargetableObject = isNonEmptyArray(
|
||||
activityTargetsToRemove,
|
||||
)
|
||||
? existingActivityTargetsForTargetableObject.filter(
|
||||
(existingActivityTarget) =>
|
||||
activityTargetsToRemove.some(
|
||||
(activityTargetToRemove) =>
|
||||
activityTargetToRemove.id !== existingActivityTarget.id,
|
||||
),
|
||||
)
|
||||
: existingActivityTargetsForTargetableObject;
|
||||
|
||||
overwriteFindManyActivityTargetsQueryInCache({
|
||||
objectRecordsToOverwrite: newActivityTargetsForTargetableObject,
|
||||
queryVariables: findManyActivitiyTargetsQueryFilter,
|
||||
});
|
||||
|
||||
const existingActivityIds = existingActivityTargetsForTargetableObject
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const currentFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
id: {
|
||||
in: existingActivityIds.toSorted(sortByAscString),
|
||||
},
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
const existingActivities = readFindManyActivitiesQueryInCache({
|
||||
queryVariables: currentFindManyActivitiesQueryVariables,
|
||||
});
|
||||
|
||||
const activityIdsAfterRemoval = existingActivityIds.filter(
|
||||
(existingActivityId) => existingActivityId !== activityIdToRemove,
|
||||
);
|
||||
|
||||
const nextFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
id: {
|
||||
in: activityIdsAfterRemoval.toSorted(sortByAscString),
|
||||
},
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
const newActivities = existingActivities.filter(
|
||||
(existingActivity) => existingActivity.id !== activityIdToRemove,
|
||||
);
|
||||
|
||||
overwriteFindManyActivitiesInCache({
|
||||
objectRecordsToOverwrite: newActivities,
|
||||
queryVariables: nextFindManyActivitiesQueryVariables,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
removeFromActivitiesQueries,
|
||||
};
|
||||
};
|
||||
@ -1,14 +1,21 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
|
||||
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
|
||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||
import { useInjectIntoTimelineActivitiesQueries } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQueries';
|
||||
import { timelineTargetableObjectState } from '@/activities/timeline/states/timelineTargetableObjectState';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
|
||||
// TODO: create a generic way to have records only in cache for create mode and delete them afterwards ?
|
||||
export const useUpsertActivity = () => {
|
||||
const [isCreatingActivity, setIsCreatingActivity] = useRecoilState(
|
||||
isCreatingActivityState,
|
||||
const [isActivityInCreateMode, setIsActivityInCreateMode] = useRecoilState(
|
||||
isActivityInCreateModeState,
|
||||
);
|
||||
|
||||
const { updateOneRecord: updateOneActivity } = useUpdateOneRecord<Activity>({
|
||||
@ -17,26 +24,67 @@ export const useUpsertActivity = () => {
|
||||
|
||||
const { createActivityInDB } = useCreateActivityInDB();
|
||||
|
||||
const upsertActivity = ({
|
||||
const [, setIsUpsertingActivityInDB] = useRecoilState(
|
||||
isUpsertingActivityInDBState,
|
||||
);
|
||||
|
||||
const setActivityInDrawer = useSetRecoilState(activityInDrawerState);
|
||||
|
||||
const timelineTargetableObject = useRecoilValue(
|
||||
timelineTargetableObjectState,
|
||||
);
|
||||
|
||||
const { injectIntoTimelineActivitiesQueries } =
|
||||
useInjectIntoTimelineActivitiesQueries();
|
||||
|
||||
const { makeActivityWithConnection } = useActivityConnectionUtils();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const upsertActivity = async ({
|
||||
activity,
|
||||
input,
|
||||
}: {
|
||||
activity: Activity;
|
||||
input: Partial<Activity>;
|
||||
}) => {
|
||||
if (isCreatingActivity) {
|
||||
createActivityInDB({
|
||||
setIsUpsertingActivityInDB(true);
|
||||
|
||||
if (isActivityInCreateMode) {
|
||||
const activityToCreate: Activity = {
|
||||
...activity,
|
||||
...input,
|
||||
};
|
||||
|
||||
const { activityWithConnection } =
|
||||
makeActivityWithConnection(activityToCreate);
|
||||
|
||||
// Call optimistic effects
|
||||
if (timelineTargetableObject) {
|
||||
injectIntoTimelineActivitiesQueries({
|
||||
timelineTargetableObject: timelineTargetableObject,
|
||||
activityToInject: activityWithConnection,
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
});
|
||||
}
|
||||
|
||||
await createActivityInDB(activityToCreate);
|
||||
|
||||
await apolloClient.refetchQueries({
|
||||
include: ['FindManyActivities', 'FindManyActivityTargets'],
|
||||
});
|
||||
|
||||
setIsCreatingActivity(false);
|
||||
setActivityInDrawer(activityToCreate);
|
||||
|
||||
setIsActivityInCreateMode(false);
|
||||
} else {
|
||||
updateOneActivity?.({
|
||||
await updateOneActivity?.({
|
||||
idToUpdate: activity.id,
|
||||
updateOneRecordInput: input,
|
||||
});
|
||||
}
|
||||
|
||||
setIsUpsertingActivityInDB(false);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user