Files v2 - Add new FILES field type (#17236)
In this PR : - New field type FieldMetadataType.FILES added (as jsonb in DB) - Backend: GraphQL types, validation, REST API schema, data processor handling - Frontend: field configuration in settings - Feature flag IS_FILES_FIELD_ENABLED to gate the feature - Integration tests for create/filter validation - Dev seed: Feature flag enabled + FILES field on survey result object To do in next PRs : - Backend : -- new workspaceFile controller (for download) & new workspaceFile upload resolver (for upload) -- pre-hook/post-hook to ensure fileId existence + listener to ensure cleaning - Frontend : FILES field display/edit in table view, record, ...
This commit is contained in:
+2
@@ -9,6 +9,7 @@ import { FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-en
|
||||
import { type FlatFieldMetadataTypeValidator } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
|
||||
import { FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
|
||||
import { validateEnumSelectFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-enum-flat-field-metadata.util';
|
||||
import { validateFilesFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-files-flat-field-metadata.util';
|
||||
import { validateMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util';
|
||||
import { validateMorphRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-flat-field-metadata.util';
|
||||
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/flat-entity-validation-args.type';
|
||||
@@ -60,6 +61,7 @@ export class FlatFieldMetadataTypeValidatorService {
|
||||
DATE: DEFAULT_NO_VALIDATION,
|
||||
DATE_TIME: DEFAULT_NO_VALIDATION,
|
||||
EMAILS: DEFAULT_NO_VALIDATION,
|
||||
FILES: validateFilesFlatFieldMetadata,
|
||||
FULL_NAME: DEFAULT_NO_VALIDATION,
|
||||
LINKS: DEFAULT_NO_VALIDATION,
|
||||
NUMBER: DEFAULT_NO_VALIDATION,
|
||||
|
||||
+1
@@ -172,6 +172,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
|
||||
case FieldMetadataType.NUMERIC:
|
||||
case FieldMetadataType.LINKS:
|
||||
case FieldMetadataType.CURRENCY:
|
||||
case FieldMetadataType.FILES:
|
||||
case FieldMetadataType.FULL_NAME:
|
||||
case FieldMetadataType.POSITION:
|
||||
case FieldMetadataType.ADDRESS:
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { validateFilesFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-files-flat-field-metadata.util';
|
||||
|
||||
const createFlatEntityToValidate = (
|
||||
overrides: Partial<FlatFieldMetadata<FieldMetadataType.FILES>> = {},
|
||||
): FlatFieldMetadata<FieldMetadataType.FILES> =>
|
||||
({
|
||||
type: FieldMetadataType.FILES,
|
||||
name: 'testFilesField',
|
||||
label: 'Test Files Field',
|
||||
settings: { maxNumberOfValues: 5 },
|
||||
isUnique: false,
|
||||
...overrides,
|
||||
}) as FlatFieldMetadata<FieldMetadataType.FILES>;
|
||||
|
||||
const callValidator = (
|
||||
flatEntityToValidate: FlatFieldMetadata<FieldMetadataType.FILES>,
|
||||
featureFlagEnabled = true,
|
||||
) =>
|
||||
validateFilesFlatFieldMetadata({
|
||||
flatEntityToValidate,
|
||||
additionalCacheDataMaps: {
|
||||
featureFlagsMap: {
|
||||
[FeatureFlagKey.IS_FILES_FIELD_ENABLED]: featureFlagEnabled,
|
||||
},
|
||||
},
|
||||
} as Parameters<typeof validateFilesFlatFieldMetadata>[0]);
|
||||
|
||||
describe('validateFilesFlatFieldMetadata', () => {
|
||||
it('should return no errors for a valid files field', () => {
|
||||
const errors = callValidator(createFlatEntityToValidate());
|
||||
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should return error when feature flag is disabled', () => {
|
||||
const errors = callValidator(createFlatEntityToValidate(), false);
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].code).toBe(FieldMetadataExceptionCode.INVALID_FIELD_INPUT);
|
||||
expect(errors[0].message).toContain('Files field type is not supported');
|
||||
});
|
||||
|
||||
it('should return error when isUnique is true', () => {
|
||||
const errors = callValidator(
|
||||
createFlatEntityToValidate({ isUnique: true }),
|
||||
);
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].code).toBe(FieldMetadataExceptionCode.INVALID_FIELD_INPUT);
|
||||
expect(errors[0].message).toContain(
|
||||
'Files field is not supported for unique fields',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when settings is undefined', () => {
|
||||
const errors = callValidator(
|
||||
createFlatEntityToValidate({ settings: undefined }),
|
||||
);
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].code).toBe(FieldMetadataExceptionCode.INVALID_FIELD_INPUT);
|
||||
expect(errors[0].message).toContain(
|
||||
'maxNumberOfValues must be defined in settings',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when maxNumberOfValues is 0', () => {
|
||||
const errors = callValidator(
|
||||
createFlatEntityToValidate({ settings: { maxNumberOfValues: 0 } }),
|
||||
);
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].code).toBe(FieldMetadataExceptionCode.INVALID_FIELD_INPUT);
|
||||
});
|
||||
|
||||
it('should return error when maxNumberOfValues exceeds max (11)', () => {
|
||||
const errors = callValidator(
|
||||
createFlatEntityToValidate({ settings: { maxNumberOfValues: 11 } }),
|
||||
);
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].code).toBe(FieldMetadataExceptionCode.INVALID_FIELD_INPUT);
|
||||
});
|
||||
});
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FILES_FIELD_MAX_NUMBER_OF_VALUES } from 'twenty-shared/constants';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type FlatFieldMetadataTypeValidationArgs } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
|
||||
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
|
||||
|
||||
export const validateFilesFlatFieldMetadata = ({
|
||||
flatEntityToValidate,
|
||||
additionalCacheDataMaps,
|
||||
}: FlatFieldMetadataTypeValidationArgs<FieldMetadataType.FILES>): FlatFieldMetadataValidationError[] => {
|
||||
const errors: FlatFieldMetadataValidationError[] = [];
|
||||
const { featureFlagsMap } = additionalCacheDataMaps;
|
||||
|
||||
if (featureFlagsMap[FeatureFlagKey.IS_FILES_FIELD_ENABLED] !== true) {
|
||||
errors.push({
|
||||
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
message: 'Files field type is not supported',
|
||||
userFriendlyMessage: msg`Files field type is not supported`,
|
||||
});
|
||||
}
|
||||
|
||||
if (flatEntityToValidate.isUnique === true) {
|
||||
errors.push({
|
||||
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
message: 'Files field is not supported for unique fields',
|
||||
userFriendlyMessage: msg`Files field is not supported for unique fields`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isDefined(flatEntityToValidate?.settings?.maxNumberOfValues) ||
|
||||
flatEntityToValidate.settings.maxNumberOfValues < 1 ||
|
||||
flatEntityToValidate.settings.maxNumberOfValues >
|
||||
FILES_FIELD_MAX_NUMBER_OF_VALUES
|
||||
) {
|
||||
errors.push({
|
||||
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
message: `maxNumberOfValues must be defined in settings and be a number greater than 0 and less than or equal to ${FILES_FIELD_MAX_NUMBER_OF_VALUES}`,
|
||||
userFriendlyMessage: msg`maxNumberOfValues must be defined in settings and be a number greater than 0 and less than or equal to ${FILES_FIELD_MAX_NUMBER_OF_VALUES}`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
Reference in New Issue
Block a user