mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-23 14:03:35 +03:00
4902 bug fix fix api filter for enum (#4909)
- Handle NUMERIC, SELECT, PROBABILITY, RATING FieldMetadataTypes Those filters now works: - http://localhost:3000/rest/opportunities?filter=stage[eq]:MEETING - http://localhost:3000/rest/opportunities?filter=stage[in]:[MEETING,NEW] When providing wrong enum values, the following error messages are returned: - http://localhost:3000/rest/opportunities?filter=stage[eq]:MEETINGG > BadRequestException: 'filter' enum value 'MEETINGG' not available in 'stage' enum. Available enum values are ['NEW', 'SCREENING', 'MEETING', 'PROPOSAL', 'CUSTOMER'] - http://localhost:3000/rest/opportunities?filter=stage[in]:[MEETING,NEWW] > BadRequestException: 'filter' enum value 'NEWW' not available in 'stage' enum. Available enum values are ['NEW', 'SCREENING', 'MEETING', 'PROPOSAL', 'CUSTOMER']
This commit is contained in:
parent
cfcc93dee1
commit
01991fe717
@ -29,9 +29,38 @@ export const fieldCurrencyMock = {
|
|||||||
defaultValue: { amountMicros: null, currencyCode: "''" },
|
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 = {
|
export const objectMetadataItemMock = {
|
||||||
targetTableName: 'testingObject',
|
targetTableName: 'testingObject',
|
||||||
nameSingular: 'objectName',
|
nameSingular: 'objectName',
|
||||||
namePlural: 'objectsName',
|
namePlural: 'objectsName',
|
||||||
fields: [fieldNumberMock, fieldStringMock, fieldLinkMock, fieldCurrencyMock],
|
fields: [
|
||||||
|
fieldNumberMock,
|
||||||
|
fieldStringMock,
|
||||||
|
fieldLinkMock,
|
||||||
|
fieldCurrencyMock,
|
||||||
|
fieldSelectMock,
|
||||||
|
],
|
||||||
} as ObjectMetadataEntity;
|
} as ObjectMetadataEntity;
|
||||||
|
@ -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']`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -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(
|
||||||
|
"', '",
|
||||||
|
)}']`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -1,5 +1,7 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
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 { 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 { parseBaseFilter } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils';
|
||||||
import {
|
import {
|
||||||
@ -8,6 +10,7 @@ import {
|
|||||||
} from 'src/engine/api/rest/api-rest-query-builder/utils/fields.utils';
|
} 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 { 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 { 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 {
|
export enum Conjunctions {
|
||||||
or = 'or',
|
or = 'or',
|
||||||
@ -17,7 +20,7 @@ export enum Conjunctions {
|
|||||||
|
|
||||||
export const parseFilter = (
|
export const parseFilter = (
|
||||||
filterQuery: string,
|
filterQuery: string,
|
||||||
objectMetadataItem,
|
objectMetadataItem: ObjectMetadataInterface,
|
||||||
): Record<string, FieldValue> => {
|
): Record<string, FieldValue> => {
|
||||||
const result = {};
|
const result = {};
|
||||||
const match = filterQuery.match(
|
const match = filterQuery.match(
|
||||||
@ -51,8 +54,13 @@ export const parseFilter = (
|
|||||||
}
|
}
|
||||||
const { fields, comparator, value } = parseBaseFilter(filterQuery);
|
const { fields, comparator, value } = parseBaseFilter(filterQuery);
|
||||||
|
|
||||||
|
const fieldName = fields[0];
|
||||||
|
|
||||||
checkFields(objectMetadataItem, fields);
|
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);
|
const formattedValue = formatFieldValue(value, fieldType, comparator);
|
||||||
|
|
||||||
return fields.reverse().reduce(
|
return fields.reverse().reduce(
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
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 { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
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 { 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';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
|
|
||||||
export const getFieldType = (
|
export const getFieldType = (
|
||||||
objectMetadata: ObjectMetadataEntity,
|
objectMetadata: ObjectMetadataInterface,
|
||||||
fieldName: string,
|
fieldName: string,
|
||||||
): FieldMetadataType | undefined => {
|
): FieldMetadataType | undefined => {
|
||||||
for (const fieldMetdata of objectMetadata.fields) {
|
for (const fieldMetdata of objectMetadata.fields) {
|
||||||
@ -18,7 +19,7 @@ export const getFieldType = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const checkFields = (
|
export const checkFields = (
|
||||||
objectMetadata: ObjectMetadataEntity,
|
objectMetadata: ObjectMetadataInterface,
|
||||||
fieldNames: string[],
|
fieldNames: string[],
|
||||||
): void => {
|
): void => {
|
||||||
const fieldMetadataNames = objectMetadata.fields
|
const fieldMetadataNames = objectMetadata.fields
|
||||||
|
@ -22,6 +22,8 @@ export const mapFieldMetadataToGraphqlQuery = (
|
|||||||
FieldMetadataType.DATE_TIME,
|
FieldMetadataType.DATE_TIME,
|
||||||
FieldMetadataType.EMAIL,
|
FieldMetadataType.EMAIL,
|
||||||
FieldMetadataType.NUMBER,
|
FieldMetadataType.NUMBER,
|
||||||
|
FieldMetadataType.SELECT,
|
||||||
|
FieldMetadataType.RATING,
|
||||||
FieldMetadataType.BOOLEAN,
|
FieldMetadataType.BOOLEAN,
|
||||||
FieldMetadataType.POSITION,
|
FieldMetadataType.POSITION,
|
||||||
].includes(fieldType);
|
].includes(fieldType);
|
||||||
|
@ -11,6 +11,7 @@ describe('computeSchemaComponents', () => {
|
|||||||
).toEqual({
|
).toEqual({
|
||||||
ObjectName: {
|
ObjectName: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
description: undefined,
|
||||||
required: ['fieldNumber'],
|
required: ['fieldNumber'],
|
||||||
example: { fieldNumber: '' },
|
example: { fieldNumber: '' },
|
||||||
properties: {
|
properties: {
|
||||||
@ -31,6 +32,9 @@ describe('computeSchemaComponents', () => {
|
|||||||
fieldNumber: {
|
fieldNumber: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
|
fieldSelect: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
fieldString: {
|
fieldString: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user