feat: drop target column map (#4670)

This PR is dropping the column `targetColumnMap` of fieldMetadata
entities.
The goal of this column was to properly map field to their respecting
column in the table.
We decide to drop it and instead compute the column name on the fly when
we need it, as it's more easier to support.
Some parts of the code has been refactored to try making implementation
of composite type more easier to understand and maintain.

Fix #3760

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2024-04-08 16:00:28 +02:00
committed by GitHub
parent 84f8c14e52
commit 5019b5febc
72 changed files with 1432 additions and 1853 deletions

View File

@ -1,41 +0,0 @@
import { BadRequestException } from '@nestjs/common';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { generateTargetColumnMap } from 'src/engine/metadata-modules/field-metadata/utils/generate-target-column-map.util';
describe('generateTargetColumnMap', () => {
it('should generate a target column map for a given type', () => {
const textMap = generateTargetColumnMap(
FieldMetadataType.TEXT,
false,
'name',
);
expect(textMap).toEqual({ value: 'name' });
const linkMap = generateTargetColumnMap(
FieldMetadataType.LINK,
false,
'website',
);
expect(linkMap).toEqual({ label: 'websiteLabel', url: 'websiteUrl' });
const currencyMap = generateTargetColumnMap(
FieldMetadataType.CURRENCY,
true,
'price',
);
expect(currencyMap).toEqual({
amountMicros: '_priceAmountMicros',
currencyCode: '_priceCurrencyCode',
});
});
it('should throw an error for an unknown type', () => {
expect(() =>
generateTargetColumnMap('invalid' as FieldMetadataType, false, 'name'),
).toThrow(BadRequestException);
});
});

View File

@ -0,0 +1,53 @@
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { CompositeProperty } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
type ComputeColumnNameOptions = { isForeignKey?: boolean };
export function computeColumnName(
fieldName: string,
options?: ComputeColumnNameOptions,
): string;
export function computeColumnName<T extends FieldMetadataType | 'default'>(
fieldMetadata: FieldMetadataInterface<T>,
ioptions?: ComputeColumnNameOptions,
): string;
// TODO: If we need to implement custom name logic for columns, we can do it here
export function computeColumnName<T extends FieldMetadataType | 'default'>(
fieldMetadataOrFieldName: FieldMetadataInterface<T> | string,
options?: ComputeColumnNameOptions,
): string {
const generateName = (name: string) => {
return options?.isForeignKey ? `${name}Id` : name;
};
if (typeof fieldMetadataOrFieldName === 'string') {
return generateName(fieldMetadataOrFieldName);
}
if (isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) {
throw new Error(
`Cannot compute column name for composite field metadata type: ${fieldMetadataOrFieldName.type}`,
);
}
return generateName(fieldMetadataOrFieldName.name);
}
export const computeCompositeColumnName = <
T extends FieldMetadataType | 'default',
>(
fieldMetadata: FieldMetadataInterface<T>,
compositeProperty: CompositeProperty,
): string => {
if (!isCompositeFieldMetadataType(fieldMetadata.type)) {
throw new Error(
`Cannot compute composite column name for non-composite field metadata type: ${fieldMetadata.type}`,
);
}
return `${fieldMetadata.name}${pascalCase(compositeProperty.name)}`;
};

View File

@ -1,73 +0,0 @@
import { BadRequestException } from '@nestjs/common';
import { FieldMetadataTargetColumnMap } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-target-column-map.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { createCustomColumnName } from 'src/engine/utils/create-custom-column-name.util';
/**
* Generate a target column map for a given type, this is used to map the field to the correct column(s) in the database.
* This is used to support fields that map to multiple columns in the database.
*
* @param type string
* @returns FieldMetadataTargetColumnMap
*/
export function generateTargetColumnMap(
type: FieldMetadataType,
isCustomField: boolean,
fieldName: string,
): FieldMetadataTargetColumnMap {
const columnName = isCustomField
? createCustomColumnName(fieldName)
: fieldName;
switch (type) {
case FieldMetadataType.UUID:
case FieldMetadataType.TEXT:
case FieldMetadataType.PHONE:
case FieldMetadataType.EMAIL:
case FieldMetadataType.NUMBER:
case FieldMetadataType.NUMERIC:
case FieldMetadataType.PROBABILITY:
case FieldMetadataType.BOOLEAN:
case FieldMetadataType.DATE_TIME:
case FieldMetadataType.RATING:
case FieldMetadataType.SELECT:
case FieldMetadataType.MULTI_SELECT:
case FieldMetadataType.POSITION:
case FieldMetadataType.RAW_JSON:
return {
value: columnName,
};
case FieldMetadataType.LINK:
return {
label: `${columnName}Label`,
url: `${columnName}Url`,
};
case FieldMetadataType.CURRENCY:
return {
amountMicros: `${columnName}AmountMicros`,
currencyCode: `${columnName}CurrencyCode`,
};
case FieldMetadataType.FULL_NAME:
return {
firstName: `${columnName}FirstName`,
lastName: `${columnName}LastName`,
};
case FieldMetadataType.ADDRESS:
return {
addressStreet1: `${columnName}AddressStreet1`,
addressStreet2: `${columnName}AddressStreet2`,
addressCity: `${columnName}AddressCity`,
addressPostcode: `${columnName}AddressPostcode`,
addressState: `${columnName}AddressState`,
addressCountry: `${columnName}AddressCountry`,
addressLat: `${columnName}AddressLat`,
addressLng: `${columnName}AddressLng`,
};
case FieldMetadataType.RELATION:
return {};
default:
throw new BadRequestException(`Unknown type ${type}`);
}
}