Fix filter rest api (#10537)

## Context
Rest API is now using cache metadata but the change was not done
everywhere which was breaking the API
<img width="953" alt="Screenshot 2025-02-27 at 10 48 31"
src="https://github.com/user-attachments/assets/84f549a6-59b5-4989-b1ba-2d5c515ee47c"
/>
This commit is contained in:
Weiko
2025-02-27 11:05:53 +01:00
committed by GitHub
parent 83930551d8
commit 084554eaa8
12 changed files with 269 additions and 51 deletions

View File

@ -2,12 +2,14 @@ import { Injectable } from '@nestjs/common';
import { Request } from 'express'; import { Request } from 'express';
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.type'; import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.type';
import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory'; import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory'; import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable() @Injectable()
export class GetVariablesFactory { export class GetVariablesFactory {
@ -22,7 +24,10 @@ export class GetVariablesFactory {
create( create(
id: string | undefined, id: string | undefined,
request: Request, request: Request,
objectMetadata, objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
): QueryVariables { ): QueryVariables {
if (id) { if (id) {
return { filter: { id: { eq: id } } }; return { filter: { id: { eq: id } } };

View File

@ -1,31 +1,70 @@
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { checkFields } from 'src/engine/api/rest/core/query-builder/utils/check-fields.utils'; import { checkFields } from 'src/engine/api/rest/core/query-builder/utils/check-fields.utils';
import { checkArrayFields } from 'src/engine/api/rest/core/query-builder/utils/check-order-by.utils'; import { checkArrayFields } from 'src/engine/api/rest/core/query-builder/utils/check-order-by.utils';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('checkFields', () => { describe('checkFields', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should check field types', () => { it('should check field types', () => {
expect(() => expect(() =>
checkFields(objectMetadataItemMock, ['fieldNumber']), checkFields(mockObjectMetadataWithFieldMaps, ['fieldNumber']),
).not.toThrow(); ).not.toThrow();
expect(() => checkFields(objectMetadataItemMock, ['wrongField'])).toThrow(); expect(() =>
checkFields(mockObjectMetadataWithFieldMaps, ['wrongField']),
).toThrow();
expect(() => expect(() =>
checkFields(objectMetadataItemMock, ['fieldNumber', 'wrongField']), checkFields(mockObjectMetadataWithFieldMaps, [
'fieldNumber',
'wrongField',
]),
).toThrow(); ).toThrow();
}); });
it('should check field types from array of fields', () => { it('should check field types from array of fields', () => {
expect(() => expect(() =>
checkArrayFields(objectMetadataItemMock, [{ fieldNumber: undefined }]), checkArrayFields(mockObjectMetadataWithFieldMaps, [
{ fieldNumber: undefined },
]),
).not.toThrow(); ).not.toThrow();
expect(() => expect(() =>
checkArrayFields(objectMetadataItemMock, [{ wrongField: undefined }]), checkArrayFields(mockObjectMetadataWithFieldMaps, [
{ wrongField: undefined },
]),
).toThrow(); ).toThrow();
expect(() => expect(() =>
checkArrayFields(objectMetadataItemMock, [ checkArrayFields(mockObjectMetadataWithFieldMaps, [
{ fieldNumber: undefined }, { fieldNumber: undefined },
{ wrongField: undefined }, { wrongField: undefined },
]), ]),

View File

@ -1,12 +1,42 @@
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType } from 'twenty-shared';
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { getFieldType } from 'src/engine/api/rest/core/query-builder/utils/get-field-type.utils'; import { getFieldType } from 'src/engine/api/rest/core/query-builder/utils/get-field-type.utils';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('getFieldType', () => { describe('getFieldType', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should get field type', () => { it('should get field type', () => {
expect(getFieldType(objectMetadataItemMock, 'fieldNumber')).toEqual( expect(
FieldMetadataType.NUMBER, getFieldType(mockObjectMetadataWithFieldMaps, 'fieldNumber'),
); ).toEqual(FieldMetadataType.NUMBER);
}); });
}); });

View File

@ -1,16 +1,15 @@
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
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 { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
export const checkFields = ( export const checkFields = (
objectMetadata: ObjectMetadataInterface, objectMetadataItem: ObjectMetadataItemWithFieldMaps,
fieldNames: string[], fieldNames: string[],
): void => { ): void => {
const fieldMetadataNames = objectMetadata.fields const fieldMetadataNames = objectMetadataItem.fields
.map((field) => { .map((field) => {
if (isCompositeFieldMetadataType(field.type)) { if (isCompositeFieldMetadataType(field.type)) {
const compositeType = compositeTypeDefinitions.get(field.type); const compositeType = compositeTypeDefinitions.get(field.type);
@ -38,7 +37,7 @@ export const checkFields = (
if (!fieldMetadataNames.includes(fieldName)) { if (!fieldMetadataNames.includes(fieldName)) {
throw new BadRequestException( throw new BadRequestException(
`field '${fieldName}' does not exist in '${computeObjectTargetTable( `field '${fieldName}' does not exist in '${computeObjectTargetTable(
objectMetadata, objectMetadataItem,
)}' object`, )}' object`,
); );
} }

View File

@ -1,17 +1,17 @@
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
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 { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
export const checkArrayFields = ( export const checkArrayFields = (
objectMetadata: ObjectMetadataInterface, objectMetadataItem: ObjectMetadataItemWithFieldMaps,
fields: Array<Partial<ObjectRecord>>, fields: Array<Partial<ObjectRecord>>,
): void => { ): void => {
const fieldMetadataNames = objectMetadata.fields const fieldMetadataNames = objectMetadataItem.fields
.map((field) => { .map((field) => {
if (isCompositeFieldMetadataType(field.type)) { if (isCompositeFieldMetadataType(field.type)) {
const compositeType = compositeTypeDefinitions.get(field.type); const compositeType = compositeTypeDefinitions.get(field.type);
@ -39,7 +39,7 @@ export const checkArrayFields = (
if (!fieldMetadataNames.includes(fieldName)) { if (!fieldMetadataNames.includes(fieldName)) {
throw new BadRequestException( throw new BadRequestException(
`field '${fieldName}' does not exist in '${computeObjectTargetTable( `field '${fieldName}' does not exist in '${computeObjectTargetTable(
objectMetadata, objectMetadataItem,
)}' object`, )}' object`,
); );
} }

View File

@ -1,19 +1,47 @@
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { import {
fieldSelectMock, fieldSelectMock,
objectMetadataItemMock, objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock'; } from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { checkFilterEnumValues } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-enum-values'; import { checkFilterEnumValues } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-enum-values';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('checkFilterEnumValues', () => { describe('checkFilterEnumValues', () => {
const completeFieldSelectMock: FieldMetadataInterface = {
id: 'field-select-id',
type: fieldSelectMock.type,
name: fieldSelectMock.name,
label: 'Field Select',
objectMetadataId: 'object-metadata-id',
isNullable: fieldSelectMock.isNullable,
defaultValue: fieldSelectMock.defaultValue,
options: fieldSelectMock.options,
};
const fieldsById: FieldMetadataMap = {
'field-select-id': completeFieldSelectMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldSelectMock.name]: completeFieldSelectMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should check properly', () => { it('should check properly', () => {
expect(() => expect(() =>
checkFilterEnumValues( checkFilterEnumValues(
FieldMetadataType.SELECT, FieldMetadataType.SELECT,
fieldSelectMock.name, fieldSelectMock.name,
'OPTION_1', 'OPTION_1',
objectMetadataItemMock, mockObjectMetadataWithFieldMaps,
), ),
).not.toThrow(); ).not.toThrow();
@ -22,7 +50,7 @@ describe('checkFilterEnumValues', () => {
FieldMetadataType.SELECT, FieldMetadataType.SELECT,
fieldSelectMock.name, fieldSelectMock.name,
'MISSING_OPTION', 'MISSING_OPTION',
objectMetadataItemMock, mockObjectMetadataWithFieldMaps,
), ),
).toThrow( ).toThrow(
`'filter' enum value 'MISSING_OPTION' not available in '${fieldSelectMock.name}' enum. Available enum values are ['OPTION_1', 'OPTION_2']`, `'filter' enum value 'MISSING_OPTION' not available in '${fieldSelectMock.name}' enum. Available enum values are ['OPTION_1', 'OPTION_2']`,

View File

@ -1,12 +1,55 @@
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
fieldTextMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils'; import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('parseFilter', () => { describe('parseFilter', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const completeFieldTextMock: FieldMetadataInterface = {
id: 'field-text-id',
type: fieldTextMock.type,
name: fieldTextMock.name,
label: 'Field Text',
objectMetadataId: 'object-metadata-id',
isNullable: fieldTextMock.isNullable,
defaultValue: fieldTextMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
'field-text-id': completeFieldTextMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
[completeFieldTextMock.name]: completeFieldTextMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should parse string filter test 1', () => { it('should parse string filter test 1', () => {
expect( expect(
parseFilter( parseFilter(
'and(fieldNumber[eq]:1,fieldNumber[eq]:2)', 'and(fieldNumber[eq]:1,fieldNumber[eq]:2)',
objectMetadataItemMock, mockObjectMetadataWithFieldMaps,
), ),
).toEqual({ ).toEqual({
and: [{ fieldNumber: { eq: 1 } }, { fieldNumber: { eq: 2 } }], and: [{ fieldNumber: { eq: 1 } }, { fieldNumber: { eq: 2 } }],
@ -17,7 +60,7 @@ describe('parseFilter', () => {
expect( expect(
parseFilter( parseFilter(
'and(fieldNumber[eq]:1,or(fieldNumber[eq]:2,fieldNumber[eq]:3))', 'and(fieldNumber[eq]:1,or(fieldNumber[eq]:2,fieldNumber[eq]:3))',
objectMetadataItemMock, mockObjectMetadataWithFieldMaps,
), ),
).toEqual({ ).toEqual({
and: [ and: [
@ -31,7 +74,7 @@ describe('parseFilter', () => {
expect( expect(
parseFilter( parseFilter(
'and(fieldNumber[eq]:1,or(fieldNumber[eq]:2,fieldNumber[eq]:3,and(fieldNumber[eq]:6,fieldNumber[eq]:7)),or(fieldNumber[eq]:4,fieldNumber[eq]:5))', 'and(fieldNumber[eq]:1,or(fieldNumber[eq]:2,fieldNumber[eq]:3,and(fieldNumber[eq]:6,fieldNumber[eq]:7)),or(fieldNumber[eq]:4,fieldNumber[eq]:5))',
objectMetadataItemMock, mockObjectMetadataWithFieldMaps,
), ),
).toEqual({ ).toEqual({
and: [ and: [
@ -52,7 +95,7 @@ describe('parseFilter', () => {
expect( expect(
parseFilter( parseFilter(
'and(fieldText[gt]:"val,ue",or(fieldNumber[is]:NOT_NULL,not(fieldText[startsWith]:"val"),and(fieldNumber[eq]:6,fieldText[ilike]:"%val%")),or(fieldNumber[eq]:4,fieldText[is]:NULL))', 'and(fieldText[gt]:"val,ue",or(fieldNumber[is]:NOT_NULL,not(fieldText[startsWith]:"val"),and(fieldNumber[eq]:6,fieldText[ilike]:"%val%")),or(fieldNumber[eq]:4,fieldText[is]:NULL))',
objectMetadataItemMock, mockObjectMetadataWithFieldMaps,
), ),
).toEqual({ ).toEqual({
and: [ and: [
@ -78,7 +121,7 @@ describe('parseFilter', () => {
expect( expect(
parseFilter( parseFilter(
'and(fieldNumber[eq]:1,not(fieldNumber[eq]:2))', 'and(fieldNumber[eq]:1,not(fieldNumber[eq]:2))',
objectMetadataItemMock, mockObjectMetadataWithFieldMaps,
), ),
).toEqual({ ).toEqual({
and: [ and: [

View File

@ -2,13 +2,13 @@ import { BadRequestException } from '@nestjs/common';
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType } from 'twenty-shared';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export const checkFilterEnumValues = ( export const checkFilterEnumValues = (
fieldType: FieldMetadataType | undefined, fieldType: FieldMetadataType | undefined,
fieldName: string, fieldName: string,
value: string, value: string,
objectMetadataItem: ObjectMetadataInterface, objectMetadataItem: ObjectMetadataItemWithFieldMaps,
): void => { ): void => {
if ( if (
!fieldType || !fieldType ||
@ -18,9 +18,7 @@ export const checkFilterEnumValues = (
) { ) {
return; return;
} }
const field = objectMetadataItem.fields.find( const field = objectMetadataItem.fieldsByName[fieldName];
(field) => field.name === fieldName,
);
const values = /^\[.*\]$/.test(value) const values = /^\[.*\]$/.test(value)
? value.slice(1, -1).split(',') ? value.slice(1, -1).split(',')

View File

@ -1,14 +1,13 @@
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/core/query-builder/utils/filter-utils/parse-filter-content.utils';
import { parseBaseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils';
import { checkFields } from 'src/engine/api/rest/core/query-builder/utils/check-fields.utils'; import { checkFields } from 'src/engine/api/rest/core/query-builder/utils/check-fields.utils';
import { formatFieldValue } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/format-field-values.utils';
import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { checkFilterEnumValues } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-enum-values'; import { checkFilterEnumValues } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-enum-values';
import { formatFieldValue } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/format-field-values.utils';
import { parseBaseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils';
import { parseFilterContent } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter-content.utils';
import { getFieldType } from 'src/engine/api/rest/core/query-builder/utils/get-field-type.utils'; import { getFieldType } from 'src/engine/api/rest/core/query-builder/utils/get-field-type.utils';
import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export enum Conjunctions { export enum Conjunctions {
or = 'or', or = 'or',
@ -18,7 +17,7 @@ export enum Conjunctions {
export const parseFilter = ( export const parseFilter = (
filterQuery: string, filterQuery: string,
objectMetadataItem: ObjectMetadataInterface, objectMetadataItem: ObjectMetadataItemWithFieldMaps,
): Record<string, FieldValue> => { ): Record<string, FieldValue> => {
const result = {}; const result = {};
const match = filterQuery.match( const match = filterQuery.match(

View File

@ -1,10 +1,10 @@
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType } from 'twenty-shared';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export const getFieldType = ( export const getFieldType = (
objectMetadata: ObjectMetadataInterface, objectMetadataItem: ObjectMetadataItemWithFieldMaps,
fieldName: string, fieldName: string,
): FieldMetadataType | undefined => { ): FieldMetadataType | undefined => {
return objectMetadata.fields.find((field) => field.name === fieldName)?.type; return objectMetadataItem.fieldsByName[fieldName]?.type;
}; };

View File

@ -1,10 +1,79 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldCurrencyMock,
fieldNumberMock,
fieldTextMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory'; import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('FilterInputFactory', () => { describe('FilterInputFactory', () => {
const objectMetadata = { objectMetadataItem: objectMetadataItemMock }; const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const completeFieldTextMock: FieldMetadataInterface = {
id: 'field-text-id',
type: fieldTextMock.type,
name: fieldTextMock.name,
label: 'Field Text',
objectMetadataId: 'object-metadata-id',
isNullable: fieldTextMock.isNullable,
defaultValue: fieldTextMock.defaultValue,
};
const completeFieldCurrencyMock: FieldMetadataInterface = {
id: 'field-currency-id',
type: fieldCurrencyMock.type,
name: fieldCurrencyMock.name,
label: 'Field Currency',
objectMetadataId: 'object-metadata-id',
isNullable: fieldCurrencyMock.isNullable,
defaultValue: fieldCurrencyMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
'field-text-id': completeFieldTextMock,
'field-currency-id': completeFieldCurrencyMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
[completeFieldTextMock.name]: completeFieldTextMock,
[completeFieldCurrencyMock.name]: completeFieldCurrencyMock,
};
const objectMetadataMapItem = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
const objectMetadataMaps = {
byId: {
[objectMetadataItemMock.id || 'mock-id']: objectMetadataMapItem,
},
idByNameSingular: {
[objectMetadataItemMock.nameSingular]:
objectMetadataItemMock.id || 'mock-id',
},
};
const objectMetadata = {
objectMetadataMaps,
objectMetadataMapItem,
};
let service: FilterInputFactory; let service: FilterInputFactory;

View File

@ -6,10 +6,18 @@ import { addDefaultConjunctionIfMissing } from 'src/engine/api/rest/core/query-b
import { checkFilterQuery } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-query.utils'; import { checkFilterQuery } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-query.utils';
import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils'; import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils';
import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type'; import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable() @Injectable()
export class FilterInputFactory { export class FilterInputFactory {
create(request: Request, objectMetadata): Record<string, FieldValue> { create(
request: Request,
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
): Record<string, FieldValue> {
let filterQuery = request.query.filter; let filterQuery = request.query.filter;
if (typeof filterQuery !== 'string') { if (typeof filterQuery !== 'string') {
@ -20,6 +28,6 @@ export class FilterInputFactory {
filterQuery = addDefaultConjunctionIfMissing(filterQuery); filterQuery = addDefaultConjunctionIfMissing(filterQuery);
return parseFilter(filterQuery, objectMetadata.objectMetadataItem); return parseFilter(filterQuery, objectMetadata.objectMetadataMapItem);
} }
} }