diff --git a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts index 4402d3a513cf..a3368ae6a9c4 100644 --- a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts +++ b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts @@ -29,9 +29,38 @@ export const fieldCurrencyMock = { defaultValue: { amountMicros: null, currencyCode: "''" }, }; +export const fieldSelectMock = { + name: 'fieldSelect', + type: FieldMetadataType.SELECT, + isNullable: true, + defaultValue: 'OPTION_1', + options: [ + { + id: '9a519a86-422b-4598-88ae-78751353f683', + color: 'red', + label: 'Opt 1', + value: 'OPTION_1', + position: 0, + }, + { + id: '33f28d51-bc82-4e1d-ae4b-d9e4c0ed0ab4', + color: 'purple', + label: 'Opt 2', + value: 'OPTION_2', + position: 1, + }, + ], +}; + export const objectMetadataItemMock = { targetTableName: 'testingObject', nameSingular: 'objectName', namePlural: 'objectsName', - fields: [fieldNumberMock, fieldStringMock, fieldLinkMock, fieldCurrencyMock], + fields: [ + fieldNumberMock, + fieldStringMock, + fieldLinkMock, + fieldCurrencyMock, + fieldSelectMock, + ], } as ObjectMetadataEntity; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts new file mode 100644 index 000000000000..7c4f35d0a140 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts @@ -0,0 +1,30 @@ +import { checkFilterEnumValues } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + fieldSelectMock, + objectMetadataItemMock, +} from 'src/engine/api/__mocks__/object-metadata-item.mock'; + +describe('checkFilterEnumValues', () => { + it('should check properly', () => { + expect(() => + checkFilterEnumValues( + FieldMetadataType.SELECT, + fieldSelectMock.name, + 'OPTION_1', + objectMetadataItemMock, + ), + ).not.toThrow(); + + expect(() => + checkFilterEnumValues( + FieldMetadataType.SELECT, + fieldSelectMock.name, + 'MISSING_OPTION', + objectMetadataItemMock, + ), + ).toThrow( + `'filter' enum value 'MISSING_OPTION' not available in '${fieldSelectMock.name}' enum. Available enum values are ['OPTION_1', 'OPTION_2']`, + ); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values.ts new file mode 100644 index 000000000000..3a57186c15cb --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values.ts @@ -0,0 +1,42 @@ +import { BadRequestException } from '@nestjs/common'; + +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; + +export const checkFilterEnumValues = ( + fieldType: FieldMetadataType | undefined, + fieldName: string, + value: string, + objectMetadataItem: ObjectMetadataInterface, +): void => { + if ( + !fieldType || + ![FieldMetadataType.MULTI_SELECT, FieldMetadataType.SELECT].includes( + fieldType, + ) + ) { + return; + } + const field = objectMetadataItem.fields.find( + (field) => field.name === fieldName, + ); + + const values = /^\[.*\]$/.test(value) + ? value.slice(1, -1).split(',') + : [value]; + const enumValues = field?.options?.map((option) => option.value); + + if (!enumValues) { + return; + } + values.forEach((val) => { + if (!enumValues.includes(val)) { + throw new BadRequestException( + `'filter' enum value '${val}' not available in '${fieldName}' enum. Available enum values are ['${enumValues.join( + "', '", + )}']`, + ); + } + }); +}; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts index 3329d2c5d800..74d5e6179254 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts @@ -1,5 +1,7 @@ import { BadRequestException } from '@nestjs/common'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + import { parseFilterContent } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils'; import { parseBaseFilter } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; import { @@ -8,6 +10,7 @@ import { } from 'src/engine/api/rest/api-rest-query-builder/utils/fields.utils'; import { formatFieldValue } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/format-field-values.utils'; import { FieldValue } from 'src/engine/api/rest/types/api-rest-field-value.type'; +import { checkFilterEnumValues } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values'; export enum Conjunctions { or = 'or', @@ -17,7 +20,7 @@ export enum Conjunctions { export const parseFilter = ( filterQuery: string, - objectMetadataItem, + objectMetadataItem: ObjectMetadataInterface, ): Record => { const result = {}; const match = filterQuery.match( @@ -51,8 +54,13 @@ export const parseFilter = ( } const { fields, comparator, value } = parseBaseFilter(filterQuery); + const fieldName = fields[0]; + checkFields(objectMetadataItem, fields); - const fieldType = getFieldType(objectMetadataItem, fields[0]); + const fieldType = getFieldType(objectMetadataItem, fieldName); + + checkFilterEnumValues(fieldType, fieldName, value, objectMetadataItem); + const formattedValue = formatFieldValue(value, fieldType, comparator); return fields.reverse().reduce( diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/fields.utils.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/fields.utils.ts index 951e05ff06d2..eca664a7a4f0 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/fields.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/fields.utils.ts @@ -1,13 +1,14 @@ import { BadRequestException } from '@nestjs/common'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; export const getFieldType = ( - objectMetadata: ObjectMetadataEntity, + objectMetadata: ObjectMetadataInterface, fieldName: string, ): FieldMetadataType | undefined => { for (const fieldMetdata of objectMetadata.fields) { @@ -18,7 +19,7 @@ export const getFieldType = ( }; export const checkFields = ( - objectMetadata: ObjectMetadataEntity, + objectMetadata: ObjectMetadataInterface, fieldNames: string[], ): void => { const fieldMetadataNames = objectMetadata.fields diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts index 54c6cfeb5333..54d1fa83e771 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts @@ -22,6 +22,8 @@ export const mapFieldMetadataToGraphqlQuery = ( FieldMetadataType.DATE_TIME, FieldMetadataType.EMAIL, FieldMetadataType.NUMBER, + FieldMetadataType.SELECT, + FieldMetadataType.RATING, FieldMetadataType.BOOLEAN, FieldMetadataType.POSITION, ].includes(fieldType); diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts index b4440db8eb85..25bce40f6667 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts @@ -11,6 +11,7 @@ describe('computeSchemaComponents', () => { ).toEqual({ ObjectName: { type: 'object', + description: undefined, required: ['fieldNumber'], example: { fieldNumber: '' }, properties: { @@ -31,6 +32,9 @@ describe('computeSchemaComponents', () => { fieldNumber: { type: 'number', }, + fieldSelect: { + type: 'string', + }, fieldString: { type: 'string', },