Morph many to one picker (#14155)
This PR follows the multiSelect PR merged previously. It will enable morph relation Many to One to be handled from the table, using a singleSelect picker Main point : I decided to change the singleSelect API to take an array of **objectMetadataName** instead of only one to deal with both our usecases. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
-187
@@ -1,187 +0,0 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateMultipleRecordsFromManyObjects } from '@/object-record/hooks/useUpdateMultipleRecordsFromManyObjects';
|
||||
import { getTargetFieldMetadataName } from '@/object-record/multiple-objects/utils/getTargetFieldMetadataName';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useAttachMorphRelatedRecordFromRecord = () => {
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { updateMultipleRecordsFromManyObjects } =
|
||||
useUpdateMultipleRecordsFromManyObjects();
|
||||
|
||||
if (!isFieldMorphRelation(fieldDefinition)) {
|
||||
throw new Error('Field is not a morph relation');
|
||||
}
|
||||
|
||||
const objectNameSingular =
|
||||
fieldDefinition.metadata.morphRelations[0].sourceObjectMetadata
|
||||
.nameSingular;
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error('Could not find object metadata item');
|
||||
}
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const updateOneRecordAndAttachMorphRelations = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async ({
|
||||
recordId,
|
||||
relatedRecordId,
|
||||
objectNameSingulars,
|
||||
}: {
|
||||
recordId: string;
|
||||
relatedRecordId: string;
|
||||
objectNameSingulars: string[];
|
||||
}) => {
|
||||
const relatedObjectMetadataItems = objectMetadataItems.filter(
|
||||
(objectMetadataItem) =>
|
||||
objectNameSingulars.includes(objectMetadataItem.nameSingular),
|
||||
);
|
||||
|
||||
const parentRecord = snapshot
|
||||
.getLoadable(recordStoreFamilyState(recordId))
|
||||
.getValue();
|
||||
|
||||
if (isDefined(parentRecord)) {
|
||||
relatedObjectMetadataItems.forEach((relatedObjectMetadataItem) => {
|
||||
const currentMorphFieldValue =
|
||||
parentRecord[fieldDefinition.metadata.fieldName];
|
||||
|
||||
const objectRecordFromCache = getRecordFromCache({
|
||||
objectMetadataItem: relatedObjectMetadataItem,
|
||||
recordId: relatedRecordId,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
if (!isDefined(objectRecordFromCache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set(recordStoreFamilyState(recordId), {
|
||||
...parentRecord,
|
||||
[fieldDefinition.metadata.fieldName]: [
|
||||
...currentMorphFieldValue,
|
||||
objectRecordFromCache,
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const relatedObjectMetadataItemsWithCachedRecord =
|
||||
relatedObjectMetadataItems
|
||||
.map((objectMetadataItem) => {
|
||||
const cachedRelatedRecord = getRecordFromCache<ObjectRecord>({
|
||||
objectMetadataItem,
|
||||
recordId: relatedRecordId,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
return {
|
||||
cachedRelatedRecord,
|
||||
objectMetadataItem,
|
||||
};
|
||||
})
|
||||
.find((item) => isDefined(item.cachedRelatedRecord));
|
||||
|
||||
if (!relatedObjectMetadataItemsWithCachedRecord) {
|
||||
throw new Error('Could not find cached related record');
|
||||
}
|
||||
|
||||
const {
|
||||
objectMetadataItem: relatedObjectMetadataItem,
|
||||
cachedRelatedRecord,
|
||||
} = relatedObjectMetadataItemsWithCachedRecord;
|
||||
|
||||
if (
|
||||
!isDefined(cachedRelatedRecord) ||
|
||||
!isDefined(cachedRelatedRecord.id)
|
||||
) {
|
||||
throw new Error('Could not find cached related record');
|
||||
}
|
||||
|
||||
const fieldOnRelatedObject = getTargetFieldMetadataName({
|
||||
fieldDefinition,
|
||||
objectNameSingular: relatedObjectMetadataItem.nameSingular,
|
||||
});
|
||||
if (!isDefined(fieldOnRelatedObject)) {
|
||||
throw new Error('Could not find field on related object');
|
||||
}
|
||||
|
||||
const previousRecordId =
|
||||
cachedRelatedRecord?.[`${fieldOnRelatedObject}Id`];
|
||||
|
||||
if (isDefined(previousRecordId)) {
|
||||
const previousRecord = getRecordFromCache<ObjectRecord>({
|
||||
objectMetadataItem,
|
||||
recordId: previousRecordId,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const previousRecordWithRelation = {
|
||||
...cachedRelatedRecord,
|
||||
[fieldOnRelatedObject]: previousRecord,
|
||||
};
|
||||
|
||||
const gqlFields = computeDepthOneRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem: relatedObjectMetadataItem,
|
||||
record: previousRecordWithRelation,
|
||||
});
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem: relatedObjectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: {
|
||||
...cachedRelatedRecord,
|
||||
[fieldOnRelatedObject]: previousRecord,
|
||||
},
|
||||
recordGqlFields: gqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedManyRecordsArgs = [
|
||||
{
|
||||
idToUpdate: relatedRecordId,
|
||||
objectNameSingulars,
|
||||
relatedRecordId: recordId,
|
||||
// recordGqlFields: gqlFields,
|
||||
},
|
||||
];
|
||||
|
||||
await updateMultipleRecordsFromManyObjects(updatedManyRecordsArgs);
|
||||
},
|
||||
[
|
||||
fieldDefinition,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
updateMultipleRecordsFromManyObjects,
|
||||
apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
],
|
||||
);
|
||||
|
||||
return { updateOneRecordAndAttachMorphRelations };
|
||||
};
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
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 { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type useAttachRelatedRecordFromRecordProps = {
|
||||
recordObjectNameSingular: string;
|
||||
fieldNameOnRecordObject: string;
|
||||
};
|
||||
|
||||
export const useAttachRelatedRecordFromRecord = ({
|
||||
recordObjectNameSingular,
|
||||
fieldNameOnRecordObject,
|
||||
}: useAttachRelatedRecordFromRecordProps) => {
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: recordObjectNameSingular,
|
||||
});
|
||||
|
||||
const fieldOnObject = objectMetadataItem.readableFields.find((field) => {
|
||||
return field.name === fieldNameOnRecordObject;
|
||||
});
|
||||
|
||||
const relatedRecordObjectNameSingular =
|
||||
fieldOnObject?.relation?.targetObjectMetadata.nameSingular;
|
||||
|
||||
if (!relatedRecordObjectNameSingular) {
|
||||
throw new CustomError(
|
||||
`Could not find record related to ${recordObjectNameSingular}`,
|
||||
'RELATED_RECORD_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
const { objectMetadataItem: relatedObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: relatedRecordObjectNameSingular,
|
||||
});
|
||||
|
||||
const fieldOnRelatedObject =
|
||||
fieldOnObject?.relation?.targetFieldMetadata.name;
|
||||
|
||||
if (!fieldOnRelatedObject) {
|
||||
throw new CustomError(
|
||||
`Missing target field for ${fieldNameOnRecordObject}`,
|
||||
'MISSING_TARGET_FIELD',
|
||||
);
|
||||
}
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular: relatedRecordObjectNameSingular,
|
||||
});
|
||||
|
||||
const getRecordFromCache = useGetRecordFromCache({
|
||||
objectNameSingular: recordObjectNameSingular,
|
||||
});
|
||||
|
||||
const getRelatedRecordFromCache = useGetRecordFromCache({
|
||||
objectNameSingular: relatedRecordObjectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
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 = computeDepthOneRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem: relatedObjectMetadataItem,
|
||||
record: previousRecordWithRelation,
|
||||
});
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem: relatedObjectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: {
|
||||
...cachedRelatedRecord,
|
||||
[fieldOnRelatedObject]: previousRecord,
|
||||
},
|
||||
recordGqlFields: gqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
}
|
||||
|
||||
await updateOneRecord({
|
||||
idToUpdate: relatedRecordId,
|
||||
updateOneRecordInput: {
|
||||
[`${fieldOnRelatedObject}Id`]: recordId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { updateOneRecordAndAttachRelations };
|
||||
};
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
import { type Reference } from '@apollo/client';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { getRefName } from '@/object-record/cache/utils/getRefName';
|
||||
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateMultipleRecordsFromManyObjects } from '@/object-record/hooks/useUpdateMultipleRecordsFromManyObjects';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useDetachMorphRelatedRecordFromRecord = () => {
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { updateMultipleRecordsFromManyObjects } =
|
||||
useUpdateMultipleRecordsFromManyObjects();
|
||||
|
||||
if (!isFieldMorphRelation(fieldDefinition)) {
|
||||
throw new Error('Field is not a morph relation');
|
||||
}
|
||||
|
||||
const objectNameSingular =
|
||||
fieldDefinition.metadata.morphRelations[0].sourceObjectMetadata
|
||||
.nameSingular;
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error('Could not find object metadata item');
|
||||
}
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const updateOneRecordAndDetachMorphRelations = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async ({
|
||||
recordId,
|
||||
relatedRecordId,
|
||||
objectNameSingulars,
|
||||
}: {
|
||||
recordId: string;
|
||||
relatedRecordId: string;
|
||||
objectNameSingulars: string[];
|
||||
}) => {
|
||||
const relatedObjectMetadataItems = objectMetadataItems.filter(
|
||||
(objectMetadataItem) =>
|
||||
objectNameSingulars.includes(objectMetadataItem.nameSingular),
|
||||
);
|
||||
|
||||
const parentRecord = snapshot
|
||||
.getLoadable(recordStoreFamilyState(recordId))
|
||||
.getValue();
|
||||
|
||||
if (isDefined(parentRecord)) {
|
||||
relatedObjectMetadataItems.forEach((relatedObjectMetadataItem) => {
|
||||
const currentMorphFieldValue = parentRecord[
|
||||
fieldDefinition.metadata.fieldName
|
||||
] as ObjectRecord[];
|
||||
|
||||
const objectRecordFromCache = getRecordFromCache({
|
||||
objectMetadataItem: relatedObjectMetadataItem,
|
||||
recordId: relatedRecordId,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
if (!isDefined(objectRecordFromCache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(currentMorphFieldValue)) {
|
||||
set(recordStoreFamilyState(recordId), {
|
||||
...parentRecord,
|
||||
[fieldDefinition.metadata.fieldName]:
|
||||
currentMorphFieldValue.filter(
|
||||
(record) => record.id !== relatedRecordId,
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const relatedObjectMetadataItemsWithCachedRecord =
|
||||
relatedObjectMetadataItems
|
||||
.map((objectMetadataItem) => {
|
||||
const cachedRelatedRecord = getRecordFromCache<ObjectRecord>({
|
||||
objectMetadataItem,
|
||||
recordId: relatedRecordId,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
return {
|
||||
cachedRelatedRecord,
|
||||
objectMetadataItem,
|
||||
};
|
||||
})
|
||||
.find((item) => isDefined(item.cachedRelatedRecord));
|
||||
|
||||
if (!relatedObjectMetadataItemsWithCachedRecord) {
|
||||
throw new Error('Could not find cached related record');
|
||||
}
|
||||
|
||||
const { objectMetadataItem: relatedObjectMetadataItem } =
|
||||
relatedObjectMetadataItemsWithCachedRecord;
|
||||
|
||||
modifyRecordFromCache({
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
fieldModifiers: {
|
||||
[fieldDefinition.metadata.fieldName]: (
|
||||
fieldNameOnRecordObjectConnection,
|
||||
{ readField },
|
||||
) => {
|
||||
const edges = readField<{ node: Reference }[]>(
|
||||
'edges',
|
||||
fieldNameOnRecordObjectConnection,
|
||||
);
|
||||
|
||||
if (!edges) return fieldNameOnRecordObjectConnection;
|
||||
|
||||
return {
|
||||
...fieldNameOnRecordObjectConnection,
|
||||
edges: edges.filter(
|
||||
(edge) =>
|
||||
!(
|
||||
edge.node.__ref ===
|
||||
getRefName(
|
||||
relatedObjectMetadataItem.nameSingular,
|
||||
relatedRecordId,
|
||||
)
|
||||
),
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
recordId,
|
||||
});
|
||||
|
||||
const updatedManyRecordsArgs = [
|
||||
{
|
||||
idToUpdate: relatedRecordId,
|
||||
objectNameSingulars,
|
||||
relatedRecordId: null,
|
||||
},
|
||||
];
|
||||
|
||||
await updateMultipleRecordsFromManyObjects(updatedManyRecordsArgs);
|
||||
},
|
||||
[
|
||||
fieldDefinition,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
updateMultipleRecordsFromManyObjects,
|
||||
apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
],
|
||||
);
|
||||
|
||||
return { updateOneRecordAndDetachMorphRelations };
|
||||
};
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
import { type Reference } from '@apollo/client';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
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 apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: recordObjectNameSingular,
|
||||
});
|
||||
|
||||
const fieldOnObject = objectMetadataItem.readableFields.find((field) => {
|
||||
return field.name === fieldNameOnRecordObject;
|
||||
});
|
||||
|
||||
const relatedRecordObjectNameSingular =
|
||||
fieldOnObject?.relation?.targetObjectMetadata.nameSingular;
|
||||
|
||||
const fieldOnRelatedObject =
|
||||
fieldOnObject?.relation?.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: apolloCoreClient.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 };
|
||||
};
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateOneRecordV2 } from '@/object-record/hooks/useUpdateOneRecordV2';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useRecordOneToManyFieldAttachTargetRecord = () => {
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const { updateOneRecord } = useUpdateOneRecordV2();
|
||||
|
||||
const recordOneToManyFieldAttachTargetRecord = async ({
|
||||
sourceObjectNameSingular,
|
||||
targetObjectNameSingular,
|
||||
targetGQLFieldName,
|
||||
sourceRecordId,
|
||||
targetRecordId,
|
||||
}: {
|
||||
sourceObjectNameSingular: string;
|
||||
targetObjectNameSingular: string;
|
||||
targetGQLFieldName: string;
|
||||
sourceRecordId: string;
|
||||
targetRecordId: string;
|
||||
}) => {
|
||||
const targetObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === targetObjectNameSingular,
|
||||
);
|
||||
|
||||
const sourceObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === sourceObjectNameSingular,
|
||||
);
|
||||
|
||||
if (!targetObjectMetadataItem) {
|
||||
throw new CustomError(
|
||||
`Could not find related object metadata for ${targetObjectNameSingular}`,
|
||||
'RELATED_OBJECT_METADATA_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
if (!sourceObjectMetadataItem) {
|
||||
throw new CustomError(
|
||||
`Could not find source object metadata for ${sourceObjectNameSingular}`,
|
||||
'SOURCE_OBJECT_METADATA_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
const cachedTargetRecord = getRecordFromCache({
|
||||
objectMetadataItem: targetObjectMetadataItem,
|
||||
recordId: targetRecordId,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
if (!cachedTargetRecord) {
|
||||
throw new Error('Could not find cached related record');
|
||||
}
|
||||
|
||||
const previousRecordId = cachedTargetRecord?.[`${targetGQLFieldName}Id`];
|
||||
|
||||
if (isDefined(previousRecordId)) {
|
||||
const previousRecord = getRecordFromCache({
|
||||
objectMetadataItem: sourceObjectMetadataItem,
|
||||
recordId: previousRecordId,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const previousRecordWithRelation = {
|
||||
...cachedTargetRecord,
|
||||
[targetGQLFieldName]: previousRecord,
|
||||
};
|
||||
|
||||
const gqlFields = computeDepthOneRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem: targetObjectMetadataItem,
|
||||
record: previousRecordWithRelation,
|
||||
});
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem: targetObjectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: {
|
||||
...cachedTargetRecord,
|
||||
[targetGQLFieldName]: previousRecord,
|
||||
},
|
||||
recordGqlFields: gqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
}
|
||||
|
||||
await updateOneRecord({
|
||||
objectNameSingular: targetObjectNameSingular,
|
||||
idToUpdate: targetRecordId,
|
||||
updateOneRecordInput: {
|
||||
[`${targetGQLFieldName}Id`]: sourceRecordId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { recordOneToManyFieldAttachTargetRecord };
|
||||
};
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import { type Reference } from '@apollo/client';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getRefName } from '@/object-record/cache/utils/getRefName';
|
||||
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
|
||||
import { useUpdateOneRecordV2 } from '@/object-record/hooks/useUpdateOneRecordV2';
|
||||
import { CustomError } from 'twenty-shared/utils';
|
||||
|
||||
export const useRecordOneToManyFieldDetachTargetRecord = () => {
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { updateOneRecord } = useUpdateOneRecordV2();
|
||||
|
||||
const recordOneToManyFieldDetachTargetRecord = async ({
|
||||
sourceObjectNameSingular,
|
||||
targetObjectNameSingular,
|
||||
targetGQLFieldName,
|
||||
sourceRecordId,
|
||||
targetRecordId,
|
||||
}: {
|
||||
sourceObjectNameSingular: string;
|
||||
targetObjectNameSingular: string;
|
||||
targetGQLFieldName: string;
|
||||
sourceRecordId: string;
|
||||
targetRecordId: string;
|
||||
}) => {
|
||||
const targetObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === targetObjectNameSingular,
|
||||
);
|
||||
|
||||
const sourceObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === sourceObjectNameSingular,
|
||||
);
|
||||
|
||||
if (!targetObjectMetadataItem) {
|
||||
throw new CustomError(
|
||||
`Could not find related object metadata for ${targetObjectNameSingular}`,
|
||||
'RELATED_OBJECT_METADATA_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
if (!sourceObjectMetadataItem) {
|
||||
throw new CustomError(
|
||||
`Could not find source object metadata for ${sourceObjectNameSingular}`,
|
||||
'SOURCE_OBJECT_METADATA_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
modifyRecordFromCache({
|
||||
objectMetadataItem: sourceObjectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
fieldModifiers: {
|
||||
[targetGQLFieldName]: (
|
||||
fieldNameOnRecordObjectConnection,
|
||||
{ readField },
|
||||
) => {
|
||||
const edges = readField<{ node: Reference }[]>(
|
||||
'edges',
|
||||
fieldNameOnRecordObjectConnection,
|
||||
);
|
||||
|
||||
if (!edges) return fieldNameOnRecordObjectConnection;
|
||||
|
||||
return {
|
||||
...fieldNameOnRecordObjectConnection,
|
||||
edges: edges.filter(
|
||||
(edge) =>
|
||||
!(
|
||||
edge.node.__ref ===
|
||||
getRefName(targetObjectNameSingular, targetRecordId)
|
||||
),
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
recordId: sourceRecordId,
|
||||
});
|
||||
|
||||
await updateOneRecord({
|
||||
objectNameSingular: targetObjectNameSingular,
|
||||
idToUpdate: targetRecordId,
|
||||
updateOneRecordInput: {
|
||||
[`${targetGQLFieldName}Id`]: null,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { recordOneToManyFieldDetachTargetRecord };
|
||||
};
|
||||
-284
@@ -1,284 +0,0 @@
|
||||
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { generateUpdateOneRecordMutation } from '@/object-record/multiple-objects/utils/generateUpdateOneRecordMutation';
|
||||
import { getTargetFieldMetadataName } from '@/object-record/multiple-objects/utils/getTargetFieldMetadataName';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
|
||||
import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName';
|
||||
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
||||
|
||||
type UpdateManyRecordArgs = {
|
||||
idToUpdate: string;
|
||||
relatedRecordId: string | null;
|
||||
objectNameSingulars: string[];
|
||||
recordGqlFields?: Record<string, any>;
|
||||
};
|
||||
|
||||
export const useUpdateMultipleRecordsFromManyObjects = () => {
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const updateMultipleRecordsFromManyObjects = async (
|
||||
updatedManyRecordsArgs: UpdateManyRecordArgs[],
|
||||
) => {
|
||||
for (const {
|
||||
idToUpdate,
|
||||
objectNameSingulars,
|
||||
relatedRecordId,
|
||||
recordGqlFields,
|
||||
} of updatedManyRecordsArgs) {
|
||||
const objectMetadataItemArray = objectMetadataItems.filter(
|
||||
(objectMetadataItem) =>
|
||||
objectNameSingulars.includes(objectMetadataItem.nameSingular),
|
||||
);
|
||||
|
||||
if (objectMetadataItemArray.length === 0) {
|
||||
throw new CustomError(
|
||||
`Object metadata item not found ${objectNameSingulars.join(', ')}`,
|
||||
'OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataItemWithCachedRecord = objectMetadataItemArray
|
||||
.map((objectMetadataItem) => {
|
||||
const cachedRecord = getRecordFromCache({
|
||||
objectMetadataItem,
|
||||
recordId: idToUpdate,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
return {
|
||||
cachedRecord,
|
||||
objectMetadataItem,
|
||||
};
|
||||
})
|
||||
.find((item) => isDefined(item.cachedRecord));
|
||||
|
||||
if (!isDefined(objectMetadataItemWithCachedRecord)) {
|
||||
throw new CustomError(
|
||||
`Record not found ${idToUpdate}`,
|
||||
'RECORD_NOT_FOUND_IN_OBJECT_METADATA_ITEM',
|
||||
);
|
||||
}
|
||||
|
||||
const { objectMetadataItem, cachedRecord } =
|
||||
objectMetadataItemWithCachedRecord;
|
||||
|
||||
if (
|
||||
!isFieldRelation(fieldDefinition) &&
|
||||
!isFieldMorphRelation(fieldDefinition)
|
||||
) {
|
||||
throw new CustomError(
|
||||
`Should never happen`,
|
||||
'TARGET_FIELD_NAME_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
const targetFieldName = getTargetFieldMetadataName({
|
||||
fieldDefinition,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
if (!isDefined(targetFieldName)) {
|
||||
throw new CustomError(
|
||||
`Cannot find Target field name for the (morph) relation field ${fieldDefinition.metadata.fieldName} on ${objectMetadataItem.nameSingular}`,
|
||||
'TARGET_FIELD_NAME_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
const updateOneRecordInput = {
|
||||
[`${targetFieldName}Id`]: relatedRecordId,
|
||||
};
|
||||
|
||||
const optimisticRecordInput = computeOptimisticRecordFromInput({
|
||||
objectMetadataItem,
|
||||
currentWorkspaceMember: currentWorkspaceMember,
|
||||
recordInput: updateOneRecordInput,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const computedRecordGqlFields =
|
||||
recordGqlFields ??
|
||||
generateDepthOneRecordGqlFields({ objectMetadataItem });
|
||||
|
||||
const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: cachedRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
const computedOptimisticRecord = {
|
||||
...cachedRecord,
|
||||
...optimisticRecordInput,
|
||||
id: idToUpdate,
|
||||
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
||||
};
|
||||
|
||||
const optimisticRecordWithConnection =
|
||||
getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: computedOptimisticRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
const shouldHandleOptimisticCache =
|
||||
!isNull(cachedRecord) &&
|
||||
isDefined(optimisticRecordWithConnection) &&
|
||||
isDefined(cachedRecordWithConnection);
|
||||
|
||||
if (shouldHandleOptimisticCache) {
|
||||
const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem,
|
||||
record: optimisticRecordInput,
|
||||
});
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: computedOptimisticRecord,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: cachedRecordWithConnection,
|
||||
updatedRecord: optimisticRecordWithConnection,
|
||||
objectMetadataItems,
|
||||
});
|
||||
}
|
||||
|
||||
const mutationResponseField = getUpdateOneRecordMutationResponseField(
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
||||
const sanitizedInput = {
|
||||
...sanitizeRecordInput({
|
||||
objectMetadataItem,
|
||||
recordInput: updateOneRecordInput,
|
||||
}),
|
||||
};
|
||||
|
||||
const updateOneRecordMutation = generateUpdateOneRecordMutation({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const updatedRecord = await apolloCoreClient
|
||||
.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate,
|
||||
input: sanitizedInput,
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.[mutationResponseField];
|
||||
if (!isDefined(record)) return;
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: computedOptimisticRecord,
|
||||
updatedRecord: record,
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (!shouldHandleOptimisticCache) {
|
||||
throw error;
|
||||
}
|
||||
const cachedRecordKeys = new Set(Object.keys(cachedRecord));
|
||||
const recordKeysAddedByOptimisticCache = Object.keys(
|
||||
optimisticRecordInput,
|
||||
).filter((diffKey) => !cachedRecordKeys.has(diffKey));
|
||||
|
||||
const recordGqlFields = {
|
||||
...computeDepthOneRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem,
|
||||
record: cachedRecord,
|
||||
}),
|
||||
...buildRecordFromKeysWithSameValue(
|
||||
recordKeysAddedByOptimisticCache,
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: {
|
||||
...cachedRecord,
|
||||
...buildRecordFromKeysWithSameValue(
|
||||
recordKeysAddedByOptimisticCache,
|
||||
null,
|
||||
),
|
||||
},
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: optimisticRecordWithConnection,
|
||||
updatedRecord: cachedRecordWithConnection,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
const refetchAggregateQueries = async () => {
|
||||
const queryName = getAggregateQueryName(objectMetadataItem.namePlural);
|
||||
|
||||
await apolloCoreClient.refetchQueries({
|
||||
include: [queryName],
|
||||
});
|
||||
};
|
||||
await refetchAggregateQueries();
|
||||
|
||||
return updatedRecord?.data?.[mutationResponseField] ?? null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
updateMultipleRecordsFromManyObjects,
|
||||
};
|
||||
};
|
||||
@@ -2,10 +2,10 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
|
||||
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
||||
import { type RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { generateUpdateOneRecordMutation } from '@/object-record/multiple-objects/utils/generateUpdateOneRecordMutation';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useUpdateOneRecordMutation = ({
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
|
||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
|
||||
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
||||
|
||||
type UpdateOneRecordArgs<UpdatedObjectRecord> = {
|
||||
idToUpdate: string;
|
||||
updateOneRecordInput: Partial<Omit<UpdatedObjectRecord, 'id'>>;
|
||||
optimisticRecord?: Partial<ObjectRecord>;
|
||||
objectNameSingular: string;
|
||||
recordGqlFields?: Record<string, any>;
|
||||
};
|
||||
export const useUpdateOneRecordV2 = () => {
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const updateOneRecord = async <
|
||||
UpdatedObjectRecord extends ObjectRecord = ObjectRecord,
|
||||
>({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
idToUpdate,
|
||||
updateOneRecordInput,
|
||||
optimisticRecord,
|
||||
}: UpdateOneRecordArgs<UpdatedObjectRecord>) => {
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
throw new Error(
|
||||
`Object metadata item not found for ${objectNameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
const computedRecordGqlFields =
|
||||
recordGqlFields ??
|
||||
generateDepthOneRecordGqlFields({ objectMetadataItem });
|
||||
|
||||
const optimisticRecordInput =
|
||||
optimisticRecord ??
|
||||
computeOptimisticRecordFromInput({
|
||||
objectMetadataItem,
|
||||
currentWorkspaceMember: currentWorkspaceMember,
|
||||
recordInput: updateOneRecordInput,
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
const cachedRecord = getRecordFromCache({
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordId: idToUpdate,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: cachedRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
const computedOptimisticRecord = {
|
||||
...cachedRecord,
|
||||
...optimisticRecordInput,
|
||||
id: idToUpdate,
|
||||
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
||||
};
|
||||
|
||||
const optimisticRecordWithConnection =
|
||||
getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: computedOptimisticRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
const shouldHandleOptimisticCache =
|
||||
!isNull(cachedRecord) &&
|
||||
isDefined(optimisticRecordWithConnection) &&
|
||||
isDefined(cachedRecordWithConnection);
|
||||
|
||||
if (shouldHandleOptimisticCache) {
|
||||
const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem,
|
||||
record: optimisticRecordInput,
|
||||
});
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: computedOptimisticRecord,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: cachedRecordWithConnection,
|
||||
updatedRecord: optimisticRecordWithConnection,
|
||||
objectMetadataItems,
|
||||
});
|
||||
}
|
||||
|
||||
const mutationResponseField =
|
||||
getUpdateOneRecordMutationResponseField(objectNameSingular);
|
||||
|
||||
const sanitizedInput = {
|
||||
...sanitizeRecordInput({
|
||||
objectMetadataItem,
|
||||
recordInput: updateOneRecordInput,
|
||||
}),
|
||||
};
|
||||
|
||||
const updateOneRecordMutation = generateUpdateOneRecordMutation({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences: false,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const updatedRecord = await apolloCoreClient
|
||||
.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate,
|
||||
input: sanitizedInput,
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.[mutationResponseField];
|
||||
if (!isDefined(record)) return;
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: computedOptimisticRecord,
|
||||
updatedRecord: record,
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (!shouldHandleOptimisticCache) {
|
||||
throw error;
|
||||
}
|
||||
const cachedRecordKeys = new Set(Object.keys(cachedRecord));
|
||||
const recordKeysAddedByOptimisticCache = Object.keys(
|
||||
optimisticRecordInput,
|
||||
).filter((diffKey) => !cachedRecordKeys.has(diffKey));
|
||||
|
||||
const recordGqlFields = {
|
||||
...computeDepthOneRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem,
|
||||
record: cachedRecord,
|
||||
}),
|
||||
...buildRecordFromKeysWithSameValue(
|
||||
recordKeysAddedByOptimisticCache,
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: {
|
||||
...cachedRecord,
|
||||
...buildRecordFromKeysWithSameValue(
|
||||
recordKeysAddedByOptimisticCache,
|
||||
null,
|
||||
),
|
||||
},
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: optimisticRecordWithConnection,
|
||||
updatedRecord: cachedRecordWithConnection,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
return updatedRecord?.data?.[mutationResponseField] ?? null;
|
||||
};
|
||||
|
||||
return {
|
||||
updateOneRecord,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user