Add syncable NavigationMenuItem entity to core schema (#17232)

Implements a new syncable `navigationMenuItem` entity in the core schema
to replace the workspace `favorite` entity.

## Next Steps
- Frontend integration ([separate
PR](https://github.com/twentyhq/twenty/pull/17268))
- Data migration (separate PR)
This commit is contained in:
Abdul Rahman
2026-01-22 12:48:51 +00:00
committed by GitHub
parent 2cda1f5f08
commit aff577689f
71 changed files with 4050 additions and 3 deletions
@@ -7,6 +7,7 @@ import { FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES } from 'src/engine/metadata-
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant';
import { FLAT_FRONT_COMPONENT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-front-component/constants/flat-front-component-editable-properties.constant';
import { FLAT_NAVIGATION_MENU_ITEM_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-navigation-menu-item/constants/flat-navigation-menu-item-editable-properties.constant';
import { FLAT_OBJECT_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-editable-properties.constant';
import { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant';
import { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/flat-page-layout-widget-editable-properties.constant';
@@ -148,6 +149,10 @@ export const ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY = {
propertiesToCompare: [...FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES],
propertiesToStringify: [],
},
navigationMenuItem: {
propertiesToCompare: [...FLAT_NAVIGATION_MENU_ITEM_EDITABLE_PROPERTIES],
propertiesToStringify: [],
},
rowLevelPermissionPredicate: {
propertiesToCompare: [
...FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES,
@@ -8,6 +8,7 @@ import { DatabaseEventTriggerEntity } from 'src/engine/metadata-modules/database
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
@@ -49,4 +50,5 @@ export const ALL_METADATA_ENTITY_BY_METADATA_NAME = {
role: RoleEntity,
agent: AgentEntity,
commandMenuItem: CommandMenuItemEntity,
navigationMenuItem: NavigationMenuItemEntity,
} as const satisfies Record<AllMetadataName, EntityTarget<ObjectLiteral>>;
@@ -91,6 +91,24 @@ export const ALL_METADATA_RELATIONS = {
},
oneToMany: {},
},
navigationMenuItem: {
manyToOne: {
workspace: null,
userWorkspace: null,
application: null,
targetObjectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'targetObjectMetadataId',
},
folder: {
metadataName: 'navigationMenuItem',
flatEntityForeignKeyAggregator: null,
foreignKey: 'folderId',
},
},
oneToMany: {},
},
fieldMetadata: {
manyToOne: {
object: {
@@ -65,6 +65,10 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
commandMenuItem: {
objectMetadata: true,
},
navigationMenuItem: {
objectMetadata: true,
view: true,
},
pageLayout: {
objectMetadata: true,
},
@@ -2,9 +2,12 @@ import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modul
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
import { type FlatNavigationMenuItemMaps } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item-maps.type';
export type AllFlatEntityMaps = {
[P in keyof AllFlatEntityTypesByMetadataName as MetadataToFlatEntityMapsKey<P>]: FlatEntityMaps<
MetadataFlatEntity<P>
>;
} & {
flatNavigationMenuItemMaps: FlatNavigationMenuItemMaps;
};
@@ -6,6 +6,7 @@ import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/typ
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
@@ -18,6 +19,7 @@ import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity';
import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
@@ -57,6 +59,11 @@ import {
type DeleteIndexAction,
type UpdateIndexAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/index/types/workspace-migration-index-action';
import {
type CreateNavigationMenuItemAction,
type DeleteNavigationMenuItemAction,
type UpdateNavigationMenuItemAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/navigation-menu-item/types/workspace-migration-navigation-menu-item-action.type';
import {
type CreateObjectAction,
type DeleteObjectAction,
@@ -310,6 +317,15 @@ export type AllFlatEntityTypesByMetadataName = {
flatEntity: FlatCommandMenuItem;
entity: MetadataEntity<'commandMenuItem'>;
};
navigationMenuItem: {
actions: {
create: CreateNavigationMenuItemAction;
update: UpdateNavigationMenuItemAction;
delete: DeleteNavigationMenuItemAction;
};
flatEntity: FlatNavigationMenuItem;
entity: NavigationMenuItemEntity;
};
pageLayout: {
actions: {
create: CreatePageLayoutAction;
@@ -38,6 +38,13 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo
]
`;
exports[`getMetadataRelatedMetadataNames should return related metadata names for navigationMenuItem 1`] = `
[
"objectMetadata",
"navigationMenuItem",
]
`;
exports[`getMetadataRelatedMetadataNames should return related metadata names for objectMetadata 1`] = `
[
"fieldMetadata",
@@ -1,9 +1,10 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with children first (most manyToOne relations first) 1`] = `
[
"rowLevelPermissionPredicate",
"viewFilter",
"navigationMenuItem",
"pageLayoutWidget",
"viewField",
"commandMenuItem",