Feat/record optimistic effect (#3076)

* WIP

* WIP

* POC working on hard coded completedAt field

* Finished isRecordMatchingFilter, mock of pg_graphql filtering mechanism

* Fixed and cleaned

* Unregister unused optimistic effects

* Fix lint

* Fixes from review

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-12-20 20:31:48 +01:00
committed by GitHub
parent a5f28b4395
commit 687c9131f4
37 changed files with 2309 additions and 233 deletions

View File

@ -7,7 +7,7 @@ import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMeta
import { useGenerateEmptyRecord } from '@/object-record/hooks/useGenerateEmptyRecord';
import { capitalize } from '~/utils/string/capitalize';
export const useCreateManyRecords = <T>({
export const useCreateManyRecords = <T extends Record<string, unknown>>({
objectNameSingular,
}: ObjectMetadataItemIdentifier) => {
const { triggerOptimisticEffects } = useOptimisticEffect({
@ -32,10 +32,17 @@ export const useCreateManyRecords = <T>({
}));
withIds.forEach((record) => {
triggerOptimisticEffects(
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
generateEmptyRecord({ id: record.id }),
);
const emptyRecord: Record<string, unknown> | undefined =
generateEmptyRecord({
id: record.id,
});
if (emptyRecord) {
triggerOptimisticEffects({
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
createdRecords: [emptyRecord],
});
}
});
const createdObjects = await apolloClient.mutate({
@ -59,11 +66,9 @@ export const useCreateManyRecords = <T>({
`create${capitalize(objectMetadataItem.namePlural)}`
] as T[]) ?? [];
createdRecords.forEach((record) => {
triggerOptimisticEffects(
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
record,
);
triggerOptimisticEffects({
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
createdRecords,
});
return createdRecords;

View File

@ -1,5 +1,4 @@
import { useApolloClient } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { v4 } from 'uuid';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
@ -14,16 +13,16 @@ type useCreateOneRecordProps = {
export const useCreateOneRecord = <T>({
objectNameSingular,
refetchFindManyQuery = false,
}: useCreateOneRecordProps) => {
const { triggerOptimisticEffects } = useOptimisticEffect({
objectNameSingular,
});
const { objectMetadataItem, createOneRecordMutation, findManyRecordsQuery } =
useObjectMetadataItem({
const { objectMetadataItem, createOneRecordMutation } = useObjectMetadataItem(
{
objectNameSingular,
});
},
);
// TODO: type this with a minimal type at least with Record<string, any>
const apolloClient = useApolloClient();
@ -35,16 +34,16 @@ export const useCreateOneRecord = <T>({
const createOneRecord = async (input: Record<string, any>) => {
const recordId = v4();
const generatedEmptyRecord = generateEmptyRecord({
const generatedEmptyRecord = generateEmptyRecord<Record<string, unknown>>({
id: recordId,
...input,
});
if (generatedEmptyRecord) {
triggerOptimisticEffects(
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
generatedEmptyRecord,
);
triggerOptimisticEffects({
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
createdRecords: [generatedEmptyRecord],
});
}
const createdObject = await apolloClient.mutate({
@ -56,22 +55,12 @@ export const useCreateOneRecord = <T>({
[`create${capitalize(objectMetadataItem.nameSingular)}`]:
generateEmptyRecord({ id: recordId, ...input }),
},
refetchQueries: refetchFindManyQuery
? [getOperationName(findManyRecordsQuery) ?? '']
: [],
});
if (!createdObject.data) {
return null;
}
triggerOptimisticEffects(
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
createdObject.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
],
);
return createdObject.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
] as T;

View File

@ -30,11 +30,10 @@ export const useDeleteOneRecord = <T>({
const deleteOneRecord = useCallback(
async (idToDelete: string) => {
triggerOptimisticEffects(
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
undefined,
[idToDelete],
);
triggerOptimisticEffects({
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
deletedRecordIds: [idToDelete],
});
performOptimisticEvict(
capitalize(objectMetadataItem.nameSingular),

View File

@ -4,13 +4,12 @@ import { isNonEmptyArray } from '@apollo/client/utilities';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useRecordOptimisticEffect } from '@/object-metadata/hooks/useRecordOptimisticEffect';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { OrderByField } from '@/object-metadata/types/OrderByField';
import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
import { ObjectRecordFilter } from '@/object-record/types/ObjectRecordFilter';
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor';
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/search/hooks/useFilteredSearchEntityQuery';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@ -37,7 +36,7 @@ export const useFindManyRecords = <
onCompleted,
skip,
}: ObjectMetadataItemIdentifier & {
filter?: ObjectRecordFilter;
filter?: ObjectRecordQueryFilter;
orderBy?: OrderByField;
limit?: number;
onCompleted?: (data: PaginatedRecordTypeResults<RecordType>) => void;
@ -65,8 +64,11 @@ export const useFindManyRecords = <
objectNameSingular,
});
const { registerOptimisticEffect } = useOptimisticEffect({
objectNameSingular,
useRecordOptimisticEffect({
objectMetadataItem,
filter,
orderBy,
limit,
});
const { enqueueSnackBar } = useSnackBar();
@ -82,19 +84,6 @@ export const useFindManyRecords = <
orderBy: orderBy ?? {},
},
onCompleted: (data) => {
if (objectMetadataItem) {
registerOptimisticEffect({
variables: {
filter: filter ?? {},
orderBy: orderBy ?? {},
limit: limit,
},
definition: getRecordOptimisticEffectDefinition({
objectMetadataItem,
}),
});
}
onCompleted?.(data[objectMetadataItem.namePlural]);
if (data?.[objectMetadataItem.namePlural]) {

View File

@ -164,6 +164,6 @@ export const useGenerateEmptyRecord = ({
};
return {
generateEmptyRecord: generateEmptyRecord,
generateEmptyRecord,
};
};

View File

@ -5,8 +5,8 @@ import { Company } from '@/companies/types/Company';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
import { turnFiltersIntoObjectRecordFilters } from '@/object-record/utils/turnFiltersIntoWhereClause';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { PipelineStep } from '@/pipeline/types/PipelineStep';
@ -43,7 +43,7 @@ export const useObjectRecordBoard = () => {
savedPipelineStepsState,
);
const filter = turnFiltersIntoObjectRecordFilters(
const filter = turnObjectDropdownFilterIntoQueryFilter(
boardFilters,
foundObjectMetadataItem?.fields ?? [],
);

View File

@ -4,10 +4,10 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { isRecordTableInitialLoadingState } from '@/object-record/record-table/states/isRecordTableInitialLoadingState';
import { turnFiltersIntoObjectRecordFilters } from '@/object-record/utils/turnFiltersIntoWhereClause';
import { signInBackgroundMockCompanies } from '@/sign-in-background-mock/constants/signInBackgroundMockCompanies';
import { useFindManyRecords } from './useFindManyRecords';
@ -32,7 +32,7 @@ export const useObjectRecordTable = () => {
const tableSorts = useRecoilValue(tableSortsState);
const setLastRowVisible = useSetRecoilState(tableLastRowVisibleState);
const requestFilters = turnFiltersIntoObjectRecordFilters(
const requestFilters = turnObjectDropdownFilterIntoQueryFilter(
tableFilters,
foundObjectMetadataItem?.fields ?? [],
);

View File

@ -1,6 +1,6 @@
import { useApolloClient } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
@ -11,14 +11,13 @@ type useUpdateOneRecordProps = {
export const useUpdateOneRecord = <T>({
objectNameSingular,
refetchFindManyQuery = false,
}: useUpdateOneRecordProps) => {
const {
objectMetadataItem,
updateOneRecordMutation,
getRecordFromCache,
findManyRecordsQuery,
} = useObjectMetadataItem({
const { objectMetadataItem, updateOneRecordMutation, getRecordFromCache } =
useObjectMetadataItem({
objectNameSingular,
});
const { triggerOptimisticEffects } = useOptimisticEffect({
objectNameSingular,
});
@ -34,6 +33,16 @@ export const useUpdateOneRecord = <T>({
}) => {
const cachedRecord = getRecordFromCache(idToUpdate);
triggerOptimisticEffects({
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
updatedRecords: [
{
...(cachedRecord ?? {}),
...input,
},
],
});
const updatedRecord = await apolloClient.mutate({
mutation: updateOneRecordMutation,
variables: {
@ -48,18 +57,17 @@ export const useUpdateOneRecord = <T>({
...input,
},
},
refetchQueries: refetchFindManyQuery
? [getOperationName(findManyRecordsQuery) ?? '']
: [],
});
if (!updatedRecord?.data) {
return null;
}
return updatedRecord.data[
const updatedData = updatedRecord.data[
`update${capitalize(objectMetadataItem.nameSingular)}`
] as T;
return updatedData;
};
return {