Use defaultValue in currency input (#4911)

- Fix default value sent to backend, using single quotes by default
- Use default value in field definition and column definition so that
field inputs can access it
- Used currency default value in CurrencyFieldInput

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-04-11 16:49:00 +02:00
committed by GitHub
parent e48960afbe
commit c69a3f01da
17 changed files with 188 additions and 103 deletions

View File

@ -3,104 +3,103 @@ import { validateDefaultValueForType } from 'src/engine/metadata-modules/field-m
describe('validateDefaultValueForType', () => {
it('should return true for null defaultValue', () => {
expect(validateDefaultValueForType(FieldMetadataType.TEXT, null)).toBe(
true,
);
expect(
validateDefaultValueForType(FieldMetadataType.TEXT, null).isValid,
).toBe(true);
});
// Dynamic default values
it('should validate uuid dynamic default value for UUID type', () => {
expect(validateDefaultValueForType(FieldMetadataType.UUID, 'uuid')).toBe(
true,
);
expect(
validateDefaultValueForType(FieldMetadataType.UUID, 'uuid').isValid,
).toBe(true);
});
it('should validate now dynamic default value for DATE_TIME type', () => {
expect(
validateDefaultValueForType(FieldMetadataType.DATE_TIME, 'now'),
validateDefaultValueForType(FieldMetadataType.DATE_TIME, 'now').isValid,
).toBe(true);
});
it('should return false for mismatched dynamic default value', () => {
expect(validateDefaultValueForType(FieldMetadataType.UUID, 'now')).toBe(
false,
);
expect(
validateDefaultValueForType(FieldMetadataType.UUID, 'now').isValid,
).toBe(false);
});
// Static default values
it('should validate string default value for TEXT type', () => {
expect(validateDefaultValueForType(FieldMetadataType.TEXT, "'test'")).toBe(
true,
);
expect(
validateDefaultValueForType(FieldMetadataType.TEXT, "'test'").isValid,
).toBe(true);
});
it('should return false for invalid string default value for TEXT type', () => {
expect(validateDefaultValueForType(FieldMetadataType.TEXT, 123)).toBe(
false,
);
expect(
validateDefaultValueForType(FieldMetadataType.TEXT, 123).isValid,
).toBe(false);
});
it('should validate string default value for PHONE type', () => {
expect(
validateDefaultValueForType(FieldMetadataType.PHONE, "'+123456789'"),
validateDefaultValueForType(FieldMetadataType.PHONE, "'+123456789'")
.isValid,
).toBe(true);
});
it('should return false for invalid string default value for PHONE type', () => {
expect(validateDefaultValueForType(FieldMetadataType.PHONE, 123)).toBe(
false,
);
expect(
validateDefaultValueForType(FieldMetadataType.PHONE, 123).isValid,
).toBe(false);
});
it('should validate string default value for EMAIL type', () => {
expect(
validateDefaultValueForType(
FieldMetadataType.EMAIL,
"'test@example.com'",
),
validateDefaultValueForType(FieldMetadataType.EMAIL, "'test@example.com'")
.isValid,
).toBe(true);
});
it('should return false for invalid string default value for EMAIL type', () => {
expect(validateDefaultValueForType(FieldMetadataType.EMAIL, 123)).toBe(
false,
);
expect(
validateDefaultValueForType(FieldMetadataType.EMAIL, 123).isValid,
).toBe(false);
});
it('should validate number default value for NUMBER type', () => {
expect(validateDefaultValueForType(FieldMetadataType.NUMBER, 100)).toBe(
true,
);
expect(
validateDefaultValueForType(FieldMetadataType.NUMBER, 100).isValid,
).toBe(true);
});
it('should return false for invalid number default value for NUMBER type', () => {
expect(validateDefaultValueForType(FieldMetadataType.NUMBER, '100')).toBe(
false,
);
expect(
validateDefaultValueForType(FieldMetadataType.NUMBER, '100').isValid,
).toBe(false);
});
it('should validate number default value for PROBABILITY type', () => {
expect(
validateDefaultValueForType(FieldMetadataType.PROBABILITY, 0.5),
validateDefaultValueForType(FieldMetadataType.PROBABILITY, 0.5).isValid,
).toBe(true);
});
it('should return false for invalid number default value for PROBABILITY type', () => {
expect(
validateDefaultValueForType(FieldMetadataType.PROBABILITY, '50%'),
validateDefaultValueForType(FieldMetadataType.PROBABILITY, '50%').isValid,
).toBe(false);
});
it('should validate boolean default value for BOOLEAN type', () => {
expect(validateDefaultValueForType(FieldMetadataType.BOOLEAN, true)).toBe(
true,
);
expect(
validateDefaultValueForType(FieldMetadataType.BOOLEAN, true).isValid,
).toBe(true);
});
it('should return false for invalid boolean default value for BOOLEAN type', () => {
expect(validateDefaultValueForType(FieldMetadataType.BOOLEAN, 'true')).toBe(
false,
);
expect(
validateDefaultValueForType(FieldMetadataType.BOOLEAN, 'true').isValid,
).toBe(false);
});
// LINK type
@ -109,7 +108,7 @@ describe('validateDefaultValueForType', () => {
validateDefaultValueForType(FieldMetadataType.LINK, {
label: "'http://example.com'",
url: "'Example'",
}),
}).isValid,
).toBe(true);
});
@ -120,7 +119,7 @@ describe('validateDefaultValueForType', () => {
// @ts-expect-error Just for testing purposes
{ label: 123, url: {} },
FieldMetadataType.LINK,
),
).isValid,
).toBe(false);
});
@ -130,7 +129,7 @@ describe('validateDefaultValueForType', () => {
validateDefaultValueForType(FieldMetadataType.CURRENCY, {
amountMicros: '100',
currencyCode: "'USD'",
}),
}).isValid,
).toBe(true);
});
@ -141,14 +140,15 @@ describe('validateDefaultValueForType', () => {
// @ts-expect-error Just for testing purposes
{ amountMicros: 100, currencyCode: "'USD'" },
FieldMetadataType.CURRENCY,
),
).isValid,
).toBe(false);
});
// Unknown type
it('should return false for unknown type', () => {
expect(
validateDefaultValueForType('unknown' as FieldMetadataType, "'test'"),
validateDefaultValueForType('unknown' as FieldMetadataType, "'test'")
.isValid,
).toBe(false);
});
});

View File

@ -1,5 +1,5 @@
import { plainToInstance } from 'class-transformer';
import { validateSync } from 'class-validator';
import { ValidationError, validateSync } from 'class-validator';
import {
FieldMetadataClassValidation,
@ -49,17 +49,32 @@ export const defaultValueValidatorsMap = {
[FieldMetadataType.RAW_JSON]: [FieldMetadataDefaultValueRawJson],
};
type ValidationResult = {
isValid: boolean;
errors: ValidationError[];
};
export const validateDefaultValueForType = (
type: FieldMetadataType,
defaultValue: FieldMetadataDefaultValue,
): boolean => {
if (defaultValue === null) return true;
): ValidationResult => {
if (defaultValue === null) {
return {
isValid: true,
errors: [],
};
}
const validators = defaultValueValidatorsMap[type];
const validators = defaultValueValidatorsMap[type] as any[];
if (!validators) return false;
if (!validators) {
return {
isValid: false,
errors: [],
};
}
const isValid = validators.some((validator) => {
const validationResults = validators.map((validator) => {
const conputedDefaultValue = isCompositeFieldMetadataType(type)
? defaultValue
: { value: defaultValue };
@ -69,14 +84,24 @@ export const validateDefaultValueForType = (
FieldMetadataClassValidation
>(validator, conputedDefaultValue as FieldMetadataClassValidation);
return (
validateSync(defaultValueInstance, {
whitelist: true,
forbidNonWhitelisted: true,
forbidUnknownValues: true,
}).length === 0
);
const errors = validateSync(defaultValueInstance, {
whitelist: true,
forbidNonWhitelisted: true,
forbidUnknownValues: true,
});
const isValid = errors.length === 0;
return {
isValid,
errors,
};
});
return isValid;
const isValid = validationResults.some((result) => result.isValid);
return {
isValid,
errors: validationResults.flatMap((result) => result.errors),
};
};

View File

@ -14,13 +14,17 @@ import {
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { validateDefaultValueForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util';
import { LoggerService } from 'src/engine/integrations/logger/logger.service';
@Injectable()
@ValidatorConstraint({ name: 'isFieldMetadataDefaultValue', async: true })
export class IsFieldMetadataDefaultValue
implements ValidatorConstraintInterface
{
constructor(private readonly fieldMetadataService: FieldMetadataService) {}
constructor(
private readonly fieldMetadataService: FieldMetadataService,
private readonly loggerService: LoggerService,
) {}
async validate(
value: FieldMetadataDefaultValue,
@ -48,7 +52,19 @@ export class IsFieldMetadataDefaultValue
type = fieldMetadata.type;
}
return validateDefaultValueForType(type, value);
const validationResult = validateDefaultValueForType(type, value);
if (!validationResult.isValid) {
this.loggerService.error(
{
message: 'Error during field validation',
errors: validationResult.errors,
},
'Field Metadata Validation',
);
}
return validationResult.isValid;
}
defaultMessage(): string {