Spread in parent and requires FlatEntity.__universal (#17753)
# Introduction Requiring the spreaded `__universal` record that aggregates all the universal identifier ( relations fk and aggregators ) of an entity to its root It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be finalized because if we don't we would have to migrated all related entities at once in order for them to always have the universal properties ## `resolveEntityRelationUniversalIdentifiers` Introduced `resolveEntityRelationUniversalIdentifiers` a centralized utility that resolves foreign key IDs to universal identifiers using ALL_METADATA_RELATIONS metadata. It provides strict typing for both input (foreign keys) and output (universal identifiers), with nullability dynamically inferred from entity relation types. Strictly and dynamically typed for both output and input To do so added a new type and const/runtime grain to ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from the entity relation property types. And fixed incorrectly typed typeorm entities ### Usage ```ts const { availabilityObjectMetadataUniversalIdentifier, frontComponentUniversalIdentifier, } = resolveEntityRelationUniversalIdentifiers({ metadataName: 'commandMenuItem', foreignKeyValues: { availabilityObjectMetadataId: createCommandMenuItemInput.availabilityObjectMetadataId, frontComponentId: createCommandMenuItemInput.frontComponentId, }, flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps }, }); ```
This commit is contained in:
+43
-14
@@ -3,7 +3,11 @@ import { Injectable } from '@nestjs/common';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
|
||||
@@ -46,6 +50,7 @@ export class PageLayoutDuplicationService {
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatObjectMetadataMaps,
|
||||
} = await this.getPageLayoutFlatEntityMaps(workspaceId);
|
||||
|
||||
const originalFlatLayout = this.findOriginalLayoutOrThrow(
|
||||
@@ -72,23 +77,41 @@ export class PageLayoutDuplicationService {
|
||||
objectMetadataId: originalFlatLayout.objectMetadataId,
|
||||
},
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatObjectMetadataMaps,
|
||||
},
|
||||
);
|
||||
|
||||
const optimisticFlatPageLayoutMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: newFlatPageLayout,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
});
|
||||
|
||||
const { newFlatTabs, originalTabIdToNewTabIdMap } =
|
||||
this.createDuplicatedTabs({
|
||||
originalTabs: originalTabsWithWidgets.map(({ tab }) => tab),
|
||||
newPageLayoutId: newFlatPageLayout.id,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatPageLayoutMaps: optimisticFlatPageLayoutMaps,
|
||||
});
|
||||
|
||||
const optimisticFlatPageLayoutTabMaps = newFlatTabs.reduce(
|
||||
(maps, flatTab) =>
|
||||
addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: flatTab,
|
||||
flatEntityMaps: maps,
|
||||
}),
|
||||
createEmptyFlatEntityMaps(),
|
||||
);
|
||||
|
||||
const newFlatWidgets = this.createDuplicatedWidgets({
|
||||
originalTabsWithWidgets,
|
||||
originalTabIdToNewTabIdMap,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatPageLayoutTabMaps: optimisticFlatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
@@ -146,11 +169,7 @@ export class PageLayoutDuplicationService {
|
||||
);
|
||||
}
|
||||
|
||||
private async getPageLayoutFlatEntityMaps(workspaceId: string): Promise<{
|
||||
flatPageLayoutMaps: FlatPageLayoutMaps;
|
||||
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
|
||||
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
|
||||
}> {
|
||||
private async getPageLayoutFlatEntityMaps(workspaceId: string) {
|
||||
return this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
@@ -158,6 +177,7 @@ export class PageLayoutDuplicationService {
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatObjectMetadataMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -224,12 +244,14 @@ export class PageLayoutDuplicationService {
|
||||
originalTabs,
|
||||
newPageLayoutId,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
flatApplication,
|
||||
flatPageLayoutMaps,
|
||||
}: {
|
||||
originalTabs: FlatPageLayoutTab[];
|
||||
newPageLayoutId: string;
|
||||
workspaceId: string;
|
||||
workspaceCustomApplicationId: string;
|
||||
flatApplication: FlatApplication;
|
||||
flatPageLayoutMaps: AllFlatEntityMaps['flatPageLayoutMaps'];
|
||||
}): {
|
||||
newFlatTabs: FlatPageLayoutTab[];
|
||||
originalTabIdToNewTabIdMap: Map<string, string>;
|
||||
@@ -245,7 +267,8 @@ export class PageLayoutDuplicationService {
|
||||
pageLayoutId: newPageLayoutId,
|
||||
},
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
flatApplication,
|
||||
flatPageLayoutMaps,
|
||||
});
|
||||
|
||||
originalTabIdToNewTabIdMap.set(originalTab.id, newFlatTab.id);
|
||||
@@ -260,7 +283,9 @@ export class PageLayoutDuplicationService {
|
||||
originalTabsWithWidgets,
|
||||
originalTabIdToNewTabIdMap,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
flatApplication,
|
||||
flatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
}: {
|
||||
originalTabsWithWidgets: {
|
||||
tab: FlatPageLayoutTab;
|
||||
@@ -268,7 +293,9 @@ export class PageLayoutDuplicationService {
|
||||
}[];
|
||||
originalTabIdToNewTabIdMap: Map<string, string>;
|
||||
workspaceId: string;
|
||||
workspaceCustomApplicationId: string;
|
||||
flatApplication: FlatApplication;
|
||||
flatPageLayoutTabMaps: AllFlatEntityMaps['flatPageLayoutTabMaps'];
|
||||
flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps'];
|
||||
}): FlatPageLayoutWidget[] {
|
||||
return originalTabsWithWidgets.flatMap(({ tab, widgets }) => {
|
||||
const newTabId = originalTabIdToNewTabIdMap.get(tab.id)!;
|
||||
@@ -284,7 +311,9 @@ export class PageLayoutDuplicationService {
|
||||
pageLayoutTabId: newTabId,
|
||||
},
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
flatApplication,
|
||||
flatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
+56
@@ -6,8 +6,10 @@ import { v4 } from 'uuid';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
|
||||
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 { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
@@ -109,14 +111,28 @@ export class PageLayoutUpdateService {
|
||||
flatPageLayoutTabMaps,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
});
|
||||
|
||||
const { flatObjectMetadataMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const { widgetsToCreate, widgetsToUpdate, widgetsToDelete } =
|
||||
this.computeWidgetOperationsForAllTabs({
|
||||
tabs,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
@@ -196,12 +212,14 @@ export class PageLayoutUpdateService {
|
||||
flatPageLayoutTabMaps,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
}: {
|
||||
existingPageLayout: FlatPageLayout;
|
||||
tabs: UpdatePageLayoutTabWithWidgetsInput[];
|
||||
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
|
||||
workspaceId: string;
|
||||
workspaceCustomApplicationId: string;
|
||||
workspaceCustomApplicationUniversalIdentifier: string;
|
||||
}): {
|
||||
tabsToCreate: FlatPageLayoutTab[];
|
||||
tabsToUpdate: FlatPageLayoutTab[];
|
||||
@@ -238,13 +256,17 @@ export class PageLayoutUpdateService {
|
||||
title: tabInput.title,
|
||||
position: tabInput.position,
|
||||
pageLayoutId: existingPageLayout.id,
|
||||
pageLayoutUniversalIdentifier: existingPageLayout.universalIdentifier,
|
||||
workspaceId,
|
||||
createdAt: now.toISOString(),
|
||||
updatedAt: now.toISOString(),
|
||||
deletedAt: null,
|
||||
universalIdentifier: tabId,
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
widgetIds: [],
|
||||
widgetUniversalIdentifiers: [],
|
||||
icon: null,
|
||||
layoutMode: PageLayoutTabLayoutMode.GRID,
|
||||
};
|
||||
@@ -316,13 +338,19 @@ export class PageLayoutUpdateService {
|
||||
private computeWidgetOperationsForAllTabs({
|
||||
tabs,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
}: {
|
||||
tabs: UpdatePageLayoutTabWithWidgetsInput[];
|
||||
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
|
||||
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
|
||||
flatObjectMetadataMaps: MetadataFlatEntityMaps<'objectMetadata'>;
|
||||
workspaceId: string;
|
||||
workspaceCustomApplicationId: string;
|
||||
workspaceCustomApplicationUniversalIdentifier: string;
|
||||
}): {
|
||||
widgetsToCreate: FlatPageLayoutWidget[];
|
||||
widgetsToUpdate: FlatPageLayoutWidget[];
|
||||
@@ -337,8 +365,11 @@ export class PageLayoutUpdateService {
|
||||
tabId: tabInput.id,
|
||||
widgets: tabInput.widgets,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
allWidgetsToCreate.push(...widgetsToCreate);
|
||||
@@ -356,14 +387,20 @@ export class PageLayoutUpdateService {
|
||||
tabId,
|
||||
widgets,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
}: {
|
||||
tabId: string;
|
||||
widgets: UpdatePageLayoutWidgetWithIdInput[];
|
||||
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
|
||||
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
|
||||
flatObjectMetadataMaps: MetadataFlatEntityMaps<'objectMetadata'>;
|
||||
workspaceId: string;
|
||||
workspaceCustomApplicationId: string;
|
||||
workspaceCustomApplicationUniversalIdentifier: string;
|
||||
}): {
|
||||
widgetsToCreate: FlatPageLayoutWidget[];
|
||||
widgetsToUpdate: FlatPageLayoutWidget[];
|
||||
@@ -394,12 +431,29 @@ export class PageLayoutUpdateService {
|
||||
(widgetInput) => {
|
||||
const widgetId = widgetInput.id ?? v4();
|
||||
|
||||
const {
|
||||
pageLayoutTabUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
} = resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'pageLayoutWidget',
|
||||
foreignKeyValues: {
|
||||
pageLayoutTabId: widgetInput.pageLayoutTabId,
|
||||
objectMetadataId: widgetInput.objectMetadataId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatPageLayoutTabMaps,
|
||||
flatObjectMetadataMaps,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: widgetId,
|
||||
pageLayoutTabId: widgetInput.pageLayoutTabId,
|
||||
pageLayoutTabUniversalIdentifier,
|
||||
title: widgetInput.title,
|
||||
type: widgetInput.type,
|
||||
objectMetadataId: widgetInput.objectMetadataId ?? null,
|
||||
objectMetadataUniversalIdentifier,
|
||||
gridPosition: widgetInput.gridPosition,
|
||||
position: widgetInput.position ?? null,
|
||||
configuration: widgetInput.configuration ?? null,
|
||||
@@ -409,6 +463,8 @@ export class PageLayoutUpdateService {
|
||||
deletedAt: null,
|
||||
universalIdentifier: widgetId,
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
conditionalDisplay: null,
|
||||
};
|
||||
},
|
||||
|
||||
+16
-3
@@ -190,11 +190,20 @@ export class PageLayoutService {
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutToCreate =
|
||||
fromCreatePageLayoutInputToFlatPageLayoutToCreate({
|
||||
createPageLayoutInput,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
@@ -251,11 +260,14 @@ export class PageLayoutService {
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const { flatPageLayoutMaps: existingFlatPageLayoutMaps } =
|
||||
const {
|
||||
flatPageLayoutMaps: existingFlatPageLayoutMaps,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
} =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutMaps'],
|
||||
flatMapsKeys: ['flatPageLayoutMaps', 'flatObjectMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -268,6 +280,7 @@ export class PageLayoutService {
|
||||
fromUpdatePageLayoutInputToFlatPageLayoutToUpdateOrThrow({
|
||||
updatePageLayoutInput,
|
||||
flatPageLayoutMaps: existingFlatPageLayoutMaps,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
|
||||
Reference in New Issue
Block a user