Files field - add files field display and input + filtering (#17637)
This PR introduces a new FILES field type for Twenty CRM, allowing users to attach multiple files to any record. - Files field display - Files field preview - Files field input - Files field filtering To test : 1/ need to activate feature flag `IS_FILES_FIELD_ENABLED` + create a new FILES field - display in read only, edit, inline, table - edit - filter - export closes https://github.com/twentyhq/core-team-issues/issues/2154 <img width="354" height="134" alt="Screenshot 2026-02-02 at 19 11 01" src="https://github.com/user-attachments/assets/3c3de89e-f6b6-4526-b710-e4c7ee1a6d30" /> <img width="1081" height="933" alt="Screenshot 2026-02-02 at 19 10 41" src="https://github.com/user-attachments/assets/7c9a7278-edb7-4d4a-882c-f7404689d011" /> <img width="1073" height="719" alt="Screenshot 2026-02-02 at 19 10 33" src="https://github.com/user-attachments/assets/79cc372b-56a2-4cbf-b4fe-023adc4753a8" />
This commit is contained in:
-1
@@ -19,5 +19,4 @@ export enum FeatureFlagKey {
|
||||
IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_NAVIGATION_MENU_ITEM_ENABLED = 'IS_NAVIGATION_MENU_ITEM_ENABLED',
|
||||
IS_FILES_FIELD_ENABLED = 'IS_FILES_FIELD_ENABLED',
|
||||
}
|
||||
|
||||
+1
@@ -477,6 +477,7 @@ export const generateFieldFilterZodSchema = (
|
||||
return null;
|
||||
|
||||
case FieldMetadataType.RAW_JSON:
|
||||
case FieldMetadataType.FILES:
|
||||
return z
|
||||
.object({
|
||||
eq: z.string().optional().describe('Raw JSON equals'),
|
||||
|
||||
-15
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -19,15 +18,9 @@ const createFlatEntityToValidate = (
|
||||
|
||||
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', () => {
|
||||
@@ -37,14 +30,6 @@ describe('validateFilesFlatFieldMetadata', () => {
|
||||
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 }),
|
||||
|
||||
-11
@@ -3,25 +3,14 @@ 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({
|
||||
|
||||
-1
@@ -240,7 +240,6 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_SSE_DB_EVENTS_ENABLED: false,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: false,
|
||||
IS_NAVIGATION_MENU_ITEM_ENABLED: false,
|
||||
IS_FILES_FIELD_ENABLED: false,
|
||||
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED: false,
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false,
|
||||
IS_MARKETPLACE_ENABLED: false,
|
||||
|
||||
+4
-3
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
FieldMetadataType,
|
||||
FILE_CATEGORIES,
|
||||
NumberDataType,
|
||||
type FieldMetadataSettings,
|
||||
} from 'twenty-shared/types';
|
||||
@@ -346,9 +345,11 @@ export const convertObjectMetadataToSchemaProperties = ({
|
||||
},
|
||||
...(forResponse
|
||||
? {
|
||||
fileCategory: {
|
||||
extension: {
|
||||
type: 'string',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
enum: Object.values(FILE_CATEGORIES),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
-5
@@ -96,11 +96,6 @@ export const seedFeatureFlags = async ({
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_FILES_FIELD_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_MARKETPLACE_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user