mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-22 11:43:34 +03:00
Add filter on array and jsonb field types (#7839)
This PR was created by [GitStart](https://gitstart.com/) to address the requirements from this ticket: [TWNTY-6784](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-6784). This ticket was imported from: [TWNTY-6784](https://github.com/twentyhq/twenty/issues/6784) --- ### Description - Add filter on array and jsonb field types - We did not implement the contains any filter for arrays on the frontend because we would need to change the UI design since this should be an array of values, and now we have only one input ### Demo <https://www.loom.com/share/0facf752b63f4120b5d4ea4ee9772d35?sid=d7bde469-e6a9-4298-a637-d81d40695a86> Fixes #6784 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
parent
3f2751ef6c
commit
7b10bfa7d2
@ -1,16 +1,35 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { Nullable } from 'twenty-ui';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { WorkspaceActivationStatus } from '~/generated/graphql';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
|
||||
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
onInitializeRecoilSnapshot: ({ set }) => {
|
||||
set(currentWorkspaceState, {
|
||||
id: '1',
|
||||
featureFlags: [],
|
||||
allowImpersonation: false,
|
||||
activationStatus: WorkspaceActivationStatus.Active,
|
||||
metadataVersion: 1,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
describe('useColumnDefinitionsFromFieldMetadata', () => {
|
||||
it('should return empty definitions if no object is passed', () => {
|
||||
const { result } = renderHook(
|
||||
(objectMetadataItem?: Nullable<ObjectMetadataItem>) => {
|
||||
return useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(Array.isArray(result.current.columnDefinitions)).toBe(true);
|
||||
@ -32,6 +51,7 @@ describe('useColumnDefinitionsFromFieldMetadata', () => {
|
||||
},
|
||||
{
|
||||
initialProps: companyObjectMetadata,
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '../utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { formatFieldMetadataItemsAsFilterDefinitions } from '../utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { formatFieldMetadataItemsAsSortDefinitions } from '../utils/formatFieldMetadataItemsAsSortDefinitions';
|
||||
@ -23,8 +24,13 @@ export const useColumnDefinitionsFromFieldMetadata = (
|
||||
[objectMetadataItem],
|
||||
);
|
||||
|
||||
const isArrayAndJsonFilterEnabled = useIsFeatureEnabled(
|
||||
'IS_ARRAY_AND_JSON_FILTER_ENABLED',
|
||||
);
|
||||
|
||||
const filterDefinitions = formatFieldMetadataItemsAsFilterDefinitions({
|
||||
fields: activeFieldMetadataItems,
|
||||
isArrayAndJsonFilterEnabled,
|
||||
});
|
||||
|
||||
const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({
|
||||
|
@ -8,10 +8,12 @@ import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
export const formatFieldMetadataItemsAsFilterDefinitions = ({
|
||||
fields,
|
||||
isArrayAndJsonFilterEnabled,
|
||||
}: {
|
||||
fields: Array<ObjectMetadataItem['fields'][0]>;
|
||||
}): FilterDefinition[] =>
|
||||
fields.reduce((acc, field) => {
|
||||
isArrayAndJsonFilterEnabled: boolean;
|
||||
}): FilterDefinition[] => {
|
||||
return fields.reduce((acc, field) => {
|
||||
if (
|
||||
field.type === FieldMetadataType.Relation &&
|
||||
field.relationDefinition?.direction !==
|
||||
@ -37,6 +39,9 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
|
||||
FieldMetadataType.Rating,
|
||||
FieldMetadataType.Actor,
|
||||
FieldMetadataType.Phones,
|
||||
...(isArrayAndJsonFilterEnabled
|
||||
? [FieldMetadataType.Array, FieldMetadataType.RawJson]
|
||||
: []),
|
||||
].includes(field.type)
|
||||
) {
|
||||
return acc;
|
||||
@ -44,6 +49,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
|
||||
|
||||
return [...acc, formatFieldMetadataItemAsFilterDefinition({ field })];
|
||||
}, [] as FilterDefinition[]);
|
||||
};
|
||||
|
||||
export const formatFieldMetadataItemAsFilterDefinition = ({
|
||||
field,
|
||||
@ -92,6 +98,8 @@ export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => {
|
||||
return 'ACTOR';
|
||||
case FieldMetadataType.Array:
|
||||
return 'ARRAY';
|
||||
case FieldMetadataType.RawJson:
|
||||
return 'RAW_JSON';
|
||||
default:
|
||||
return 'TEXT';
|
||||
}
|
||||
|
@ -104,6 +104,17 @@ export type PhonesFilter = {
|
||||
primaryPhoneCountryCode?: StringFilter;
|
||||
};
|
||||
|
||||
export type ArrayFilter = {
|
||||
contains?: string[];
|
||||
not_contains?: string[];
|
||||
is?: IsFilter;
|
||||
};
|
||||
|
||||
export type RawJsonFilter = {
|
||||
like?: string;
|
||||
is?: IsFilter;
|
||||
};
|
||||
|
||||
export type LeafFilter =
|
||||
| UUIDFilter
|
||||
| StringFilter
|
||||
@ -117,6 +128,8 @@ export type LeafFilter =
|
||||
| LinksFilter
|
||||
| ActorFilter
|
||||
| PhonesFilter
|
||||
| ArrayFilter
|
||||
| RawJsonFilter
|
||||
| undefined;
|
||||
|
||||
export type AndObjectRecordFilter = {
|
||||
|
@ -93,6 +93,7 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
'ADDRESS',
|
||||
'ACTOR',
|
||||
'ARRAY',
|
||||
'RAW_JSON',
|
||||
'PHONES',
|
||||
].includes(filterDefinitionUsedInDropdown.type) &&
|
||||
!isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && (
|
||||
|
@ -19,4 +19,5 @@ export type FilterableFieldType = PickLiteral<
|
||||
| 'MULTI_SELECT'
|
||||
| 'ACTOR'
|
||||
| 'ARRAY'
|
||||
| 'RAW_JSON'
|
||||
>;
|
||||
|
@ -18,7 +18,6 @@ export const getOperandsForFilterDefinition = (
|
||||
case 'FULL_NAME':
|
||||
case 'ADDRESS':
|
||||
case 'LINKS':
|
||||
case 'ARRAY':
|
||||
case 'PHONES':
|
||||
return [
|
||||
ViewFilterOperand.Contains,
|
||||
@ -32,6 +31,12 @@ export const getOperandsForFilterDefinition = (
|
||||
ViewFilterOperand.LessThan,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'RAW_JSON':
|
||||
return [
|
||||
ViewFilterOperand.Contains,
|
||||
ViewFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'DATE_TIME':
|
||||
case 'DATE':
|
||||
return [
|
||||
@ -70,6 +75,12 @@ export const getOperandsForFilterDefinition = (
|
||||
...emptyOperands,
|
||||
];
|
||||
}
|
||||
case 'ARRAY':
|
||||
return [
|
||||
ViewFilterOperand.Contains,
|
||||
ViewFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import {
|
||||
ActorFilter,
|
||||
AddressFilter,
|
||||
ArrayFilter,
|
||||
CurrencyFilter,
|
||||
DateFilter,
|
||||
EmailsFilter,
|
||||
FloatFilter,
|
||||
RawJsonFilter,
|
||||
RecordGqlOperationFilter,
|
||||
RelationFilter,
|
||||
StringFilter,
|
||||
@ -290,6 +292,24 @@ export const applyEmptyFilters = (
|
||||
],
|
||||
};
|
||||
break;
|
||||
case 'ARRAY':
|
||||
emptyRecordFilter = {
|
||||
or: [
|
||||
{
|
||||
[correspondingField.name]: { is: 'NULL' } as ArrayFilter,
|
||||
},
|
||||
],
|
||||
};
|
||||
break;
|
||||
case 'RAW_JSON':
|
||||
emptyRecordFilter = {
|
||||
or: [
|
||||
{
|
||||
[correspondingField.name]: { is: 'NULL' } as RawJsonFilter,
|
||||
},
|
||||
],
|
||||
};
|
||||
break;
|
||||
case 'EMAILS':
|
||||
emptyRecordFilter = {
|
||||
or: [
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { ArrayFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||
|
||||
export const isMatchingArrayFilter = ({
|
||||
arrayFilter,
|
||||
value,
|
||||
}: {
|
||||
arrayFilter: ArrayFilter;
|
||||
value: string[];
|
||||
}) => {
|
||||
if (value === null || !Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case arrayFilter.contains !== undefined: {
|
||||
return arrayFilter.contains.every((item) => value.includes(item));
|
||||
}
|
||||
case arrayFilter.not_contains !== undefined: {
|
||||
return !arrayFilter.not_contains.some((item) => value.includes(item));
|
||||
}
|
||||
case arrayFilter.is !== undefined: {
|
||||
if (arrayFilter.is === 'NULL') {
|
||||
return value === null;
|
||||
} else {
|
||||
return value !== null;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new Error(
|
||||
`Unexpected value for array filter: ${JSON.stringify(arrayFilter)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
import { RawJsonFilter } from '../../graphql/types/RecordGqlOperationFilter';
|
||||
|
||||
export const isMatchingRawJsonFilter = ({
|
||||
rawJsonFilter,
|
||||
value,
|
||||
}: {
|
||||
rawJsonFilter: RawJsonFilter;
|
||||
value: string;
|
||||
}) => {
|
||||
switch (true) {
|
||||
case rawJsonFilter.like !== undefined: {
|
||||
const regexPattern = rawJsonFilter.like.replace(/%/g, '.*');
|
||||
const regexCaseInsensitive = new RegExp(`^${regexPattern}$`, 'i');
|
||||
|
||||
const stringValue = JSON.stringify(value);
|
||||
|
||||
return regexCaseInsensitive.test(stringValue);
|
||||
}
|
||||
case rawJsonFilter.is !== undefined: {
|
||||
if (rawJsonFilter.is === 'NULL') {
|
||||
return value === null;
|
||||
} else {
|
||||
return value !== null;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new Error(
|
||||
`Unexpected value for string filter : ${JSON.stringify(rawJsonFilter)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
@ -5,6 +5,7 @@ import {
|
||||
ActorFilter,
|
||||
AddressFilter,
|
||||
AndObjectRecordFilter,
|
||||
ArrayFilter,
|
||||
BooleanFilter,
|
||||
CurrencyFilter,
|
||||
DateFilter,
|
||||
@ -16,14 +17,17 @@ import {
|
||||
NotObjectRecordFilter,
|
||||
OrObjectRecordFilter,
|
||||
PhonesFilter,
|
||||
RawJsonFilter,
|
||||
RecordGqlOperationFilter,
|
||||
StringFilter,
|
||||
UUIDFilter,
|
||||
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||
import { isMatchingArrayFilter } from '@/object-record/record-filter/utils/isMatchingArrayFilter';
|
||||
import { isMatchingBooleanFilter } from '@/object-record/record-filter/utils/isMatchingBooleanFilter';
|
||||
import { isMatchingCurrencyFilter } from '@/object-record/record-filter/utils/isMatchingCurrencyFilter';
|
||||
import { isMatchingDateFilter } from '@/object-record/record-filter/utils/isMatchingDateFilter';
|
||||
import { isMatchingFloatFilter } from '@/object-record/record-filter/utils/isMatchingFloatFilter';
|
||||
import { isMatchingRawJsonFilter } from '@/object-record/record-filter/utils/isMatchingRawJsonFilter';
|
||||
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
||||
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@ -165,6 +169,18 @@ export const isRecordMatchingFilter = ({
|
||||
value: record[filterKey],
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.Array: {
|
||||
return isMatchingArrayFilter({
|
||||
arrayFilter: filterValue as ArrayFilter,
|
||||
value: record[filterKey],
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.RawJson: {
|
||||
return isMatchingRawJsonFilter({
|
||||
rawJsonFilter: filterValue as RawJsonFilter,
|
||||
value: record[filterKey],
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.FullName: {
|
||||
const fullNameFilter = filterValue as FullNameFilter;
|
||||
|
||||
@ -302,6 +318,7 @@ export const isRecordMatchingFilter = ({
|
||||
`Not implemented yet, use UUID filter instead on the corredponding "${filterKey}Id" field`,
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(
|
||||
`Not implemented yet for field type "${objectMetadataField.type}"`,
|
||||
|
@ -3,10 +3,12 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import {
|
||||
ActorFilter,
|
||||
AddressFilter,
|
||||
ArrayFilter,
|
||||
CurrencyFilter,
|
||||
DateFilter,
|
||||
EmailsFilter,
|
||||
FloatFilter,
|
||||
RawJsonFilter,
|
||||
RecordGqlOperationFilter,
|
||||
RelationFilter,
|
||||
StringFilter,
|
||||
@ -98,6 +100,39 @@ export const turnFiltersIntoQueryFilter = (
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'RAW_JSON':
|
||||
switch (rawUIFilter.operand) {
|
||||
case ViewFilterOperand.Contains:
|
||||
objectRecordFilters.push({
|
||||
[correspondingField.name]: {
|
||||
like: `%${rawUIFilter.value}%`,
|
||||
} as RawJsonFilter,
|
||||
});
|
||||
break;
|
||||
case ViewFilterOperand.DoesNotContain:
|
||||
objectRecordFilters.push({
|
||||
not: {
|
||||
[correspondingField.name]: {
|
||||
like: `%${rawUIFilter.value}%`,
|
||||
} as RawJsonFilter,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case ViewFilterOperand.IsEmpty:
|
||||
case ViewFilterOperand.IsNotEmpty:
|
||||
applyEmptyFilters(
|
||||
rawUIFilter.operand,
|
||||
correspondingField,
|
||||
objectRecordFilters,
|
||||
rawUIFilter.definition,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'DATE':
|
||||
case 'DATE_TIME': {
|
||||
const resolvedFilterValue = resolveFilterValue(rawUIFilter);
|
||||
@ -835,6 +870,40 @@ export const turnFiltersIntoQueryFilter = (
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ARRAY': {
|
||||
switch (rawUIFilter.operand) {
|
||||
case ViewFilterOperand.Contains: {
|
||||
objectRecordFilters.push({
|
||||
[correspondingField.name]: {
|
||||
contains: [`${rawUIFilter.value}`],
|
||||
} as ArrayFilter,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ViewFilterOperand.DoesNotContain: {
|
||||
objectRecordFilters.push({
|
||||
[correspondingField.name]: {
|
||||
not_contains: [`${rawUIFilter.value}`],
|
||||
} as ArrayFilter,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ViewFilterOperand.IsEmpty:
|
||||
case ViewFilterOperand.IsNotEmpty:
|
||||
applyEmptyFilters(
|
||||
rawUIFilter.operand,
|
||||
correspondingField,
|
||||
objectRecordFilters,
|
||||
rawUIFilter.definition,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.label} filter`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown filter type');
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFi
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews';
|
||||
import { getQueryVariablesFromView } from '@/views/utils/getQueryVariablesFromView';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({
|
||||
objectMetadataItem,
|
||||
@ -19,10 +20,15 @@ export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const isArrayAndJsonFilterEnabled = useIsFeatureEnabled(
|
||||
'IS_ARRAY_AND_JSON_FILTER_ENABLED',
|
||||
);
|
||||
|
||||
const { filter, orderBy } = getQueryVariablesFromView({
|
||||
fieldMetadataItems: activeFieldMetadataItems,
|
||||
objectMetadataItem,
|
||||
view,
|
||||
isArrayAndJsonFilterEnabled,
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -13,10 +13,12 @@ export const getQueryVariablesFromView = ({
|
||||
view,
|
||||
fieldMetadataItems,
|
||||
objectMetadataItem,
|
||||
isArrayAndJsonFilterEnabled,
|
||||
}: {
|
||||
view: View | null | undefined;
|
||||
fieldMetadataItems: FieldMetadataItem[];
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
isArrayAndJsonFilterEnabled: boolean;
|
||||
}) => {
|
||||
if (!isDefined(view)) {
|
||||
return {
|
||||
@ -29,6 +31,7 @@ export const getQueryVariablesFromView = ({
|
||||
|
||||
const filterDefinitions = formatFieldMetadataItemsAsFilterDefinitions({
|
||||
fields: fieldMetadataItems,
|
||||
isArrayAndJsonFilterEnabled,
|
||||
});
|
||||
|
||||
const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({
|
||||
|
@ -13,4 +13,5 @@ export type FeatureFlagKey =
|
||||
| 'IS_QUERY_RUNNER_TWENTY_ORM_ENABLED'
|
||||
| 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED'
|
||||
| 'IS_ANALYTICS_V2_ENABLED'
|
||||
| 'IS_UNIQUE_INDEXES_ENABLED';
|
||||
| 'IS_UNIQUE_INDEXES_ENABLED'
|
||||
| 'IS_ARRAY_AND_JSON_FILTER_ENABLED';
|
||||
|
@ -13,6 +13,8 @@ import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-obj
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
const ARRAY_OPERATORS = ['in', 'contains', 'not_contains'];
|
||||
|
||||
export class GraphqlQueryFilterFieldParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
|
||||
@ -44,13 +46,14 @@ export class GraphqlQueryFilterFieldParser {
|
||||
}
|
||||
const [[operator, value]] = Object.entries(filterValue);
|
||||
|
||||
if (operator === 'in') {
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Invalid filter value for field ${key}. Expected non-empty array`,
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
if (
|
||||
ARRAY_OPERATORS.includes(operator) &&
|
||||
(!Array.isArray(value) || value.length === 0)
|
||||
) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Invalid filter value for field ${key}. Expected non-empty array`,
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const { sql, params } = computeWhereConditionParts(
|
||||
|
@ -61,24 +61,36 @@ export const computeWhereConditionParts = (
|
||||
};
|
||||
case 'like':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}"::text LIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'ilike':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" ILIKE :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}"::text ILIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'startsWith':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}"::text LIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'endsWith':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}"::text LIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'contains':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" @> ARRAY[:...${key}${uuid}]`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
|
||||
case 'not_contains':
|
||||
return {
|
||||
sql: `NOT ("${objectNameSingular}"."${key}" && ARRAY[:...${key}${uuid}])`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
|
||||
default:
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Operator "${operator}" is not supported`,
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { GraphQLInputObjectType, GraphQLList, GraphQLString } from 'graphql';
|
||||
|
||||
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||
|
||||
export const ArrayFilterType = new GraphQLInputObjectType({
|
||||
name: 'ArrayFilter',
|
||||
fields: {
|
||||
contains: { type: new GraphQLList(GraphQLString) },
|
||||
contains_any: { type: new GraphQLList(GraphQLString) },
|
||||
not_contains: { type: new GraphQLList(GraphQLString) },
|
||||
is: { type: FilterIs },
|
||||
},
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { GraphQLInputObjectType } from 'graphql';
|
||||
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||
|
||||
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||
|
||||
@ -6,5 +6,6 @@ export const RawJsonFilterType = new GraphQLInputObjectType({
|
||||
name: 'RawJsonFilter',
|
||||
fields: {
|
||||
is: { type: FilterIs },
|
||||
like: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user