Fix nested relations (#7158)

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Weiko
2024-09-20 05:16:13 +02:00
committed by GitHub
parent 6a5f9492d3
commit b1889e4569
17 changed files with 753 additions and 531 deletions

View File

@ -0,0 +1,65 @@
import { isPlainObject } from '@nestjs/common/utils/shared.utils';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
export function formatData<T>(
data: T,
objectMetadata: ObjectMetadataMapItem,
): T {
if (!data) {
return data;
}
if (Array.isArray(data)) {
return data.map((item) => formatData(item, objectMetadata)) as T;
}
const compositeFieldMetadataCollection =
getCompositeFieldMetadataCollection(objectMetadata);
const compositeFieldMetadataMap = new Map(
compositeFieldMetadataCollection.map((fieldMetadata) => [
fieldMetadata.name,
fieldMetadata,
]),
);
const newData: object = {};
for (const [key, value] of Object.entries(data)) {
const fieldMetadata = compositeFieldMetadataMap.get(key);
if (!fieldMetadata) {
if (isPlainObject(value)) {
newData[key] = formatData(value, objectMetadata);
} else {
newData[key] = value;
}
continue;
}
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
if (!compositeType) {
continue;
}
for (const compositeProperty of compositeType.properties) {
const compositeKey = computeCompositeColumnName(
fieldMetadata.name,
compositeProperty,
);
const value = data?.[key]?.[compositeProperty.name];
if (value === undefined || value === null) {
continue;
}
newData[compositeKey] = data[key][compositeProperty.name];
}
}
return newData as T;
}

View File

@ -0,0 +1,131 @@
import { isPlainObject } from '@nestjs/common/utils/shared.utils';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import {
ObjectMetadataMap,
ObjectMetadataMapItem,
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util';
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
export function formatResult<T>(
data: T,
objectMetadata: ObjectMetadataMapItem,
objectMetadataMap: ObjectMetadataMap,
): T {
if (!data) {
return data;
}
if (Array.isArray(data)) {
return data.map((item) =>
formatResult(item, objectMetadata, objectMetadataMap),
) as T;
}
if (!isPlainObject(data)) {
return data;
}
if (!objectMetadata) {
throw new Error('Object metadata is missing');
}
const compositeFieldMetadataCollection =
getCompositeFieldMetadataCollection(objectMetadata);
const compositeFieldMetadataMap = new Map(
compositeFieldMetadataCollection.flatMap((fieldMetadata) => {
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
if (!compositeType) return [];
// Map each composite property to a [key, value] pair
return compositeType.properties.map((compositeProperty) => [
computeCompositeColumnName(fieldMetadata.name, compositeProperty),
{
parentField: fieldMetadata.name,
...compositeProperty,
},
]);
}),
);
const relationMetadataMap = new Map(
Object.values(objectMetadata.fields)
.filter(({ type }) => isRelationFieldMetadataType(type))
.map((fieldMetadata) => [
fieldMetadata.name,
{
relationMetadata:
fieldMetadata.fromRelationMetadata ??
fieldMetadata.toRelationMetadata,
relationType: computeRelationType(
fieldMetadata,
fieldMetadata.fromRelationMetadata ??
(fieldMetadata.toRelationMetadata as RelationMetadataEntity),
),
},
]),
);
const newData: object = {};
for (const [key, value] of Object.entries(data)) {
const compositePropertyArgs = compositeFieldMetadataMap.get(key);
const { relationMetadata, relationType } =
relationMetadataMap.get(key) ?? {};
if (!compositePropertyArgs && !relationMetadata) {
if (isPlainObject(value)) {
newData[key] = formatResult(value, objectMetadata, objectMetadataMap);
} else {
newData[key] = value;
}
continue;
}
if (relationMetadata) {
const toObjectMetadata =
objectMetadataMap[relationMetadata.toObjectMetadataId];
const fromObjectMetadata =
objectMetadataMap[relationMetadata.fromObjectMetadataId];
if (!toObjectMetadata) {
throw new Error(
`Object metadata for object metadataId "${relationMetadata.toObjectMetadataId}" is missing`,
);
}
if (!fromObjectMetadata) {
throw new Error(
`Object metadata for object metadataId "${relationMetadata.fromObjectMetadataId}" is missing`,
);
}
newData[key] = formatResult(
value,
relationType === 'one-to-many' ? toObjectMetadata : fromObjectMetadata,
objectMetadataMap,
);
continue;
}
if (!compositePropertyArgs) {
continue;
}
const { parentField, ...compositeProperty } = compositePropertyArgs;
if (!newData[parentField]) {
newData[parentField] = {};
}
newData[parentField][compositeProperty.name] = value;
}
return newData as T;
}

View File

@ -0,0 +1,12 @@
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
export function getCompositeFieldMetadataCollection(
objectMetadata: ObjectMetadataMapItem,
) {
const compositeFieldMetadataCollection = Object.values(
objectMetadata.fields,
).filter((fieldMetadata) => isCompositeFieldMetadataType(fieldMetadata.type));
return compositeFieldMetadataCollection;
}