Relations many in table view (#5842)

Closes #5924.

Adding the "many" side of relations in the table view, and fixing some
issues (glitch in Multi record select, cache update after update).

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Marie
2024-06-27 11:28:03 +02:00
committed by GitHub
parent dcb709feee
commit 7eb69a78ef
82 changed files with 1531 additions and 751 deletions

View File

@ -0,0 +1,113 @@
import { useApolloClient } from '@apollo/client';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from '~/utils/isDefined';
type useAttachRelatedRecordFromRecordProps = {
recordObjectNameSingular: string;
fieldNameOnRecordObject: string;
};
export const useAttachRelatedRecordFromRecord = ({
recordObjectNameSingular,
fieldNameOnRecordObject,
}: useAttachRelatedRecordFromRecordProps) => {
const apolloClient = useApolloClient();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: recordObjectNameSingular,
});
const fieldOnObject = objectMetadataItem.fields.find((field) => {
return field.name === fieldNameOnRecordObject;
});
const relatedRecordObjectNameSingular =
fieldOnObject?.relationDefinition?.targetObjectMetadata.nameSingular;
if (!relatedRecordObjectNameSingular) {
throw new Error(
`Could not find record related to ${recordObjectNameSingular}`,
);
}
const { objectMetadataItem: relatedObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: relatedRecordObjectNameSingular,
});
const fieldOnRelatedObject =
fieldOnObject?.relationDefinition?.targetFieldMetadata.name;
if (!fieldOnRelatedObject) {
throw new Error(`Missing target field for ${fieldNameOnRecordObject}`);
}
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: relatedRecordObjectNameSingular,
});
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular: recordObjectNameSingular,
});
const getRelatedRecordFromCache = useGetRecordFromCache({
objectNameSingular: relatedRecordObjectNameSingular,
});
const { objectMetadataItems } = useObjectMetadataItems();
const updateOneRecordAndAttachRelations = async ({
recordId,
relatedRecordId,
}: {
recordId: string;
relatedRecordId: string;
}) => {
const cachedRelatedRecord =
getRelatedRecordFromCache<ObjectRecord>(relatedRecordId);
if (!cachedRelatedRecord) {
throw new Error('could not find cached related record');
}
const previousRecordId = cachedRelatedRecord?.[`${fieldOnRelatedObject}Id`];
if (isDefined(previousRecordId)) {
const previousRecord = getRecordFromCache<ObjectRecord>(previousRecordId);
const previousRecordWithRelation = {
...cachedRelatedRecord,
[fieldOnRelatedObject]: previousRecord,
};
const gqlFields = generateDepthOneRecordGqlFields({
objectMetadataItem: relatedObjectMetadataItem,
record: previousRecordWithRelation,
});
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem: relatedObjectMetadataItem,
cache: apolloClient.cache,
record: {
...cachedRelatedRecord,
[fieldOnRelatedObject]: previousRecord,
},
recordGqlFields: gqlFields,
});
}
await updateOneRecord({
idToUpdate: relatedRecordId,
updateOneRecordInput: {
[`${fieldOnRelatedObject}Id`]: recordId,
},
});
};
return { updateOneRecordAndAttachRelations };
};

View File

@ -0,0 +1,88 @@
import { Reference, useApolloClient } from '@apollo/client';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getRefName } from '@/object-record/cache/utils/getRefName';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
type useDetachRelatedRecordFromRecordProps = {
recordObjectNameSingular: string;
fieldNameOnRecordObject: string;
};
export const useDetachRelatedRecordFromRecord = ({
recordObjectNameSingular,
fieldNameOnRecordObject,
}: useDetachRelatedRecordFromRecordProps) => {
const apolloClient = useApolloClient();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: recordObjectNameSingular,
});
const fieldOnObject = objectMetadataItem.fields.find((field) => {
return field.name === fieldNameOnRecordObject;
});
const relatedRecordObjectNameSingular =
fieldOnObject?.relationDefinition?.targetObjectMetadata.nameSingular;
const fieldOnRelatedObject =
fieldOnObject?.relationDefinition?.targetFieldMetadata.name;
if (!relatedRecordObjectNameSingular) {
throw new Error(
`Could not find record related to ${recordObjectNameSingular}`,
);
}
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: relatedRecordObjectNameSingular,
});
const updateOneRecordAndDetachRelations = async ({
recordId,
relatedRecordId,
}: {
recordId: string;
relatedRecordId: string;
}) => {
modifyRecordFromCache({
objectMetadataItem,
cache: apolloClient.cache,
fieldModifiers: {
[fieldNameOnRecordObject]: (
fieldNameOnRecordObjectConnection,
{ readField },
) => {
const edges = readField<{ node: Reference }[]>(
'edges',
fieldNameOnRecordObjectConnection,
);
if (!edges) return fieldNameOnRecordObjectConnection;
return {
...fieldNameOnRecordObjectConnection,
edges: edges.filter(
(edge) =>
!(
edge.node.__ref ===
getRefName(relatedRecordObjectNameSingular, relatedRecordId)
),
),
};
},
},
recordId,
});
await updateOneRecord({
idToUpdate: relatedRecordId,
updateOneRecordInput: {
[`${fieldOnRelatedObject}Id`]: null,
},
});
};
return { updateOneRecordAndDetachRelations };
};

View File

@ -19,6 +19,7 @@ export const useFieldContext = ({
objectNameSingular,
objectRecordId,
customUseUpdateOneObjectHook,
overridenIsFieldEmpty,
}: {
clearable?: boolean;
fieldMetadataName: string;
@ -27,6 +28,7 @@ export const useFieldContext = ({
objectNameSingular: string;
objectRecordId: string;
customUseUpdateOneObjectHook?: RecordUpdateHook;
overridenIsFieldEmpty?: boolean;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
@ -78,6 +80,7 @@ export const useFieldContext = ({
customUseUpdateOneObjectHook ?? useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
clearable,
overridenIsFieldEmpty,
}}
>
{children}

View File

@ -47,9 +47,11 @@ export const useUpdateOneRecord = <
const updateOneRecord = async ({
idToUpdate,
updateOneRecordInput,
optimisticRecord,
}: {
idToUpdate: string;
updateOneRecordInput: Partial<Omit<UpdatedObjectRecord, 'id'>>;
optimisticRecord?: Partial<ObjectRecord>;
}) => {
const sanitizedInput = {
...sanitizeRecordInput({
@ -68,16 +70,16 @@ export const useUpdateOneRecord = <
computeReferences: true,
});
const optimisticRecord = {
const computedOptimisticRecord = {
...cachedRecord,
...sanitizedInput,
...(optimisticRecord ?? sanitizedInput),
...{ id: idToUpdate },
...{ __typename: capitalize(objectMetadataItem.nameSingular) },
};
const optimisticRecordWithConnection =
getRecordNodeFromRecord<ObjectRecord>({
record: optimisticRecord,
record: computedOptimisticRecord,
objectMetadataItem,
objectMetadataItems,
recordGqlFields: computedRecordGqlFields,
@ -92,7 +94,7 @@ export const useUpdateOneRecord = <
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
record: optimisticRecord,
record: computedOptimisticRecord,
});
triggerUpdateRecordOptimisticEffect({