mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-25 09:13:22 +03:00
Allow filtering by multi-select fields. <img width="1053" alt="Screenshot 2024-11-11 at 11 54 45" src="https://github.com/user-attachments/assets/a79b2251-94e3-48f8-abda-e808103a6c39"> --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
parent
ac1197afe1
commit
badebc513f
@ -61,7 +61,7 @@ describe('useColumnDefinitionsFromFieldMetadata', () => {
|
|||||||
result.current;
|
result.current;
|
||||||
|
|
||||||
expect(columnDefinitions.length).toBe(21);
|
expect(columnDefinitions.length).toBe(21);
|
||||||
expect(filterDefinitions.length).toBe(14);
|
expect(filterDefinitions.length).toBe(15);
|
||||||
expect(sortDefinitions.length).toBe(14);
|
expect(sortDefinitions.length).toBe(14);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -35,6 +35,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
|
|||||||
FieldMetadataType.Address,
|
FieldMetadataType.Address,
|
||||||
FieldMetadataType.Relation,
|
FieldMetadataType.Relation,
|
||||||
FieldMetadataType.Select,
|
FieldMetadataType.Select,
|
||||||
|
FieldMetadataType.MultiSelect,
|
||||||
FieldMetadataType.Currency,
|
FieldMetadataType.Currency,
|
||||||
FieldMetadataType.Rating,
|
FieldMetadataType.Rating,
|
||||||
FieldMetadataType.Actor,
|
FieldMetadataType.Actor,
|
||||||
|
@ -21,11 +21,7 @@ export type BooleanFilter = {
|
|||||||
|
|
||||||
export type StringFilter = {
|
export type StringFilter = {
|
||||||
eq?: string;
|
eq?: string;
|
||||||
gt?: string;
|
|
||||||
gte?: string;
|
|
||||||
in?: string[];
|
in?: string[];
|
||||||
lt?: string;
|
|
||||||
lte?: string;
|
|
||||||
neq?: string;
|
neq?: string;
|
||||||
startsWith?: string;
|
startsWith?: string;
|
||||||
like?: string;
|
like?: string;
|
||||||
@ -35,6 +31,12 @@ export type StringFilter = {
|
|||||||
is?: IsFilter;
|
is?: IsFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RatingFilter = {
|
||||||
|
eq?: string;
|
||||||
|
in?: string[];
|
||||||
|
is?: IsFilter;
|
||||||
|
};
|
||||||
|
|
||||||
export type FloatFilter = {
|
export type FloatFilter = {
|
||||||
eq?: number;
|
eq?: number;
|
||||||
gt?: number;
|
gt?: number;
|
||||||
@ -104,10 +106,21 @@ export type PhonesFilter = {
|
|||||||
primaryPhoneCountryCode?: StringFilter;
|
primaryPhoneCountryCode?: StringFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ArrayFilter = {
|
export type SelectFilter = {
|
||||||
contains?: string[];
|
|
||||||
not_contains?: string[];
|
|
||||||
is?: IsFilter;
|
is?: IsFilter;
|
||||||
|
in?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MultiSelectFilter = {
|
||||||
|
is?: IsFilter;
|
||||||
|
isEmptyArray?: boolean;
|
||||||
|
containsAny?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ArrayFilter = {
|
||||||
|
is?: IsFilter;
|
||||||
|
isEmptyArray?: boolean;
|
||||||
|
containsIlike?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RawJsonFilter = {
|
export type RawJsonFilter = {
|
||||||
|
@ -93,7 +93,9 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
<ObjectFilterDropdownSourceSelect />
|
<ObjectFilterDropdownSourceSelect />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
|
{['SELECT', 'MULTI_SELECT'].includes(
|
||||||
|
filterDefinitionUsedInDropdown.type,
|
||||||
|
) && (
|
||||||
<>
|
<>
|
||||||
<ObjectFilterDropdownSearchInput />
|
<ObjectFilterDropdownSearchInput />
|
||||||
<ObjectFilterDropdownOptionSelect />
|
<ObjectFilterDropdownOptionSelect />
|
||||||
|
@ -58,8 +58,14 @@ export const getOperandsForFilterDefinition = (
|
|||||||
];
|
];
|
||||||
case 'RELATION':
|
case 'RELATION':
|
||||||
return [...relationOperands, ...emptyOperands];
|
return [...relationOperands, ...emptyOperands];
|
||||||
|
case 'MULTI_SELECT':
|
||||||
|
return [
|
||||||
|
ViewFilterOperand.Contains,
|
||||||
|
ViewFilterOperand.DoesNotContain,
|
||||||
|
...emptyOperands,
|
||||||
|
];
|
||||||
case 'SELECT':
|
case 'SELECT':
|
||||||
return [...relationOperands];
|
return [ViewFilterOperand.Is, ViewFilterOperand.IsNot, ...emptyOperands];
|
||||||
case 'ACTOR': {
|
case 'ACTOR': {
|
||||||
if (isActorSourceCompositeFilter(filterDefinition)) {
|
if (isActorSourceCompositeFilter(filterDefinition)) {
|
||||||
return [
|
return [
|
||||||
|
@ -158,62 +158,6 @@ describe('isMatchingStringFilter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('gt', () => {
|
|
||||||
it('value is greater than gt filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { gt: 'a' }, value: 'b' }),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('value is not greater than gt filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { gt: 'b' }, value: 'a' }),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('gte', () => {
|
|
||||||
it('value is greater than or equal to gte filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { gte: 'a' }, value: 'a' }),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('value is not greater than or equal to gte filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { gte: 'b' }, value: 'a' }),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('lt', () => {
|
|
||||||
it('value is less than lt filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { lt: 'b' }, value: 'a' }),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('value is not less than lt filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { lt: 'a' }, value: 'b' }),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('lte', () => {
|
|
||||||
it('value is less than or equal to lte filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { lte: 'a' }, value: 'a' }),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('value is not less than or equal to lte filter', () => {
|
|
||||||
expect(
|
|
||||||
isMatchingStringFilter({ stringFilter: { lte: 'a' }, value: 'b' }),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('startsWith', () => {
|
describe('startsWith', () => {
|
||||||
it('value starts with the startsWith filter', () => {
|
it('value starts with the startsWith filter', () => {
|
||||||
expect(
|
expect(
|
||||||
|
@ -3,15 +3,18 @@ import { isNonEmptyString } from '@sniptt/guards';
|
|||||||
import {
|
import {
|
||||||
ActorFilter,
|
ActorFilter,
|
||||||
AddressFilter,
|
AddressFilter,
|
||||||
|
ArrayFilter,
|
||||||
CurrencyFilter,
|
CurrencyFilter,
|
||||||
DateFilter,
|
DateFilter,
|
||||||
EmailsFilter,
|
EmailsFilter,
|
||||||
FloatFilter,
|
FloatFilter,
|
||||||
|
MultiSelectFilter,
|
||||||
|
RatingFilter,
|
||||||
RawJsonFilter,
|
RawJsonFilter,
|
||||||
RecordGqlOperationFilter,
|
RecordGqlOperationFilter,
|
||||||
RelationFilter,
|
RelationFilter,
|
||||||
|
SelectFilter,
|
||||||
StringFilter,
|
StringFilter,
|
||||||
UUIDFilter,
|
|
||||||
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { Field } from '~/generated/graphql';
|
import { Field } from '~/generated/graphql';
|
||||||
@ -241,7 +244,7 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
return {
|
return {
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
eq: convertRatingToRatingValue(parseFloat(filter.value)),
|
eq: convertRatingToRatingValue(parseFloat(filter.value)),
|
||||||
} as StringFilter,
|
} as RatingFilter,
|
||||||
};
|
};
|
||||||
case ViewFilterOperand.GreaterThan:
|
case ViewFilterOperand.GreaterThan:
|
||||||
return {
|
return {
|
||||||
@ -249,7 +252,7 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
in: convertGreaterThanRatingToArrayOfRatingValues(
|
in: convertGreaterThanRatingToArrayOfRatingValues(
|
||||||
parseFloat(filter.value),
|
parseFloat(filter.value),
|
||||||
),
|
),
|
||||||
} as StringFilter,
|
} as RatingFilter,
|
||||||
};
|
};
|
||||||
case ViewFilterOperand.LessThan:
|
case ViewFilterOperand.LessThan:
|
||||||
return {
|
return {
|
||||||
@ -257,7 +260,7 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
in: convertLessThanRatingToArrayOfRatingValues(
|
in: convertLessThanRatingToArrayOfRatingValues(
|
||||||
parseFloat(filter.value),
|
parseFloat(filter.value),
|
||||||
),
|
),
|
||||||
} as StringFilter,
|
} as RatingFilter,
|
||||||
};
|
};
|
||||||
case ViewFilterOperand.IsEmpty:
|
case ViewFilterOperand.IsEmpty:
|
||||||
case ViewFilterOperand.IsNotEmpty:
|
case ViewFilterOperand.IsNotEmpty:
|
||||||
@ -309,30 +312,28 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
|
|
||||||
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
||||||
|
|
||||||
if (parsedRecordIds.length > 0) {
|
if (parsedRecordIds.length === 0) return;
|
||||||
switch (filter.operand) {
|
switch (filter.operand) {
|
||||||
case ViewFilterOperand.Is:
|
case ViewFilterOperand.Is:
|
||||||
return {
|
return {
|
||||||
|
[correspondingField.name + 'Id']: {
|
||||||
|
in: parsedRecordIds,
|
||||||
|
} as RelationFilter,
|
||||||
|
};
|
||||||
|
case ViewFilterOperand.IsNot: {
|
||||||
|
if (parsedRecordIds.length === 0) return;
|
||||||
|
return {
|
||||||
|
not: {
|
||||||
[correspondingField.name + 'Id']: {
|
[correspondingField.name + 'Id']: {
|
||||||
in: parsedRecordIds,
|
in: parsedRecordIds,
|
||||||
} as RelationFilter,
|
} as RelationFilter,
|
||||||
};
|
},
|
||||||
case ViewFilterOperand.IsNot:
|
};
|
||||||
if (parsedRecordIds.length > 0) {
|
|
||||||
return {
|
|
||||||
not: {
|
|
||||||
[correspondingField.name + 'Id']: {
|
|
||||||
in: parsedRecordIds,
|
|
||||||
} as RelationFilter,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (filter.operand) {
|
switch (filter.operand) {
|
||||||
@ -349,7 +350,6 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case 'CURRENCY':
|
case 'CURRENCY':
|
||||||
switch (filter.operand) {
|
switch (filter.operand) {
|
||||||
@ -601,6 +601,56 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'MULTI_SELECT': {
|
||||||
|
if (isEmptyOperand) {
|
||||||
|
return getEmptyRecordGqlOperationFilter(
|
||||||
|
filter.operand,
|
||||||
|
correspondingField,
|
||||||
|
filter.definition,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = resolveFilterValue(
|
||||||
|
filter as Filter & { definition: { type: 'MULTI_SELECT' } },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.length === 0) return;
|
||||||
|
|
||||||
|
switch (filter.operand) {
|
||||||
|
case ViewFilterOperand.Contains:
|
||||||
|
return {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
containsAny: options,
|
||||||
|
} as MultiSelectFilter,
|
||||||
|
};
|
||||||
|
case ViewFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
containsAny: options,
|
||||||
|
} as MultiSelectFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
isEmptyArray: true,
|
||||||
|
} as MultiSelectFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
is: 'NULL',
|
||||||
|
} as MultiSelectFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'SELECT': {
|
case 'SELECT': {
|
||||||
if (isEmptyOperand) {
|
if (isEmptyOperand) {
|
||||||
return getEmptyRecordGqlOperationFilter(
|
return getEmptyRecordGqlOperationFilter(
|
||||||
@ -609,44 +659,61 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
filter.definition,
|
filter.definition,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const stringifiedSelectValues = filter.value;
|
const options = resolveFilterValue(
|
||||||
let parsedOptionValues: string[] = [];
|
filter as Filter & { definition: { type: 'SELECT' } },
|
||||||
|
);
|
||||||
|
|
||||||
if (!isNonEmptyString(stringifiedSelectValues)) {
|
if (options.length === 0) return;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
switch (filter.operand) {
|
||||||
parsedOptionValues = JSON.parse(stringifiedSelectValues);
|
case ViewFilterOperand.Is:
|
||||||
} catch (e) {
|
return {
|
||||||
throw new Error(
|
[correspondingField.name]: {
|
||||||
`Cannot parse filter value for SELECT filter : "${stringifiedSelectValues}"`,
|
in: options,
|
||||||
);
|
} as SelectFilter,
|
||||||
}
|
};
|
||||||
|
case ViewFilterOperand.IsNot:
|
||||||
if (parsedOptionValues.length > 0) {
|
return {
|
||||||
switch (filter.operand) {
|
not: {
|
||||||
case ViewFilterOperand.Is:
|
|
||||||
return {
|
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
in: parsedOptionValues,
|
in: options,
|
||||||
} as UUIDFilter,
|
} as SelectFilter,
|
||||||
};
|
},
|
||||||
case ViewFilterOperand.IsNot:
|
};
|
||||||
return {
|
default:
|
||||||
not: {
|
throw new Error(
|
||||||
[correspondingField.name]: {
|
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
||||||
in: parsedOptionValues,
|
);
|
||||||
} as UUIDFilter,
|
}
|
||||||
},
|
}
|
||||||
};
|
case 'ARRAY': {
|
||||||
default:
|
switch (filter.operand) {
|
||||||
throw new Error(
|
case ViewFilterOperand.Contains:
|
||||||
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
return {
|
||||||
);
|
[correspondingField.name]: {
|
||||||
}
|
containsIlike: `%${filter.value}%`,
|
||||||
|
} as ArrayFilter,
|
||||||
|
};
|
||||||
|
case ViewFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
containsIlike: `%${filter.value}%`,
|
||||||
|
} as ArrayFilter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case ViewFilterOperand.IsEmpty:
|
||||||
|
case ViewFilterOperand.IsNotEmpty:
|
||||||
|
return getEmptyRecordGqlOperationFilter(
|
||||||
|
filter.operand,
|
||||||
|
correspondingField,
|
||||||
|
filter.definition,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// TODO: fix this with a new composite field in ViewFilter entity
|
// TODO: fix this with a new composite field in ViewFilter entity
|
||||||
case 'ACTOR': {
|
case 'ACTOR': {
|
||||||
@ -665,18 +732,17 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
case ViewFilterOperand.IsNot: {
|
case ViewFilterOperand.IsNot: {
|
||||||
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
||||||
|
|
||||||
if (parsedRecordIds.length > 0) {
|
if (parsedRecordIds.length === 0) return;
|
||||||
return {
|
|
||||||
not: {
|
return {
|
||||||
[correspondingField.name]: {
|
not: {
|
||||||
source: {
|
[correspondingField.name]: {
|
||||||
in: parsedRecordIds,
|
source: {
|
||||||
} as RelationFilter,
|
in: parsedRecordIds,
|
||||||
},
|
} as RelationFilter,
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
}
|
};
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case ViewFilterOperand.Contains:
|
case ViewFilterOperand.Contains:
|
||||||
return {
|
return {
|
||||||
@ -716,7 +782,6 @@ const computeFilterRecordGqlOperationFilter = (
|
|||||||
`Unknown operand ${filter.operand} for ${filter.definition.label} filter`,
|
`Unknown operand ${filter.operand} for ${filter.definition.label} filter`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case 'EMAILS':
|
case 'EMAILS':
|
||||||
switch (filter.operand) {
|
switch (filter.operand) {
|
||||||
@ -806,7 +871,7 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!currentViewFilterGroup) {
|
if (!currentViewFilterGroup) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupFilters = filters.filter(
|
const groupFilters = filters.filter(
|
||||||
|
@ -6,12 +6,14 @@ import {
|
|||||||
DateFilter,
|
DateFilter,
|
||||||
EmailsFilter,
|
EmailsFilter,
|
||||||
FloatFilter,
|
FloatFilter,
|
||||||
|
MultiSelectFilter,
|
||||||
|
RatingFilter,
|
||||||
RawJsonFilter,
|
RawJsonFilter,
|
||||||
RecordGqlOperationFilter,
|
RecordGqlOperationFilter,
|
||||||
RelationFilter,
|
RelationFilter,
|
||||||
|
SelectFilter,
|
||||||
StringFilter,
|
StringFilter,
|
||||||
URLFilter,
|
URLFilter,
|
||||||
UUIDFilter,
|
|
||||||
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
@ -255,7 +257,7 @@ export const getEmptyRecordGqlOperationFilter = (
|
|||||||
break;
|
break;
|
||||||
case 'RATING':
|
case 'RATING':
|
||||||
emptyRecordFilter = {
|
emptyRecordFilter = {
|
||||||
[correspondingField.name]: { is: 'NULL' } as StringFilter,
|
[correspondingField.name]: { is: 'NULL' } as RatingFilter,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'DATE':
|
case 'DATE':
|
||||||
@ -266,7 +268,21 @@ export const getEmptyRecordGqlOperationFilter = (
|
|||||||
break;
|
break;
|
||||||
case 'SELECT':
|
case 'SELECT':
|
||||||
emptyRecordFilter = {
|
emptyRecordFilter = {
|
||||||
[correspondingField.name]: { is: 'NULL' } as UUIDFilter,
|
[correspondingField.name]: { is: 'NULL' } as SelectFilter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'MULTI_SELECT':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: { is: 'NULL' } as MultiSelectFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
isEmptyArray: true,
|
||||||
|
} as MultiSelectFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'RELATION':
|
case 'RELATION':
|
||||||
@ -296,6 +312,9 @@ export const getEmptyRecordGqlOperationFilter = (
|
|||||||
{
|
{
|
||||||
[correspondingField.name]: { is: 'NULL' } as ArrayFilter,
|
[correspondingField.name]: { is: 'NULL' } as ArrayFilter,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: { isEmptyArray: true } as ArrayFilter,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
@ -5,19 +5,9 @@ export const isMatchingArrayFilter = ({
|
|||||||
value,
|
value,
|
||||||
}: {
|
}: {
|
||||||
arrayFilter: ArrayFilter;
|
arrayFilter: ArrayFilter;
|
||||||
value: string[];
|
value: string[] | null;
|
||||||
}) => {
|
}) => {
|
||||||
if (value === null || !Array.isArray(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (true) {
|
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: {
|
case arrayFilter.is !== undefined: {
|
||||||
if (arrayFilter.is === 'NULL') {
|
if (arrayFilter.is === 'NULL') {
|
||||||
return value === null;
|
return value === null;
|
||||||
@ -25,6 +15,16 @@ export const isMatchingArrayFilter = ({
|
|||||||
return value !== null;
|
return value !== null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case arrayFilter.isEmptyArray !== undefined: {
|
||||||
|
return Array.isArray(value) && value.length === 0;
|
||||||
|
}
|
||||||
|
case arrayFilter.containsIlike !== undefined: {
|
||||||
|
const searchTerm = arrayFilter.containsIlike.toLowerCase();
|
||||||
|
return (
|
||||||
|
Array.isArray(value) &&
|
||||||
|
value.some((item) => item.toLowerCase().includes(searchTerm))
|
||||||
|
);
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected value for array filter: ${JSON.stringify(arrayFilter)}`,
|
`Unexpected value for array filter: ${JSON.stringify(arrayFilter)}`,
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { MultiSelectFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
|
||||||
|
export const isMatchingMultiSelectFilter = ({
|
||||||
|
multiSelectFilter,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
multiSelectFilter: MultiSelectFilter;
|
||||||
|
value: string[] | null;
|
||||||
|
}) => {
|
||||||
|
switch (true) {
|
||||||
|
case multiSelectFilter.containsAny !== undefined: {
|
||||||
|
return (
|
||||||
|
Array.isArray(value) &&
|
||||||
|
multiSelectFilter.containsAny.every((item) => value.includes(item))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case multiSelectFilter.isEmptyArray !== undefined: {
|
||||||
|
return Array.isArray(value) && value.length === 0;
|
||||||
|
}
|
||||||
|
case multiSelectFilter.is !== undefined: {
|
||||||
|
if (multiSelectFilter.is === 'NULL') {
|
||||||
|
return value === null;
|
||||||
|
} else {
|
||||||
|
return value !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected value for multi-select filter: ${JSON.stringify(
|
||||||
|
multiSelectFilter,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
import { RatingFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
|
||||||
|
export const isMatchingRatingFilter = ({
|
||||||
|
ratingFilter,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
ratingFilter: RatingFilter;
|
||||||
|
value: string | null;
|
||||||
|
}) => {
|
||||||
|
switch (true) {
|
||||||
|
case ratingFilter.eq !== undefined: {
|
||||||
|
return value === ratingFilter.eq;
|
||||||
|
}
|
||||||
|
case ratingFilter.in !== undefined: {
|
||||||
|
return value !== null && ratingFilter.in.includes(value);
|
||||||
|
}
|
||||||
|
case ratingFilter.is !== undefined: {
|
||||||
|
if (ratingFilter.is === 'NULL') {
|
||||||
|
return value === null;
|
||||||
|
} else {
|
||||||
|
return value !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected value for rating filter : ${JSON.stringify(ratingFilter)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import { SelectFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
|
||||||
|
export const isMatchingSelectFilter = ({
|
||||||
|
selectFilter,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
selectFilter: SelectFilter;
|
||||||
|
value: string;
|
||||||
|
}) => {
|
||||||
|
switch (true) {
|
||||||
|
case selectFilter.in !== undefined: {
|
||||||
|
return selectFilter.in.includes(value);
|
||||||
|
}
|
||||||
|
case selectFilter.is !== undefined: {
|
||||||
|
if (selectFilter.is === 'NULL') {
|
||||||
|
return value === null;
|
||||||
|
} else {
|
||||||
|
return value !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected value for select filter : ${JSON.stringify(selectFilter)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -48,18 +48,6 @@ export const isMatchingStringFilter = ({
|
|||||||
|
|
||||||
return regexCaseInsensitive.test(value);
|
return regexCaseInsensitive.test(value);
|
||||||
}
|
}
|
||||||
case stringFilter.gt !== undefined: {
|
|
||||||
return value > stringFilter.gt;
|
|
||||||
}
|
|
||||||
case stringFilter.gte !== undefined: {
|
|
||||||
return value >= stringFilter.gte;
|
|
||||||
}
|
|
||||||
case stringFilter.lt !== undefined: {
|
|
||||||
return value < stringFilter.lt;
|
|
||||||
}
|
|
||||||
case stringFilter.lte !== undefined: {
|
|
||||||
return value <= stringFilter.lte;
|
|
||||||
}
|
|
||||||
case stringFilter.startsWith !== undefined: {
|
case stringFilter.startsWith !== undefined: {
|
||||||
return value.startsWith(stringFilter.startsWith);
|
return value.startsWith(stringFilter.startsWith);
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,14 @@ import {
|
|||||||
FullNameFilter,
|
FullNameFilter,
|
||||||
LeafObjectRecordFilter,
|
LeafObjectRecordFilter,
|
||||||
LinksFilter,
|
LinksFilter,
|
||||||
|
MultiSelectFilter,
|
||||||
NotObjectRecordFilter,
|
NotObjectRecordFilter,
|
||||||
OrObjectRecordFilter,
|
OrObjectRecordFilter,
|
||||||
PhonesFilter,
|
PhonesFilter,
|
||||||
|
RatingFilter,
|
||||||
RawJsonFilter,
|
RawJsonFilter,
|
||||||
RecordGqlOperationFilter,
|
RecordGqlOperationFilter,
|
||||||
|
SelectFilter,
|
||||||
StringFilter,
|
StringFilter,
|
||||||
UUIDFilter,
|
UUIDFilter,
|
||||||
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
@ -27,7 +30,10 @@ import { isMatchingBooleanFilter } from '@/object-record/record-filter/utils/isM
|
|||||||
import { isMatchingCurrencyFilter } from '@/object-record/record-filter/utils/isMatchingCurrencyFilter';
|
import { isMatchingCurrencyFilter } from '@/object-record/record-filter/utils/isMatchingCurrencyFilter';
|
||||||
import { isMatchingDateFilter } from '@/object-record/record-filter/utils/isMatchingDateFilter';
|
import { isMatchingDateFilter } from '@/object-record/record-filter/utils/isMatchingDateFilter';
|
||||||
import { isMatchingFloatFilter } from '@/object-record/record-filter/utils/isMatchingFloatFilter';
|
import { isMatchingFloatFilter } from '@/object-record/record-filter/utils/isMatchingFloatFilter';
|
||||||
|
import { isMatchingMultiSelectFilter } from '@/object-record/record-filter/utils/isMatchingMultiSelectFilter';
|
||||||
|
import { isMatchingRatingFilter } from '@/object-record/record-filter/utils/isMatchingRatingFilter';
|
||||||
import { isMatchingRawJsonFilter } from '@/object-record/record-filter/utils/isMatchingRawJsonFilter';
|
import { isMatchingRawJsonFilter } from '@/object-record/record-filter/utils/isMatchingRawJsonFilter';
|
||||||
|
import { isMatchingSelectFilter } from '@/object-record/record-filter/utils/isMatchingSelectFilter';
|
||||||
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
||||||
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
@ -160,15 +166,27 @@ export const isRecordMatchingFilter = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (objectMetadataField.type) {
|
switch (objectMetadataField.type) {
|
||||||
case FieldMetadataType.Select:
|
|
||||||
case FieldMetadataType.Rating:
|
case FieldMetadataType.Rating:
|
||||||
case FieldMetadataType.MultiSelect:
|
return isMatchingRatingFilter({
|
||||||
|
ratingFilter: filterValue as RatingFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
case FieldMetadataType.Text: {
|
case FieldMetadataType.Text: {
|
||||||
return isMatchingStringFilter({
|
return isMatchingStringFilter({
|
||||||
stringFilter: filterValue as StringFilter,
|
stringFilter: filterValue as StringFilter,
|
||||||
value: record[filterKey],
|
value: record[filterKey],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case FieldMetadataType.Select:
|
||||||
|
return isMatchingSelectFilter({
|
||||||
|
selectFilter: filterValue as SelectFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
case FieldMetadataType.MultiSelect:
|
||||||
|
return isMatchingMultiSelectFilter({
|
||||||
|
multiSelectFilter: filterValue as MultiSelectFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
case FieldMetadataType.Array: {
|
case FieldMetadataType.Array: {
|
||||||
return isMatchingArrayFilter({
|
return isMatchingArrayFilter({
|
||||||
arrayFilter: filterValue as ArrayFilter,
|
arrayFilter: filterValue as ArrayFilter,
|
||||||
|
@ -75,7 +75,10 @@ export const ViewBarFilterEffect = ({
|
|||||||
? JSON.parse(viewFilterUsedInDropdown.value)
|
? JSON.parse(viewFilterUsedInDropdown.value)
|
||||||
: [];
|
: [];
|
||||||
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecords);
|
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecords);
|
||||||
} else if (filterDefinitionUsedInDropdown?.type === 'SELECT') {
|
} else if (
|
||||||
|
isDefined(filterDefinitionUsedInDropdown) &&
|
||||||
|
['SELECT', 'MULTI_SELECT'].includes(filterDefinitionUsedInDropdown.type)
|
||||||
|
) {
|
||||||
const viewFilterUsedInDropdown =
|
const viewFilterUsedInDropdown =
|
||||||
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||||
(filter) =>
|
(filter) =>
|
||||||
|
@ -2,6 +2,7 @@ import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
|||||||
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { resolveNumberViewFilterValue } from '@/views/view-filter-value/utils/resolveNumberViewFilterValue';
|
import { resolveNumberViewFilterValue } from '@/views/view-filter-value/utils/resolveNumberViewFilterValue';
|
||||||
|
import { resolveSelectViewFilterValue } from '@/views/view-filter-value/utils/resolveSelectViewFilterValue';
|
||||||
import {
|
import {
|
||||||
resolveDateViewFilterValue,
|
resolveDateViewFilterValue,
|
||||||
ResolvedDateViewFilterValue,
|
ResolvedDateViewFilterValue,
|
||||||
@ -14,7 +15,9 @@ type ResolvedFilterValue<
|
|||||||
? ResolvedDateViewFilterValue<O>
|
? ResolvedDateViewFilterValue<O>
|
||||||
: T extends 'NUMBER'
|
: T extends 'NUMBER'
|
||||||
? ReturnType<typeof resolveNumberViewFilterValue>
|
? ReturnType<typeof resolveNumberViewFilterValue>
|
||||||
: string;
|
: T extends 'SELECT' | 'MULTI_SELECT'
|
||||||
|
? string[]
|
||||||
|
: string;
|
||||||
|
|
||||||
type PartialFilter<
|
type PartialFilter<
|
||||||
T extends FilterableFieldType,
|
T extends FilterableFieldType,
|
||||||
@ -36,6 +39,9 @@ export const resolveFilterValue = <
|
|||||||
return resolveDateViewFilterValue(filter) as ResolvedFilterValue<T, O>;
|
return resolveDateViewFilterValue(filter) as ResolvedFilterValue<T, O>;
|
||||||
case 'NUMBER':
|
case 'NUMBER':
|
||||||
return resolveNumberViewFilterValue(filter) as ResolvedFilterValue<T, O>;
|
return resolveNumberViewFilterValue(filter) as ResolvedFilterValue<T, O>;
|
||||||
|
case 'SELECT':
|
||||||
|
case 'MULTI_SELECT':
|
||||||
|
return resolveSelectViewFilterValue(filter) as ResolvedFilterValue<T, O>;
|
||||||
default:
|
default:
|
||||||
return filter.value as ResolvedFilterValue<T, O>;
|
return filter.value as ResolvedFilterValue<T, O>;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const selectViewFilterValueSchema = z
|
||||||
|
.string()
|
||||||
|
.transform((val) => (val === '' ? [] : JSON.parse(val)))
|
||||||
|
.refine(
|
||||||
|
(parsed) =>
|
||||||
|
Array.isArray(parsed) && parsed.every((item) => typeof item === 'string'),
|
||||||
|
{
|
||||||
|
message: 'Expected an array of strings',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const resolveSelectViewFilterValue = (
|
||||||
|
viewFilter: Pick<ViewFilter, 'value'>,
|
||||||
|
) => {
|
||||||
|
return selectViewFilterValueSchema.parse(viewFilter.value);
|
||||||
|
};
|
@ -13,7 +13,7 @@ import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metada
|
|||||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
|
|
||||||
const ARRAY_OPERATORS = ['in', 'contains', 'not_contains'];
|
const ARRAY_OPERATORS = ['in', 'contains', 'notContains'];
|
||||||
|
|
||||||
export class GraphqlQueryFilterFieldParser {
|
export class GraphqlQueryFilterFieldParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private fieldMetadataMapByName: FieldMetadataMap;
|
||||||
|
@ -19,6 +19,11 @@ export const computeWhereConditionParts = (
|
|||||||
const uuid = Math.random().toString(36).slice(2, 7);
|
const uuid = Math.random().toString(36).slice(2, 7);
|
||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
|
case 'isEmptyArray':
|
||||||
|
return {
|
||||||
|
sql: `"${objectNameSingular}"."${key}" = '{}'`,
|
||||||
|
params: {},
|
||||||
|
};
|
||||||
case 'eq':
|
case 'eq':
|
||||||
return {
|
return {
|
||||||
sql: `"${objectNameSingular}"."${key}" = :${key}${uuid}`,
|
sql: `"${objectNameSingular}"."${key}" = :${key}${uuid}`,
|
||||||
@ -84,10 +89,19 @@ export const computeWhereConditionParts = (
|
|||||||
sql: `"${objectNameSingular}"."${key}" @> ARRAY[:...${key}${uuid}]`,
|
sql: `"${objectNameSingular}"."${key}" @> ARRAY[:...${key}${uuid}]`,
|
||||||
params: { [`${key}${uuid}`]: value },
|
params: { [`${key}${uuid}`]: value },
|
||||||
};
|
};
|
||||||
|
case 'notContains':
|
||||||
case 'not_contains':
|
|
||||||
return {
|
return {
|
||||||
sql: `NOT ("${objectNameSingular}"."${key}" && ARRAY[:...${key}${uuid}])`,
|
sql: `NOT ("${objectNameSingular}"."${key}"::text[] && ARRAY[:...${key}${uuid}]::text[])`,
|
||||||
|
params: { [`${key}${uuid}`]: value },
|
||||||
|
};
|
||||||
|
case 'containsAny':
|
||||||
|
return {
|
||||||
|
sql: `"${objectNameSingular}"."${key}"::text[] && ARRAY[:...${key}${uuid}]::text[]`,
|
||||||
|
params: { [`${key}${uuid}`]: value },
|
||||||
|
};
|
||||||
|
case 'containsIlike':
|
||||||
|
return {
|
||||||
|
sql: `EXISTS (SELECT 1 FROM unnest("${objectNameSingular}"."${key}") AS elem WHERE elem ILIKE :${key}${uuid})`,
|
||||||
params: { [`${key}${uuid}`]: value },
|
params: { [`${key}${uuid}`]: value },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
import { GraphQLInputObjectType, GraphQLInputType, GraphQLList } from 'graphql';
|
import {
|
||||||
|
GraphQLBoolean,
|
||||||
|
GraphQLInputObjectType,
|
||||||
|
GraphQLInputType,
|
||||||
|
GraphQLList,
|
||||||
|
} from 'graphql';
|
||||||
|
|
||||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||||
|
|
||||||
@ -103,7 +108,9 @@ export class InputTypeFactory {
|
|||||||
eq: { type: enumType },
|
eq: { type: enumType },
|
||||||
neq: { type: enumType },
|
neq: { type: enumType },
|
||||||
in: { type: new GraphQLList(enumType) },
|
in: { type: new GraphQLList(enumType) },
|
||||||
|
containsAny: { type: new GraphQLList(enumType) },
|
||||||
is: { type: FilterIs },
|
is: { type: FilterIs },
|
||||||
|
isEmptyArray: { type: GraphQLBoolean },
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { GraphQLInputObjectType, GraphQLList, GraphQLString } from 'graphql';
|
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||||
|
|
||||||
export const ArrayFilterType = new GraphQLInputObjectType({
|
export const ArrayFilterType = new GraphQLInputObjectType({
|
||||||
name: 'ArrayFilter',
|
name: 'ArrayFilter',
|
||||||
fields: {
|
fields: {
|
||||||
contains: { type: new GraphQLList(GraphQLString) },
|
containsIlike: { type: GraphQLString },
|
||||||
not_contains: { type: new GraphQLList(GraphQLString) },
|
|
||||||
is: { type: FilterIs },
|
is: { type: FilterIs },
|
||||||
|
isEmptyArray: { type: GraphQLBoolean },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
GraphQLBoolean,
|
||||||
|
GraphQLInputObjectType,
|
||||||
|
GraphQLList,
|
||||||
|
GraphQLString,
|
||||||
|
} from 'graphql';
|
||||||
|
|
||||||
|
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||||
|
|
||||||
|
export const MultiSelectFilterType = new GraphQLInputObjectType({
|
||||||
|
name: 'MultiSelectFilter',
|
||||||
|
fields: {
|
||||||
|
containsAny: { type: new GraphQLList(GraphQLString) },
|
||||||
|
is: { type: FilterIs },
|
||||||
|
isEmptyArray: { type: GraphQLBoolean },
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
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 SelectFilterType = new GraphQLInputObjectType({
|
||||||
|
name: 'SelectFilter',
|
||||||
|
fields: {
|
||||||
|
in: { type: new GraphQLList(GraphQLString) },
|
||||||
|
is: { type: FilterIs },
|
||||||
|
},
|
||||||
|
});
|
@ -27,6 +27,8 @@ import {
|
|||||||
StringFilterType,
|
StringFilterType,
|
||||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
|
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
|
||||||
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
|
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
|
||||||
|
import { MultiSelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/multi-select-filter.input-type';
|
||||||
|
import { SelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/select-filter.input-type';
|
||||||
import {
|
import {
|
||||||
BigFloatScalarType,
|
BigFloatScalarType,
|
||||||
UUIDScalarType,
|
UUIDScalarType,
|
||||||
@ -115,6 +117,8 @@ export class TypeMapperService {
|
|||||||
[FieldMetadataType.RAW_JSON, RawJsonFilterType],
|
[FieldMetadataType.RAW_JSON, RawJsonFilterType],
|
||||||
[FieldMetadataType.RICH_TEXT, StringFilterType],
|
[FieldMetadataType.RICH_TEXT, StringFilterType],
|
||||||
[FieldMetadataType.ARRAY, ArrayFilterType],
|
[FieldMetadataType.ARRAY, ArrayFilterType],
|
||||||
|
[FieldMetadataType.MULTI_SELECT, MultiSelectFilterType],
|
||||||
|
[FieldMetadataType.SELECT, SelectFilterType],
|
||||||
[FieldMetadataType.TS_VECTOR, StringFilterType], // TODO: Add TSVectorFilterType
|
[FieldMetadataType.TS_VECTOR, StringFilterType], // TODO: Add TSVectorFilterType
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -58,7 +58,9 @@ export const generateFields = <
|
|||||||
? {
|
? {
|
||||||
nullable: fieldMetadata.isNullable,
|
nullable: fieldMetadata.isNullable,
|
||||||
defaultValue: fieldMetadata.defaultValue,
|
defaultValue: fieldMetadata.defaultValue,
|
||||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
isArray:
|
||||||
|
kind !== InputTypeDefinitionKind.Filter &&
|
||||||
|
fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||||
settings: fieldMetadata.settings,
|
settings: fieldMetadata.settings,
|
||||||
isIdField: fieldMetadata.name === 'id',
|
isIdField: fieldMetadata.name === 'id',
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user