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:
@ -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 };
|
||||
};
|
||||
@ -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 };
|
||||
};
|
||||
@ -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}
|
||||
|
||||
@ -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({
|
||||
|
||||
Reference in New Issue
Block a user