Activity injection into Apollo cache (#3665)
- Created addRecordInCache to inject a record in Apollo cache and inject single read query on this record - Created createOneRecordInCache and createManyRecordsInCache that uses this addRecordInCache - Created useOpenCreateActivityDrawerV2 hook to create an activity in cache and inject it into all other relevant requests in the app before opening activity drawer - Refactored DEFAULT_SEARCH_REQUEST_LIMIT constant and hardcoded arbitrary request limits - Added Apollo dev logs to see errors in the console when manipulating cache
This commit is contained in:
72
packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts
vendored
Normal file
72
packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import gql from 'graphql-tag';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useAddRecordInCache = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const generateFindOneRecordQuery = useGenerateFindOneRecordQuery();
|
||||
|
||||
const findOneRecordQuery = generateFindOneRecordQuery({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set }) =>
|
||||
(record: ObjectRecord) => {
|
||||
apolloClient.writeFragment({
|
||||
id: `${capitalize(objectMetadataItem.nameSingular)}:${record.id}`,
|
||||
fragment: gql`
|
||||
fragment Create${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}InCache on ${capitalize(objectMetadataItem.nameSingular)} {
|
||||
__typename
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join('\n')}
|
||||
}
|
||||
`,
|
||||
data: {
|
||||
__typename: `${capitalize(objectMetadataItem.nameSingular)}`,
|
||||
...record,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: Turn into injectIntoFindOneRecordQueryCache
|
||||
apolloClient.writeQuery({
|
||||
query: findOneRecordQuery,
|
||||
variables: {
|
||||
objectRecordId: record.id,
|
||||
},
|
||||
data: {
|
||||
[objectMetadataItem.nameSingular]: {
|
||||
__typename: `${capitalize(objectMetadataItem.nameSingular)}`,
|
||||
...record,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: remove this once we get rid of entityFieldsFamilyState
|
||||
set(recordStoreFamilyState(record.id), record);
|
||||
},
|
||||
[
|
||||
objectMetadataItem,
|
||||
apolloClient,
|
||||
mapFieldMetadataToGraphQLQuery,
|
||||
findOneRecordQuery,
|
||||
],
|
||||
);
|
||||
};
|
||||
41
packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateCachedObjectRecord.ts
vendored
Normal file
41
packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateCachedObjectRecord.ts
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
45
packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts
vendored
Normal file
45
packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
import { gql, useApolloClient } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGetRecordFromCache = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
return <CachedObjectRecord extends ObjectRecord = ObjectRecord>(
|
||||
recordId: string,
|
||||
) => {
|
||||
if (!objectMetadataItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
const cacheReadFragment = gql`
|
||||
fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join('\n')}
|
||||
}
|
||||
`;
|
||||
|
||||
const cache = apolloClient.cache;
|
||||
const cachedRecordId = cache.identify({
|
||||
__typename: capitalize(objectMetadataItem.nameSingular),
|
||||
id: recordId,
|
||||
});
|
||||
|
||||
return cache.readFragment<CachedObjectRecord & { __typename: string }>({
|
||||
id: cachedRecordId,
|
||||
fragment: cacheReadFragment,
|
||||
});
|
||||
};
|
||||
};
|
||||
31
packages/twenty-front/src/modules/object-record/cache/hooks/useModifyRecordFromCache.ts
vendored
Normal file
31
packages/twenty-front/src/modules/object-record/cache/hooks/useModifyRecordFromCache.ts
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { Modifiers } from '@apollo/client/cache';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useModifyRecordFromCache = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const { cache } = useApolloClient();
|
||||
|
||||
return <CachedObjectRecord extends ObjectRecord = ObjectRecord>(
|
||||
recordId: string,
|
||||
fieldModifiers: Modifiers<CachedObjectRecord>,
|
||||
) => {
|
||||
if (!objectMetadataItem) return;
|
||||
|
||||
const cachedRecordId = cache.identify({
|
||||
__typename: capitalize(objectMetadataItem.nameSingular),
|
||||
id: recordId,
|
||||
});
|
||||
|
||||
cache.modify<CachedObjectRecord>({
|
||||
id: cachedRecordId,
|
||||
fields: fieldModifiers,
|
||||
});
|
||||
};
|
||||
};
|
||||
31
packages/twenty-front/src/modules/object-record/cache/utils/getCacheReferenceFromRecord.ts
vendored
Normal file
31
packages/twenty-front/src/modules/object-record/cache/utils/getCacheReferenceFromRecord.ts
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import { ApolloClient, makeReference, Reference } from '@apollo/client';
|
||||
|
||||
import { getCachedRecordFromRecord } from '@/object-record/cache/utils/getCachedRecordFromRecord';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const getCacheReferenceFromRecord = <T extends ObjectRecord>({
|
||||
apolloClient,
|
||||
objectNameSingular,
|
||||
record,
|
||||
}: {
|
||||
apolloClient: ApolloClient<object>;
|
||||
objectNameSingular: string;
|
||||
record: T;
|
||||
}): Reference => {
|
||||
const cachedRecord = getCachedRecordFromRecord({
|
||||
objectNameSingular,
|
||||
record,
|
||||
});
|
||||
|
||||
const id = apolloClient.cache.identify(cachedRecord);
|
||||
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
`Could not identify record "${objectNameSingular}", id : "${record.id}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const recordReference = makeReference(id);
|
||||
|
||||
return recordReference;
|
||||
};
|
||||
43
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordEdgesFromRecords.ts
vendored
Normal file
43
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordEdgesFromRecords.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
import { ApolloClient, makeReference } from '@apollo/client';
|
||||
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { getCachedRecordFromRecord } from '@/object-record/cache/utils/getCachedRecordFromRecord';
|
||||
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const getCachedRecordEdgesFromRecords = <T extends ObjectRecord>({
|
||||
apolloClient,
|
||||
objectNameSingular,
|
||||
records,
|
||||
}: {
|
||||
apolloClient: ApolloClient<object>;
|
||||
objectNameSingular: string;
|
||||
records: T[];
|
||||
}): CachedObjectRecordEdge[] => {
|
||||
const cachedRecordEdges = records.map((record) => {
|
||||
const cachedRecord = getCachedRecordFromRecord({
|
||||
objectNameSingular,
|
||||
record,
|
||||
});
|
||||
|
||||
const id = apolloClient.cache.identify(cachedRecord);
|
||||
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
`Could not identify record "${objectNameSingular}", id : "${record.id}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const reference = makeReference(id);
|
||||
|
||||
const cachedObjectRecordEdge: CachedObjectRecordEdge = {
|
||||
cursor: '',
|
||||
node: reference,
|
||||
__typename: getEdgeTypename({ objectNameSingular }),
|
||||
};
|
||||
|
||||
return cachedObjectRecordEdge;
|
||||
});
|
||||
|
||||
return cachedRecordEdges;
|
||||
};
|
||||
16
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordFromRecord.ts
vendored
Normal file
16
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordFromRecord.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const getCachedRecordFromRecord = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
record,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
record: T;
|
||||
}): CachedObjectRecord<T> => {
|
||||
return {
|
||||
__typename: getNodeTypename({ objectNameSingular }),
|
||||
...record,
|
||||
};
|
||||
};
|
||||
9
packages/twenty-front/src/modules/object-record/cache/utils/getConnectionTypename.ts
vendored
Normal file
9
packages/twenty-front/src/modules/object-record/cache/utils/getConnectionTypename.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getConnectionTypename = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return `${capitalize(objectNameSingular)}Connection`;
|
||||
};
|
||||
9
packages/twenty-front/src/modules/object-record/cache/utils/getEdgeTypename.ts
vendored
Normal file
9
packages/twenty-front/src/modules/object-record/cache/utils/getEdgeTypename.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getEdgeTypename = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return `${capitalize(objectNameSingular)}Edge`;
|
||||
};
|
||||
8
packages/twenty-front/src/modules/object-record/cache/utils/getEmptyPageInfo.ts
vendored
Normal file
8
packages/twenty-front/src/modules/object-record/cache/utils/getEmptyPageInfo.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export const getEmptyPageInfo = () => {
|
||||
return {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
};
|
||||
};
|
||||
9
packages/twenty-front/src/modules/object-record/cache/utils/getNodeTypename.ts
vendored
Normal file
9
packages/twenty-front/src/modules/object-record/cache/utils/getNodeTypename.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getNodeTypename = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return capitalize(objectNameSingular);
|
||||
};
|
||||
19
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromEdges.ts
vendored
Normal file
19
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromEdges.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename';
|
||||
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
|
||||
export const getRecordConnectionFromEdges = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
edges,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
edges: ObjectRecordEdge<T>[];
|
||||
}) => {
|
||||
return {
|
||||
__typename: getConnectionTypename({ objectNameSingular }),
|
||||
edges: edges,
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<T>;
|
||||
};
|
||||
24
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromRecords.ts
vendored
Normal file
24
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromRecords.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename';
|
||||
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
|
||||
import { getRecordEdgeFromRecord } from '@/object-record/cache/utils/getRecordEdgeFromRecord';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const getRecordConnectionFromRecords = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
records,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
records: T[];
|
||||
}) => {
|
||||
return {
|
||||
__typename: getConnectionTypename({ objectNameSingular }),
|
||||
edges: records.map((record) => {
|
||||
return getRecordEdgeFromRecord({
|
||||
objectNameSingular,
|
||||
record,
|
||||
});
|
||||
}),
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<T>;
|
||||
};
|
||||
21
packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts
vendored
Normal file
21
packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
|
||||
import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
|
||||
export const getRecordEdgeFromRecord = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
record,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
record: T;
|
||||
}) => {
|
||||
return {
|
||||
__typename: getEdgeTypename({ objectNameSingular }),
|
||||
node: {
|
||||
__typename: getNodeTypename({ objectNameSingular }),
|
||||
...record,
|
||||
},
|
||||
cursor: '',
|
||||
} as ObjectRecordEdge<T>;
|
||||
};
|
||||
10
packages/twenty-front/src/modules/object-record/cache/utils/getRecordsFromRecordConnection.ts
vendored
Normal file
10
packages/twenty-front/src/modules/object-record/cache/utils/getRecordsFromRecordConnection.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const getRecordsFromRecordConnection = <T extends ObjectRecord>({
|
||||
recordConnection,
|
||||
}: {
|
||||
recordConnection: ObjectRecordConnection<T>;
|
||||
}): T[] => {
|
||||
return recordConnection.edges.map((edge) => edge.node);
|
||||
};
|
||||
Reference in New Issue
Block a user