mirror of
https://github.com/twentyhq/twenty.git
synced 2024-09-11 23:37:21 +03:00
feat: delete favorite in cache on related record deletion (#3751)
* feat: delete favorite in cache on related record deletion * fix: fix useCreateOneRecord tests * fix: fix usePipelineSteps tests * fix: fix useCreateManyRecords tests * fix: add null relation field values in useGenerateObjectRecordOptimisticResponse
This commit is contained in:
parent
142affbeea
commit
7adb5cc00d
@ -1,8 +1,10 @@
|
||||
import { ApolloCache, StoreObject } from '@apollo/client';
|
||||
|
||||
import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection';
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
@ -15,15 +17,27 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
records: CachedObjectRecord[];
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) => {
|
||||
const objectEdgeTypeName = `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Edge`;
|
||||
|
||||
records.forEach((record) =>
|
||||
triggerUpdateRelationsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord: null,
|
||||
nextRecord: record,
|
||||
getRelationMetadata,
|
||||
}),
|
||||
);
|
||||
|
||||
cache.modify<StoreObject>({
|
||||
fields: {
|
||||
[objectMetadataItem.namePlural]: (
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { ApolloCache, StoreObject } from '@apollo/client';
|
||||
|
||||
import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection';
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
|
||||
@ -12,10 +14,12 @@ export const triggerDeleteRecordsOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
records: Pick<CachedObjectRecord, 'id' | '__typename'>[];
|
||||
records: CachedObjectRecord[];
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) => {
|
||||
cache.modify<StoreObject>({
|
||||
fields: {
|
||||
@ -64,5 +68,15 @@ export const triggerDeleteRecordsOptimisticEffect = ({
|
||||
},
|
||||
});
|
||||
|
||||
cache.gc();
|
||||
records.forEach((record) => {
|
||||
triggerUpdateRelationsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord: record,
|
||||
nextRecord: null,
|
||||
getRelationMetadata,
|
||||
});
|
||||
|
||||
cache.evict({ id: cache.identify(record) });
|
||||
});
|
||||
};
|
||||
|
@ -2,9 +2,11 @@ import { ApolloCache, StoreObject } from '@apollo/client';
|
||||
|
||||
import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection';
|
||||
import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges';
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -14,16 +16,29 @@ import { capitalize } from '~/utils/string/capitalize';
|
||||
export const triggerUpdateRecordOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
record,
|
||||
previousRecord,
|
||||
nextRecord,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
record: CachedObjectRecord;
|
||||
previousRecord: CachedObjectRecord;
|
||||
nextRecord: CachedObjectRecord;
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) => {
|
||||
const objectEdgeTypeName = `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Edge`;
|
||||
|
||||
triggerUpdateRelationsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord,
|
||||
nextRecord,
|
||||
getRelationMetadata,
|
||||
});
|
||||
|
||||
// Optimistically update record lists
|
||||
cache.modify<StoreObject>({
|
||||
fields: {
|
||||
[objectMetadataItem.namePlural]: (
|
||||
@ -49,18 +64,20 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
);
|
||||
let nextCachedEdges = cachedEdges ? [...cachedEdges] : [];
|
||||
|
||||
// Test if the record matches this list's filters
|
||||
if (variables?.filter) {
|
||||
const matchesFilter = isRecordMatchingFilter({
|
||||
record,
|
||||
record: nextRecord,
|
||||
filter: variables.filter,
|
||||
objectMetadataItem,
|
||||
});
|
||||
const recordIndex = nextCachedEdges.findIndex(
|
||||
(cachedEdge) => readField('id', cachedEdge.node) === record.id,
|
||||
(cachedEdge) => readField('id', cachedEdge.node) === nextRecord.id,
|
||||
);
|
||||
|
||||
// If after update, the record matches this list's filters, then add it to the list
|
||||
if (matchesFilter && recordIndex === -1) {
|
||||
const nodeReference = toReference(record);
|
||||
const nodeReference = toReference(nextRecord);
|
||||
nodeReference &&
|
||||
nextCachedEdges.push({
|
||||
__typename: objectEdgeTypeName,
|
||||
@ -69,11 +86,13 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
});
|
||||
}
|
||||
|
||||
// If after update, the record does not match this list's filters anymore, then remove it from the list
|
||||
if (!matchesFilter && recordIndex > -1) {
|
||||
nextCachedEdges.splice(recordIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort updated list
|
||||
if (variables?.orderBy) {
|
||||
nextCachedEdges = sortCachedObjectEdges({
|
||||
edges: nextCachedEdges,
|
||||
@ -82,6 +101,7 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
});
|
||||
}
|
||||
|
||||
// Limit the updated list to the required size
|
||||
if (isDefined(variables?.first)) {
|
||||
// If previous edges length was exactly at the required limit,
|
||||
// but after update next edges length is under the limit,
|
||||
|
@ -0,0 +1,112 @@
|
||||
import { ApolloCache } from '@apollo/client';
|
||||
|
||||
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
|
||||
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { coreObjectNamesToDeleteOnRelationDetach } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord,
|
||||
nextRecord,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
previousRecord: CachedObjectRecord | null;
|
||||
nextRecord: CachedObjectRecord | null;
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) =>
|
||||
// Optimistically update relation records
|
||||
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
|
||||
if (nextRecord && !(fieldMetadataItem.name in nextRecord)) return;
|
||||
|
||||
const relationMetadata = getRelationMetadata({
|
||||
fieldMetadataItem,
|
||||
});
|
||||
|
||||
if (!relationMetadata) return;
|
||||
|
||||
const {
|
||||
// Object metadata for the related record
|
||||
relationObjectMetadataItem,
|
||||
// Field on the related record
|
||||
relationFieldMetadataItem,
|
||||
} = relationMetadata;
|
||||
|
||||
const previousFieldValue:
|
||||
| ObjectRecordConnection
|
||||
| CachedObjectRecord
|
||||
| null = previousRecord?.[fieldMetadataItem.name];
|
||||
const nextFieldValue: ObjectRecordConnection | CachedObjectRecord | null =
|
||||
nextRecord?.[fieldMetadataItem.name];
|
||||
|
||||
if (isDeeplyEqual(previousFieldValue, nextFieldValue)) return;
|
||||
|
||||
const isPreviousFieldValueRecordConnection = isObjectRecordConnection(
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
previousFieldValue,
|
||||
);
|
||||
const relationRecordsToDetach = isPreviousFieldValueRecordConnection
|
||||
? previousFieldValue.edges.map(({ node }) => node as CachedObjectRecord)
|
||||
: [previousFieldValue].filter(isDefined);
|
||||
|
||||
const isNextFieldValueRecordConnection = isObjectRecordConnection(
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
nextFieldValue,
|
||||
);
|
||||
const relationRecordsToAttach = isNextFieldValueRecordConnection
|
||||
? nextFieldValue.edges.map(({ node }) => node as CachedObjectRecord)
|
||||
: [nextFieldValue].filter(isDefined);
|
||||
|
||||
if (previousRecord && relationRecordsToDetach.length) {
|
||||
const shouldDeleteRelationRecord =
|
||||
coreObjectNamesToDeleteOnRelationDetach.includes(
|
||||
relationObjectMetadataItem.nameSingular as CoreObjectNameSingular,
|
||||
);
|
||||
|
||||
if (shouldDeleteRelationRecord) {
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem: relationObjectMetadataItem,
|
||||
records: relationRecordsToDetach,
|
||||
getRelationMetadata,
|
||||
});
|
||||
} else {
|
||||
relationRecordsToDetach.forEach((relationRecordToDetach) => {
|
||||
triggerDetachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordId: previousRecord.id,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationRecordId: relationRecordToDetach.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (nextRecord && relationRecordsToAttach.length) {
|
||||
relationRecordsToAttach.forEach((relationRecordToAttach) =>
|
||||
triggerAttachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordId: nextRecord.id,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationRecordId: relationRecordToAttach.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
|
||||
export const coreObjectNamesToDeleteOnRelationDetach = [
|
||||
CoreObjectNameSingular.Favorite,
|
||||
];
|
@ -1,41 +0,0 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGenerateCachedObjectRecord = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const generateCachedObjectRecord = <
|
||||
GeneratedObjectRecord extends ObjectRecord,
|
||||
>(
|
||||
input: Record<string, unknown>,
|
||||
) => {
|
||||
const recordSchema = z.object(
|
||||
Object.fromEntries(
|
||||
objectMetadataItem.fields.map((fieldMetadataItem) => [
|
||||
fieldMetadataItem.name,
|
||||
z.unknown().default(generateEmptyFieldValue(fieldMetadataItem)),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
__typename: capitalize(objectMetadataItem.nameSingular),
|
||||
...recordSchema.parse({
|
||||
id: v4(),
|
||||
createdAt: new Date().toISOString(),
|
||||
...input,
|
||||
}),
|
||||
} as GeneratedObjectRecord & { __typename: string };
|
||||
};
|
||||
|
||||
return {
|
||||
generateCachedObjectRecord,
|
||||
};
|
||||
};
|
@ -0,0 +1,72 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGenerateObjectRecordOptimisticResponse = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
|
||||
const generateObjectRecordOptimisticResponse = <
|
||||
GeneratedObjectRecord extends ObjectRecord,
|
||||
>(
|
||||
input: Record<string, unknown>,
|
||||
) => {
|
||||
const recordSchema = z.object(
|
||||
Object.fromEntries(
|
||||
objectMetadataItem.fields.map((fieldMetadataItem) => [
|
||||
fieldMetadataItem.name,
|
||||
z.unknown().default(generateEmptyFieldValue(fieldMetadataItem)),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
const inputWithRelationFields = objectMetadataItem.fields.reduce(
|
||||
(result, fieldMetadataItem) => {
|
||||
const relationIdFieldName = `${fieldMetadataItem.name}Id`;
|
||||
|
||||
if (!(relationIdFieldName in input)) return result;
|
||||
|
||||
const relationMetadata = getRelationMetadata({ fieldMetadataItem });
|
||||
|
||||
if (!relationMetadata) return result;
|
||||
|
||||
const relationRecordTypeName = capitalize(
|
||||
relationMetadata.relationObjectMetadataItem.nameSingular,
|
||||
);
|
||||
const relationRecordId = result[relationIdFieldName] as string | null;
|
||||
|
||||
return {
|
||||
...result,
|
||||
[fieldMetadataItem.name]: relationRecordId
|
||||
? {
|
||||
__typename: relationRecordTypeName,
|
||||
id: relationRecordId,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
},
|
||||
input,
|
||||
);
|
||||
|
||||
return {
|
||||
__typename: capitalize(objectMetadataItem.nameSingular),
|
||||
...recordSchema.parse({
|
||||
id: v4(),
|
||||
createdAt: new Date().toISOString(),
|
||||
...inputWithRelationFields,
|
||||
}),
|
||||
} as GeneratedObjectRecord & { __typename: string };
|
||||
};
|
||||
|
||||
return {
|
||||
generateObjectRecordOptimisticResponse,
|
||||
};
|
||||
};
|
@ -1,5 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { Person } from '@/people/types/Person';
|
||||
|
||||
export const query = gql`
|
||||
mutation CreatePeople($data: [PersonCreateInput!]!) {
|
||||
createPeople(data: $data) {
|
||||
@ -67,12 +69,15 @@ export const query = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const variables = {
|
||||
data: [
|
||||
{ id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' },
|
||||
{ id: '37faabcd-cb39-4a0a-8618-7e3fda9afca0' },
|
||||
],
|
||||
};
|
||||
const data = [
|
||||
{
|
||||
id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9',
|
||||
name: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
{ id: '37faabcd-cb39-4a0a-8618-7e3fda9afca0', jobTitle: 'manager' },
|
||||
] satisfies Partial<Person>[];
|
||||
|
||||
export const variables = { data };
|
||||
|
||||
export const responseData = {
|
||||
opportunities: {
|
||||
@ -114,3 +119,8 @@ export const responseData = {
|
||||
avatarUrl: '',
|
||||
companyId: '',
|
||||
};
|
||||
|
||||
export const response = data.map((personData) => ({
|
||||
...responseData,
|
||||
...personData,
|
||||
}));
|
||||
|
@ -67,10 +67,6 @@ export const query = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const variables = {
|
||||
input: { id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' },
|
||||
};
|
||||
|
||||
export const responseData = {
|
||||
opportunities: {
|
||||
edges: [],
|
||||
|
@ -1,19 +1,27 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { mocked } from '@storybook/test';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import {
|
||||
query,
|
||||
responseData,
|
||||
response,
|
||||
variables,
|
||||
} from '@/object-record/hooks/__mocks__/useCreateManyRecords';
|
||||
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
|
||||
|
||||
const people = [
|
||||
{ id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' },
|
||||
{ id: '37faabcd-cb39-4a0a-8618-7e3fda9afca0' },
|
||||
];
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(),
|
||||
}));
|
||||
|
||||
mocked(v4)
|
||||
.mockReturnValueOnce(variables.data[0].id)
|
||||
.mockReturnValueOnce(variables.data[1].id);
|
||||
|
||||
const input = variables.data.map(({ id: _id, ...personInput }) => personInput);
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
@ -23,10 +31,7 @@ const mocks = [
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
createPeople: people.map((person) => ({
|
||||
id: person.id,
|
||||
...responseData,
|
||||
})),
|
||||
createPeople: response,
|
||||
},
|
||||
})),
|
||||
},
|
||||
@ -43,19 +48,18 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
describe('useCreateManyRecords', () => {
|
||||
it('works as expected', async () => {
|
||||
const { result } = renderHook(
|
||||
() => useCreateManyRecords({ objectNameSingular: 'person' }),
|
||||
() =>
|
||||
useCreateManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Person,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.createManyRecords(people);
|
||||
expect(res).toBeDefined();
|
||||
expect(Array.isArray(res)).toBe(true);
|
||||
expect(res?.length).toBe(2);
|
||||
expect(res?.[0].id).toBe(people[0].id);
|
||||
expect(res?.[1].id).toBe(people[1].id);
|
||||
const res = await result.current.createManyRecords(input);
|
||||
expect(res).toEqual(response);
|
||||
});
|
||||
|
||||
expect(mocks[0].result).toHaveBeenCalled();
|
||||
|
@ -3,24 +3,29 @@ import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import {
|
||||
query,
|
||||
responseData,
|
||||
variables,
|
||||
} from '@/object-record/hooks/__mocks__/useCreateOneRecord';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
|
||||
const person = { id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' };
|
||||
const personId = 'a7286b9a-c039-4a89-9567-2dfa7953cda9';
|
||||
const input = { name: { firstName: 'John', lastName: 'Doe' } };
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(() => personId),
|
||||
}));
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query,
|
||||
variables,
|
||||
variables: { input: { ...input, id: personId } },
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
createPerson: { ...person, ...responseData },
|
||||
createPerson: { ...responseData, ...input, id: personId },
|
||||
},
|
||||
})),
|
||||
},
|
||||
@ -37,16 +42,20 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
describe('useCreateOneRecord', () => {
|
||||
it('works as expected', async () => {
|
||||
const { result } = renderHook(
|
||||
() => useCreateOneRecord({ objectNameSingular: 'person' }),
|
||||
() =>
|
||||
useCreateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Person,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.createOneRecord(person);
|
||||
const res = await result.current.createOneRecord(input);
|
||||
console.log('res', res);
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toHaveProperty('id', person.id);
|
||||
expect(res).toHaveProperty('id', personId);
|
||||
});
|
||||
|
||||
expect(mocks[0].result).toHaveBeenCalled();
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse';
|
||||
import { getCreateManyRecordsMutationResponseField } from '@/object-record/hooks/useGenerateCreateManyRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
@ -13,29 +15,33 @@ export const useCreateManyRecords = <
|
||||
>({
|
||||
objectNameSingular,
|
||||
}: ObjectMetadataItemIdentifier) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { objectMetadataItem, createManyRecordsMutation } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
const { generateObjectRecordOptimisticResponse } =
|
||||
useGenerateObjectRecordOptimisticResponse({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
|
||||
const createManyRecords = async (data: Partial<CreatedObjectRecord>[]) => {
|
||||
const optimisticallyCreatedRecords = data.map((record) =>
|
||||
generateCachedObjectRecord<CreatedObjectRecord>(record),
|
||||
);
|
||||
|
||||
const sanitizedCreateManyRecordsInput = data.map((input, index) =>
|
||||
const sanitizedCreateManyRecordsInput = data.map((input) =>
|
||||
sanitizeRecordInput({
|
||||
objectMetadataItem,
|
||||
recordInput: { ...input, id: optimisticallyCreatedRecords[index].id },
|
||||
recordInput: { ...input, id: v4() },
|
||||
}),
|
||||
);
|
||||
|
||||
const optimisticallyCreatedRecords = sanitizedCreateManyRecordsInput.map(
|
||||
(record) =>
|
||||
generateObjectRecordOptimisticResponse<CreatedObjectRecord>(record),
|
||||
);
|
||||
|
||||
const mutationResponseField = getCreateManyRecordsMutationResponseField(
|
||||
objectMetadataItem.namePlural,
|
||||
);
|
||||
@ -57,6 +63,7 @@ export const useCreateManyRecords = <
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records,
|
||||
getRelationMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import { v4 } from 'uuid';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { useAddRecordInCache } from '@/object-record/cache/hooks/useAddRecordInCache';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const useCreateManyRecordsInCache = <T extends ObjectRecord>({
|
||||
@ -13,9 +13,10 @@ export const useCreateManyRecordsInCache = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
const { generateObjectRecordOptimisticResponse } =
|
||||
useGenerateObjectRecordOptimisticResponse({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const addRecordInCache = useAddRecordInCache({
|
||||
objectMetadataItem,
|
||||
@ -30,9 +31,8 @@ export const useCreateManyRecordsInCache = <T extends ObjectRecord>({
|
||||
const createdRecordsInCache = [] as T[];
|
||||
|
||||
for (const record of recordsWithId) {
|
||||
const generatedCachedObjectRecord = generateCachedObjectRecord<T>({
|
||||
...record,
|
||||
});
|
||||
const generatedCachedObjectRecord =
|
||||
generateObjectRecordOptimisticResponse<T>(record);
|
||||
|
||||
if (generatedCachedObjectRecord) {
|
||||
addRecordInCache(generatedCachedObjectRecord);
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse';
|
||||
import { getCreateOneRecordMutationResponseField } from '@/object-record/hooks/useGenerateCreateOneRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
@ -16,25 +18,27 @@ export const useCreateOneRecord = <
|
||||
>({
|
||||
objectNameSingular,
|
||||
}: useCreateOneRecordProps) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { objectMetadataItem, createOneRecordMutation } = useObjectMetadataItem(
|
||||
{ objectNameSingular },
|
||||
);
|
||||
|
||||
// TODO: type this with a minimal type at least with Record<string, any>
|
||||
const apolloClient = useApolloClient();
|
||||
const { generateObjectRecordOptimisticResponse } =
|
||||
useGenerateObjectRecordOptimisticResponse({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
|
||||
const createOneRecord = async (input: Partial<CreatedObjectRecord>) => {
|
||||
const sanitizedCreateOneRecordInput = sanitizeRecordInput({
|
||||
objectMetadataItem,
|
||||
recordInput: input,
|
||||
recordInput: { ...input, id: v4() },
|
||||
});
|
||||
|
||||
const optimisticallyCreatedRecord =
|
||||
generateCachedObjectRecord<CreatedObjectRecord>({
|
||||
generateObjectRecordOptimisticResponse<CreatedObjectRecord>({
|
||||
...input,
|
||||
...sanitizedCreateOneRecordInput,
|
||||
});
|
||||
@ -45,10 +49,7 @@ export const useCreateOneRecord = <
|
||||
const createdObject = await apolloClient.mutate({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: {
|
||||
...sanitizedCreateOneRecordInput,
|
||||
id: optimisticallyCreatedRecord.id,
|
||||
},
|
||||
input: sanitizedCreateOneRecordInput,
|
||||
},
|
||||
optimisticResponse: {
|
||||
[mutationResponseField]: optimisticallyCreatedRecord,
|
||||
@ -62,6 +63,7 @@ export const useCreateOneRecord = <
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records: [record],
|
||||
getRelationMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useAddRecordInCache } from '@/object-record/cache/hooks/useAddRecordInCache';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
type useCreateOneRecordInCacheProps = {
|
||||
@ -14,19 +14,18 @@ export const useCreateOneRecordInCache = <T>({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
const { generateObjectRecordOptimisticResponse } =
|
||||
useGenerateObjectRecordOptimisticResponse({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const addRecordInCache = useAddRecordInCache({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const createOneRecordInCache = async (input: ObjectRecord) => {
|
||||
const generatedCachedObjectRecord = generateCachedObjectRecord({
|
||||
createdAt: new Date().toISOString(),
|
||||
...input,
|
||||
});
|
||||
const generatedCachedObjectRecord =
|
||||
generateObjectRecordOptimisticResponse(input);
|
||||
|
||||
addRecordInCache(generatedCachedObjectRecord);
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/hooks/useGenerateDeleteManyRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
@ -19,13 +15,13 @@ type useDeleteOneRecordProps = {
|
||||
export const useDeleteManyRecords = ({
|
||||
objectNameSingular,
|
||||
}: useDeleteOneRecordProps) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { objectMetadataItem, deleteManyRecordsMutation, getRecordFromCache } =
|
||||
useObjectMetadataItem({ objectNameSingular });
|
||||
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const mutationResponseField = getDeleteManyRecordsMutationResponseField(
|
||||
objectMetadataItem.namePlural,
|
||||
);
|
||||
@ -47,49 +43,15 @@ export const useDeleteManyRecords = ({
|
||||
|
||||
if (!records?.length) return;
|
||||
|
||||
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
|
||||
const relationMetadata = getRelationMetadata({ fieldMetadataItem });
|
||||
|
||||
if (!relationMetadata) return;
|
||||
|
||||
const { relationObjectMetadataItem, relationFieldMetadataItem } =
|
||||
relationMetadata;
|
||||
|
||||
records.forEach((record) => {
|
||||
const cachedRecord = getRecordFromCache(record.id, cache);
|
||||
|
||||
if (!cachedRecord) return;
|
||||
|
||||
const previousFieldValue:
|
||||
| ObjectRecordConnection
|
||||
| ObjectRecord
|
||||
| null = cachedRecord[fieldMetadataItem.name];
|
||||
|
||||
const relationRecordIds = isObjectRecordConnection(
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
previousFieldValue,
|
||||
)
|
||||
? previousFieldValue.edges.map(({ node }) => node.id)
|
||||
: [previousFieldValue?.id].filter(isDefined);
|
||||
|
||||
relationRecordIds.forEach((relationRecordId) =>
|
||||
triggerDetachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular,
|
||||
recordId: record.id,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationRecordId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
const cachedRecords = records
|
||||
.map((record) => getRecordFromCache(record.id, cache))
|
||||
.filter(isDefined);
|
||||
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records: records,
|
||||
records: cachedRecords,
|
||||
getRelationMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,15 +1,10 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/generateDeleteOneRecordMutation';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useDeleteOneRecordProps = {
|
||||
@ -20,13 +15,13 @@ type useDeleteOneRecordProps = {
|
||||
export const useDeleteOneRecord = ({
|
||||
objectNameSingular,
|
||||
}: useDeleteOneRecordProps) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { objectMetadataItem, deleteOneRecordMutation, getRecordFromCache } =
|
||||
useObjectMetadataItem({ objectNameSingular });
|
||||
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const mutationResponseField =
|
||||
getDeleteOneRecordMutationResponseField(objectNameSingular);
|
||||
|
||||
@ -46,47 +41,15 @@ export const useDeleteOneRecord = ({
|
||||
|
||||
if (!record) return;
|
||||
|
||||
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
|
||||
const relationMetadata = getRelationMetadata({ fieldMetadataItem });
|
||||
const cachedRecord = getRecordFromCache(record.id, cache);
|
||||
|
||||
if (!relationMetadata) return;
|
||||
|
||||
const { relationObjectMetadataItem, relationFieldMetadataItem } =
|
||||
relationMetadata;
|
||||
|
||||
const cachedRecord = getRecordFromCache(record.id, cache);
|
||||
|
||||
if (!cachedRecord) return;
|
||||
|
||||
const previousFieldValue:
|
||||
| ObjectRecordConnection
|
||||
| ObjectRecord
|
||||
| null = cachedRecord[fieldMetadataItem.name];
|
||||
|
||||
const relationRecordIds = isObjectRecordConnection(
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
previousFieldValue,
|
||||
)
|
||||
? previousFieldValue.edges.map(({ node }) => node.id)
|
||||
: [previousFieldValue?.id].filter(isDefined);
|
||||
|
||||
relationRecordIds.forEach((relationRecordId) =>
|
||||
triggerDetachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular,
|
||||
recordId: record.id,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationRecordId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
if (!cachedRecord) return;
|
||||
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records: [record],
|
||||
records: [cachedRecord],
|
||||
getRelationMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
|
||||
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse';
|
||||
import { getUpdateOneRecordMutationResponseField } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useUpdateOneRecordProps = {
|
||||
objectNameSingular: string;
|
||||
@ -21,12 +17,17 @@ export const useUpdateOneRecord = <
|
||||
>({
|
||||
objectNameSingular,
|
||||
}: useUpdateOneRecordProps) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { objectMetadataItem, updateOneRecordMutation, getRecordFromCache } =
|
||||
useObjectMetadataItem({ objectNameSingular });
|
||||
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
const { generateObjectRecordOptimisticResponse } =
|
||||
useGenerateObjectRecordOptimisticResponse({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
|
||||
const updateOneRecord = async ({
|
||||
idToUpdate,
|
||||
@ -42,13 +43,11 @@ export const useUpdateOneRecord = <
|
||||
recordInput: updateOneRecordInput,
|
||||
});
|
||||
|
||||
const optimisticallyUpdatedRecord = {
|
||||
const optimisticallyUpdatedRecord = generateObjectRecordOptimisticResponse({
|
||||
...(cachedRecord ?? {}),
|
||||
...updateOneRecordInput,
|
||||
...sanitizedUpdateOneRecordInput,
|
||||
__typename: capitalize(objectNameSingular),
|
||||
id: idToUpdate,
|
||||
};
|
||||
});
|
||||
|
||||
const mutationResponseField =
|
||||
getUpdateOneRecordMutationResponseField(objectNameSingular);
|
||||
@ -65,60 +64,14 @@ export const useUpdateOneRecord = <
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.[mutationResponseField];
|
||||
|
||||
if (!record) return;
|
||||
|
||||
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
|
||||
const relationMetadata = getRelationMetadata({ fieldMetadataItem });
|
||||
|
||||
if (!relationMetadata) return;
|
||||
|
||||
const { relationObjectMetadataItem, relationFieldMetadataItem } =
|
||||
relationMetadata;
|
||||
|
||||
const previousFieldValue = cachedRecord?.[fieldMetadataItem.name];
|
||||
const nextFieldValue =
|
||||
updateOneRecordInput[fieldMetadataItem.name] ?? null;
|
||||
|
||||
if (
|
||||
!(fieldMetadataItem.name in updateOneRecordInput) ||
|
||||
isObjectRecordConnection(
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
previousFieldValue,
|
||||
) ||
|
||||
isDeeplyEqual(previousFieldValue, nextFieldValue)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousFieldValue) {
|
||||
triggerDetachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular,
|
||||
recordId: record.id,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationRecordId: previousFieldValue.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (nextFieldValue) {
|
||||
triggerAttachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular,
|
||||
recordId: record.id,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationRecordId: nextFieldValue.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!record || !cachedRecord) return;
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
record,
|
||||
previousRecord: cachedRecord,
|
||||
nextRecord: record,
|
||||
getRelationMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -13,22 +13,22 @@ export const sanitizeRecordInput = ({
|
||||
return Object.fromEntries(
|
||||
Object.entries(recordInput)
|
||||
.map<[string, unknown] | undefined>(([fieldName, fieldValue]) => {
|
||||
const fieldDefinition = objectMetadataItem.fields.find(
|
||||
const fieldMetadataItem = objectMetadataItem.fields.find(
|
||||
(field) => field.name === fieldName,
|
||||
);
|
||||
|
||||
if (!fieldDefinition) return undefined;
|
||||
if (!fieldMetadataItem) return undefined;
|
||||
|
||||
if (
|
||||
fieldDefinition.type === FieldMetadataType.Relation &&
|
||||
fieldMetadataItem.type === FieldMetadataType.Relation &&
|
||||
isFieldRelationValue(fieldValue)
|
||||
) {
|
||||
const relationIdFieldName = `${fieldDefinition.name}Id`;
|
||||
const relationIdFieldDefinition = objectMetadataItem.fields.find(
|
||||
const relationIdFieldName = `${fieldMetadataItem.name}Id`;
|
||||
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
|
||||
(field) => field.name === relationIdFieldName,
|
||||
);
|
||||
|
||||
return relationIdFieldDefinition
|
||||
return relationIdFieldMetadataItem
|
||||
? [relationIdFieldName, fieldValue?.id ?? null]
|
||||
: undefined;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export const currentPipelineId = 'f088c8c9-05d2-4276-b065-b863cc7d0b33';
|
||||
|
||||
const data = {
|
||||
color: 'yellow',
|
||||
id: 'columnId',
|
||||
id: mockId,
|
||||
position: 1,
|
||||
name: 'Column Title',
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user