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:
Lucas Bordeau
2024-02-21 18:54:14 +01:00
committed by GitHub
parent 02e9846282
commit 140d3460eb
26 changed files with 832 additions and 382 deletions

View File

@ -5,7 +5,7 @@ import { useSetRecoilState } from 'recoil';
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { avatarUrl } from '~/testing/mock-data/users';
import { CommentHeader } from '../CommentHeader';
@ -32,7 +32,7 @@ const meta: Meta<typeof CommentHeader> = {
<Story />
</>
),
ComponentDecorator,
ComponentWithRouterDecorator,
],
argTypes: {
actionBar: {

View File

@ -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,
);
});
});

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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,
);

View File

@ -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,
};
};

View File

@ -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);

View File

@ -1,18 +1,47 @@
import { useEffect, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useActivities } from '@/activities/hooks/useActivities';
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
import { Note } from '@/activities/types/Note';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { ActivityTargetableObject } from '../../types/ActivityTargetableEntity';
export const useNotes = (targetableObject: ActivityTargetableObject) => {
const notesQueryVariables = useMemo(
() =>
({
filter: {
type: { eq: 'Note' },
},
orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
}) as ObjectRecordQueryVariables,
[],
);
const { activities, initialized, loading } = useActivities({
activitiesFilters: {
type: { eq: 'Note' },
},
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
activitiesFilters: notesQueryVariables.filter ?? {},
activitiesOrderByVariables: notesQueryVariables.orderBy ?? {},
targetableObjects: [targetableObject],
});
const [currentNotesQueryVariables, setCurrentNotesQueryVariables] =
useRecoilState(currentNotesQueryVariablesState);
// TODO: fix useEffect, remove with better pattern
useEffect(() => {
if (!isDeeplyEqual(notesQueryVariables, currentNotesQueryVariables)) {
setCurrentNotesQueryVariables(notesQueryVariables);
}
}, [
notesQueryVariables,
currentNotesQueryVariables,
setCurrentNotesQueryVariables,
]);
return {
notes: activities as Note[],
initialized,

View File

@ -0,0 +1,9 @@
import { atom } from 'recoil';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
export const currentNotesQueryVariablesState =
atom<ObjectRecordQueryVariables | null>({
default: null,
key: 'currentNotesQueryVariablesState',
});

View File

@ -1,17 +1,23 @@
import { useLocation } from 'react-router-dom';
import styled from '@emotion/styled';
import { isNonEmptyArray } from '@sniptt/guards';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { useRemoveFromActivitiesQueries } from '@/activities/hooks/useRemoveFromActivitiesQueries';
import { useRemoveFromActivityTargetsQueries } from '@/activities/hooks/useRemoveFromActivityTargetsQueries';
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
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 { useRemoveFromTimelineActivitiesQueries } from '@/activities/timeline/hooks/useRemoveFromTimelineActivitiesQueries';
import { timelineTargetableObjectState } from '@/activities/timeline/states/timelineTargetableObjectState';
import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
@ -37,13 +43,11 @@ export const ActivityActionBar = () => {
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
const { deleteOneRecord: deleteOneActivity } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.Activity,
refetchFindManyQuery: true,
});
const { deleteManyRecords: deleteManyActivityTargets } = useDeleteManyRecords(
{
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
refetchFindManyQuery: true,
},
);
@ -56,15 +60,34 @@ export const ActivityActionBar = () => {
const [isUpsertingActivityInDB] = useRecoilState(
isUpsertingActivityInDBState,
);
const timelineTargetableObject = useRecoilValue(
timelineTargetableObjectState,
const objectShowPageTargetableObject = useRecoilValue(
objectShowPageTargetableObjectState,
);
const openCreateActivity = useOpenCreateActivityDrawer();
const { removeFromTimelineActivitiesQueries } =
useRemoveFromTimelineActivitiesQueries();
const currentCompletedTaskQueryVariables = useRecoilValue(
currentCompletedTaskQueryVariablesState,
);
const currentIncompleteTaskQueryVariables = useRecoilValue(
currentIncompleteTaskQueryVariablesState,
);
const currentNotesQueryVariables = useRecoilValue(
currentNotesQueryVariablesState,
);
const { pathname } = useLocation();
const { removeFromActivitiesQueries } = useRemoveFromActivitiesQueries();
const { removeFromActivityTargetsQueries } =
useRemoveFromActivityTargetsQueries();
const weAreOnObjectShowPage = pathname.startsWith('/object');
const weAreOnTaskPage = pathname.startsWith('/tasks');
const deleteActivity = async () => {
setIsRightDrawerOpen(false);
const deleteActivity = () => {
if (viewableActivityId) {
if (isActivityInCreateMode && isDefined(temporaryActivityForEditor)) {
deleteActivityFromCache(temporaryActivityForEditor);
@ -74,22 +97,77 @@ export const ActivityActionBar = () => {
const activityTargetIdsToDelete =
activityInDrawer?.activityTargets.map(mapToRecordId) ?? [];
if (isDefined(timelineTargetableObject)) {
removeFromTimelineActivitiesQueries({
activityTargetsToRemove: activityInDrawer?.activityTargets ?? [],
if (weAreOnTaskPage) {
removeFromActivitiesQueries({
activityIdToRemove: viewableActivityId,
targetableObjects: [],
activitiesFilters: currentCompletedTaskQueryVariables?.filter,
activitiesOrderByVariables:
currentCompletedTaskQueryVariables?.orderBy,
});
removeFromActivitiesQueries({
activityIdToRemove: viewableActivityId,
targetableObjects: [],
activitiesFilters: currentIncompleteTaskQueryVariables?.filter,
activitiesOrderByVariables:
currentIncompleteTaskQueryVariables?.orderBy,
});
} else if (
weAreOnObjectShowPage &&
isDefined(objectShowPageTargetableObject)
) {
removeFromActivitiesQueries({
activityIdToRemove: viewableActivityId,
targetableObjects: [objectShowPageTargetableObject],
activitiesFilters: {},
activitiesOrderByVariables:
FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
});
if (isDefined(currentCompletedTaskQueryVariables)) {
removeFromActivitiesQueries({
activityIdToRemove: viewableActivityId,
targetableObjects: [objectShowPageTargetableObject],
activitiesFilters: currentCompletedTaskQueryVariables?.filter,
activitiesOrderByVariables:
currentCompletedTaskQueryVariables?.orderBy,
});
}
if (isDefined(currentIncompleteTaskQueryVariables)) {
removeFromActivitiesQueries({
activityIdToRemove: viewableActivityId,
targetableObjects: [objectShowPageTargetableObject],
activitiesFilters: currentIncompleteTaskQueryVariables?.filter,
activitiesOrderByVariables:
currentIncompleteTaskQueryVariables?.orderBy,
});
}
if (isDefined(currentNotesQueryVariables)) {
removeFromActivitiesQueries({
activityIdToRemove: viewableActivityId,
targetableObjects: [objectShowPageTargetableObject],
activitiesFilters: currentNotesQueryVariables?.filter,
activitiesOrderByVariables: currentNotesQueryVariables?.orderBy,
});
}
removeFromActivityTargetsQueries({
activityTargetsToRemove: activityInDrawer?.activityTargets ?? [],
targetableObjects: [objectShowPageTargetableObject],
});
}
if (isNonEmptyArray(activityTargetIdsToDelete)) {
deleteManyActivityTargets(activityTargetIdsToDelete);
await deleteManyActivityTargets(activityTargetIdsToDelete);
}
deleteOneActivity?.(viewableActivityId);
await deleteOneActivity?.(viewableActivityId);
}
}
}
setIsRightDrawerOpen(false);
};
const record = useRecoilValue(
@ -98,7 +176,7 @@ export const ActivityActionBar = () => {
const addActivity = () => {
setIsRightDrawerOpen(false);
if (record && timelineTargetableObject) {
if (record && objectShowPageTargetableObject) {
openCreateActivity({
type: record.type,
customAssignee: record.assignee,

View File

@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { RightDrawerActivityTopBar } from '../RightDrawerActivityTopBar';
@ -14,7 +14,7 @@ const meta: Meta<typeof RightDrawerActivityTopBar> = {
<Story />
</div>
),
ComponentDecorator,
ComponentWithRouterDecorator,
],
parameters: {
msw: graphqlMocks,

View File

@ -1,12 +1,18 @@
import { useEffect, useMemo } from 'react';
import { isNonEmptyArray } from '@sniptt/guards';
import { DateTime } from 'luxon';
import { useRecoilState } from 'recoil';
import { useActivities } from '@/activities/hooks/useActivities';
import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
import { Activity } from '@/activities/types/Activity';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
import { parseDate } from '~/utils/date-utils';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
type UseTasksProps = {
filterDropdownId?: string;
@ -21,27 +27,94 @@ export const useTasks = ({
filterDropdownId,
});
const assigneeIdFilter = selectedFilter
? {
assigneeId: {
in: JSON.parse(selectedFilter.value),
},
}
: undefined;
const assigneeIdFilter = useMemo(
() =>
selectedFilter
? {
assigneeId: {
in: JSON.parse(selectedFilter.value),
},
}
: undefined,
[selectedFilter],
);
const skipActivityTargets = !isNonEmptyArray(targetableObjects);
const completedQueryVariables = useMemo(
() =>
({
filter: {
completedAt: { is: 'NOT_NULL' },
type: { eq: 'Task' },
...assigneeIdFilter,
},
orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
}) as ObjectRecordQueryVariables,
[assigneeIdFilter],
);
const incompleteQueryVariables = useMemo(
() =>
({
filter: {
completedAt: { is: 'NULL' },
type: { eq: 'Task' },
...assigneeIdFilter,
},
orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
}) as ObjectRecordQueryVariables,
[assigneeIdFilter],
);
const [
currentCompletedTaskQueryVariables,
setCurrentCompletedTaskQueryVariables,
] = useRecoilState(currentCompletedTaskQueryVariablesState);
const [
currentIncompleteTaskQueryVariables,
setCurrentIncompleteTaskQueryVariables,
] = useRecoilState(currentIncompleteTaskQueryVariablesState);
// TODO: fix useEffect, remove with better pattern
useEffect(() => {
if (
!isDeeplyEqual(
completedQueryVariables,
currentCompletedTaskQueryVariables,
)
) {
setCurrentCompletedTaskQueryVariables(completedQueryVariables);
}
}, [
completedQueryVariables,
currentCompletedTaskQueryVariables,
setCurrentCompletedTaskQueryVariables,
]);
useEffect(() => {
if (
!isDeeplyEqual(
incompleteQueryVariables,
currentIncompleteTaskQueryVariables,
)
) {
setCurrentIncompleteTaskQueryVariables(incompleteQueryVariables);
}
}, [
incompleteQueryVariables,
currentIncompleteTaskQueryVariables,
setCurrentIncompleteTaskQueryVariables,
]);
const {
activities: completeTasksData,
initialized: initializedCompleteTasks,
} = useActivities({
targetableObjects,
activitiesFilters: {
completedAt: { is: 'NOT_NULL' },
type: { eq: 'Task' },
...assigneeIdFilter,
},
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
activitiesFilters: completedQueryVariables.filter ?? {},
activitiesOrderByVariables: completedQueryVariables.orderBy ?? {},
skipActivityTargets,
});
@ -50,12 +123,8 @@ export const useTasks = ({
initialized: initializedIncompleteTasks,
} = useActivities({
targetableObjects,
activitiesFilters: {
completedAt: { is: 'NULL' },
type: { eq: 'Task' },
...assigneeIdFilter,
},
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
activitiesFilters: incompleteQueryVariables.filter ?? {},
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? {},
skipActivityTargets,
});

View File

@ -0,0 +1,9 @@
import { atom } from 'recoil';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
export const currentCompletedTaskQueryVariablesState =
atom<ObjectRecordQueryVariables | null>({
default: null,
key: 'currentCompletedTaskQueryVariablesState',
});

View File

@ -0,0 +1,9 @@
import { atom } from 'recoil';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
export const currentIncompleteTaskQueryVariablesState =
atom<ObjectRecordQueryVariables | null>({
default: null,
key: 'currentIncompleteTaskQueryVariablesState',
});

View File

@ -5,7 +5,7 @@ import { useSetRecoilState } from 'recoil';
import { useActivities } from '@/activities/hooks/useActivities';
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
import { timelineTargetableObjectState } from '@/activities/timeline/states/timelineTargetableObjectState';
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
@ -44,7 +44,7 @@ export const Timeline = ({
});
const setTimelineTargetableObject = useSetRecoilState(
timelineTargetableObjectState,
objectShowPageTargetableObjectState,
);
useEffect(() => {

View File

@ -1,10 +1,10 @@
import { useInjectIntoActivitiesQuery } from '@/activities/hooks/useInjectIntoActivitiesQuery';
import { useInjectIntoActivitiesQueries } from '@/activities/hooks/useInjectIntoActivitiesQueries';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
export const useInjectIntoTimelineActivitiesQueries = () => {
const { injectActivitiesQueries } = useInjectIntoActivitiesQuery();
const { injectActivitiesQueries } = useInjectIntoActivitiesQueries();
const injectIntoTimelineActivitiesQueries = ({
activityToInject,

View File

@ -1,138 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useRemoveFromActivitiesQueries } from '@/activities/hooks/useRemoveFromActivitiesQueries';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
import { timelineTargetableObjectState } from '@/activities/timeline/states/timelineTargetableObjectState';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
export const useRemoveFromTimelineActivitiesQueries = () => {
const timelineTargetableObject = useRecoilValue(
timelineTargetableObjectState,
);
// 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 } = useRemoveFromActivitiesQueries();
const removeFromTimelineActivitiesQueries = ({
activityIdToRemove,
activityTargetsToRemove,
}: {
activityIdToRemove: string;
activityTargetsToRemove: ActivityTarget[];
}) => {
if (!timelineTargetableObject) {
throw new Error('Timeline targetable object is not defined');
}
removeFromActivitiesQueries({
activityIdToRemove,
activityTargetsToRemove,
targetableObjects: [timelineTargetableObject],
activitiesFilters: {},
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
});
// const targetObjectFieldName = getActivityTargetObjectFieldIdName({
// nameSingular: timelineTargetableObject.targetObjectNameSingular,
// });
// const activitiyTargetsForTargetableObjectQueryVariables = {
// filter: {
// [targetObjectFieldName]: {
// eq: timelineTargetableObject.id,
// },
// },
// };
// const existingActivityTargetsForTargetableObject =
// readFindManyActivityTargetsQueryInCache({
// queryVariables: activitiyTargetsForTargetableObjectQueryVariables,
// });
// const newActivityTargetsForTargetableObject = isNonEmptyArray(
// activityTargetsToRemove,
// )
// ? existingActivityTargetsForTargetableObject.filter(
// (existingActivityTarget) =>
// activityTargetsToRemove.some(
// (activityTargetToRemove) =>
// activityTargetToRemove.id !== existingActivityTarget.id,
// ),
// )
// : existingActivityTargetsForTargetableObject;
// overwriteFindManyActivityTargetsQueryInCache({
// objectRecordsToOverwrite: newActivityTargetsForTargetableObject,
// queryVariables: activitiyTargetsForTargetableObjectQueryVariables,
// });
// const existingActivityIds = existingActivityTargetsForTargetableObject
// ?.map((activityTarget) => activityTarget.activityId)
// .filter(isNonEmptyString);
// const timelineActivitiesQueryVariablesBeforeDrawerMount =
// makeTimelineActivitiesQueryVariables({
// activityIds: existingActivityIds,
// });
// const existingActivities = readFindManyActivitiesQueryInCache({
// queryVariables: timelineActivitiesQueryVariablesBeforeDrawerMount,
// });
// const activityIdsAfterRemoval = existingActivityIds.filter(
// (existingActivityId) => existingActivityId !== activityIdToRemove,
// );
// const timelineActivitiesQueryVariablesAfterRemoval =
// makeTimelineActivitiesQueryVariables({
// activityIds: activityIdsAfterRemoval,
// });
// const newActivities = existingActivities
// .filter((existingActivity) => existingActivity.id !== activityIdToRemove)
// .toSorted(sortObjectRecordByDateField('createdAt', 'DescNullsFirst'));
// overwriteFindManyActivitiesInCache({
// objectRecordsToOverwrite: newActivities,
// queryVariables: timelineActivitiesQueryVariablesAfterRemoval,
// });
};
return {
removeFromTimelineActivitiesQueries,
};
};

View File

@ -2,12 +2,12 @@ import { useEffect, useState } from 'react';
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
import { useActivityTargetsForTargetableObject } from '@/activities/hooks/useActivityTargetsForTargetableObject';
import { timelineTargetableObjectState } from '@/activities/timeline/states/timelineTargetableObjectState';
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
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 { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
@ -22,15 +22,15 @@ export const useTimelineActivities = ({
}) => {
const { makeActivityWithoutConnection } = useActivityConnectionUtils();
const [, setTimelineTargetableObject] = useRecoilState(
timelineTargetableObjectState,
const [, setObjectShowPageTargetableObject] = useRecoilState(
objectShowPageTargetableObjectState,
);
useEffect(() => {
if (isDefined(targetableObject)) {
setTimelineTargetableObject(targetableObject);
setObjectShowPageTargetableObject(targetableObject);
}
}, [targetableObject, setTimelineTargetableObject]);
}, [targetableObject, setObjectShowPageTargetableObject]);
const {
activityTargets,

View File

@ -2,8 +2,8 @@ import { atom } from 'recoil';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
export const timelineTargetableObjectState =
export const objectShowPageTargetableObjectState =
atom<ActivityTargetableObject | null>({
key: 'timelineTargetableObjectState',
key: 'objectShowPageTargetableObjectState',
default: null,
});

View File

@ -117,7 +117,10 @@ export const triggerUpdateRecordOptimisticEffect = ({
const rootQueryNextEdgesShouldBeSorted = isDefined(rootQueryOrderBy);
if (rootQueryNextEdgesShouldBeSorted) {
if (
rootQueryNextEdgesShouldBeSorted &&
Object.getOwnPropertyNames(rootQueryOrderBy).length > 0
) {
rootQueryNextEdges = sortCachedObjectEdges({
edges: rootQueryNextEdges,
orderBy: rootQueryOrderBy,