Add optimistic rendering for tasks (#1140)

* Add optimistic rendering for tasks

* Refetch activities for lists updates
This commit is contained in:
Emilien Chauvet
2023-08-09 20:05:08 +02:00
committed by GitHub
parent 702b6e5154
commit db8a176342
5 changed files with 109 additions and 37 deletions

View File

@ -1,3 +1,5 @@
import { useApolloClient } from '@apollo/client';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
@ -11,6 +13,8 @@ import {
useUpdateActivityMutation,
} from '~/generated/graphql';
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
export type OwnProps = {
activity: Pick<Activity, 'id'> & {
accountOwner?: Pick<User, 'id' | 'displayName'> | null;
@ -41,6 +45,8 @@ export function ActivityAssigneePicker({
entityType: Entity.User,
id: user.id,
name: user.displayName,
firstName: user.firstName,
lastName: user.lastName,
avatarType: 'rounded',
avatarUrl: user.avatarUrl ?? '',
}),
@ -48,17 +54,34 @@ export function ActivityAssigneePicker({
searchOnFields: ['firstName', 'lastName'],
});
async function handleEntitySelected(
const client = useApolloClient();
const cachedActivity = client.readFragment({
id: `Activity:${activity.id}`,
fragment: ACTIVITY_UPDATE_FRAGMENT,
});
function handleEntitySelected(
selectedUser: UserForSelect | null | undefined,
) {
if (selectedUser) {
await updateActivity({
updateActivity({
variables: {
where: { id: activity.id },
data: {
assignee: { connect: { id: selectedUser.id } },
},
},
optimisticResponse: {
__typename: 'Mutation',
updateOneActivity: {
...cachedActivity,
assignee: {
__typename: 'User',
...selectedUser,
displayName: selectedUser.name,
},
},
},
});
}

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useApolloClient } from '@apollo/client';
import { BlockNoteEditor } from '@blocknote/core';
import { useBlockNote } from '@blocknote/react';
import styled from '@emotion/styled';
@ -8,7 +8,7 @@ import debounce from 'lodash.debounce';
import { BlockEditor } from '@/ui/editor/components/BlockEditor';
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
import { GET_ACTIVITIES_BY_TARGETS } from '../queries/select';
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
const BlockNoteStyledContainer = styled.div`
width: 100%;
@ -22,6 +22,12 @@ type OwnProps = {
export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
const [updateActivityMutation] = useUpdateActivityMutation();
const client = useApolloClient();
const cachedActivity = client.readFragment({
id: `Activity:${activity.id}`,
fragment: ACTIVITY_UPDATE_FRAGMENT,
});
const [body, setBody] = useState<string | null>(null);
useEffect(() => {
@ -42,12 +48,18 @@ export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
body: activityBody,
},
},
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
optimisticResponse: {
__typename: 'Mutation',
updateOneActivity: {
...cachedActivity,
body: activityBody,
},
},
});
}
return debounce(onInternalChange, 200);
}, [activity, updateActivityMutation, setBody]);
}, [updateActivityMutation, activity.id, cachedActivity]);
const editor: BlockNoteEditor | null = useBlockNote({
initialContent: activity.body ? JSON.parse(activity.body) : undefined,

View File

@ -1,14 +1,12 @@
import React, { useCallback, useState } from 'react';
import { useApolloClient } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
import { ActivityComments } from '@/activities/components/ActivityComments';
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
import {
GET_ACTIVITIES,
GET_ACTIVITIES_BY_TARGETS,
} from '@/activities/queries';
import { GET_ACTIVITIES } from '@/activities/queries';
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
import { IconCalendar } from '@/ui/icon/index';
@ -24,6 +22,7 @@ import { debounce } from '~/utils/debounce';
import { ActivityAssigneeEditableField } from '../editable-fields/components/ActivityAssigneeEditableField';
import { ActivityRelationEditableField } from '../editable-fields/components/ActivityRelationEditableField';
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
import { CommentForDrawer } from '../types/CommentForDrawer';
import { ActivityTitle } from './ActivityTitle';
@ -96,6 +95,12 @@ export function ActivityEditor({
const [updateActivityMutation] = useUpdateActivityMutation();
const client = useApolloClient();
const cachedActivity = client.readFragment({
id: `Activity:${activity.id}`,
fragment: ACTIVITY_UPDATE_FRAGMENT,
});
const updateTitle = useCallback(
(newTitle: string) => {
updateActivityMutation({
@ -107,10 +112,17 @@ export function ActivityEditor({
title: newTitle ?? '',
},
},
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
optimisticResponse: {
__typename: 'Mutation',
updateOneActivity: {
__typename: 'Activity',
...cachedActivity,
title: newTitle,
},
},
});
},
[activity, updateActivityMutation],
[activity.id, cachedActivity, updateActivityMutation],
);
const handleActivityCompletionChange = useCallback(
@ -124,11 +136,19 @@ export function ActivityEditor({
completedAt: value ? new Date().toISOString() : null,
},
},
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
optimisticResponse: {
__typename: 'Mutation',
updateOneActivity: {
__typename: 'Activity',
...cachedActivity,
completedAt: value ? new Date().toISOString() : null,
},
},
refetchQueries: [getOperationName(GET_ACTIVITIES) ?? ''],
});
setCompletedAt(value ? new Date().toISOString() : null);
},
[activity, updateActivityMutation],
[activity.id, cachedActivity, updateActivityMutation],
);
const debouncedUpdateTitle = debounce(updateTitle, 200);

View File

@ -1,32 +1,43 @@
import { useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useApolloClient } from '@apollo/client';
import {
GET_ACTIVITIES,
GET_ACTIVITIES_BY_TARGETS,
} from '@/activities/queries';
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
type Task = Pick<Activity, 'id'>;
export function useCompleteTask(task: Task) {
const [updateActivityMutation] = useUpdateActivityMutation();
const client = useApolloClient();
const cachedTask = client.readFragment({
id: `Activity:${task.id}`,
fragment: ACTIVITY_UPDATE_FRAGMENT,
});
console.log('cachedTask', cachedTask);
const completeTask = useCallback(
(value: boolean) => {
const completedAt = value ? new Date().toISOString() : null;
updateActivityMutation({
variables: {
where: { id: task.id },
data: {
completedAt: value ? new Date().toISOString() : null,
completedAt,
},
},
optimisticResponse: {
__typename: 'Mutation',
updateOneActivity: {
...cachedTask,
completedAt,
},
},
refetchQueries: [
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
getOperationName(GET_ACTIVITIES) ?? '',
],
});
},
[task, updateActivityMutation],
[cachedTask, task.id, updateActivityMutation],
);
return {

View File

@ -60,24 +60,30 @@ export const DELETE_ACTIVITY = gql`
}
`;
export const ACTIVITY_UPDATE_FRAGMENT = gql`
fragment ActivityUpdateParts on Activity {
id
body
title
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
}
}
`;
export const UPDATE_ACTIVITY = gql`
mutation UpdateActivity(
$where: ActivityWhereUniqueInput!
$data: ActivityUpdateInput!
) {
updateOneActivity(where: $where, data: $data) {
id
body
title
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
}
...ActivityUpdateParts
}
}
`;