Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b7b33fe18 | ||
|
|
f1125d0894 |
+3
-4
@@ -9,7 +9,6 @@ import { contextStoreFilterGroupsComponentState } from '@/context-store/states/c
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@@ -90,8 +89,8 @@ export const buildHeadlessCommandContextApi = ({
|
||||
? (currentWorkspaceMember?.timeZone ?? systemTimeZone)
|
||||
: systemTimeZone;
|
||||
|
||||
const fieldMetadataItemByIdMap = store.get(
|
||||
fieldMetadataItemByIdMapSelector.atom,
|
||||
const flattenedFieldMetadataItems = objectMetadataItems.flatMap(
|
||||
(objectMetadataItem) => objectMetadataItem.fields,
|
||||
);
|
||||
|
||||
const graphqlFilter = isDefined(objectMetadataItem)
|
||||
@@ -100,7 +99,7 @@ export const buildHeadlessCommandContextApi = ({
|
||||
contextStoreFilters: filters,
|
||||
contextStoreFilterGroups: filterGroups,
|
||||
objectMetadataItem,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
flattenedFieldMetadataItems,
|
||||
filterValueDependencies: {
|
||||
currentWorkspaceMemberId: currentWorkspaceMember?.id,
|
||||
timeZone: userTimezone,
|
||||
|
||||
+16
-9
@@ -5,7 +5,7 @@ import { contextStoreFiltersComponentState } from '@/context-store/states/contex
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
@@ -50,22 +50,29 @@ export const useFindManyRecordsSelectedInContextStore = ({
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
|
||||
const allFieldMetadataItems = objectMetadataItems.flatMap(
|
||||
(objectMetadataItem) => objectMetadataItem.fields,
|
||||
);
|
||||
|
||||
const isSoftDeleteFilterActive = contextStoreFilters.some(
|
||||
(filter) =>
|
||||
fieldMetadataItemByIdMap.get(filter.fieldMetadataId)?.name ===
|
||||
'deletedAt' && filter.operand === RecordFilterOperand.IS_NOT_EMPTY,
|
||||
);
|
||||
const isSoftDeleteFilterActive = contextStoreFilters.some((filter) => {
|
||||
const foundFieldMetadataItem = allFieldMetadataItems.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === filter.fieldMetadataId,
|
||||
);
|
||||
|
||||
return (
|
||||
foundFieldMetadataItem?.name === 'deletedAt' &&
|
||||
filter.operand === RecordFilterOperand.IS_NOT_EMPTY
|
||||
);
|
||||
});
|
||||
|
||||
const queryFilter = computeContextStoreFilters({
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
flattenedFieldMetadataItems: allFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
});
|
||||
|
||||
+2
-4
@@ -29,8 +29,7 @@ describe('computeContextStoreFilters', () => {
|
||||
contextStoreFilters: [],
|
||||
contextStoreFilterGroups: [],
|
||||
objectMetadataItem: personObjectMetadataItem,
|
||||
findFieldMetadataItemById: (id) =>
|
||||
personObjectMetadataItem.fields.find((field) => field.id === id),
|
||||
flattenedFieldMetadataItems: personObjectMetadataItem.fields,
|
||||
filterValueDependencies: mockFilterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue: '',
|
||||
});
|
||||
@@ -75,8 +74,7 @@ describe('computeContextStoreFilters', () => {
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups: [],
|
||||
objectMetadataItem: personObjectMetadataItem,
|
||||
findFieldMetadataItemById: (id) =>
|
||||
personObjectMetadataItem.fields.find((field) => field.id === id),
|
||||
flattenedFieldMetadataItems: personObjectMetadataItem.fields,
|
||||
filterValueDependencies: mockFilterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue: '',
|
||||
});
|
||||
|
||||
+12
-5
@@ -1,7 +1,9 @@
|
||||
import { type ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
import {
|
||||
type RecordFilterValueDependencies,
|
||||
@@ -9,7 +11,6 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
computeRecordGqlOperationFilter,
|
||||
type FindFieldMetadataItemById,
|
||||
turnAnyFieldFilterIntoRecordGqlFilter,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
@@ -18,7 +19,7 @@ type ComputeContextStoreFiltersProps = {
|
||||
contextStoreFilters: RecordFilter[];
|
||||
contextStoreFilterGroups: RecordFilterGroup[];
|
||||
objectMetadataItem: EnrichedObjectMetadataItem;
|
||||
findFieldMetadataItemById: FindFieldMetadataItemById;
|
||||
flattenedFieldMetadataItems: FieldMetadataItem[];
|
||||
filterValueDependencies: RecordFilterValueDependencies;
|
||||
contextStoreAnyFieldFilterValue: string;
|
||||
};
|
||||
@@ -28,7 +29,7 @@ export const computeContextStoreFilters = ({
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
findFieldMetadataItemById,
|
||||
flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
}: ComputeContextStoreFiltersProps) => {
|
||||
@@ -40,12 +41,18 @@ export const computeContextStoreFilters = ({
|
||||
fields: objectMetadataItem.fields,
|
||||
});
|
||||
|
||||
const fields = augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem?.fields ?? [],
|
||||
recordFilters: contextStoreFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
});
|
||||
|
||||
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
||||
queryFilter = makeAndFilterVariables([
|
||||
recordGqlFilterForAnyFieldFilter,
|
||||
computeRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilters: contextStoreFilters,
|
||||
recordFilterGroups: contextStoreFilterGroups,
|
||||
}),
|
||||
@@ -74,7 +81,7 @@ export const computeContextStoreFilters = ({
|
||||
},
|
||||
computeRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilters: contextStoreFilters,
|
||||
recordFilterGroups: contextStoreFilterGroups,
|
||||
}),
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
export const fieldMetadataItemByIdMapSelector = createAtomSelector<
|
||||
Map<string, FieldMetadataItem>
|
||||
>({
|
||||
key: 'fieldMetadataItemByIdMapSelector',
|
||||
get: ({ get }) =>
|
||||
new Map(
|
||||
get(flattenedFieldMetadataItemsSelector).map((field) => [
|
||||
field.id,
|
||||
field,
|
||||
]),
|
||||
),
|
||||
});
|
||||
+9
-4
@@ -1,4 +1,4 @@
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useRecordCalendarContextOrThrow } from '@/object-record/record-calendar/contexts/RecordCalendarContext';
|
||||
import { useRecordCalendarMonthDaysRange } from '@/object-record/record-calendar/month/hooks/useRecordCalendarMonthDaysRange';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
@@ -7,6 +7,7 @@ import { anyFieldFilterValueComponentState } from '@/object-record/record-filter
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -48,8 +49,8 @@ export const useRecordCalendarQueryDateRangeFilter = (
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const anyFieldFilterValue = useAtomComponentStateValue(
|
||||
@@ -110,7 +111,11 @@ export const useRecordCalendarQueryDateRangeFilter = (
|
||||
filterValueDependencies,
|
||||
recordFilters: calendarRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: calendarRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
});
|
||||
|
||||
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||
|
||||
+11
-20
@@ -22,15 +22,6 @@ const petMockObjectMetadataItem = getMockObjectMetadataItemOrThrow('pet');
|
||||
|
||||
const personMockObjectMetadataItem = getMockObjectMetadataItemOrThrow('person');
|
||||
|
||||
const findCompanyFieldById = (id: string) =>
|
||||
companyMockObjectMetadataItem.fields.find((field) => field.id === id);
|
||||
|
||||
const findPersonFieldById = (id: string) =>
|
||||
personMockObjectMetadataItem.fields.find((field) => field.id === id);
|
||||
|
||||
const findPetFieldById = (id: string) =>
|
||||
petMockObjectMetadataItem.fields.find((field) => field.id === id);
|
||||
|
||||
const mockFilterValueDependencies: RecordFilterValueDependencies = {
|
||||
currentWorkspaceMemberId: '32219445-f587-4c40-b2b1-6d3205ed96da',
|
||||
timeZone: 'Europe/Paris',
|
||||
@@ -63,7 +54,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
|
||||
filterValueDependencies: mockFilterValueDependencies,
|
||||
recordFilters: [nameFilter],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -116,7 +107,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
|
||||
filterValueDependencies: mockFilterValueDependencies,
|
||||
recordFilters: [nameFilter, employeesFilter],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -196,7 +187,7 @@ describe('should work as expected for the different field types', () => {
|
||||
addressFilterIsNotEmpty,
|
||||
],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -660,7 +651,7 @@ describe('should work as expected for the different field types', () => {
|
||||
phonesFilterIsNotEmpty,
|
||||
],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findPersonFieldById,
|
||||
fields: personMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -857,7 +848,7 @@ describe('should work as expected for the different field types', () => {
|
||||
emailsFilterIsNotEmpty,
|
||||
],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findPersonFieldById,
|
||||
fields: personMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -1069,7 +1060,7 @@ describe('should work as expected for the different field types', () => {
|
||||
dateFilterIsNotEmpty,
|
||||
],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -1171,7 +1162,7 @@ describe('should work as expected for the different field types', () => {
|
||||
employeesFilterIsNotEmpty,
|
||||
],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -1273,7 +1264,7 @@ describe('should work as expected for the different field types', () => {
|
||||
ARRFilterIsNot,
|
||||
],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -1350,7 +1341,7 @@ describe('should work as expected for the different field types', () => {
|
||||
filterValueDependencies: mockFilterValueDependencies,
|
||||
recordFilters: [ARRFilterIn, ARRFilterNotIn],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -1410,7 +1401,7 @@ describe('should work as expected for the different field types', () => {
|
||||
filterValueDependencies: mockFilterValueDependencies,
|
||||
recordFilters: [selectFilterIs, selectFilterIsNot],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findPetFieldById,
|
||||
fields: petMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -1483,7 +1474,7 @@ describe('should work as expected for the different field types', () => {
|
||||
multiSelectFilterDoesNotContain,
|
||||
],
|
||||
recordFilterGroups: [],
|
||||
findFieldMetadataItemById: findCompanyFieldById,
|
||||
fields: companyMockObjectMetadataItem.fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
// Relation-traversal filters reference target fields that live on a
|
||||
// different object than the one being queried, so they aren't in the
|
||||
// source object's own field list. The shared GraphQL filter dispatcher
|
||||
// looks the target up by id in `fieldMetadataItems` and drops the filter
|
||||
// when it can't find it — this helper merges the resolved target fields
|
||||
// in from the workspace-wide flat list so callers don't silently lose
|
||||
// relation-traversal filters.
|
||||
export const augmentFieldsWithRelationTargets = ({
|
||||
baseFields,
|
||||
recordFilters,
|
||||
allFieldMetadataItems,
|
||||
}: {
|
||||
baseFields: FieldMetadataItem[];
|
||||
recordFilters: Pick<RecordFilter, 'relationTargetFieldMetadataId'>[];
|
||||
allFieldMetadataItems: FieldMetadataItem[];
|
||||
}): FieldMetadataItem[] => {
|
||||
const targetFieldIds = new Set(
|
||||
recordFilters
|
||||
.map((filter) => filter.relationTargetFieldMetadataId)
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
if (targetFieldIds.size === 0) {
|
||||
return baseFields;
|
||||
}
|
||||
|
||||
const baseFieldIds = new Set(baseFields.map((field) => field.id));
|
||||
const additionalTargetFields = allFieldMetadataItems.filter(
|
||||
(field) => targetFieldIds.has(field.id) && !baseFieldIds.has(field.id),
|
||||
);
|
||||
|
||||
if (additionalTargetFields.length === 0) {
|
||||
return baseFields;
|
||||
}
|
||||
|
||||
return [...baseFields, ...additionalTargetFields];
|
||||
};
|
||||
+4
-4
@@ -6,7 +6,7 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
@@ -54,8 +54,8 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const computedFilter = computeContextStoreFilters({
|
||||
@@ -63,7 +63,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
});
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@ import { contextStoreFilterGroupsComponentState } from '@/context-store/states/c
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
|
||||
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize';
|
||||
@@ -89,8 +89,8 @@ export const useRecordIndexLazyFetchRecords = ({
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const findManyRecordsParams = useFindManyRecordIndexTableParams(
|
||||
@@ -107,7 +107,7 @@ export const useRecordIndexLazyFetchRecords = ({
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
});
|
||||
|
||||
+9
-4
@@ -1,11 +1,12 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
@@ -49,12 +50,16 @@ export const useFindManyRecordIndexTableParams = (
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const currentFilters = computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem?.fields ?? [],
|
||||
recordFilters: currentRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
recordFilters: currentRecordFilters,
|
||||
filterValueDependencies,
|
||||
|
||||
+9
-4
@@ -1,11 +1,12 @@
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useRelevantRecordsGqlFields } from '@/object-record/record-field/hooks/useRelevantRecordsGqlFields';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { recordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/selectors/recordGroupDefinitionsComponentSelector';
|
||||
import { computeRecordGroupOptionsFilter } from '@/object-record/record-group/utils/computeRecordGroupOptionsFilter';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
@@ -38,15 +39,19 @@ export const useRecordIndexGroupCommonQueryVariables = () => {
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const requestFilters = computeRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: currentRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useAtomComponentStateValue(
|
||||
|
||||
+9
-4
@@ -1,5 +1,5 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
|
||||
@@ -9,6 +9,7 @@ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { useAggregateGqlFieldsFromRecordIndexGroupAggregates } from '@/object-record/record-index/hooks/useAggregateGqlFieldsFromRecordIndexGroupAggregates';
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { buildGroupByFieldObject } from '@/page-layout/widgets/graph/utils/buildGroupByFieldObject';
|
||||
@@ -48,15 +49,19 @@ export const useRecordIndexGroupsAggregatesGroupBy = ({
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const requestFilters = computeRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: currentRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
});
|
||||
|
||||
const { recordAggregateGqlField } =
|
||||
|
||||
+10
-5
@@ -1,10 +1,11 @@
|
||||
import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent';
|
||||
import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { recordIndexHasRecordsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexHasRecordsComponentSelector';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
@@ -40,8 +41,8 @@ export const RecordTableEmptyHasNewRecordEffect = () => {
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const currentRecordFilters = useAtomComponentStateValue(
|
||||
@@ -63,7 +64,11 @@ export const RecordTableEmptyHasNewRecordEffect = () => {
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {
|
||||
filter: computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: currentRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
@@ -77,7 +82,7 @@ export const RecordTableEmptyHasNewRecordEffect = () => {
|
||||
currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
currentRecordSorts,
|
||||
fieldMetadataItemByIdMap,
|
||||
flattenedFieldMetadataItems,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+9
-4
@@ -1,4 +1,4 @@
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { transformAggregateRawValueIntoAggregateDisplayValue } from '@/object-record/record-aggregate/utils/transformAggregateRawValueIntoAggregateDisplayValue';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
@@ -7,6 +7,7 @@ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
import { getRecordAggregateDisplayLabel } from '@/object-record/record-index/utils/getRecordndexAggregateDisplayLabel';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
@@ -46,14 +47,18 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
||||
|
||||
const dateLocale = useAtomStateValue(dateLocaleState);
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const requestFilters = computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: currentRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
filterValueDependencies,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
recordFilters: currentRecordFilters,
|
||||
|
||||
+10
-5
@@ -1,10 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
|
||||
@@ -16,8 +17,8 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => {
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const currentRecordFilters = useAtomComponentStateValue(
|
||||
@@ -39,7 +40,11 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => {
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {
|
||||
filter: computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: currentRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
@@ -53,7 +58,7 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => {
|
||||
currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
currentRecordSorts,
|
||||
fieldMetadataItemByIdMap,
|
||||
flattenedFieldMetadataItems,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+4
-4
@@ -4,7 +4,7 @@ import { contextStoreFilterGroupsComponentState } from '@/context-store/states/c
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useIncrementalUpdateManyRecords } from '@/object-record/hooks/useIncrementalUpdateManyRecords';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
@@ -45,8 +45,8 @@ export const useUpdateMultipleRecordsActions = ({
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const graphqlFilter = computeContextStoreFilters({
|
||||
@@ -54,7 +54,7 @@ export const useUpdateMultipleRecordsActions = ({
|
||||
contextStoreFilters,
|
||||
contextStoreFilterGroups,
|
||||
objectMetadataItem,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
});
|
||||
|
||||
+9
-4
@@ -1,6 +1,7 @@
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import {
|
||||
computeRecordGqlOperationFilter,
|
||||
@@ -40,14 +41,18 @@ export const useGraphWidgetQueryCommon = ({
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const widgetRecordFilters = configuration.filter?.recordFilters ?? [];
|
||||
|
||||
const gqlOperationFilter = computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: widgetRecordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
filterValueDependencies,
|
||||
recordFilters: widgetRecordFilters,
|
||||
recordFilterGroups: configuration.filter?.recordFilterGroups ?? [],
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { useContext, useId, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { styled } from '@linaria/react';
|
||||
import { AppTooltip, TooltipDelay, TooltipPosition } from 'twenty-ui/display';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
import { isDefined, formatToShortNumber } from 'twenty-shared/utils';
|
||||
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
|
||||
@@ -13,13 +10,10 @@ import {
|
||||
} from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
|
||||
import { EllipsisDisplay } from '@/ui/field/display/components/EllipsisDisplay';
|
||||
import { isDefined, formatToShortNumber } from 'twenty-shared/utils';
|
||||
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
const StyledCurrencyIconContainer = styled.span`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
type CurrencyDisplayProps = {
|
||||
currencyValue: FieldCurrencyValue | null | undefined;
|
||||
@@ -63,7 +57,7 @@ export const CurrencyDisplay = ({
|
||||
<EllipsisDisplay>
|
||||
{shouldShowCurrencyTooltip && (
|
||||
<>
|
||||
<StyledCurrencyIconContainer
|
||||
<span
|
||||
id={tooltipAnchorId}
|
||||
onMouseEnter={() => setShouldRenderTooltip(true)}
|
||||
onMouseLeave={() => setShouldRenderTooltip(false)}
|
||||
@@ -73,7 +67,7 @@ export const CurrencyDisplay = ({
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledCurrencyIconContainer>{' '}
|
||||
</span>{' '}
|
||||
</>
|
||||
)}
|
||||
{amountToDisplay !== null
|
||||
|
||||
+9
-4
@@ -1,10 +1,11 @@
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -27,8 +28,8 @@ export const useGetRecordIndexTotalCount = () => {
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const recordGroupsVisibilityFilter = useGetViewGroupsFilters();
|
||||
@@ -37,7 +38,11 @@ export const useGetRecordIndexTotalCount = () => {
|
||||
filterValueDependencies,
|
||||
recordFilters: [...currentRecordFilters, ...recordGroupsVisibilityFilter],
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem.fields,
|
||||
recordFilters: [...currentRecordFilters, ...recordGroupsVisibilityFilter],
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useAtomComponentStateValue(
|
||||
|
||||
@@ -213,8 +213,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
viewFilterGroupId: viewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
|
||||
subFieldName: viewFilter.subFieldName ?? null,
|
||||
relationTargetFieldMetadataId:
|
||||
viewFilter.relationTargetFieldMetadataId ?? null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { fieldMetadataItemByIdMapSelector } from '@/object-metadata/states/fieldMetadataItemByIdMapSelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
@@ -15,8 +15,8 @@ export const useQueryVariablesFromParentView = ({
|
||||
}) => {
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const fieldMetadataItemByIdMap = useAtomStateValue(
|
||||
fieldMetadataItemByIdMapSelector,
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const contextStoreRecordShowParentView = useAtomComponentStateValue(
|
||||
@@ -33,7 +33,7 @@ export const useQueryVariablesFromParentView = ({
|
||||
recordSorts: contextStoreRecordShowParentView?.parentViewSorts ?? [],
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
findFieldMetadataItemById: (id) => fieldMetadataItemByIdMap.get(id),
|
||||
flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
});
|
||||
|
||||
|
||||
+10
-7
@@ -1,13 +1,12 @@
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { augmentFieldsWithRelationTargets } from '@/object-record/record-filter/utils/augmentFieldsWithRelationTargets';
|
||||
import { type RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
||||
import { type RecordFilterValueDependencies } from 'twenty-shared/types';
|
||||
import {
|
||||
computeRecordGqlOperationFilter,
|
||||
type FindFieldMetadataItemById,
|
||||
} from 'twenty-shared/utils';
|
||||
import { computeRecordGqlOperationFilter } from 'twenty-shared/utils';
|
||||
|
||||
export const getQueryVariablesFromFiltersAndSorts = ({
|
||||
recordFilterGroups,
|
||||
@@ -15,7 +14,7 @@ export const getQueryVariablesFromFiltersAndSorts = ({
|
||||
recordSorts,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems = [],
|
||||
findFieldMetadataItemById,
|
||||
flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
}: {
|
||||
recordFilterGroups: RecordFilterGroup[];
|
||||
@@ -23,11 +22,15 @@ export const getQueryVariablesFromFiltersAndSorts = ({
|
||||
recordSorts: RecordSort[];
|
||||
objectMetadataItem: EnrichedObjectMetadataItem;
|
||||
objectMetadataItems?: EnrichedObjectMetadataItem[];
|
||||
findFieldMetadataItemById: FindFieldMetadataItemById;
|
||||
flattenedFieldMetadataItems: FieldMetadataItem[];
|
||||
filterValueDependencies: RecordFilterValueDependencies;
|
||||
}) => {
|
||||
const filter = computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById,
|
||||
fields: augmentFieldsWithRelationTargets({
|
||||
baseFields: objectMetadataItem?.fields ?? [],
|
||||
recordFilters,
|
||||
allFieldMetadataItems: flattenedFieldMetadataItems,
|
||||
}),
|
||||
filterValueDependencies,
|
||||
recordFilterGroups,
|
||||
recordFilters,
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
@RegisteredInstanceCommand('2.4.0', 1747234400000)
|
||||
export class AddRelationTargetFieldMetadataIdToViewFilterEarly2_4FastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFilter" ADD COLUMN IF NOT EXISTS "relationTargetFieldMetadataId" uuid`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFilter" DROP COLUMN IF EXISTS "relationTargetFieldMetadataId"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
@RegisteredInstanceCommand('2.5.0', 1747234500000)
|
||||
export class AddRelationTargetFieldMetadataIdToViewFilterEarly2_5FastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFilter" ADD COLUMN IF NOT EXISTS "relationTargetFieldMetadataId" uuid`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFilter" DROP COLUMN IF EXISTS "relationTargetFieldMetadataId"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -22,6 +22,7 @@ import { AddCacheTokensToAgentChatThreadFastInstanceCommand } from 'src/database
|
||||
import { AddLogoToApplicationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-2/2-2-instance-command-fast-1777539664664-add-logo-to-application';
|
||||
import { AddSubFieldNameToViewSortEarlyFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1747234200000-add-sub-field-name-to-view-sort';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarlyFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1747234300000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarly2_5FastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1747234500000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddUpgradeMigrationWorkspaceIdIndexFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777308014234-add-upgrade-migration-workspace-id-index';
|
||||
import { AddDeletedAtToAgentChatThreadFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777682000000-add-deleted-at-to-agent-chat-thread';
|
||||
import { ConnectionProviderSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777896012579-connection-provider-syncable-entity';
|
||||
@@ -31,6 +32,7 @@ import { AddToolAndWorkflowActionTriggerSettingsFastInstanceCommand } from 'src/
|
||||
import { BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1777966965588-backfill-application-variable-universal-identifier';
|
||||
import { MigrateToolTriggerSettingsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1797000002000-migrate-tool-trigger-settings';
|
||||
import { AddMetadataToBillingPriceFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1777100000000-add-metadata-to-billing-price';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarly2_4FastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1747234400000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddEmailGroupChannelTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1778256809018-add-email-group-channel-type';
|
||||
import { AddApplicationIdToPublicDomainFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1798000003000-add-application-id-to-public-domain';
|
||||
import { AddIsInternalMessagesImportEnabledFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778525104406-add-is-internal-messages-import-enabled';
|
||||
@@ -63,11 +65,13 @@ export const INSTANCE_COMMANDS = [
|
||||
AddConditionalAvailabilityExpressionToPageLayoutWidgetFastInstanceCommand,
|
||||
AddSubFieldNameToViewSortEarlyFastInstanceCommand,
|
||||
AddRelationTargetFieldMetadataIdToViewFilterEarlyFastInstanceCommand,
|
||||
AddRelationTargetFieldMetadataIdToViewFilterEarly2_5FastInstanceCommand,
|
||||
AddUpgradeMigrationWorkspaceIdIndexFastInstanceCommand,
|
||||
AddIsPreInstalledToApplicationRegistrationFastInstanceCommand,
|
||||
AddProviderExecutedToAgentMessagePartFastInstanceCommand,
|
||||
BackfillPageLayoutWidgetPositionSlowInstanceCommand,
|
||||
AddMetadataToBillingPriceFastInstanceCommand,
|
||||
AddRelationTargetFieldMetadataIdToViewFilterEarly2_4FastInstanceCommand,
|
||||
AddCacheTokensToAgentChatThreadFastInstanceCommand,
|
||||
AddLogoToApplicationFastInstanceCommand,
|
||||
AddDeletedAtToAgentChatThreadFastInstanceCommand,
|
||||
|
||||
+28
-5
@@ -256,14 +256,37 @@ export class CommonGroupByQueryRunnerService extends CommonBaseQueryRunnerServic
|
||||
options: field.options as PartialFieldMetadataItemOption[],
|
||||
}));
|
||||
|
||||
// Relation-traversal filters reference target fields on related objects
|
||||
// that aren't in the source object's field list above. Resolve and add
|
||||
// them so the shared dispatcher can look them up by id.
|
||||
const relationTargetFieldsFromFilters = recordFilters
|
||||
.map((filter) => filter.relationTargetFieldMetadataId)
|
||||
.filter(isDefined)
|
||||
.filter((id) => !fields.some((field) => field.id === id))
|
||||
.map((id) => {
|
||||
const field = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!field) return null;
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
options: field.options as PartialFieldMetadataItemOption[],
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
fields.push(...relationTargetFieldsFromFilters);
|
||||
|
||||
const filtersFromView = computeRecordGqlOperationFilter({
|
||||
recordFilters,
|
||||
recordFilterGroups: recordFilterGroups,
|
||||
findFieldMetadataItemById: (id) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
}),
|
||||
fields,
|
||||
filterValueDependencies: {
|
||||
timeZone: 'UTC', // TODO: see if we use workspace member timezone here
|
||||
},
|
||||
|
||||
+3
-36
@@ -259,18 +259,7 @@ describe('computeSchemaComponents', () => {
|
||||
"properties": {
|
||||
"additionalPhones": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"callingCode": {
|
||||
"type": "string",
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string",
|
||||
},
|
||||
"number": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
"type": "string",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
@@ -502,18 +491,7 @@ describe('computeSchemaComponents', () => {
|
||||
"properties": {
|
||||
"additionalPhones": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"callingCode": {
|
||||
"type": "string",
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string",
|
||||
},
|
||||
"number": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
"type": "string",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
@@ -774,18 +752,7 @@ describe('computeSchemaComponents', () => {
|
||||
"properties": {
|
||||
"additionalPhones": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"callingCode": {
|
||||
"type": "string",
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string",
|
||||
},
|
||||
"number": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
"type": "string",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@
|
||||
* |___/
|
||||
*/
|
||||
|
||||
export const TWENTY_CURRENT_VERSION = '2.6.0' as const;
|
||||
export const TWENTY_CURRENT_VERSION = '2.5.0' as const;
|
||||
|
||||
+1
-3
@@ -7,6 +7,4 @@
|
||||
* |___/
|
||||
*/
|
||||
|
||||
export const TWENTY_NEXT_VERSIONS = [
|
||||
'2.7.0',
|
||||
] as const;
|
||||
export const TWENTY_NEXT_VERSIONS = ['2.6.0'] as const;
|
||||
|
||||
-1
@@ -16,5 +16,4 @@ export const TWENTY_PREVIOUS_VERSIONS = [
|
||||
'2.2.0',
|
||||
'2.3.0',
|
||||
'2.4.0',
|
||||
'2.5.0',
|
||||
] as const;
|
||||
|
||||
+32
-5
@@ -107,12 +107,39 @@ export class ViewQueryParamsService {
|
||||
: RecordFilterGroupLogicalOperator.AND,
|
||||
}));
|
||||
|
||||
const filter = computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
const filterFieldMetadataIds = recordFilters.flatMap((filter) =>
|
||||
isDefined(filter.relationTargetFieldMetadataId)
|
||||
? [filter.fieldMetadataId, filter.relationTargetFieldMetadataId]
|
||||
: [filter.fieldMetadataId],
|
||||
);
|
||||
|
||||
const fields = filterFieldMetadataIds
|
||||
.map((fieldMetadataId) => {
|
||||
const field = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: fieldMetadataId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!field) return null;
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
options: field.options?.map((opt) => ({
|
||||
id: opt.id ?? '',
|
||||
label: opt.label,
|
||||
value: opt.value,
|
||||
color: 'color' in opt ? opt.color : undefined,
|
||||
position: opt.position,
|
||||
})),
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
const filter = computeRecordGqlOperationFilter({
|
||||
fields,
|
||||
recordFilters,
|
||||
recordFilterGroups,
|
||||
filterValueDependencies: { currentWorkspaceMemberId, timeZone },
|
||||
|
||||
+18
-5
@@ -5,6 +5,7 @@ import {
|
||||
RecordFilterGroupLogicalOperator,
|
||||
RowLevelPermissionPredicateGroupLogicalOperator,
|
||||
type CompositeFieldSubFieldName,
|
||||
type PartialFieldMetadataItemOption,
|
||||
type RecordGqlOperationFilter,
|
||||
type RowLevelPermissionPredicateValue,
|
||||
} from 'twenty-shared/types';
|
||||
@@ -215,14 +216,26 @@ export const buildRowLevelPermissionRecordFilter = ({
|
||||
predicateGroup.parentRowLevelPermissionPredicateGroupId,
|
||||
}));
|
||||
|
||||
const fieldMetadataItems = predicates
|
||||
.map((predicate) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: predicate.fieldMetadataId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
}),
|
||||
)
|
||||
.filter(isDefined)
|
||||
.map((field) => ({
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
options: field.options as PartialFieldMetadataItemOption[],
|
||||
}));
|
||||
|
||||
return computeRecordGqlOperationFilter({
|
||||
recordFilters,
|
||||
recordFilterGroups,
|
||||
findFieldMetadataItemById: (id) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
}),
|
||||
fields: fieldMetadataItems,
|
||||
filterValueDependencies: {
|
||||
currentWorkspaceMemberId: workspaceMember?.id,
|
||||
},
|
||||
|
||||
+1
-12
@@ -308,18 +308,7 @@ export const convertObjectMetadataToSchemaProperties = ({
|
||||
additionalPhones: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: {
|
||||
type: 'string',
|
||||
},
|
||||
countryCode: {
|
||||
type: 'string',
|
||||
},
|
||||
callingCode: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
primaryPhoneCountryCode: {
|
||||
|
||||
+1
@@ -93,6 +93,7 @@ export class ChartDataQueryService {
|
||||
}: ExecuteGroupByQueryParams): Promise<GroupByRawResult[]> {
|
||||
const gqlOperationFilter = convertChartFilterToGqlOperationFilter({
|
||||
filter,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
userTimezone,
|
||||
});
|
||||
|
||||
+33
-7
@@ -2,6 +2,7 @@ import {
|
||||
type ChartFilter,
|
||||
type CompositeFieldSubFieldName,
|
||||
type FilterableAndTSVectorFieldType,
|
||||
type PartialFieldMetadataItem,
|
||||
type RecordFilterGroupLogicalOperator,
|
||||
type ViewFilterOperand,
|
||||
} from 'twenty-shared/types';
|
||||
@@ -17,15 +18,18 @@ import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
type ConvertChartFilterToGqlOperationFilterParams = {
|
||||
filter: ChartFilter | undefined;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
userTimezone: string;
|
||||
};
|
||||
|
||||
export const convertChartFilterToGqlOperationFilter = ({
|
||||
filter,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
userTimezone,
|
||||
}: ConvertChartFilterToGqlOperationFilterParams): ObjectRecordFilter => {
|
||||
@@ -40,6 +44,34 @@ export const convertChartFilterToGqlOperationFilter = ({
|
||||
return {};
|
||||
}
|
||||
|
||||
const fieldIds = flatObjectMetadata.fieldIds ?? [];
|
||||
const fields: PartialFieldMetadataItem[] = fieldIds
|
||||
.map((fieldId: string) => {
|
||||
const field = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: fieldId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(field)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
options: field.options?.map((opt) => ({
|
||||
id: opt.id ?? '',
|
||||
label: opt.label,
|
||||
value: opt.value,
|
||||
color: 'color' in opt ? opt.color : undefined,
|
||||
position: opt.position,
|
||||
})),
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
const convertedRecordFilters: Omit<RecordFilter, 'id'>[] = recordFilters.map(
|
||||
(recordFilter) => {
|
||||
const field = findFlatEntityByIdInFlatEntityMaps({
|
||||
@@ -58,8 +90,6 @@ export const convertChartFilterToGqlOperationFilter = ({
|
||||
subFieldName: (recordFilter.subFieldName ?? undefined) as
|
||||
| CompositeFieldSubFieldName
|
||||
| undefined,
|
||||
relationTargetFieldMetadataId:
|
||||
recordFilter.relationTargetFieldMetadataId ?? null,
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -74,11 +104,7 @@ export const convertChartFilterToGqlOperationFilter = ({
|
||||
}));
|
||||
|
||||
return computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
}),
|
||||
fields,
|
||||
recordFilters: convertedRecordFilters,
|
||||
recordFilterGroups: convertedRecordFilterGroups,
|
||||
filterValueDependencies: {
|
||||
|
||||
+33
-6
@@ -1,7 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
type FieldMetadataComplexOption,
|
||||
type FieldMetadataDefaultOption,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
computeRecordGqlOperationFilter,
|
||||
isDefined,
|
||||
isRecordFilterValueValid,
|
||||
resolveInput,
|
||||
} from 'twenty-shared/utils';
|
||||
@@ -58,12 +63,38 @@ export class FindRecordsWorkflowAction implements WorkflowAction {
|
||||
const executionContext =
|
||||
await this.workflowExecutionContextService.getExecutionContext(runInfo);
|
||||
|
||||
const { flatFieldMetadataMaps } =
|
||||
const { flatObjectMetadata, flatFieldMetadataMaps } =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
workflowActionInput.objectName,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const fields = flatObjectMetadata.fieldIds
|
||||
.map((fieldId) => {
|
||||
const field = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: fieldId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
// Note: force cast is required until we deprecate the CreateFieldInput and UpdateFieldInput
|
||||
// type derivation from the FieldMetadataDto
|
||||
options: field.options as
|
||||
| (FieldMetadataDefaultOption & { id: string })[]
|
||||
| (FieldMetadataComplexOption & { id: string })[]
|
||||
| null,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
if (workflowActionInput.filter?.recordFilters) {
|
||||
for (const filter of workflowActionInput.filter.recordFilters) {
|
||||
if (!isRecordFilterValueValid(filter)) {
|
||||
@@ -79,11 +110,7 @@ export class FindRecordsWorkflowAction implements WorkflowAction {
|
||||
workflowActionInput.filter?.recordFilters &&
|
||||
workflowActionInput.filter?.recordFilterGroups
|
||||
? computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
}),
|
||||
fields,
|
||||
recordFilters: workflowActionInput.filter.recordFilters,
|
||||
recordFilterGroups: workflowActionInput.filter.recordFilterGroups,
|
||||
filterValueDependencies: {
|
||||
|
||||
-230
@@ -1,230 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
import request from 'supertest';
|
||||
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
|
||||
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const client = request(`http://localhost:${APP_PORT}`);
|
||||
|
||||
const TEST_COMPANY_AIRBNB_ID = '20202020-aaaa-4000-8000-000000000001';
|
||||
const TEST_COMPANY_STRIPE_ID = '20202020-aaaa-4000-8000-000000000002';
|
||||
const TEST_PERSON_AIRBNB_1_ID = '20202020-bbbb-4000-8000-000000000001';
|
||||
const TEST_PERSON_AIRBNB_2_ID = '20202020-bbbb-4000-8000-000000000002';
|
||||
const TEST_PERSON_STRIPE_1_ID = '20202020-bbbb-4000-8000-000000000003';
|
||||
const SHARED_JOB_TITLE = 'chart-test-relation-traversal';
|
||||
const ALL_TEST_PERSON_IDS = [
|
||||
TEST_PERSON_AIRBNB_1_ID,
|
||||
TEST_PERSON_AIRBNB_2_ID,
|
||||
TEST_PERSON_STRIPE_1_ID,
|
||||
];
|
||||
const ALL_TEST_COMPANY_IDS = [TEST_COMPANY_AIRBNB_ID, TEST_COMPANY_STRIPE_ID];
|
||||
|
||||
describe('BarChartData with relation-traversal filter (e2e)', () => {
|
||||
let personObjectMetadataId: string | null = null;
|
||||
let personIdFieldMetadataId: string | null = null;
|
||||
let personJobTitleFieldMetadataId: string | null = null;
|
||||
let personCompanyFieldMetadataId: string | null = null;
|
||||
let companyNameFieldMetadataId: string | null = null;
|
||||
|
||||
const lookupMetadataIds = async () => {
|
||||
const objectsResponse = await makeMetadataAPIRequest({
|
||||
query: gql`
|
||||
query Objects($filter: ObjectFilter!, $paging: CursorPaging!) {
|
||||
objects(filter: $filter, paging: $paging) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { paging: { first: 1000 }, filter: {} },
|
||||
});
|
||||
|
||||
expect(objectsResponse.body.errors).toBeUndefined();
|
||||
|
||||
const objects: Array<{
|
||||
id: string;
|
||||
nameSingular: string;
|
||||
fieldsList: Array<{ id: string; name: string }>;
|
||||
}> = objectsResponse.body.data.objects.edges.map(
|
||||
(edge: { node: unknown }) => edge.node,
|
||||
);
|
||||
|
||||
const personObject = objects.find((o) => o.nameSingular === 'person');
|
||||
const companyObject = objects.find((o) => o.nameSingular === 'company');
|
||||
|
||||
personObjectMetadataId = personObject?.id ?? null;
|
||||
personIdFieldMetadataId =
|
||||
personObject?.fieldsList.find((f) => f.name === 'id')?.id ?? null;
|
||||
personJobTitleFieldMetadataId =
|
||||
personObject?.fieldsList.find((f) => f.name === 'jobTitle')?.id ?? null;
|
||||
personCompanyFieldMetadataId =
|
||||
personObject?.fieldsList.find((f) => f.name === 'company')?.id ?? null;
|
||||
companyNameFieldMetadataId =
|
||||
companyObject?.fieldsList.find((f) => f.name === 'name')?.id ?? null;
|
||||
|
||||
if (
|
||||
!personObjectMetadataId ||
|
||||
!personIdFieldMetadataId ||
|
||||
!personJobTitleFieldMetadataId ||
|
||||
!personCompanyFieldMetadataId ||
|
||||
!companyNameFieldMetadataId
|
||||
) {
|
||||
throw new Error('Failed to resolve required metadata ids for chart test');
|
||||
}
|
||||
};
|
||||
|
||||
const seedTestRecords = async () => {
|
||||
await makeGraphqlAPIRequest(
|
||||
createManyOperationFactory({
|
||||
objectMetadataSingularName: 'company',
|
||||
objectMetadataPluralName: 'companies',
|
||||
gqlFields: 'id',
|
||||
data: [
|
||||
{ id: TEST_COMPANY_AIRBNB_ID, name: 'AirbnbChartTest' },
|
||||
{ id: TEST_COMPANY_STRIPE_ID, name: 'StripeChartTest' },
|
||||
],
|
||||
upsert: true,
|
||||
}),
|
||||
);
|
||||
|
||||
await makeGraphqlAPIRequest(
|
||||
createManyOperationFactory({
|
||||
objectMetadataSingularName: 'person',
|
||||
objectMetadataPluralName: 'people',
|
||||
gqlFields: 'id',
|
||||
data: [
|
||||
{
|
||||
id: TEST_PERSON_AIRBNB_1_ID,
|
||||
companyId: TEST_COMPANY_AIRBNB_ID,
|
||||
jobTitle: SHARED_JOB_TITLE,
|
||||
},
|
||||
{
|
||||
id: TEST_PERSON_AIRBNB_2_ID,
|
||||
companyId: TEST_COMPANY_AIRBNB_ID,
|
||||
jobTitle: SHARED_JOB_TITLE,
|
||||
},
|
||||
{
|
||||
id: TEST_PERSON_STRIPE_1_ID,
|
||||
companyId: TEST_COMPANY_STRIPE_ID,
|
||||
jobTitle: SHARED_JOB_TITLE,
|
||||
},
|
||||
],
|
||||
upsert: true,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const queryBarChartCount = async (extraRecordFilters: object[] = []) => {
|
||||
const filterGroupId = uuidv4();
|
||||
const allRecordFilters = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'TEXT',
|
||||
operand: 'CONTAINS',
|
||||
value: SHARED_JOB_TITLE,
|
||||
fieldMetadataId: personJobTitleFieldMetadataId,
|
||||
recordFilterGroupId: filterGroupId,
|
||||
},
|
||||
...extraRecordFilters.map((filter) => ({
|
||||
...filter,
|
||||
recordFilterGroupId: filterGroupId,
|
||||
})),
|
||||
];
|
||||
|
||||
const response = await client
|
||||
.post('/metadata')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query BarChartData($input: BarChartDataInput!) {
|
||||
barChartData(input: $input) {
|
||||
data
|
||||
indexBy
|
||||
keys
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
objectMetadataId: personObjectMetadataId,
|
||||
configuration: {
|
||||
configurationType: 'BAR_CHART',
|
||||
layout: 'VERTICAL',
|
||||
aggregateFieldMetadataId: personIdFieldMetadataId,
|
||||
aggregateOperation: 'COUNT',
|
||||
primaryAxisGroupByFieldMetadataId: personJobTitleFieldMetadataId,
|
||||
primaryAxisOrderBy: 'VALUE_DESC',
|
||||
filter: {
|
||||
recordFilters: allRecordFilters,
|
||||
recordFilterGroups: [
|
||||
{ id: filterGroupId, logicalOperator: 'AND' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.body.errors).toBeUndefined();
|
||||
const data: Array<Record<string, string | number>> =
|
||||
response.body.data.barChartData.data;
|
||||
const row = data.find((entry) => entry.jobTitle === SHARED_JOB_TITLE);
|
||||
|
||||
return typeof row?.id === 'number' ? row.id : 0;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await lookupMetadataIds();
|
||||
await seedTestRecords();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await makeGraphqlAPIRequest(
|
||||
deleteManyOperationFactory({
|
||||
objectMetadataSingularName: 'person',
|
||||
objectMetadataPluralName: 'people',
|
||||
gqlFields: 'id',
|
||||
filter: { id: { in: ALL_TEST_PERSON_IDS } },
|
||||
}),
|
||||
);
|
||||
await makeGraphqlAPIRequest(
|
||||
deleteManyOperationFactory({
|
||||
objectMetadataSingularName: 'company',
|
||||
objectMetadataPluralName: 'companies',
|
||||
gqlFields: 'id',
|
||||
filter: { id: { in: ALL_TEST_COMPANY_IDS } },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should count all 3 test people without a relation-traversal filter', async () => {
|
||||
const count = await queryBarChartCount();
|
||||
|
||||
expect(count).toBe(3);
|
||||
});
|
||||
|
||||
it('should apply a one-hop relation-traversal filter and only count Airbnb people', async () => {
|
||||
const count = await queryBarChartCount([
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'TEXT',
|
||||
operand: 'CONTAINS',
|
||||
value: 'AirbnbChartTest',
|
||||
fieldMetadataId: personCompanyFieldMetadataId,
|
||||
relationTargetFieldMetadataId: companyNameFieldMetadataId,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
});
|
||||
-348
@@ -1,348 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
import request from 'supertest';
|
||||
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
|
||||
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import {
|
||||
destroyWorkflowRun,
|
||||
runWorkflowVersion,
|
||||
waitForWorkflowCompletion,
|
||||
} from 'test/integration/graphql/suites/workflow/utils/workflow-run-test.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const client = request(`http://localhost:${APP_PORT}`);
|
||||
|
||||
const TEST_COMPANY_AIRBNB_ID = '20202020-eeee-4000-8000-000000000001';
|
||||
const TEST_COMPANY_STRIPE_ID = '20202020-eeee-4000-8000-000000000002';
|
||||
const TEST_PERSON_AIRBNB_1_ID = '20202020-ffff-4000-8000-000000000001';
|
||||
const TEST_PERSON_AIRBNB_2_ID = '20202020-ffff-4000-8000-000000000002';
|
||||
const TEST_PERSON_STRIPE_1_ID = '20202020-ffff-4000-8000-000000000003';
|
||||
const ALL_TEST_PERSON_IDS = [
|
||||
TEST_PERSON_AIRBNB_1_ID,
|
||||
TEST_PERSON_AIRBNB_2_ID,
|
||||
TEST_PERSON_STRIPE_1_ID,
|
||||
];
|
||||
const ALL_TEST_COMPANY_IDS = [TEST_COMPANY_AIRBNB_ID, TEST_COMPANY_STRIPE_ID];
|
||||
|
||||
describe('FindRecords workflow action with relation-traversal filter (e2e)', () => {
|
||||
let createdWorkflowId: string | null = null;
|
||||
let createdWorkflowVersionId: string | null = null;
|
||||
let findRecordsStepId: string | null = null;
|
||||
let createdWorkflowRunId: string | null = null;
|
||||
let personCompanyFieldMetadataId: string | null = null;
|
||||
let companyNameFieldMetadataId: string | null = null;
|
||||
|
||||
const lookupFieldMetadataIds = async () => {
|
||||
const objectsResponse = await makeMetadataAPIRequest({
|
||||
query: gql`
|
||||
query Objects($filter: ObjectFilter!, $paging: CursorPaging!) {
|
||||
objects(filter: $filter, paging: $paging) {
|
||||
edges {
|
||||
node {
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { paging: { first: 1000 }, filter: {} },
|
||||
});
|
||||
|
||||
expect(objectsResponse.body.errors).toBeUndefined();
|
||||
|
||||
const objects: Array<{
|
||||
nameSingular: string;
|
||||
fieldsList: Array<{ id: string; name: string }>;
|
||||
}> = objectsResponse.body.data.objects.edges.map(
|
||||
(edge: { node: unknown }) => edge.node,
|
||||
);
|
||||
|
||||
const personObject = objects.find((o) => o.nameSingular === 'person');
|
||||
const companyObject = objects.find((o) => o.nameSingular === 'company');
|
||||
|
||||
personCompanyFieldMetadataId =
|
||||
personObject?.fieldsList.find((f) => f.name === 'company')?.id ?? null;
|
||||
companyNameFieldMetadataId =
|
||||
companyObject?.fieldsList.find((f) => f.name === 'name')?.id ?? null;
|
||||
|
||||
if (!personCompanyFieldMetadataId || !companyNameFieldMetadataId) {
|
||||
throw new Error(
|
||||
`Could not resolve required field metadata ids — person.company=${personCompanyFieldMetadataId}, company.name=${companyNameFieldMetadataId}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const seedTestRecords = async () => {
|
||||
await makeGraphqlAPIRequest(
|
||||
createManyOperationFactory({
|
||||
objectMetadataSingularName: 'company',
|
||||
objectMetadataPluralName: 'companies',
|
||||
gqlFields: 'id',
|
||||
data: [
|
||||
{ id: TEST_COMPANY_AIRBNB_ID, name: 'AirbnbWorkflowTest' },
|
||||
{ id: TEST_COMPANY_STRIPE_ID, name: 'StripeWorkflowTest' },
|
||||
],
|
||||
upsert: true,
|
||||
}),
|
||||
);
|
||||
|
||||
await makeGraphqlAPIRequest(
|
||||
createManyOperationFactory({
|
||||
objectMetadataSingularName: 'person',
|
||||
objectMetadataPluralName: 'people',
|
||||
gqlFields: 'id',
|
||||
data: [
|
||||
{
|
||||
id: TEST_PERSON_AIRBNB_1_ID,
|
||||
companyId: TEST_COMPANY_AIRBNB_ID,
|
||||
jobTitle: 'workflow-test-airbnb-1',
|
||||
},
|
||||
{
|
||||
id: TEST_PERSON_AIRBNB_2_ID,
|
||||
companyId: TEST_COMPANY_AIRBNB_ID,
|
||||
jobTitle: 'workflow-test-airbnb-2',
|
||||
},
|
||||
{
|
||||
id: TEST_PERSON_STRIPE_1_ID,
|
||||
companyId: TEST_COMPANY_STRIPE_ID,
|
||||
jobTitle: 'workflow-test-stripe-1',
|
||||
},
|
||||
],
|
||||
upsert: true,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const buildWorkflow = async () => {
|
||||
const createWorkflowResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation CreateWorkflow {
|
||||
createWorkflow(data: { name: "Relation Traversal Find Records Test" }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(createWorkflowResponse.body.errors).toBeUndefined();
|
||||
createdWorkflowId = createWorkflowResponse.body.data.createWorkflow.id;
|
||||
|
||||
const getWorkflowResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query GetWorkflow($id: UUID!) {
|
||||
workflow(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
versions { edges { node { id } } }
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowId },
|
||||
});
|
||||
|
||||
createdWorkflowVersionId =
|
||||
getWorkflowResponse.body.data.workflow.versions.edges[0].node.id;
|
||||
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) {
|
||||
updateWorkflowVersion(id: $id, data: $data) { id }
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: createdWorkflowVersionId,
|
||||
data: {
|
||||
trigger: {
|
||||
name: 'Manual Trigger',
|
||||
type: 'MANUAL',
|
||||
settings: { outputSchema: {} },
|
||||
nextStepIds: [],
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createStepResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation CreateWorkflowVersionStep($input: CreateWorkflowVersionStepInput!) {
|
||||
createWorkflowVersionStep(input: $input) { stepsDiff }
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: createdWorkflowVersionId,
|
||||
stepType: 'FIND_RECORDS',
|
||||
parentStepId: 'trigger',
|
||||
position: { x: 200, y: 0 },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(createStepResponse.body.errors).toBeUndefined();
|
||||
|
||||
const getVersionResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query GetWorkflowVersion($id: UUID!) {
|
||||
workflowVersion(filter: { id: { eq: $id } }) { steps }
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
const steps = getVersionResponse.body.data.workflowVersion.steps;
|
||||
const findRecordsStep = steps.find(
|
||||
(step: { type: string }) => step.type === 'FIND_RECORDS',
|
||||
);
|
||||
|
||||
expect(findRecordsStep).toBeDefined();
|
||||
findRecordsStepId = findRecordsStep.id;
|
||||
|
||||
const filterGroupId = uuidv4();
|
||||
const filterId = uuidv4();
|
||||
|
||||
const updateStepResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation UpdateWorkflowVersionStep($input: UpdateWorkflowVersionStepInput!) {
|
||||
updateWorkflowVersionStep(input: $input) { id }
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: createdWorkflowVersionId,
|
||||
step: {
|
||||
...findRecordsStep,
|
||||
settings: {
|
||||
...findRecordsStep.settings,
|
||||
input: {
|
||||
...findRecordsStep.settings.input,
|
||||
objectName: 'person',
|
||||
limit: 25,
|
||||
filter: {
|
||||
recordFilters: [
|
||||
{
|
||||
id: filterId,
|
||||
type: 'TEXT',
|
||||
label: 'Company → Name',
|
||||
value: 'AirbnbWorkflowTest',
|
||||
operand: 'CONTAINS',
|
||||
displayValue: 'AirbnbWorkflowTest',
|
||||
fieldMetadataId: personCompanyFieldMetadataId,
|
||||
relationTargetFieldMetadataId:
|
||||
companyNameFieldMetadataId,
|
||||
recordFilterGroupId: filterGroupId,
|
||||
},
|
||||
],
|
||||
recordFilterGroups: [
|
||||
{ id: filterGroupId, logicalOperator: 'AND' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateStepResponse.body.errors).toBeUndefined();
|
||||
|
||||
const activateResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation ActivateWorkflowVersion($workflowVersionId: UUID!) {
|
||||
activateWorkflowVersion(workflowVersionId: $workflowVersionId)
|
||||
}
|
||||
`,
|
||||
variables: { workflowVersionId: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
expect(activateResponse.body.errors).toBeUndefined();
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await lookupFieldMetadataIds();
|
||||
await seedTestRecords();
|
||||
await buildWorkflow();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (createdWorkflowRunId) {
|
||||
await destroyWorkflowRun(createdWorkflowRunId);
|
||||
}
|
||||
if (createdWorkflowId) {
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `mutation DestroyWorkflow($id: ID!) { destroyWorkflow(id: $id) { id } }`,
|
||||
variables: { id: createdWorkflowId },
|
||||
});
|
||||
}
|
||||
await makeGraphqlAPIRequest(
|
||||
deleteManyOperationFactory({
|
||||
objectMetadataSingularName: 'person',
|
||||
objectMetadataPluralName: 'people',
|
||||
gqlFields: 'id',
|
||||
filter: { id: { in: ALL_TEST_PERSON_IDS } },
|
||||
}),
|
||||
);
|
||||
await makeGraphqlAPIRequest(
|
||||
deleteManyOperationFactory({
|
||||
objectMetadataSingularName: 'company',
|
||||
objectMetadataPluralName: 'companies',
|
||||
gqlFields: 'id',
|
||||
filter: { id: { in: ALL_TEST_COMPANY_IDS } },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply a one-hop relation-traversal filter and return only matching records', async () => {
|
||||
const workflowRunId = await runWorkflowVersion({
|
||||
workflowVersionId: createdWorkflowVersionId!,
|
||||
});
|
||||
|
||||
createdWorkflowRunId = workflowRunId;
|
||||
|
||||
const workflowRun = await waitForWorkflowCompletion(workflowRunId);
|
||||
|
||||
expect(workflowRun?.status).toBe('COMPLETED');
|
||||
expect(workflowRun?.state?.stepInfos?.[findRecordsStepId!]?.status).toBe(
|
||||
'SUCCESS',
|
||||
);
|
||||
|
||||
const result = workflowRun?.state?.stepInfos?.[findRecordsStepId!]
|
||||
?.result as
|
||||
| { all?: Array<{ id: string }>; totalCount?: number }
|
||||
| undefined;
|
||||
|
||||
const returnedIds = (result?.all ?? []).map((record) => record.id);
|
||||
|
||||
expect(returnedIds).toContain(TEST_PERSON_AIRBNB_1_ID);
|
||||
expect(returnedIds).toContain(TEST_PERSON_AIRBNB_2_ID);
|
||||
expect(returnedIds).not.toContain(TEST_PERSON_STRIPE_1_ID);
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ export type ChartRecordFilter = {
|
||||
type?: string;
|
||||
recordFilterGroupId?: string | null;
|
||||
subFieldName?: string | null;
|
||||
relationTargetFieldMetadataId?: SerializedRelation | null;
|
||||
};
|
||||
|
||||
export type ChartRecordFilterGroup = {
|
||||
|
||||
+1
-2
@@ -27,8 +27,7 @@ describe('computeRecordGqlOperationFilter', () => {
|
||||
];
|
||||
|
||||
const filter = computeRecordGqlOperationFilter({
|
||||
findFieldMetadataItemById: (id) =>
|
||||
id === companyIdField.id ? companyIdField : undefined,
|
||||
fields: [companyIdField],
|
||||
recordFilters,
|
||||
recordFilterGroups: [],
|
||||
filterValueDependencies: {
|
||||
|
||||
+4
-7
@@ -21,14 +21,11 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
||||
const findFieldMetadataItemById = (id: string) => fieldById.get(id);
|
||||
|
||||
it('should return undefined when group is not found', () => {
|
||||
const result = turnRecordFilterGroupsIntoGqlOperationFilter({
|
||||
filterValueDependencies: {},
|
||||
filters: [],
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilterGroups: [],
|
||||
currentRecordFilterGroupId: 'nonexistent',
|
||||
});
|
||||
@@ -48,7 +45,7 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
|
||||
recordFilterGroupId: 'group1',
|
||||
},
|
||||
],
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilterGroups: [
|
||||
{
|
||||
id: 'group1',
|
||||
@@ -73,7 +70,7 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
|
||||
recordFilterGroupId: 'group1',
|
||||
},
|
||||
],
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilterGroups: [
|
||||
{
|
||||
id: 'group1',
|
||||
@@ -98,7 +95,7 @@ describe('turnRecordFilterGroupsIntoGqlOperationFilter', () => {
|
||||
recordFilterGroupId: 'subgroup1',
|
||||
},
|
||||
],
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilterGroups: [
|
||||
{
|
||||
id: 'group1',
|
||||
|
||||
+56
-59
@@ -131,9 +131,6 @@ const fields = [
|
||||
|
||||
const filterValueDependencies = { timeZone: 'UTC' };
|
||||
|
||||
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
||||
const findFieldMetadataItemById = (id: string) => fieldById.get(id);
|
||||
|
||||
const makeFilter = (
|
||||
fieldMetadataId: string,
|
||||
operand: RecordFilterOperand,
|
||||
@@ -159,7 +156,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'x',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
@@ -169,7 +166,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-text', RecordFilterOperand.CONTAINS, ''),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
@@ -184,7 +181,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'test',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ name: { ilike: '%test%' } });
|
||||
@@ -198,7 +195,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
'test',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ not: { name: { ilike: '%test%' } } });
|
||||
@@ -210,7 +207,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-number', RecordFilterOperand.IS, '42'),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ amount: { eq: 42 } });
|
||||
@@ -220,7 +217,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-number', RecordFilterOperand.IS_NOT, '42'),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ not: { amount: { eq: 42 } } });
|
||||
@@ -234,7 +231,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
'10',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ amount: { gte: 10 } });
|
||||
@@ -248,7 +245,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.LESS_THAN_OR_EQUAL,
|
||||
'100',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ amount: { lte: 100 } });
|
||||
@@ -264,7 +261,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_AFTER,
|
||||
'2024-03-15',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ createdAt: { gte: '2024-03-15' } });
|
||||
@@ -278,7 +275,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_BEFORE,
|
||||
'2024-03-15',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ createdAt: { lt: '2024-03-15' } });
|
||||
@@ -292,7 +289,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS,
|
||||
'2024-03-15',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ createdAt: { eq: '2024-03-15' } });
|
||||
@@ -302,7 +299,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-date', RecordFilterOperand.IS_IN_PAST, ''),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('createdAt.lt');
|
||||
@@ -316,7 +313,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_IN_FUTURE,
|
||||
'',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('createdAt.gte');
|
||||
@@ -326,7 +323,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-date', RecordFilterOperand.IS_TODAY, ''),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('createdAt.eq');
|
||||
@@ -340,7 +337,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_RELATIVE,
|
||||
'PAST_7_DAY',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('and');
|
||||
@@ -356,7 +353,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_AFTER,
|
||||
'2024-03-15T10:00:00Z',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('updatedAt.gte');
|
||||
@@ -370,7 +367,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_BEFORE,
|
||||
'2024-03-15T10:00:00Z',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('updatedAt.lt');
|
||||
@@ -384,7 +381,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS,
|
||||
'2024-03-15T10:00:00Z',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('and');
|
||||
@@ -398,7 +395,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_IN_PAST,
|
||||
'',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('updatedAt.lt');
|
||||
@@ -412,7 +409,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_IN_FUTURE,
|
||||
'',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('updatedAt.gt');
|
||||
@@ -426,7 +423,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_TODAY,
|
||||
'',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('and');
|
||||
@@ -440,7 +437,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_RELATIVE,
|
||||
`PAST_7_DAY;;UTC;;`,
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('and');
|
||||
@@ -452,7 +449,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-rating', RecordFilterOperand.IS, '3'),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('rating.eq');
|
||||
@@ -466,7 +463,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
'3',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('rating.in');
|
||||
@@ -480,7 +477,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.LESS_THAN_OR_EQUAL,
|
||||
'3',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('rating.in');
|
||||
@@ -492,7 +489,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-bool', RecordFilterOperand.IS, 'true'),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ isActive: { eq: true } });
|
||||
@@ -502,7 +499,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
const result = turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: makeFilter('f-bool', RecordFilterOperand.IS, 'false'),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ isActive: { eq: false } });
|
||||
@@ -518,7 +515,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS,
|
||||
'["ACTIVE","PENDING"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('status.in');
|
||||
@@ -532,7 +529,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_NOT,
|
||||
'["ACTIVE"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('not');
|
||||
@@ -548,7 +545,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'["TAG1","TAG2"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('tags.containsAny');
|
||||
@@ -562,7 +559,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
'["TAG1"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('or');
|
||||
@@ -578,7 +575,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS,
|
||||
'["550e8400-e29b-41d4-a716-446655440000"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('companyId.in');
|
||||
@@ -592,7 +589,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS_NOT,
|
||||
'["550e8400-e29b-41d4-a716-446655440000"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('or');
|
||||
@@ -608,7 +605,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'test',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ metadata: { like: '%test%' } });
|
||||
@@ -622,7 +619,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
'test',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ not: { metadata: { like: '%test%' } } });
|
||||
@@ -638,7 +635,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'doc',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ attachments: { like: '%doc%' } });
|
||||
@@ -654,7 +651,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.VECTOR_SEARCH,
|
||||
'hello world',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ search: { search: 'hello world' } });
|
||||
@@ -672,7 +669,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
'CURRENCY',
|
||||
'amountMicros',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('revenue');
|
||||
@@ -688,7 +685,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'John',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('or');
|
||||
@@ -704,7 +701,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'Paris',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('or');
|
||||
@@ -720,7 +717,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'api',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -747,7 +744,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'xyz123',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -772,7 +769,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
'api',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -803,7 +800,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
'xyz123',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -834,7 +831,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
'PHONES',
|
||||
'primaryPhoneNumber',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -852,7 +849,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
'EMAILS',
|
||||
'primaryEmail',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -870,7 +867,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
'LINKS',
|
||||
'primaryLinkUrl',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -886,7 +883,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.CONTAINS,
|
||||
'["item1"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -900,7 +897,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
'["item1"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('not');
|
||||
@@ -916,7 +913,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS,
|
||||
'["550e8400-e29b-41d4-a716-446655440000"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('recordId.in');
|
||||
@@ -934,13 +931,13 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
...makeFilter('f-relation', RecordFilterOperand.CONTAINS, 'Acme'),
|
||||
relationTargetFieldMetadataId: 'f-text',
|
||||
} as RecordFilter,
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ company: { name: { ilike: '%Acme%' } } });
|
||||
});
|
||||
|
||||
// If the target field is no longer resolvable (e.g. it was
|
||||
// If the target field is no longer in fieldMetadataItems (e.g. it was
|
||||
// deleted from the workspace), dropping the filter is the safe path —
|
||||
// the alternative would silently interpret the text value as a UUID
|
||||
// list against the relation FK.
|
||||
@@ -951,7 +948,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
...makeFilter('f-relation', RecordFilterOperand.CONTAINS, 'Acme'),
|
||||
relationTargetFieldMetadataId: 'nonexistent-target',
|
||||
} as RecordFilter,
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
@@ -966,7 +963,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
...makeFilter('f-relation', RecordFilterOperand.IS_EMPTY, ''),
|
||||
relationTargetFieldMetadataId: 'f-text',
|
||||
} as RecordFilter,
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
// Without traversal, the RELATION case would have produced a filter
|
||||
@@ -987,7 +984,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
...makeFilter('f-relation', RecordFilterOperand.IS, '["ACTIVE"]'),
|
||||
relationTargetFieldMetadataId: 'f-select',
|
||||
} as RecordFilter,
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ company: { status: { in: ['ACTIVE'] } } });
|
||||
@@ -1004,7 +1001,7 @@ describe('turnRecordFilterIntoRecordGqlOperationFilter', () => {
|
||||
RecordFilterOperand.IS,
|
||||
'["550e8400-e29b-41d4-a716-446655440000"]',
|
||||
),
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('companyId.in');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
type PartialFieldMetadataItem,
|
||||
type RecordFilterValueDependencies,
|
||||
type RecordGqlOperationFilter,
|
||||
} from '@/types';
|
||||
@@ -7,20 +8,17 @@ import {
|
||||
type RecordFilter,
|
||||
type RecordFilterGroup,
|
||||
} from '@/utils/filter/turnRecordFilterGroupIntoGqlOperationFilter';
|
||||
import {
|
||||
type FindFieldMetadataItemById,
|
||||
turnRecordFilterIntoRecordGqlOperationFilter,
|
||||
} from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
|
||||
import { turnRecordFilterIntoRecordGqlOperationFilter } from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
|
||||
import { isDefined } from '@/utils/validation/isDefined';
|
||||
|
||||
export const computeRecordGqlOperationFilter = ({
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilters,
|
||||
recordFilterGroups,
|
||||
filterValueDependencies,
|
||||
}: {
|
||||
recordFilters: Omit<RecordFilter, 'id'>[];
|
||||
findFieldMetadataItemById: FindFieldMetadataItemById;
|
||||
fields: PartialFieldMetadataItem[];
|
||||
recordFilterGroups: RecordFilterGroup[];
|
||||
filterValueDependencies: RecordFilterValueDependencies;
|
||||
}): RecordGqlOperationFilter => {
|
||||
@@ -30,7 +28,7 @@ export const computeRecordGqlOperationFilter = ({
|
||||
.map((regularFilter) => {
|
||||
return turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
recordFilter: regularFilter,
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
filterValueDependencies,
|
||||
});
|
||||
})
|
||||
@@ -44,7 +42,7 @@ export const computeRecordGqlOperationFilter = ({
|
||||
turnRecordFilterGroupsIntoGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
filters: recordFilters,
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilterGroups,
|
||||
currentRecordFilterGroupId: outermostFilterGroupId,
|
||||
});
|
||||
|
||||
@@ -221,13 +221,11 @@ export const turnAnyFieldFilterIntoRecordGqlFilter = ({
|
||||
}
|
||||
}
|
||||
|
||||
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
||||
|
||||
const baseRecordGqlOperationFilters = anyFieldRecordFilters
|
||||
.map((recordFilter) =>
|
||||
turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies: {},
|
||||
findFieldMetadataItemById: (id) => fieldById.get(id),
|
||||
fieldMetadataItems: fields,
|
||||
recordFilter,
|
||||
}),
|
||||
)
|
||||
|
||||
+6
-8
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
type CompositeFieldSubFieldName,
|
||||
type FilterableAndTSVectorFieldType,
|
||||
type PartialFieldMetadataItem,
|
||||
RecordFilterGroupLogicalOperator,
|
||||
type RecordFilterValueDependencies,
|
||||
type RecordGqlOperationFilter,
|
||||
@@ -8,10 +9,7 @@ import {
|
||||
} from '@/types';
|
||||
|
||||
import { isDefined } from '@/utils';
|
||||
import {
|
||||
type FindFieldMetadataItemById,
|
||||
turnRecordFilterIntoRecordGqlOperationFilter,
|
||||
} from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
|
||||
import { turnRecordFilterIntoRecordGqlOperationFilter } from '@/utils/filter/turnRecordFilterIntoGqlOperationFilter';
|
||||
|
||||
export type RecordFilter = {
|
||||
id: string;
|
||||
@@ -33,13 +31,13 @@ export type RecordFilterGroup = {
|
||||
export const turnRecordFilterGroupsIntoGqlOperationFilter = ({
|
||||
filterValueDependencies,
|
||||
filters,
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilterGroups,
|
||||
currentRecordFilterGroupId,
|
||||
}: {
|
||||
filterValueDependencies: RecordFilterValueDependencies;
|
||||
filters: Omit<RecordFilter, 'id'>[];
|
||||
findFieldMetadataItemById: FindFieldMetadataItemById;
|
||||
fields: PartialFieldMetadataItem[];
|
||||
recordFilterGroups: RecordFilterGroup[];
|
||||
currentRecordFilterGroupId?: string;
|
||||
}): RecordGqlOperationFilter | undefined => {
|
||||
@@ -60,7 +58,7 @@ export const turnRecordFilterGroupsIntoGqlOperationFilter = ({
|
||||
turnRecordFilterIntoRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilter: recordFilter,
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems: fields,
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
@@ -75,7 +73,7 @@ export const turnRecordFilterGroupsIntoGqlOperationFilter = ({
|
||||
turnRecordFilterGroupsIntoGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
filters,
|
||||
findFieldMetadataItemById,
|
||||
fields,
|
||||
recordFilterGroups,
|
||||
currentRecordFilterGroupId: subRecordFilterGroup.id,
|
||||
}),
|
||||
|
||||
@@ -62,23 +62,21 @@ type FieldShared = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type FindFieldMetadataItemById = (id: string) => FieldShared | undefined;
|
||||
|
||||
type TurnRecordFilterIntoRecordGqlOperationFilterParams = {
|
||||
filterValueDependencies: RecordFilterValueDependencies;
|
||||
recordFilter: Omit<RecordFilter, 'id'>;
|
||||
findFieldMetadataItemById: FindFieldMetadataItemById;
|
||||
fieldMetadataItems: FieldShared[];
|
||||
};
|
||||
|
||||
export const turnRecordFilterIntoRecordGqlOperationFilter = ({
|
||||
recordFilter,
|
||||
findFieldMetadataItemById,
|
||||
fieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
}: TurnRecordFilterIntoRecordGqlOperationFilterParams):
|
||||
| RecordGqlOperationFilter
|
||||
| undefined => {
|
||||
const sourceFieldMetadataItem = findFieldMetadataItemById(
|
||||
recordFilter.fieldMetadataId,
|
||||
const sourceFieldMetadataItem = fieldMetadataItems.find(
|
||||
(field) => field.id === recordFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(sourceFieldMetadataItem)) {
|
||||
@@ -93,8 +91,8 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
|
||||
sourceFieldMetadataItem.type === FieldMetadataType.RELATION &&
|
||||
isDefined(recordFilter.relationTargetFieldMetadataId)
|
||||
) {
|
||||
const targetFieldMetadataItem = findFieldMetadataItemById(
|
||||
recordFilter.relationTargetFieldMetadataId,
|
||||
const targetFieldMetadataItem = fieldMetadataItems.find(
|
||||
(field) => field.id === recordFilter.relationTargetFieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(targetFieldMetadataItem)) {
|
||||
|
||||
@@ -104,7 +104,6 @@ export type {
|
||||
RecordFilterGroup,
|
||||
} from './filter/turnRecordFilterGroupIntoGqlOperationFilter';
|
||||
export { turnRecordFilterGroupsIntoGqlOperationFilter } from './filter/turnRecordFilterGroupIntoGqlOperationFilter';
|
||||
export type { FindFieldMetadataItemById } from './filter/turnRecordFilterIntoGqlOperationFilter';
|
||||
export { turnRecordFilterIntoRecordGqlOperationFilter } from './filter/turnRecordFilterIntoGqlOperationFilter';
|
||||
export { combineFilters } from './filter/utils/combineFilters';
|
||||
export { convertViewFilterOperandToCoreOperand } from './filter/utils/convert-view-filter-operand-to-core-operand.util';
|
||||
|
||||
Reference in New Issue
Block a user