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:
@ -1,188 +1,61 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
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';
|
||||
|
||||
export const addressFields = (
|
||||
fieldMetadata?: FieldMetadataInterface,
|
||||
): FieldMetadataInterface[] => {
|
||||
const inferredFieldMetadata = fieldMetadata as
|
||||
| FieldMetadataInterface<FieldMetadataType.ADDRESS>
|
||||
| undefined;
|
||||
const targetColumnMap = inferredFieldMetadata
|
||||
? generateTargetColumnMap(
|
||||
inferredFieldMetadata.type,
|
||||
inferredFieldMetadata.isCustom ?? false,
|
||||
inferredFieldMetadata.name,
|
||||
)
|
||||
: {
|
||||
addressStreet1: 'addressStreet1',
|
||||
addressStreet2: 'addressStreet2',
|
||||
addressCity: 'addressCity',
|
||||
addressPostcode: 'addressPostcode',
|
||||
addressState: 'addressState',
|
||||
addressCountry: 'addressCountry',
|
||||
addressLat: 'addressLat',
|
||||
addressLng: 'addressLng',
|
||||
};
|
||||
|
||||
return [
|
||||
export const addressCompositeType: CompositeType = {
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
properties: [
|
||||
{
|
||||
id: 'addressStreet1',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
name: 'addressStreet1',
|
||||
label: 'Address',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressStreet1,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressStreet1 ?? undefined,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
{
|
||||
id: 'addressStreet2',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'addressStreet2',
|
||||
label: 'Address 2',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressStreet2,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressStreet2 ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
{
|
||||
id: 'addressCity',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'addressCity',
|
||||
label: 'City',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressCity,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressCity ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
{
|
||||
id: 'addressPostcode',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'addressPostcode',
|
||||
label: 'Postcode',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressPostcode,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressPostcode ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
{
|
||||
id: 'addressState',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'addressState',
|
||||
label: 'State',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressState,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressState ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
{
|
||||
id: 'addressCountry',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'addressCountry',
|
||||
label: 'Country',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressCountry,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressCountry ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
id: 'addressLat',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
name: 'addressLat',
|
||||
label: 'Latitude',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressLat,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressLat ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.NUMBER>,
|
||||
type: FieldMetadataType.NUMERIC,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
id: 'addressLng',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
objectMetadataId: FieldMetadataType.ADDRESS.toString(),
|
||||
name: 'addressLng',
|
||||
label: 'Longitude',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.addressLng,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.addressLng ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.NUMBER>,
|
||||
];
|
||||
type: FieldMetadataType.NUMERIC,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const addressObjectDefinition = {
|
||||
id: FieldMetadataType.ADDRESS.toString(),
|
||||
nameSingular: 'address',
|
||||
namePlural: 'address',
|
||||
labelSingular: 'Address',
|
||||
labelPlural: 'Addresses',
|
||||
targetTableName: '',
|
||||
fields: addressFields(),
|
||||
fromRelations: [],
|
||||
toRelations: [],
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
} satisfies ObjectMetadataInterface;
|
||||
|
||||
export type AddressMetadata = {
|
||||
addressStreet1: string;
|
||||
addressStreet2: string;
|
||||
|
||||
@ -1,80 +1,25 @@
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
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';
|
||||
|
||||
export const currencyFields = (
|
||||
fieldMetadata?: FieldMetadataInterface,
|
||||
): FieldMetadataInterface[] => {
|
||||
const inferredFieldMetadata = fieldMetadata as
|
||||
| FieldMetadataInterface<FieldMetadataType.CURRENCY>
|
||||
| undefined;
|
||||
const targetColumnMap = inferredFieldMetadata
|
||||
? generateTargetColumnMap(
|
||||
inferredFieldMetadata.type,
|
||||
inferredFieldMetadata.isCustom ?? false,
|
||||
inferredFieldMetadata.name,
|
||||
)
|
||||
: {
|
||||
amountMicros: 'amountMicros',
|
||||
currencyCode: 'currencyCode',
|
||||
};
|
||||
|
||||
return [
|
||||
export const currencyCompositeType: CompositeType = {
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
properties: [
|
||||
{
|
||||
id: 'amountMicros',
|
||||
type: FieldMetadataType.NUMERIC,
|
||||
objectMetadataId: FieldMetadataType.CURRENCY.toString(),
|
||||
name: 'amountMicros',
|
||||
label: 'AmountMicros',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.amountMicros,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.amountMicros ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.NUMERIC>,
|
||||
type: FieldMetadataType.NUMERIC,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
id: 'currencyCode',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.CURRENCY.toString(),
|
||||
name: 'currencyCode',
|
||||
label: 'Currency Code',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.currencyCode,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue:
|
||||
inferredFieldMetadata.defaultValue?.currencyCode ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
];
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const currencyObjectDefinition = {
|
||||
id: FieldMetadataType.CURRENCY.toString(),
|
||||
nameSingular: 'currency',
|
||||
namePlural: 'currency',
|
||||
labelSingular: 'Currency',
|
||||
labelPlural: 'Currency',
|
||||
targetTableName: '',
|
||||
fields: currencyFields(),
|
||||
fromRelations: [],
|
||||
toRelations: [],
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
} satisfies ObjectMetadataInterface;
|
||||
|
||||
export type CurrencyMetadata = {
|
||||
amountMicros: number;
|
||||
currencyCode: string;
|
||||
|
||||
@ -1,78 +1,25 @@
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
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';
|
||||
|
||||
export const fullNameFields = (
|
||||
fieldMetadata?: FieldMetadataInterface,
|
||||
): FieldMetadataInterface[] => {
|
||||
const inferredFieldMetadata = fieldMetadata as
|
||||
| FieldMetadataInterface<FieldMetadataType.FULL_NAME>
|
||||
| undefined;
|
||||
const targetColumnMap = inferredFieldMetadata
|
||||
? generateTargetColumnMap(
|
||||
inferredFieldMetadata.type,
|
||||
inferredFieldMetadata.isCustom ?? false,
|
||||
inferredFieldMetadata.name,
|
||||
)
|
||||
: {
|
||||
firstName: 'firstName',
|
||||
lastName: 'lastName',
|
||||
};
|
||||
|
||||
return [
|
||||
export const fullNameCompositeType: CompositeType = {
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
properties: [
|
||||
{
|
||||
id: 'firstName',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.FULL_NAME.toString(),
|
||||
name: 'firstName',
|
||||
label: 'First Name',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.firstName,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue: inferredFieldMetadata.defaultValue?.firstName ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
{
|
||||
id: 'lastName',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.FULL_NAME.toString(),
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'lastName',
|
||||
label: 'Last Name',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.lastName,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue: inferredFieldMetadata.defaultValue?.lastName ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
];
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const fullNameObjectDefinition = {
|
||||
id: FieldMetadataType.FULL_NAME.toString(),
|
||||
nameSingular: 'fullName',
|
||||
namePlural: 'fullName',
|
||||
labelSingular: 'FullName',
|
||||
labelPlural: 'FullName',
|
||||
targetTableName: '',
|
||||
fields: fullNameFields(),
|
||||
fromRelations: [],
|
||||
toRelations: [],
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
} satisfies ObjectMetadataInterface;
|
||||
|
||||
export type FullNameMetadata = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
|
||||
@ -1,21 +1,22 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
import { currencyFields } from 'src/engine/metadata-modules/field-metadata/composite-types/currency.composite-type';
|
||||
import { fullNameFields } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
|
||||
import { linkFields } from 'src/engine/metadata-modules/field-metadata/composite-types/link.composite-type';
|
||||
import { currencyCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/currency.composite-type';
|
||||
import { fullNameCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
|
||||
import { linkCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/link.composite-type';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { addressFields } from 'src/engine/metadata-modules/field-metadata/composite-types/address.composite-type';
|
||||
import { addressCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/address.composite-type';
|
||||
|
||||
export type CompositeFieldsDefinitionFunction = (
|
||||
fieldMetadata?: FieldMetadataInterface,
|
||||
) => FieldMetadataInterface[];
|
||||
|
||||
export const compositeDefinitions = new Map<
|
||||
string,
|
||||
CompositeFieldsDefinitionFunction
|
||||
export const compositeTypeDefintions = new Map<
|
||||
FieldMetadataType,
|
||||
CompositeType
|
||||
>([
|
||||
[FieldMetadataType.LINK, linkFields],
|
||||
[FieldMetadataType.CURRENCY, currencyFields],
|
||||
[FieldMetadataType.FULL_NAME, fullNameFields],
|
||||
[FieldMetadataType.ADDRESS, addressFields],
|
||||
[FieldMetadataType.LINK, linkCompositeType],
|
||||
[FieldMetadataType.CURRENCY, currencyCompositeType],
|
||||
[FieldMetadataType.FULL_NAME, fullNameCompositeType],
|
||||
[FieldMetadataType.ADDRESS, addressCompositeType],
|
||||
]);
|
||||
|
||||
@ -1,78 +1,25 @@
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
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';
|
||||
|
||||
export const linkFields = (
|
||||
fieldMetadata?: FieldMetadataInterface,
|
||||
): FieldMetadataInterface[] => {
|
||||
const inferredFieldMetadata = fieldMetadata as
|
||||
| FieldMetadataInterface<FieldMetadataType.LINK>
|
||||
| undefined;
|
||||
const targetColumnMap = inferredFieldMetadata
|
||||
? generateTargetColumnMap(
|
||||
inferredFieldMetadata.type,
|
||||
inferredFieldMetadata.isCustom ?? false,
|
||||
inferredFieldMetadata.name,
|
||||
)
|
||||
: {
|
||||
label: 'label',
|
||||
url: 'url',
|
||||
};
|
||||
|
||||
return [
|
||||
export const linkCompositeType: CompositeType = {
|
||||
type: FieldMetadataType.LINK,
|
||||
properties: [
|
||||
{
|
||||
id: 'label',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.LINK.toString(),
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.label,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue: inferredFieldMetadata.defaultValue?.label ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
{
|
||||
id: 'url',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: FieldMetadataType.LINK.toString(),
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
label: 'Url',
|
||||
targetColumnMap: {
|
||||
value: targetColumnMap.url,
|
||||
},
|
||||
isNullable: true,
|
||||
...(inferredFieldMetadata
|
||||
? {
|
||||
defaultValue: inferredFieldMetadata.defaultValue?.url ?? null,
|
||||
}
|
||||
: {}),
|
||||
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
|
||||
];
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const linkObjectDefinition = {
|
||||
id: FieldMetadataType.LINK.toString(),
|
||||
nameSingular: 'link',
|
||||
namePlural: 'link',
|
||||
labelSingular: 'Link',
|
||||
labelPlural: 'Link',
|
||||
targetTableName: '',
|
||||
fields: linkFields(),
|
||||
fromRelations: [],
|
||||
toRelations: [],
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
} satisfies ObjectMetadataInterface;
|
||||
|
||||
export type LinkMetadata = {
|
||||
label: string;
|
||||
url: string;
|
||||
|
||||
@ -11,7 +11,6 @@ import {
|
||||
} from 'typeorm';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { FieldMetadataTargetColumnMap } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-target-column-map.interface';
|
||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||
|
||||
@ -74,9 +73,6 @@ export class FieldMetadataEntity<
|
||||
@Column({ nullable: false })
|
||||
label: string;
|
||||
|
||||
@Column({ nullable: false, type: 'jsonb' })
|
||||
targetColumnMap: FieldMetadataTargetColumnMap<T>;
|
||||
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
defaultValue: FieldMetadataDefaultValue<T>;
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ import {
|
||||
WorkspaceMigrationColumnDrop,
|
||||
WorkspaceMigrationTableAction,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { generateTargetColumnMap } from 'src/engine/metadata-modules/field-metadata/utils/generate-target-column-map.util';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
@ -37,7 +36,7 @@ import {
|
||||
RelationMetadataType,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
||||
import { computeCustomName } from 'src/engine/utils/compute-custom-name.util';
|
||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
|
||||
import {
|
||||
@ -128,11 +127,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
|
||||
const createdFieldMetadata = await fieldMetadataRepository.save({
|
||||
...fieldMetadataInput,
|
||||
targetColumnMap: generateTargetColumnMap(
|
||||
fieldMetadataInput.type,
|
||||
!fieldMetadataInput.isRemoteCreation,
|
||||
fieldMetadataInput.name,
|
||||
),
|
||||
isNullable: generateNullable(
|
||||
fieldMetadataInput.type,
|
||||
fieldMetadataInput.isNullable,
|
||||
@ -318,14 +312,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
: updatableFieldInput.defaultValue !== null
|
||||
? updatableFieldInput.defaultValue
|
||||
: null,
|
||||
// If the name is updated, the targetColumnMap should be updated as well
|
||||
targetColumnMap: updatableFieldInput.name
|
||||
? generateTargetColumnMap(
|
||||
existingFieldMetadata.type,
|
||||
existingFieldMetadata.isCustom,
|
||||
updatableFieldInput.name,
|
||||
)
|
||||
: existingFieldMetadata.targetColumnMap,
|
||||
});
|
||||
const updatedFieldMetadata = await fieldMetadataRepository.findOneOrFail({
|
||||
where: { id },
|
||||
@ -417,10 +403,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.DROP,
|
||||
columnName: computeCustomName(
|
||||
fieldMetadata.name,
|
||||
fieldMetadata.isCustom,
|
||||
),
|
||||
columnName: computeColumnName(fieldMetadata),
|
||||
} satisfies WorkspaceMigrationColumnDrop,
|
||||
],
|
||||
} satisfies WorkspaceMigrationTableAction,
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export interface CompositeProperty {
|
||||
name: string;
|
||||
description?: string;
|
||||
type: FieldMetadataType;
|
||||
hidden: 'input' | 'output' | true | false;
|
||||
isRequired: boolean;
|
||||
}
|
||||
|
||||
export interface CompositeType {
|
||||
type: FieldMetadataType;
|
||||
properties: CompositeProperty[];
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export interface FieldMetadataTargetColumnMapValue {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface FieldMetadataTargetColumnMapLink {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface FieldMetadataTargetColumnMapCurrency {
|
||||
amountMicros: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface FieldMetadataTargetColumnMapFullName {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export type FieldMetadataTargetColumnMapAddress = {
|
||||
addressStreet1: string;
|
||||
addressStreet2: string;
|
||||
addressCity: string;
|
||||
addressState: string;
|
||||
addressZipCode: string;
|
||||
addressCountry: string;
|
||||
addressLat: number;
|
||||
addressLng: number;
|
||||
};
|
||||
|
||||
type AllFieldMetadataTypes = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type FieldMetadataTypeMapping = {
|
||||
[FieldMetadataType.LINK]: FieldMetadataTargetColumnMapLink;
|
||||
[FieldMetadataType.CURRENCY]: FieldMetadataTargetColumnMapCurrency;
|
||||
[FieldMetadataType.FULL_NAME]: FieldMetadataTargetColumnMapFullName;
|
||||
[FieldMetadataType.ADDRESS]: FieldMetadataTargetColumnMapAddress;
|
||||
};
|
||||
|
||||
type TypeByFieldMetadata<T extends FieldMetadataType | 'default'> = [
|
||||
T,
|
||||
] extends [keyof FieldMetadataTypeMapping]
|
||||
? FieldMetadataTypeMapping[T]
|
||||
: T extends 'default'
|
||||
? AllFieldMetadataTypes
|
||||
: FieldMetadataTargetColumnMapValue;
|
||||
|
||||
export type FieldMetadataTargetColumnMap<
|
||||
T extends FieldMetadataType | 'default' = 'default',
|
||||
> = TypeByFieldMetadata<T>;
|
||||
@ -1,4 +1,3 @@
|
||||
import { FieldMetadataTargetColumnMap } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-target-column-map.interface';
|
||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||
|
||||
@ -12,7 +11,6 @@ export interface FieldMetadataInterface<
|
||||
type: FieldMetadataType;
|
||||
name: string;
|
||||
label: string;
|
||||
targetColumnMap: FieldMetadataTargetColumnMap<T>;
|
||||
defaultValue?: FieldMetadataDefaultValue<T>;
|
||||
options?: FieldMetadataOptions<T>;
|
||||
objectMetadataId: string;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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)}`;
|
||||
};
|
||||
@ -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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user