fix: rating type issues (#3638)
* fix: rating type issues * fix: rebase --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,6 +1,8 @@
|
||||
import { IsString, IsNumber, IsOptional, IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class FieldMetadataDefaultOptions {
|
||||
import { IsValidGraphQLEnumName } from 'src/metadata/field-metadata/validators/is-valid-graphql-enum-name.validator';
|
||||
|
||||
export class FieldMetadataDefaultOption {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
id?: string;
|
||||
@ -13,11 +15,11 @@ export class FieldMetadataDefaultOptions {
|
||||
label: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@IsValidGraphQLEnumName()
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class FieldMetadataComplexOptions extends FieldMetadataDefaultOptions {
|
||||
export class FieldMetadataComplexOption extends FieldMetadataDefaultOption {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
color: string;
|
||||
|
||||
@ -25,7 +25,13 @@ import { UpdateFieldInput } from 'src/metadata/field-metadata/dtos/update-field.
|
||||
import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { FieldMetadataEntity } from './field-metadata.entity';
|
||||
import {
|
||||
FieldMetadataEntity,
|
||||
FieldMetadataType,
|
||||
} from './field-metadata.entity';
|
||||
|
||||
import { isEnumFieldMetadataType } from './utils/is-enum-field-metadata-type.util';
|
||||
import { generateRatingOptions } from './utils/generate-rating-optionts.util';
|
||||
|
||||
@Injectable()
|
||||
export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntity> {
|
||||
@ -60,6 +66,21 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
throw new NotFoundException('Object does not exist');
|
||||
}
|
||||
|
||||
// Double check in case the service is directly called
|
||||
if (isEnumFieldMetadataType(fieldMetadataInput.type)) {
|
||||
if (
|
||||
!fieldMetadataInput.options &&
|
||||
fieldMetadataInput.type !== FieldMetadataType.RATING
|
||||
) {
|
||||
throw new BadRequestException('Options are required for enum fields');
|
||||
}
|
||||
}
|
||||
|
||||
// Generate options for rating fields
|
||||
if (fieldMetadataInput.type === FieldMetadataType.RATING) {
|
||||
fieldMetadataInput.options = generateRatingOptions();
|
||||
}
|
||||
|
||||
const fieldAlreadyExists = await this.fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
name: fieldMetadataInput.name,
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import {
|
||||
FieldMetadataComplexOptions,
|
||||
FieldMetadataDefaultOptions,
|
||||
FieldMetadataComplexOption,
|
||||
FieldMetadataDefaultOption,
|
||||
} from 'src/metadata/field-metadata/dtos/options.input';
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
|
||||
type FieldMetadataOptionsMapping = {
|
||||
[FieldMetadataType.RATING]: FieldMetadataDefaultOptions[];
|
||||
[FieldMetadataType.SELECT]: FieldMetadataComplexOptions[];
|
||||
[FieldMetadataType.MULTI_SELECT]: FieldMetadataComplexOptions[];
|
||||
[FieldMetadataType.RATING]: FieldMetadataDefaultOption[];
|
||||
[FieldMetadataType.SELECT]: FieldMetadataComplexOption[];
|
||||
[FieldMetadataType.MULTI_SELECT]: FieldMetadataComplexOption[];
|
||||
};
|
||||
|
||||
type OptionsByFieldMetadata<T extends FieldMetadataType | 'default'> =
|
||||
T extends keyof FieldMetadataOptionsMapping
|
||||
? FieldMetadataOptionsMapping[T]
|
||||
: T extends 'default'
|
||||
? FieldMetadataDefaultOptions[] | FieldMetadataComplexOptions[]
|
||||
? FieldMetadataDefaultOption[] | FieldMetadataComplexOption[]
|
||||
: never;
|
||||
|
||||
export type FieldMetadataOptions<
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
|
||||
import { FieldMetadataDefaultOption } from 'src/metadata/field-metadata/dtos/options.input';
|
||||
|
||||
const range = {
|
||||
start: 1,
|
||||
end: 5,
|
||||
};
|
||||
|
||||
export function generateRatingOptions(): FieldMetadataDefaultOption[] {
|
||||
const options: FieldMetadataDefaultOption[] = [];
|
||||
|
||||
for (let i = range.start; i <= range.end; i++) {
|
||||
options.push({
|
||||
id: uuidV4(),
|
||||
label: i.toString(),
|
||||
value: `RATING_${i}`,
|
||||
position: i - 1,
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
@ -5,14 +5,16 @@ import { FieldMetadataOptions } from 'src/metadata/field-metadata/interfaces/fie
|
||||
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
FieldMetadataComplexOptions,
|
||||
FieldMetadataDefaultOptions,
|
||||
FieldMetadataComplexOption,
|
||||
FieldMetadataDefaultOption,
|
||||
} from 'src/metadata/field-metadata/dtos/options.input';
|
||||
|
||||
import { isEnumFieldMetadataType } from './is-enum-field-metadata-type.util';
|
||||
|
||||
export const optionsValidatorsMap = {
|
||||
[FieldMetadataType.RATING]: [FieldMetadataDefaultOptions],
|
||||
[FieldMetadataType.SELECT]: [FieldMetadataComplexOptions],
|
||||
[FieldMetadataType.MULTI_SELECT]: [FieldMetadataComplexOptions],
|
||||
// RATING doesn't need to be provided as it's the backend that will generate the options
|
||||
[FieldMetadataType.SELECT]: [FieldMetadataComplexOption],
|
||||
[FieldMetadataType.MULTI_SELECT]: [FieldMetadataComplexOption],
|
||||
};
|
||||
|
||||
export const validateOptionsForType = (
|
||||
@ -25,6 +27,14 @@ export const validateOptionsForType = (
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isEnumFieldMetadataType(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === FieldMetadataType.RATING) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validators = optionsValidatorsMap[type];
|
||||
|
||||
if (!validators) return false;
|
||||
@ -33,7 +43,7 @@ export const validateOptionsForType = (
|
||||
return validators.some((validator) => {
|
||||
const optionsInstance = plainToInstance<
|
||||
any,
|
||||
FieldMetadataDefaultOptions | FieldMetadataComplexOptions
|
||||
FieldMetadataDefaultOption | FieldMetadataComplexOption
|
||||
>(validator, option);
|
||||
|
||||
return (
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
const graphQLEnumNameRegex = /^[_A-Za-z][_0-9A-Za-z]+$/;
|
||||
|
||||
export function IsValidGraphQLEnumName(validationOptions?: ValidationOptions) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isValidGraphQLEnumName',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any) {
|
||||
return typeof value === 'string' && graphQLEnumNameRegex.test(value);
|
||||
},
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
return `${args.property} must match the ${graphQLEnumNameRegex} format`;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -8,8 +8,8 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f
|
||||
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
import {
|
||||
FieldMetadataComplexOptions,
|
||||
FieldMetadataDefaultOptions,
|
||||
FieldMetadataComplexOption,
|
||||
FieldMetadataDefaultOption,
|
||||
} from 'src/metadata/field-metadata/dtos/options.input';
|
||||
import { isEnumFieldMetadataType } from 'src/metadata/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
|
||||
@ -54,7 +54,7 @@ export class EnumTypeDefinitionFactory {
|
||||
// FixMe: It's a hack until Typescript get fixed on union types for reduce function
|
||||
// https://github.com/microsoft/TypeScript/issues/36390
|
||||
const enumOptions = fieldMetadata.options as Array<
|
||||
FieldMetadataDefaultOptions | FieldMetadataComplexOptions
|
||||
FieldMetadataDefaultOption | FieldMetadataComplexOption
|
||||
>;
|
||||
|
||||
if (!enumOptions) {
|
||||
@ -74,6 +74,7 @@ export class EnumTypeDefinitionFactory {
|
||||
description: fieldMetadata.description,
|
||||
values: enumOptions.reduce(
|
||||
(acc, enumOption) => {
|
||||
// Key must match this regex: /^[_A-Za-z][_0-9A-Za-z]+$/
|
||||
acc[enumOption.value] = {
|
||||
value: enumOption.value,
|
||||
description: enumOption.label,
|
||||
|
||||
@ -39,7 +39,7 @@ import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migrati
|
||||
import { ReflectiveMetadataFactory } from 'src/workspace/workspace-sync-metadata/reflective-metadata.factory';
|
||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
import { FieldMetadataComplexOptions } from 'src/metadata/field-metadata/dtos/options.input';
|
||||
import { FieldMetadataComplexOption } from 'src/metadata/field-metadata/dtos/options.input';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSyncMetadataService {
|
||||
@ -314,7 +314,7 @@ export class WorkspaceSyncMetadataService {
|
||||
convertedField.options
|
||||
? {
|
||||
options: this.generateUUIDForNewSelectFieldOptions(
|
||||
convertedField.options as FieldMetadataComplexOptions[],
|
||||
convertedField.options as FieldMetadataComplexOption[],
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
@ -323,8 +323,8 @@ export class WorkspaceSyncMetadataService {
|
||||
}
|
||||
|
||||
private generateUUIDForNewSelectFieldOptions(
|
||||
options: FieldMetadataComplexOptions[],
|
||||
): FieldMetadataComplexOptions[] {
|
||||
options: FieldMetadataComplexOption[],
|
||||
): FieldMetadataComplexOption[] {
|
||||
return options.map((option) => ({
|
||||
...option,
|
||||
id: uuidV4(),
|
||||
|
||||
Reference in New Issue
Block a user