Skip to content

Commit

Permalink
feat: simplification of default-value specification in FieldMetadata (t…
Browse files Browse the repository at this point in the history
…wentyhq#4592)

* feat: wip refactor default-value

* feat: health check to migrate default value

* fix: tests

* fix: refactor defaultValue to make it more clean

* fix: unit tests

* fix: front-end default value
  • Loading branch information
magrinj authored Mar 27, 2024
1 parent 3cc23fb commit e98dd60
Show file tree
Hide file tree
Showing 43 changed files with 481 additions and 328 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ export const useFieldMetadataItem = () => {
options?: Omit<FieldMetadataOption, 'id'>[];
type: FieldMetadataType;
},
) =>
createOneFieldMetadataItem({
...formatFieldMetadataItemInput(input),
defaultValue: input.defaultValue,
) => {
const formatedInput = formatFieldMetadataItemInput(input);
const defaultValue = input.defaultValue
? `'${input.defaultValue}'`
: formatedInput.defaultValue ?? undefined;

return createOneFieldMetadataItem({
...formatedInput,
defaultValue,
objectMetadataId: input.objectMetadataId,
type: input.type,
});
};

const editMetadataField = (
input: Pick<Field, 'id' | 'label' | 'icon' | 'description'> & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('formatFieldMetadataItemInput', () => {
value: 'OPTION_2',
},
],
defaultValue: 'OPTION_1',
defaultValue: "'OPTION_1'",
};

const result = formatFieldMetadataItemInput(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const formatFieldMetadataItemInput = (

return {
defaultValue: defaultOption
? getOptionValueFromLabel(defaultOption.label)
? `'${getOptionValueFromLabel(defaultOption.label)}'`
: undefined,
description: input.description?.trim() ?? null,
icon: input.icon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const SettingsObjectFieldEdit = () => {

const selectOptions = activeMetadataField.options?.map((option) => ({
...option,
isDefault: defaultValue?.value === option.value,
isDefault: defaultValue === `'${option.value}'`,
}));
selectOptions?.sort(
(optionA, optionB) => optionA.position - optionB.position,
Expand Down
16 changes: 7 additions & 9 deletions packages/twenty-server/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ServeStaticModule } from '@nestjs/serve-static';
import { DevtoolsModule } from '@nestjs/devtools-integration';
import { GraphQLModule } from '@nestjs/graphql';

import { existsSync } from 'fs';
Expand All @@ -11,7 +10,6 @@ import { YogaDriverConfig, YogaDriver } from '@graphql-yoga/nestjs';

import { ApiRestModule } from 'src/engine/api/rest/api-rest.module';
import { ModulesModule } from 'src/modules/modules.module';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { CoreGraphQLApiModule } from 'src/engine/api/graphql/core-graphql-api.module';
import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module';
import { GraphQLConfigModule } from 'src/engine/api/graphql/graphql-config/graphql-config.module';
Expand All @@ -23,13 +21,13 @@ import { IntegrationsModule } from './engine/integrations/integrations.module';
@Module({
imports: [
// Nest.js devtools, use devtools.nestjs.com to debug
DevtoolsModule.registerAsync({
useFactory: (environmentService: EnvironmentService) => ({
http: environmentService.get('DEBUG_MODE'),
port: environmentService.get('DEBUG_PORT'),
}),
inject: [EnvironmentService],
}),
// DevtoolsModule.registerAsync({
// useFactory: (environmentService: EnvironmentService) => ({
// http: environmentService.get('DEBUG_MODE'),
// port: environmentService.get('DEBUG_PORT'),
// }),
// inject: [EnvironmentService],
// }),
ConfigModule.forRoot({
isGlobal: true,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ export const currencyFields = (
isNullable: true,
...(inferredFieldMetadata
? {
defaultValue: {
value: inferredFieldMetadata.defaultValue?.amountMicros ?? null,
},
defaultValue:
inferredFieldMetadata.defaultValue?.amountMicros ?? null,
}
: {}),
} satisfies FieldMetadataInterface<FieldMetadataType.NUMERIC>,
Expand All @@ -52,9 +51,8 @@ export const currencyFields = (
isNullable: true,
...(inferredFieldMetadata
? {
defaultValue: {
value: inferredFieldMetadata.defaultValue?.currencyCode ?? null,
},
defaultValue:
inferredFieldMetadata.defaultValue?.currencyCode ?? null,
}
: {}),
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ export const fullNameFields = (
isNullable: true,
...(inferredFieldMetadata
? {
defaultValue: {
value: inferredFieldMetadata.defaultValue?.firstName ?? null,
},
defaultValue: inferredFieldMetadata.defaultValue?.firstName ?? null,
}
: {}),
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
Expand All @@ -52,9 +50,7 @@ export const fullNameFields = (
isNullable: true,
...(inferredFieldMetadata
? {
defaultValue: {
value: inferredFieldMetadata.defaultValue?.lastName ?? null,
},
defaultValue: inferredFieldMetadata.defaultValue?.lastName ?? null,
}
: {}),
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ export const linkFields = (
isNullable: true,
...(inferredFieldMetadata
? {
defaultValue: {
value: inferredFieldMetadata.defaultValue?.label ?? null,
},
defaultValue: inferredFieldMetadata.defaultValue?.label ?? null,
}
: {}),
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
Expand All @@ -52,9 +50,7 @@ export const linkFields = (
isNullable: true,
...(inferredFieldMetadata
? {
defaultValue: {
value: inferredFieldMetadata.defaultValue?.url ?? null,
},
defaultValue: inferredFieldMetadata.defaultValue?.url ?? null,
}
: {}),
} satisfies FieldMetadataInterface<FieldMetadataType.TEXT>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,88 +6,95 @@ import {
IsNotEmpty,
IsNumber,
IsNumberString,
IsString,
Matches,
ValidateIf,
} from 'class-validator';

import { IsQuotedString } from 'src/engine/metadata-modules/field-metadata/validators/is-quoted-string.validator';

export const fieldMetadataDefaultValueFunctionName = {
UUID: 'uuid',
NOW: 'now',
} as const;

export type FieldMetadataDefaultValueFunctionNames =
(typeof fieldMetadataDefaultValueFunctionName)[keyof typeof fieldMetadataDefaultValueFunctionName];

export class FieldMetadataDefaultValueString {
@ValidateIf((_object, value) => value !== null)
@IsString()
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
value: string | null;
}

export class FieldMetadataDefaultValueRawJson {
@ValidateIf((_object, value) => value !== null)
@ValidateIf((object, value) => value !== null)
@IsJSON()
value: JSON | null;
}

export class FieldMetadataDefaultValueNumber {
@ValidateIf((_object, value) => value !== null)
@ValidateIf((object, value) => value !== null)
@IsNumber()
value: number | null;
}

export class FieldMetadataDefaultValueBoolean {
@ValidateIf((_object, value) => value !== null)
@ValidateIf((object, value) => value !== null)
@IsBoolean()
value: boolean | null;
}

export class FieldMetadataDefaultValueStringArray {
@ValidateIf((_object, value) => value !== null)
@ValidateIf((object, value) => value !== null)
@IsArray()
@IsString({ each: true })
@IsQuotedString({ each: true })
value: string[] | null;
}

export class FieldMetadataDefaultValueDateTime {
@ValidateIf((_object, value) => value !== null)
@ValidateIf((object, value) => value !== null)
@IsDate()
value: Date | null;
}

export class FieldMetadataDefaultValueLink {
@ValidateIf((_object, value) => value !== null)
@IsString()
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
label: string | null;

@ValidateIf((_object, value) => value !== null)
@IsString()
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
url: string | null;
}

export class FieldMetadataDefaultValueCurrency {
@ValidateIf((_object, value) => value !== null)
@ValidateIf((object, value) => value !== null)
@IsNumberString()
amountMicros: string | null;

@ValidateIf((_object, value) => value !== null)
@IsString()
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
currencyCode: string | null;
}

export class FieldMetadataDefaultValueFullName {
@ValidateIf((_object, value) => value !== null)
@IsString()
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
firstName: string | null;

@ValidateIf((_object, value) => value !== null)
@IsString()
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
lastName: string | null;
}

export class FieldMetadataDynamicDefaultValueUuid {
@Matches('uuid')
export class FieldMetadataDefaultValueUuidFunction {
@Matches(fieldMetadataDefaultValueFunctionName.UUID)
@IsNotEmpty()
@IsString()
type: 'uuid';
value: typeof fieldMetadataDefaultValueFunctionName.UUID;
}

export class FieldMetadataDynamicDefaultValueNow {
@Matches('now')
export class FieldMetadataDefaultValueNowFunction {
@Matches(fieldMetadataDefaultValueFunctionName.NOW)
@IsNotEmpty()
@IsString()
type: 'now';
value: typeof fieldMetadataDefaultValueFunctionName.NOW;
}
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
existingFieldMetadata.type !== FieldMetadataType.SELECT
? existingFieldMetadata.defaultValue
: updatableFieldInput.defaultValue
? // Todo: we need to rework DefaultValue typing and format to be simpler, there is no need to have this complexity
{ value: updatableFieldInput.defaultValue as unknown as string }
? updatableFieldInput.defaultValue
: null,
// If the name is updated, the targetColumnMap should be updated as well
targetColumnMap: updatableFieldInput.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,25 @@ import {
FieldMetadataDefaultValueNumber,
FieldMetadataDefaultValueString,
FieldMetadataDefaultValueStringArray,
FieldMetadataDynamicDefaultValueNow,
FieldMetadataDynamicDefaultValueUuid,
FieldMetadataDefaultValueUuidFunction,
FieldMetadataDefaultValueNowFunction,
} from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';

type FieldMetadataScalarDefaultValue =
| FieldMetadataDefaultValueString
| FieldMetadataDefaultValueNumber
| FieldMetadataDefaultValueBoolean
| FieldMetadataDefaultValueDateTime;
type ExtractValueType<T> = T extends { value: infer V } ? V : T;

export type FieldMetadataDynamicDefaultValue =
| FieldMetadataDynamicDefaultValueUuid
| FieldMetadataDynamicDefaultValueNow;

type AllFieldMetadataDefaultValueTypes =
| FieldMetadataScalarDefaultValue
| FieldMetadataDynamicDefaultValue
| FieldMetadataDefaultValueLink
| FieldMetadataDefaultValueCurrency
| FieldMetadataDefaultValueFullName;
type UnionOfValues<T> = T[keyof T];

type FieldMetadataDefaultValueMapping = {
[FieldMetadataType.UUID]:
| FieldMetadataDefaultValueString
| FieldMetadataDynamicDefaultValueUuid;
| FieldMetadataDefaultValueUuidFunction;
[FieldMetadataType.TEXT]: FieldMetadataDefaultValueString;
[FieldMetadataType.PHONE]: FieldMetadataDefaultValueString;
[FieldMetadataType.EMAIL]: FieldMetadataDefaultValueString;
[FieldMetadataType.DATE_TIME]:
| FieldMetadataDefaultValueDateTime
| FieldMetadataDynamicDefaultValueNow;
| FieldMetadataDefaultValueNowFunction;
[FieldMetadataType.BOOLEAN]: FieldMetadataDefaultValueBoolean;
[FieldMetadataType.NUMBER]: FieldMetadataDefaultValueNumber;
[FieldMetadataType.POSITION]: FieldMetadataDefaultValueNumber;
Expand All @@ -54,35 +41,31 @@ type FieldMetadataDefaultValueMapping = {
[FieldMetadataType.RAW_JSON]: FieldMetadataDefaultValueRawJson;
};

export type FieldMetadataClassValidation =
UnionOfValues<FieldMetadataDefaultValueMapping>;

export type FieldMetadataFunctionDefaultValue = ExtractValueType<
FieldMetadataDefaultValueUuidFunction | FieldMetadataDefaultValueNowFunction
>;

type DefaultValueByFieldMetadata<T extends FieldMetadataType | 'default'> = [
T,
] extends [keyof FieldMetadataDefaultValueMapping]
? FieldMetadataDefaultValueMapping[T] | null
? ExtractValueType<FieldMetadataDefaultValueMapping[T]> | null
: T extends 'default'
? AllFieldMetadataDefaultValueTypes | null
? ExtractValueType<UnionOfValues<FieldMetadataDefaultValueMapping>> | null
: never;

export type FieldMetadataDefaultValue<
T extends FieldMetadataType | 'default' = 'default',
> = DefaultValueByFieldMetadata<T>;

type FieldMetadataDefaultValueExtractNestedType<T> = T extends {
value: infer U;
}
? U
: T extends object
? { [K in keyof T]: T[K] } extends { value: infer V }
? V
: T[keyof T]
: never;

type FieldMetadataDefaultValueExtractedTypes = {
[K in keyof FieldMetadataDefaultValueMapping]: FieldMetadataDefaultValueExtractNestedType<
[K in keyof FieldMetadataDefaultValueMapping]: ExtractValueType<
FieldMetadataDefaultValueMapping[K]
>;
};

export type FieldMetadataDefaultSerializableValue =
| FieldMetadataDefaultValueExtractedTypes[keyof FieldMetadataDefaultValueExtractedTypes]
| FieldMetadataDynamicDefaultValue
| null;
Loading

0 comments on commit e98dd60

Please sign in to comment.