mirror of
https://github.com/twentyhq/twenty.git
synced 2026-05-19 22:07:21 -04:00
fix(views): make relation traversal filters survive serialize + reload
Four bugs caught in PR review of the one-hop relation filter feature: - turnRecordFilterIntoRecordGqlOperationFilter (twenty-shared) looks up the target field in the same `fieldMetadataItems` list it received, but every frontend caller was passing only the current object's fields. The target field never resolved, so the serializer fell through to the legacy RELATION case and threw a ZodError trying to parse the text value as record IDs. Fix: feed every call site the `flattenedFieldMetadataItemsSelector` value (a memoized flat-map of every object's fields). `turnAnyFieldFilterIntoRecordGqlFilter` iterates and is left on the current-object list. - mapViewFiltersToFilters derived `type` and `label` from the source RELATION field on load, so a saved `Company → Name` filter came back as type=RELATION / label=Company and the advanced-filter UI reopened it with record-picker semantics. Add an optional `allFieldMetadataItems` param, resolve the target field through it, and use the target's filterType / build a `source → target` label. Callers updated to pass the flattened list. - view-filter.entity.ts comment promised "graceful fallback to relation-by-record semantics" on ON DELETE SET NULL, but nothing rewrites the persisted operand/value to fit the new shape — load path was the implicit dead-letter office. Rewrite the comment to describe the actual contract (row survives, caller drops the filter); no behavior change. - relationTargetFieldMetadataIdUsedInDropdownComponentState had a writer in the advanced-filter field-select hook and a reader in the simple operand dropdown (chip-edit), but the contexts never met: chip-edit init didn't restore the state, so editing a saved traversal filter via its chip showed the source field's operand list. Wire setEditableFilterChipDropdownStates to also restore relationTargetFieldMetadataId from the loaded recordFilter. Also unfreezes the viewMapFunctions test that was already failing on the branch (the persisted relation-target field wasn't covered in the expected shape).
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
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';
|
||||
@@ -9,6 +10,7 @@ import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/ho
|
||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import {
|
||||
combineFilters,
|
||||
computeRecordGqlOperationFilter,
|
||||
@@ -47,8 +49,12 @@ export const useFindManyRecordIndexTableParams = (
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const currentFilters = computeRecordGqlOperationFilter({
|
||||
fields: objectMetadataItem?.fields ?? [],
|
||||
fields: flattenedFieldMetadataItems,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
recordFilters: currentRecordFilters,
|
||||
filterValueDependencies,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
|
||||
import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
|
||||
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
|
||||
import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
@@ -129,9 +130,13 @@ export const useLoadRecordIndexStates = () => {
|
||||
.filter(isDefined);
|
||||
|
||||
const allFilterableFields = getFilterableFields(objectMetadataItem);
|
||||
const flattenedFieldMetadataItems = store.get(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
const recordFilters = mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
allFilterableFields,
|
||||
flattenedFieldMetadataItems,
|
||||
);
|
||||
|
||||
const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups(
|
||||
@@ -141,6 +146,7 @@ export const useLoadRecordIndexStates = () => {
|
||||
const contextStoreFilters = mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterableFieldMetadataItems,
|
||||
flattenedFieldMetadataItems,
|
||||
);
|
||||
|
||||
let recordIndexGroupFieldMetadataItemValue = undefined;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
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';
|
||||
@@ -12,6 +13,7 @@ import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import {
|
||||
combineFilters,
|
||||
computeRecordGqlOperationFilter,
|
||||
@@ -36,11 +38,15 @@ export const useRecordIndexGroupCommonQueryVariables = () => {
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const requestFilters = computeRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
fields: objectMetadataItem.fields,
|
||||
fields: flattenedFieldMetadataItems,
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useAtomComponentStateValue(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
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';
|
||||
@@ -12,6 +13,7 @@ import { useAggregateGqlFieldsFromRecordIndexGroupAggregates } from '@/object-re
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { buildGroupByFieldObject } from '@/page-layout/widgets/graph/utils/buildGroupByFieldObject';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { useMemo } from 'react';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
@@ -46,11 +48,15 @@ export const useRecordIndexGroupsAggregatesGroupBy = ({
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const requestFilters = computeRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
fields: objectMetadataItem.fields,
|
||||
fields: flattenedFieldMetadataItems,
|
||||
});
|
||||
|
||||
const { recordAggregateGqlField } =
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent';
|
||||
import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail';
|
||||
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';
|
||||
@@ -13,6 +14,7 @@ import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEvent
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@@ -50,6 +52,10 @@ export const RecordTableEmptyHasNewRecordEffect = () => {
|
||||
currentRecordFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const queryId = `record-table-empty-${objectMetadataItem.nameSingular}`;
|
||||
|
||||
const operationSignature = useMemo(
|
||||
@@ -57,7 +63,7 @@ export const RecordTableEmptyHasNewRecordEffect = () => {
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {
|
||||
filter: computeRecordGqlOperationFilter({
|
||||
fields: objectMetadataItem.fields,
|
||||
fields: flattenedFieldMetadataItems,
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
@@ -67,6 +73,7 @@ export const RecordTableEmptyHasNewRecordEffect = () => {
|
||||
}),
|
||||
[
|
||||
objectMetadataItem,
|
||||
flattenedFieldMetadataItems,
|
||||
currentRecordFilters,
|
||||
currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
@@ -45,10 +46,14 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
||||
|
||||
const dateLocale = useAtomStateValue(dateLocaleState);
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const requestFilters = computeRecordGqlOperationFilter({
|
||||
fields: objectMetadataItem.fields,
|
||||
fields: flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
recordFilters: currentRecordFilters,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
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';
|
||||
@@ -8,6 +9,7 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { computeRecordGqlOperationFilter } from 'twenty-shared/utils';
|
||||
|
||||
export const RecordTableVirtualizedSSESubscribeEffect = () => {
|
||||
@@ -26,6 +28,10 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => {
|
||||
currentRecordFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const queryId = `record-table-virtualized-${objectMetadataItem.nameSingular}`;
|
||||
|
||||
const operationSignature = useMemo(
|
||||
@@ -33,7 +39,7 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => {
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {
|
||||
filter: computeRecordGqlOperationFilter({
|
||||
fields: objectMetadataItem.fields,
|
||||
fields: flattenedFieldMetadataItems,
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
@@ -43,6 +49,7 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => {
|
||||
}),
|
||||
[
|
||||
objectMetadataItem,
|
||||
flattenedFieldMetadataItems,
|
||||
currentRecordFilters,
|
||||
currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import {
|
||||
computeRecordGqlOperationFilter,
|
||||
isDefined,
|
||||
@@ -38,8 +40,12 @@ export const useGraphWidgetQueryCommon = ({
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const gqlOperationFilter = computeRecordGqlOperationFilter({
|
||||
fields: objectMetadataItem.fields,
|
||||
fields: flattenedFieldMetadataItems,
|
||||
filterValueDependencies,
|
||||
recordFilters: configuration.filter?.recordFilters ?? [],
|
||||
recordFilterGroups: configuration.filter?.recordFilterGroups ?? [],
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useSetAdvancedFilterDropdownStates } from '@/object-record/advanced-filter/hooks/useSetAdvancedFilterDropdownAllRowsStates';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { getFilterableFields } from '@/views/utils/getFilterableFields';
|
||||
@@ -39,6 +41,10 @@ export const RecordTableSettingsFiltersInitializeStateEffect = ({
|
||||
currentRecordFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const [hasInitializedFilters, setHasInitializedFilters] = useState(false);
|
||||
|
||||
const stateAlreadyHasFilters =
|
||||
@@ -58,6 +64,7 @@ export const RecordTableSettingsFiltersInitializeStateEffect = ({
|
||||
const recordFilters = mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterableFields,
|
||||
flattenedFieldMetadataItems,
|
||||
);
|
||||
|
||||
setCurrentRecordFilters(recordFilters);
|
||||
@@ -72,6 +79,7 @@ export const RecordTableSettingsFiltersInitializeStateEffect = ({
|
||||
}, [
|
||||
view,
|
||||
objectMetadataItem,
|
||||
flattenedFieldMetadataItems,
|
||||
hasInitializedFilters,
|
||||
stateAlreadyHasFilters,
|
||||
setCurrentRecordFilters,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { type ViewFilter as GqlViewFilter } from '~/generated-metadata/graphql';
|
||||
import { type ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { getFilterableFields } from '@/views/utils/getFilterableFields';
|
||||
@@ -7,12 +9,20 @@ import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
export const useMapViewFiltersToFilters = () => {
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const flattenedFieldMetadataItems = useAtomStateValue(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const mapViewFiltersToRecordFilters = (
|
||||
viewFilters: ViewFilter[] | GqlViewFilter[],
|
||||
) => {
|
||||
const filterableFieldMetadataItems =
|
||||
getFilterableFields(objectMetadataItem);
|
||||
return mapViewFiltersToFilters(viewFilters, filterableFieldMetadataItems);
|
||||
return mapViewFiltersToFilters(
|
||||
viewFilters,
|
||||
filterableFieldMetadataItems,
|
||||
flattenedFieldMetadataItems,
|
||||
);
|
||||
};
|
||||
|
||||
return { mapViewFiltersToRecordFilters };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
|
||||
import { relationTargetFieldMetadataIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/relationTargetFieldMetadataIdUsedInDropdownComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
@@ -59,6 +60,15 @@ export const useSetEditableFilterChipDropdownStates = () => {
|
||||
}),
|
||||
recordFilter.subFieldName,
|
||||
);
|
||||
|
||||
store.set(
|
||||
relationTargetFieldMetadataIdUsedInDropdownComponentState.atomFamily({
|
||||
instanceId: getEditableChipObjectFilterDropdownComponentInstanceId({
|
||||
recordFilterId: recordFilter.id,
|
||||
}),
|
||||
}),
|
||||
recordFilter.relationTargetFieldMetadataId,
|
||||
);
|
||||
},
|
||||
[store, filterableFieldMetadataItems],
|
||||
);
|
||||
|
||||
@@ -43,6 +43,8 @@ describe('mapViewFiltersToFilters', () => {
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
positionInRecordFilterGroup: undefined,
|
||||
recordFilterGroupId: undefined,
|
||||
subFieldName: undefined,
|
||||
relationTargetFieldMetadataId: null,
|
||||
},
|
||||
];
|
||||
expect(
|
||||
|
||||
@@ -15,6 +15,10 @@ import { type ViewFilter } from '@/views/types/ViewFilter';
|
||||
export const mapViewFiltersToFilters = (
|
||||
viewFilters: ViewFilter[] | GqlViewFilter[],
|
||||
availableFieldMetadataItems: FieldMetadataItem[],
|
||||
// All field metadata items across every object, used to resolve relation
|
||||
// traversal targets that live on a different object than the source field.
|
||||
// Defaults to `availableFieldMetadataItems` for non-traversal callers.
|
||||
allFieldMetadataItems: FieldMetadataItem[] = availableFieldMetadataItems,
|
||||
): RecordFilter[] => {
|
||||
return viewFilters
|
||||
.map((viewFilter) => {
|
||||
@@ -28,18 +32,6 @@ export const mapViewFiltersToFilters = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
availableFieldMetadataItem.type,
|
||||
);
|
||||
|
||||
const label = isSystemSearchVectorField(availableFieldMetadataItem.name)
|
||||
? 'Search'
|
||||
: availableFieldMetadataItem.label;
|
||||
|
||||
const operand = viewFilter.operand;
|
||||
|
||||
const stringValue = convertViewFilterValueToString(viewFilter.value);
|
||||
|
||||
// The codegen-generated `GqlViewFilter` and the local `ViewFilter`
|
||||
// type don't both expose this field; `in` narrows safely across both.
|
||||
const relationTargetFieldMetadataId =
|
||||
@@ -47,6 +39,32 @@ export const mapViewFiltersToFilters = (
|
||||
? (viewFilter.relationTargetFieldMetadataId ?? null)
|
||||
: null;
|
||||
|
||||
const relationTargetFieldMetadataItem = isDefined(
|
||||
relationTargetFieldMetadataId,
|
||||
)
|
||||
? allFieldMetadataItems.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === relationTargetFieldMetadataId,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
// For relation traversal, the operand picker / value input must match
|
||||
// the target field's type, and the label must reflect both hops so the
|
||||
// filter is recognizable in the UI (e.g. "Company → Name").
|
||||
const filterType = isDefined(relationTargetFieldMetadataItem)
|
||||
? getFilterTypeFromFieldType(relationTargetFieldMetadataItem.type)
|
||||
: getFilterTypeFromFieldType(availableFieldMetadataItem.type);
|
||||
|
||||
const label = isSystemSearchVectorField(availableFieldMetadataItem.name)
|
||||
? 'Search'
|
||||
: isDefined(relationTargetFieldMetadataItem)
|
||||
? `${availableFieldMetadataItem.label} → ${relationTargetFieldMetadataItem.label}`
|
||||
: availableFieldMetadataItem.label;
|
||||
|
||||
const operand = viewFilter.operand;
|
||||
|
||||
const stringValue = convertViewFilterValueToString(viewFilter.value);
|
||||
|
||||
return {
|
||||
id: viewFilter.id,
|
||||
fieldMetadataId: viewFilter.fieldMetadataId,
|
||||
|
||||
@@ -71,8 +71,10 @@ export class ViewFilterEntity
|
||||
@Column({ nullable: true, type: 'uuid', default: null })
|
||||
relationTargetFieldMetadataId: string | null;
|
||||
|
||||
// ON DELETE SET NULL — if the target field is deleted the filter survives
|
||||
// and falls back to relation-by-record semantics rather than being lost.
|
||||
// ON DELETE SET NULL keeps the row when the target field is deleted, but
|
||||
// the operand/value stay shaped for the original target. The load path
|
||||
// therefore drops any filter where `relationTargetFieldMetadataId` has
|
||||
// become null after a non-null persisted value (deferred to caller).
|
||||
@ManyToOne(() => FieldMetadataEntity, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
|
||||
Reference in New Issue
Block a user