Refactor/finish activities optimistic (#4106)
* Finished optimistic effects * Fixed tests * Added unit test on useActivityConnectionUtils to prepare for refactor * Fixed console.log
This commit is contained in:
@ -0,0 +1,119 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||
import { Comment } from '@/activities/types/Comment';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
|
||||
const mockActivityWithConnectionRelation = {
|
||||
activityTargets: {
|
||||
edges: [
|
||||
{
|
||||
__typename: 'ActivityTargetEdge',
|
||||
node: {
|
||||
id: '20202020-1029-4661-9e91-83bad932bdff',
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
},
|
||||
comments: {
|
||||
edges: [
|
||||
{
|
||||
__typename: 'CommentEdge',
|
||||
node: {
|
||||
id: '20202020-1029-4661-9e91-83bad932bdee',
|
||||
},
|
||||
},
|
||||
] as ObjectRecordEdge<Comment>[],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockActivityWithArrayRelation = {
|
||||
activityTargets: [
|
||||
{
|
||||
id: '20202020-1029-4661-9e91-83bad932bdff',
|
||||
},
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
id: '20202020-1029-4661-9e91-83bad932bdee',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('useActivityConnectionUtils', () => {
|
||||
it('Should turn activity with connection relation in activity with array relation', async () => {
|
||||
const { result } = renderHook(() => useActivityConnectionUtils(), {
|
||||
wrapper: ({ children }) => (
|
||||
<RecoilRoot
|
||||
initializeState={(snapshot) => {
|
||||
snapshot.set(
|
||||
objectMetadataItemsState,
|
||||
getObjectMetadataItemsMock(),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecoilRoot>
|
||||
),
|
||||
});
|
||||
|
||||
const { makeActivityWithoutConnection } = result.current;
|
||||
|
||||
const { activity: activityWithArrayRelation } =
|
||||
makeActivityWithoutConnection(mockActivityWithConnectionRelation as any);
|
||||
|
||||
expect(activityWithArrayRelation).toBeDefined();
|
||||
|
||||
expect(activityWithArrayRelation.activityTargets[0].id).toEqual(
|
||||
mockActivityWithArrayRelation.activityTargets[0].id,
|
||||
);
|
||||
});
|
||||
|
||||
it('Should turn activity with connection relation in activity with array relation', async () => {
|
||||
const { result } = renderHook(() => useActivityConnectionUtils(), {
|
||||
wrapper: ({ children }) => (
|
||||
<RecoilRoot
|
||||
initializeState={(snapshot) => {
|
||||
snapshot.set(
|
||||
objectMetadataItemsState,
|
||||
getObjectMetadataItemsMock(),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecoilRoot>
|
||||
),
|
||||
});
|
||||
|
||||
const { makeActivityWithConnection } = result.current;
|
||||
|
||||
const { activityWithConnection } = makeActivityWithConnection(
|
||||
mockActivityWithArrayRelation as any,
|
||||
);
|
||||
|
||||
expect(activityWithConnection).toBeDefined();
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
mockActivityWithConnectionRelation,
|
||||
activityWithConnection,
|
||||
mockActivityWithArrayRelation,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(activityWithConnection.activityTargets.edges[0].node.id).toEqual(
|
||||
mockActivityWithConnectionRelation.activityTargets.edges[0].node.id,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -2,10 +2,10 @@ import { useEffect, useState } from 'react';
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||
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';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { Comment } from '@/activities/types/Comment';
|
||||
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
|
||||
import { useMapConnectionToRecords } from '@/object-record/hooks/useMapConnectionToRecords';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useActivityConnectionUtils = () => {
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const makeActivityWithoutConnection = (
|
||||
activityWithConnections: Activity & {
|
||||
activityTargets: ObjectRecordConnection<ActivityTarget>;
|
||||
comments: ObjectRecordConnection<Comment>;
|
||||
},
|
||||
) => {
|
||||
if (!isDefined(activityWithConnections)) {
|
||||
throw new Error('Activity with connections is not defined');
|
||||
}
|
||||
|
||||
const hasActivityTargetsConnection = isObjectRecordConnection(
|
||||
CoreObjectNameSingular.ActivityTarget,
|
||||
activityWithConnections?.activityTargets,
|
||||
);
|
||||
|
||||
const activityTargets: ActivityTarget[] = [];
|
||||
|
||||
if (hasActivityTargetsConnection) {
|
||||
const newActivityTargets = mapConnectionToRecords({
|
||||
objectRecordConnection: activityWithConnections?.activityTargets,
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
depth: 5,
|
||||
}) as ActivityTarget[];
|
||||
|
||||
activityTargets.push(...newActivityTargets);
|
||||
}
|
||||
|
||||
const hasCommentsConnection = isObjectRecordConnection(
|
||||
CoreObjectNameSingular.Comment,
|
||||
activityWithConnections?.comments,
|
||||
);
|
||||
|
||||
const comments: Comment[] = [];
|
||||
|
||||
if (hasCommentsConnection) {
|
||||
const newComments = mapConnectionToRecords({
|
||||
objectRecordConnection: activityWithConnections?.comments,
|
||||
objectNameSingular: CoreObjectNameSingular.Comment,
|
||||
depth: 5,
|
||||
}) as Comment[];
|
||||
|
||||
comments.push(...newComments);
|
||||
}
|
||||
|
||||
const activity: Activity = {
|
||||
...activityWithConnections,
|
||||
activityTargets,
|
||||
comments,
|
||||
};
|
||||
|
||||
return { activity };
|
||||
};
|
||||
|
||||
const makeActivityWithConnection = (activity: Activity) => {
|
||||
const activityTargetEdges = isNonEmptyArray(activity?.activityTargets)
|
||||
? activity.activityTargets.map((activityTarget) => ({
|
||||
node: activityTarget,
|
||||
cursor: '',
|
||||
}))
|
||||
: [];
|
||||
|
||||
const commentEdges = isNonEmptyArray(activity?.comments)
|
||||
? activity.comments.map((comment) => ({
|
||||
node: comment,
|
||||
cursor: '',
|
||||
}))
|
||||
: [];
|
||||
|
||||
const activityTargets = {
|
||||
__typename: 'ActivityTargetConnection',
|
||||
edges: activityTargetEdges,
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<ActivityTarget>;
|
||||
|
||||
const comments = {
|
||||
__typename: 'CommentConnection',
|
||||
edges: commentEdges,
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<Comment>;
|
||||
|
||||
const activityWithConnection = {
|
||||
...activity,
|
||||
activityTargets,
|
||||
comments,
|
||||
} as Activity & {
|
||||
activityTargets: ObjectRecordConnection<ActivityTarget>;
|
||||
comments: ObjectRecordConnection<Comment>;
|
||||
};
|
||||
|
||||
return { activityWithConnection };
|
||||
};
|
||||
|
||||
return {
|
||||
makeActivityWithoutConnection,
|
||||
makeActivityWithConnection,
|
||||
};
|
||||
};
|
||||
@ -1,9 +1,8 @@
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
|
||||
import { useModifyActivityTargetsOnActivityCache } from '@/activities/hooks/useModifyActivityTargetsOnActivityCache';
|
||||
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||
import { ActivityForEditor } from '@/activities/types/ActivityForEditor';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
@ -20,9 +19,6 @@ export const useCreateActivityInDB = () => {
|
||||
|
||||
const { makeActivityWithConnection } = useActivityConnectionUtils();
|
||||
|
||||
const { modifyActivityTargetsOnActivityCache } =
|
||||
useModifyActivityTargetsOnActivityCache();
|
||||
|
||||
const createActivityInDB = async (activityToCreate: ActivityForEditor) => {
|
||||
const { activityWithConnection } = makeActivityWithConnection(
|
||||
activityToCreate as any, // TODO: fix type
|
||||
@ -45,12 +41,6 @@ export const useCreateActivityInDB = () => {
|
||||
skipOptimisticEffect: true,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: replace by trigger optimistic effect
|
||||
modifyActivityTargetsOnActivityCache({
|
||||
activityId: activityToCreate.id,
|
||||
activityTargets: activityTargetsToCreate,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||
import { ActivityForEditor } from '@/activities/types/ActivityForEditor';
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
|
||||
@ -0,0 +1,175 @@
|
||||
import { isNonEmptyArray, 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 { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
// TODO: create a generic hook from this
|
||||
export const useInjectIntoActivitiesQueries = () => {
|
||||
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 injectActivitiesQueries = ({
|
||||
activityToInject,
|
||||
activityTargetsToInject,
|
||||
targetableObjects,
|
||||
activitiesFilters,
|
||||
activitiesOrderByVariables,
|
||||
injectOnlyInIdFilter,
|
||||
}: {
|
||||
activityToInject: Activity;
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
activitiesFilters?: ObjectRecordQueryFilter;
|
||||
activitiesOrderByVariables?: OrderByField;
|
||||
injectOnlyInIdFilter?: boolean;
|
||||
}) => {
|
||||
const hasActivityTargets = isNonEmptyArray(targetableObjects);
|
||||
|
||||
if (hasActivityTargets) {
|
||||
const findManyActivitiyTargetsQueryFilter = getActivityTargetsFilter({
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const findManyActivitiyTargetsQueryVariables = {
|
||||
filter: findManyActivitiyTargetsQueryFilter,
|
||||
};
|
||||
|
||||
const existingActivityTargetsWithMaybeDuplicates =
|
||||
readFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: findManyActivitiyTargetsQueryVariables,
|
||||
});
|
||||
|
||||
const existingActivityTargetsWithoutDuplicates: ObjectRecord[] =
|
||||
existingActivityTargetsWithMaybeDuplicates.filter(
|
||||
(existingActivityTarget) =>
|
||||
!activityTargetsToInject.some(
|
||||
(activityTargetToInject) =>
|
||||
activityTargetToInject.id === existingActivityTarget.id,
|
||||
),
|
||||
);
|
||||
|
||||
const existingActivityIdsFromTargets =
|
||||
existingActivityTargetsWithoutDuplicates
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const currentFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
id: {
|
||||
in: existingActivityIdsFromTargets.toSorted(sortByAscString),
|
||||
},
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
const existingActivities = readFindManyActivitiesQueryInCache({
|
||||
queryVariables: currentFindManyActivitiesQueryVariables,
|
||||
});
|
||||
|
||||
const nextActivityIds = [
|
||||
...existingActivityIdsFromTargets,
|
||||
activityToInject.id,
|
||||
];
|
||||
|
||||
const nextFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
id: {
|
||||
in: nextActivityIds.toSorted(sortByAscString),
|
||||
},
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
const newActivities = [...existingActivities];
|
||||
|
||||
if (!injectOnlyInIdFilter) {
|
||||
const newActivity = {
|
||||
...activityToInject,
|
||||
__typename: 'Activity',
|
||||
};
|
||||
|
||||
newActivities.unshift(newActivity);
|
||||
}
|
||||
|
||||
overwriteFindManyActivitiesInCache({
|
||||
objectRecordsToOverwrite: newActivities,
|
||||
queryVariables: nextFindManyActivitiesQueryVariables,
|
||||
});
|
||||
} else {
|
||||
const currentFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
const existingActivities = readFindManyActivitiesQueryInCache({
|
||||
queryVariables: currentFindManyActivitiesQueryVariables,
|
||||
});
|
||||
|
||||
const nextFindManyActivitiesQueryVariables = {
|
||||
filter: {
|
||||
...activitiesFilters,
|
||||
},
|
||||
orderBy: activitiesOrderByVariables,
|
||||
};
|
||||
|
||||
const newActivities = [...existingActivities];
|
||||
|
||||
if (!injectOnlyInIdFilter) {
|
||||
const newActivity = {
|
||||
...activityToInject,
|
||||
__typename: 'Activity',
|
||||
};
|
||||
|
||||
newActivities.unshift(newActivity);
|
||||
}
|
||||
|
||||
overwriteFindManyActivitiesInCache({
|
||||
objectRecordsToOverwrite: newActivities,
|
||||
queryVariables: nextFindManyActivitiesQueryVariables,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
injectActivitiesQueries,
|
||||
};
|
||||
};
|
||||
@ -1,133 +0,0 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,81 @@
|
||||
import { isNonEmptyArray } 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 { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
// TODO: create a generic hook from this
|
||||
export const useInjectIntoActivityTargetsQueries = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivityTargetsQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache:
|
||||
overwriteFindManyActivityTargetsQueryInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const injectActivityTargetsQueries = ({
|
||||
activityTargetsToInject,
|
||||
targetableObjects,
|
||||
}: {
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
}) => {
|
||||
const hasActivityTargets = isNonEmptyArray(targetableObjects);
|
||||
|
||||
if (!hasActivityTargets) {
|
||||
return;
|
||||
}
|
||||
|
||||
const findManyActivitiyTargetsQueryFilter = getActivityTargetsFilter({
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const findManyActivitiyTargetsQueryVariables = {
|
||||
filter: findManyActivitiyTargetsQueryFilter,
|
||||
};
|
||||
|
||||
const existingActivityTargetsWithMaybeDuplicates =
|
||||
readFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: findManyActivitiyTargetsQueryVariables,
|
||||
});
|
||||
|
||||
const existingActivityTargetsWithoutDuplicates: ObjectRecord[] =
|
||||
existingActivityTargetsWithMaybeDuplicates.filter(
|
||||
(existingActivityTarget) =>
|
||||
!activityTargetsToInject.some(
|
||||
(activityTargetToInject) =>
|
||||
activityTargetToInject.id === existingActivityTarget.id,
|
||||
),
|
||||
);
|
||||
|
||||
const newActivityTargets = [
|
||||
...existingActivityTargetsWithoutDuplicates,
|
||||
...activityTargetsToInject,
|
||||
];
|
||||
|
||||
overwriteFindManyActivityTargetsQueryInCache({
|
||||
objectRecordsToOverwrite: newActivityTargets,
|
||||
queryVariables: findManyActivitiyTargetsQueryVariables,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectActivityTargetsQueries,
|
||||
};
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
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';
|
||||
@ -9,8 +8,10 @@ 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 { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
// TODO: improve, no bug if query to inject doesn't exist
|
||||
export const useRemoveFromActivitiesQueries = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItemOnly({
|
||||
@ -40,22 +41,13 @@ export const useRemoveFromActivitiesQueries = () => {
|
||||
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;
|
||||
@ -64,28 +56,15 @@ export const useRemoveFromActivitiesQueries = () => {
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const findManyActivityTargetsQueryVariables = {
|
||||
filter: findManyActivitiyTargetsQueryFilter,
|
||||
} as ObjectRecordQueryVariables;
|
||||
|
||||
const existingActivityTargetsForTargetableObject =
|
||||
readFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: findManyActivitiyTargetsQueryFilter,
|
||||
queryVariables: findManyActivityTargetsQueryVariables,
|
||||
});
|
||||
|
||||
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);
|
||||
@ -104,6 +83,10 @@ export const useRemoveFromActivitiesQueries = () => {
|
||||
queryVariables: currentFindManyActivitiesQueryVariables,
|
||||
});
|
||||
|
||||
if (!isNonEmptyArray(existingActivities)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activityIdsAfterRemoval = existingActivityIds.filter(
|
||||
(existingActivityId) => existingActivityId !== activityIdToRemove,
|
||||
);
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
import { isNonEmptyArray } 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 { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
|
||||
|
||||
export const useRemoveFromActivityTargetsQueries = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivityTargetsQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache:
|
||||
overwriteFindManyActivityTargetsQueryInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const removeFromActivityTargetsQueries = ({
|
||||
activityTargetsToRemove,
|
||||
targetableObjects,
|
||||
}: {
|
||||
activityTargetsToRemove: ActivityTarget[];
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
}) => {
|
||||
const findManyActivitiyTargetsQueryFilter = getActivityTargetsFilter({
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const findManyActivityTargetsQueryVariables = {
|
||||
filter: findManyActivitiyTargetsQueryFilter,
|
||||
} as ObjectRecordQueryVariables;
|
||||
|
||||
const existingActivityTargetsForTargetableObject =
|
||||
readFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: findManyActivityTargetsQueryVariables,
|
||||
});
|
||||
|
||||
const newActivityTargetsForTargetableObject = isNonEmptyArray(
|
||||
activityTargetsToRemove,
|
||||
)
|
||||
? existingActivityTargetsForTargetableObject.filter(
|
||||
(existingActivityTarget) =>
|
||||
activityTargetsToRemove.some(
|
||||
(activityTargetToRemove) =>
|
||||
activityTargetToRemove.id !== existingActivityTarget.id,
|
||||
),
|
||||
)
|
||||
: existingActivityTargetsForTargetableObject;
|
||||
|
||||
overwriteFindManyActivityTargetsQueryInCache({
|
||||
objectRecordsToOverwrite: newActivityTargetsForTargetableObject,
|
||||
queryVariables: findManyActivityTargetsQueryVariables,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
removeFromActivityTargetsQueries,
|
||||
};
|
||||
};
|
||||
@ -1,16 +1,22 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||
import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
|
||||
import { useInjectIntoActivitiesQueries } from '@/activities/hooks/useInjectIntoActivitiesQueries';
|
||||
import { useInjectIntoActivityTargetsQueries } from '@/activities/hooks/useInjectIntoActivityTargetsQueries';
|
||||
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
|
||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||
import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
|
||||
import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
|
||||
import { useInjectIntoTimelineActivitiesQueries } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQueries';
|
||||
import { timelineTargetableObjectState } from '@/activities/timeline/states/timelineTargetableObjectState';
|
||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
|
||||
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';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
// TODO: create a generic way to have records only in cache for create mode and delete them afterwards ?
|
||||
export const useUpsertActivity = () => {
|
||||
@ -30,16 +36,35 @@ export const useUpsertActivity = () => {
|
||||
|
||||
const setActivityInDrawer = useSetRecoilState(activityInDrawerState);
|
||||
|
||||
const timelineTargetableObject = useRecoilValue(
|
||||
timelineTargetableObjectState,
|
||||
const objectShowPageTargetableObject = useRecoilValue(
|
||||
objectShowPageTargetableObjectState,
|
||||
);
|
||||
|
||||
const { injectActivitiesQueries } = useInjectIntoActivitiesQueries();
|
||||
const { injectActivityTargetsQueries } =
|
||||
useInjectIntoActivityTargetsQueries();
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const weAreOnObjectShowPage = pathname.startsWith('/object');
|
||||
const weAreOnTaskPage = pathname.startsWith('/tasks');
|
||||
|
||||
const { injectIntoTimelineActivitiesQueries } =
|
||||
useInjectIntoTimelineActivitiesQueries();
|
||||
|
||||
const { makeActivityWithConnection } = useActivityConnectionUtils();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
const currentCompletedTaskQueryVariables = useRecoilValue(
|
||||
currentCompletedTaskQueryVariablesState,
|
||||
);
|
||||
|
||||
const currentIncompleteTaskQueryVariables = useRecoilValue(
|
||||
currentIncompleteTaskQueryVariablesState,
|
||||
);
|
||||
|
||||
const currentNotesQueryVariables = useRecoilValue(
|
||||
currentNotesQueryVariablesState,
|
||||
);
|
||||
|
||||
const upsertActivity = async ({
|
||||
activity,
|
||||
@ -59,21 +84,91 @@ export const useUpsertActivity = () => {
|
||||
const { activityWithConnection } =
|
||||
makeActivityWithConnection(activityToCreate);
|
||||
|
||||
if (weAreOnTaskPage) {
|
||||
if (isDefined(activityWithConnection.completedAt)) {
|
||||
injectActivitiesQueries({
|
||||
activitiesFilters: currentCompletedTaskQueryVariables?.filter,
|
||||
activitiesOrderByVariables:
|
||||
currentCompletedTaskQueryVariables?.orderBy,
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
activityToInject: activityWithConnection,
|
||||
targetableObjects: [],
|
||||
});
|
||||
} else {
|
||||
injectActivitiesQueries({
|
||||
activitiesFilters: currentIncompleteTaskQueryVariables?.filter,
|
||||
activitiesOrderByVariables:
|
||||
currentIncompleteTaskQueryVariables?.orderBy,
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
activityToInject: activityWithConnection,
|
||||
targetableObjects: [],
|
||||
});
|
||||
}
|
||||
|
||||
injectActivityTargetsQueries({
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
targetableObjects: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Call optimistic effects
|
||||
if (timelineTargetableObject) {
|
||||
if (weAreOnObjectShowPage && objectShowPageTargetableObject) {
|
||||
injectIntoTimelineActivitiesQueries({
|
||||
timelineTargetableObject: timelineTargetableObject,
|
||||
timelineTargetableObject: objectShowPageTargetableObject,
|
||||
activityToInject: activityWithConnection,
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
});
|
||||
|
||||
const injectOnlyInIdFilterForTaskQueries =
|
||||
activityWithConnection.type !== 'Task';
|
||||
|
||||
const injectOnlyInIdFilterForNotesQueries =
|
||||
activityWithConnection.type !== 'Note';
|
||||
|
||||
if (isDefined(currentCompletedTaskQueryVariables)) {
|
||||
injectActivitiesQueries({
|
||||
activitiesFilters: currentCompletedTaskQueryVariables?.filter,
|
||||
activitiesOrderByVariables:
|
||||
currentCompletedTaskQueryVariables?.orderBy,
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
activityToInject: activityWithConnection,
|
||||
targetableObjects: [objectShowPageTargetableObject],
|
||||
injectOnlyInIdFilter: injectOnlyInIdFilterForTaskQueries,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(currentIncompleteTaskQueryVariables)) {
|
||||
injectActivitiesQueries({
|
||||
activitiesFilters:
|
||||
currentIncompleteTaskQueryVariables?.filter ?? {},
|
||||
activitiesOrderByVariables:
|
||||
currentIncompleteTaskQueryVariables?.orderBy ?? {},
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
activityToInject: activityWithConnection,
|
||||
targetableObjects: [objectShowPageTargetableObject],
|
||||
injectOnlyInIdFilter: injectOnlyInIdFilterForTaskQueries,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(currentNotesQueryVariables)) {
|
||||
injectActivitiesQueries({
|
||||
activitiesFilters: currentNotesQueryVariables?.filter,
|
||||
activitiesOrderByVariables: currentNotesQueryVariables?.orderBy,
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
activityToInject: activityWithConnection,
|
||||
targetableObjects: [objectShowPageTargetableObject],
|
||||
injectOnlyInIdFilter: injectOnlyInIdFilterForNotesQueries,
|
||||
});
|
||||
}
|
||||
|
||||
injectActivityTargetsQueries({
|
||||
activityTargetsToInject: activityToCreate.activityTargets,
|
||||
targetableObjects: [objectShowPageTargetableObject],
|
||||
});
|
||||
}
|
||||
|
||||
await createActivityInDB(activityToCreate);
|
||||
|
||||
await apolloClient.refetchQueries({
|
||||
include: ['FindManyActivities', 'FindManyActivityTargets'],
|
||||
});
|
||||
|
||||
setActivityInDrawer(activityToCreate);
|
||||
|
||||
setIsActivityInCreateMode(false);
|
||||
|
||||
Reference in New Issue
Block a user