Files
twenty/packages
Félix MalfaitandClaude Opus 4.7 8461ddbadd feat(views): persist one-hop relation filters via relationTargetFieldMetadataId
Introduces a dedicated, stable identifier for the relation-traversal filter
target on `ViewFilterEntity` so saved views survive renames of the field
they reference, and removes the shortcut that previously cast a target
field's NAME into the closed `CompositeFieldSubFieldName` slot.

Schema
 - New column `core.viewFilter.relationTargetFieldMetadataId` (uuid,
   nullable, FK to `core.fieldMetadata.id` with ON DELETE SET NULL so
   filters degrade gracefully if their target field is deleted, rather
   than vanishing).
 - Partial index on the column where not-null.
 - Instance-fast migration (2.5.0, ts 1798000005000) adds the column,
   the FK, and the index; registered in `INSTANCE_COMMANDS`.
 - Field metadata configuration wired through:
   `FLAT_VIEW_FILTER_EDITABLE_PROPERTIES`,
   `all-entity-properties-configuration-by-metadata-name`,
   `all-many-to-one-metadata-foreign-key`,
   `all-many-to-one-metadata-relations` (universal foreign key
   `relationTargetFieldMetadataUniversalIdentifier`).

API
 - `ViewFilterDTO`, `CreateViewFilterInput`, `UpdateViewFilterInputUpdates`
   and `UpsertViewWidgetViewFilterInput` all carry the new optional UUID.
 - View → flat converters (`fromViewFilterEntityToFlatViewFilter`,
   `fromCreateViewFilterInputToFlatViewFilterToCreate`,
   `fromViewFilterManifestToUniversalFlatViewFilter`,
   `createStandardViewFilterFlatMetadata`,
   widget upsert, create-action handler) thread the new column
   through both the foreign-key and universal-identifier sides.

Shared
 - `RecordFilter.relationTargetFieldMetadataId` added on both the
   frontend and twenty-shared types.
 - `turnRecordFilterIntoRecordGqlOperationFilter` now branches on
   `relationTargetFieldMetadataId` BEFORE the emptiness shortcut and
   synthesizes the inner filter against the target field, wrapping it
   under the relation field's name (`{ company: { name: { ... } } }`).
   Falls through to the existing relation-by-record path if the target
   field isn't present in the provided `fieldMetadataItems`.

Frontend
 - New dedicated state
   `relationTargetFieldMetadataIdUsedInDropdownComponentState` (the
   existing `subFieldNameUsedInDropdownComponentState` stays narrowly
   typed for composite sub-fields).
 - `useSelectFieldUsedInAdvancedFilterDropdown` accepts an optional
   `relationTargetFieldMetadataItem`. When present it stores the target
   field's id on `RecordFilter.relationTargetFieldMetadataId` and uses
   the TARGET field's type (so operand picker / value input behave as if
   filtering it directly) and a "Relation → Target" label.
 - `ObjectFilterDropdownInnerSelectOperandDropdown` resolves the
   effective field via `useGetFieldMetadataItemByIdOrThrow` when the
   relation-target state is set, so operand options match the target's
   filterable type.
 - `AdvancedFilterFieldSelectMenu` routes MANY_TO_ONE clicks into the
   same sub-menu flow as composite fields, with a `'RELATION'` sentinel
   on `objectFilterDropdownSubMenuFieldType`.
 - `AdvancedFilterSubFieldSelectMenu` renders the target object's
   filterable fields via `useFilterableFieldMetadataItems`; the ghost
   `-1` selectable id that the composite branch used for the "Any X
   field" item is no longer included in the relation list (fixes the
   keyboard-navigation issue cubic-dev-ai flagged).
 - `ObjectFilterDropdownFilterSelectMenuItem` shows the sub-menu
   chevron for MANY_TO_ONE relations via a new
   `isManyToOneRelationField` util.
 - `mapRecordFilterToViewFilter` / `mapViewFiltersToFilters`,
   `areViewFiltersEqual`, the save mutation inputs, and the GraphQL
   fragment all thread the new field through the round-trip so saved
   views actually persist relation traversal.
 - Role-permissions sub-field menu narrows the `'RELATION'` sentinel
   back out (it never deals with relations).

Out of scope
 - Normal filter dropdown (`ViewBarFilterDropdownFieldSelectMenu`) —
   composite drill-down doesn't exist there today, so adding relation
   drill-down would mean introducing the entire sub-menu pattern on the
   normal surface. Tracking as follow-up; advanced filter is the only
   surface that currently builds nested filter shapes.
 - REST DSL, ONE_TO_MANY reverse traversal, aggregates — unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 15:23:34 +02:00
..
2026-05-04 11:09:34 +02:00