Revert 21949 (#22081)

#21949 introduced deterministic uuid utils with usage in the same PR. 
Usage was not uniform and expected a backfill command as well. 
Since we want to release I'm reverting all the changes from that PR that
concerns twenty-server and only keeping the unused utils in
twenty-shared and I'll introduce usages within the same PR as backfill
command
This commit is contained in:
Weiko
2026-06-24 15:37:16 +02:00
committed by GitHub
parent b5a1aed24b
commit 56e20a81ea
26 changed files with 514 additions and 402 deletions
@@ -202,7 +202,6 @@ export class RefactorNavigationCommandsCommand extends ActiveOrSuspendedWorkspac
workspaceId,
position: nextPosition++,
now,
universalIdentifier,
}),
);
}
@@ -13,6 +13,8 @@ import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-la
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
import { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
@@ -506,32 +508,39 @@ export class BackfillRecordPageLayoutsCommand extends ActiveOrSuspendedWorkspace
const allViewFields: UniversalFlatViewField[] = [];
for (const customObject of customObjectsWithoutPageLayout) {
const fieldsView = computeFlatRecordPageFieldsViewToCreate({
objectMetadata: customObject,
flatApplication: twentyStandardFlatApplication,
});
const objectFieldMetadatas = Object.values(
flatFieldMetadataMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((field) => field.objectMetadataId === customObject.id);
const {
pageLayouts,
pageLayoutTabs,
pageLayoutWidgets,
recordPageFieldsView,
recordPageFieldsViewFields,
} = computeFlatDefaultRecordPageLayoutToCreate({
objectMetadata: customObject,
flatApplication: twentyStandardFlatApplication,
const viewFields = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: objectFieldMetadatas,
viewUniversalIdentifier: fieldsView.universalIdentifier,
flatApplication: twentyStandardFlatApplication,
labelIdentifierFieldMetadataUniversalIdentifier:
customObject.labelIdentifierFieldMetadataUniversalIdentifier,
workspaceId,
excludeLabelIdentifier: true,
});
const { pageLayouts, pageLayoutTabs, pageLayoutWidgets } =
computeFlatDefaultRecordPageLayoutToCreate({
objectMetadata: customObject,
flatApplication: twentyStandardFlatApplication,
recordPageFieldsView: fieldsView,
workspaceId,
});
allPageLayouts.push(...pageLayouts);
allTabs.push(...pageLayoutTabs);
allWidgets.push(...pageLayoutWidgets);
allViews.push(recordPageFieldsView);
allViewFields.push(...recordPageFieldsViewFields);
allViews.push(fieldsView);
allViewFields.push(...viewFields);
}
const result =
@@ -57,10 +57,6 @@ const buildExistingNavigationItem = ({
workspaceId: WORKSPACE_ID,
position,
now: NOW,
universalIdentifier: v5(
objectUniversalIdentifier,
NAVIGATION_COMMAND_UUID_NAMESPACE,
),
});
describe('buildNavigationCommandMenuItemOperationsOrThrow', () => {
@@ -69,7 +69,6 @@ export const buildNavigationCommandMenuItemOperationsOrThrow = ({
workspaceId,
position: nextPosition++,
now,
universalIdentifier: commandMenuItemUniversalIdentifier,
}),
);
}
@@ -1,4 +1,5 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { v5 } from 'uuid';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
@@ -10,6 +11,9 @@ import {
NAVIGATION_INTERPOLATED_SHORT_LABEL,
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
const NAVIGATION_COMMAND_UUID_NAMESPACE =
'b31830da-2ae0-48eb-a915-12fa4ab96dd3';
const baseObjectMetadata = {
id: 'obj-id-1',
universalIdentifier: 'obj-universal-1',
@@ -25,14 +29,18 @@ const baseArgs = {
workspaceId: 'ws-id-1',
position: 5,
now: '2026-01-01T00:00:00.000Z',
universalIdentifier: 'nav-universal-1',
};
describe('buildNavigationFlatCommandMenuItem', () => {
it('should use the provided universalIdentifier', () => {
it('should produce a deterministic universalIdentifier via UUID v5', () => {
const result = buildNavigationFlatCommandMenuItem(baseArgs);
expect(result.universalIdentifier).toBe('nav-universal-1');
const expectedUniversalIdentifier = v5(
baseObjectMetadata.universalIdentifier,
NAVIGATION_COMMAND_UUID_NAMESPACE,
);
expect(result.universalIdentifier).toBe(expectedUniversalIdentifier);
});
it('should set label and shortLabel as interpolation templates', () => {
@@ -1,6 +1,7 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { FeatureFlagKey } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v5 } from 'uuid';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
@@ -53,7 +54,6 @@ export const buildNavigationFlatCommandMenuItem = ({
workspaceId,
position,
now,
universalIdentifier,
}: {
objectMetadata: {
id: string;
@@ -67,8 +67,12 @@ export const buildNavigationFlatCommandMenuItem = ({
workspaceId: string;
position: number;
now: string;
universalIdentifier: string;
}): FlatCommandMenuItem => {
const universalIdentifier = v5(
objectMetadata.universalIdentifier,
NAVIGATION_COMMAND_UUID_NAMESPACE,
);
const conditionalAvailabilityExpression =
buildNavigationConditionalAvailabilityExpression({
universalIdentifier: objectMetadata.universalIdentifier,
@@ -1,101 +0,0 @@
import { compositeTypeDefinitions, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { generateDeterministicIndexNameV2 } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name-v2';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
type FlatIndexFieldForName = {
fieldMetadataUniversalIdentifier: string;
order: number;
subFieldName: string | null;
};
export const computeFlatIndexNameOrThrow = ({
flatObjectMetadata,
objectFlatFieldMetadatas,
universalFlatIndexFieldMetadatas,
isUnique,
indexWhereClause,
}: {
flatObjectMetadata: UniversalFlatObjectMetadata;
objectFlatFieldMetadatas: UniversalFlatFieldMetadata[];
universalFlatIndexFieldMetadatas: FlatIndexFieldForName[];
isUnique: boolean;
indexWhereClause: string | null;
}): string => {
const orderedIndexColumnNames = [...universalFlatIndexFieldMetadatas]
.sort((a, b) => a.order - b.order)
.map((flatIndexField) => {
const relatedFlatFieldMetadata = objectFlatFieldMetadatas.find(
(flatFieldMetadata) =>
flatFieldMetadata.universalIdentifier ===
flatIndexField.fieldMetadataUniversalIdentifier,
);
if (!isDefined(relatedFlatFieldMetadata)) {
throw new FlatEntityMapsException(
'Could not find flat index field related field in cache',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
// Composite parent with an explicit sub-field → single sub-column.
// Composite parent without sub-field falls through to the legacy
// scalar branch below, which produces a deterministic name based on
// the parent name (the runner handles the multi-column SQL expansion
// via isIncludedInUniqueConstraint).
if (
isCompositeFieldMetadataType(relatedFlatFieldMetadata.type) &&
isDefined(flatIndexField.subFieldName)
) {
const property = compositeTypeDefinitions
.get(relatedFlatFieldMetadata.type)
?.properties.find(
(compositeProperty) =>
compositeProperty.name === flatIndexField.subFieldName,
);
if (!isDefined(property)) {
throw new FlatEntityMapsException(
`Composite sub-field "${flatIndexField.subFieldName}" not found on ${relatedFlatFieldMetadata.name}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
return computeCompositeColumnName(
{
name: relatedFlatFieldMetadata.name,
type: relatedFlatFieldMetadata.type,
},
property,
);
}
const isManyToOneRelation =
isMorphOrRelationUniversalFlatFieldMetadata(relatedFlatFieldMetadata) &&
relatedFlatFieldMetadata.universalSettings?.relationType ===
RelationType.MANY_TO_ONE;
return isManyToOneRelation
? computeMorphOrRelationFieldJoinColumnName({
name: relatedFlatFieldMetadata.name,
})
: relatedFlatFieldMetadata.name;
});
return generateDeterministicIndexNameV2({
flatObjectMetadata,
orderedIndexColumnNames,
isUnique,
indexWhereClause,
});
};
@@ -1,63 +0,0 @@
import { getIndexUniversalIdentifier } from 'twenty-shared/application';
import { computeFlatIndexNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/compute-flat-index-name-or-throw.util';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
type FlatIndexWithoutDeterministicIdentifiers = Omit<
UniversalFlatIndexMetadata,
| 'name'
| 'universalIdentifier'
| 'objectMetadataUniversalIdentifier'
| 'applicationUniversalIdentifier'
| 'universalFlatIndexFieldMetadatas'
> & {
universalFlatIndexFieldMetadatas: Array<
Omit<
UniversalFlatIndexMetadata['universalFlatIndexFieldMetadatas'][number],
'indexMetadataUniversalIdentifier'
>
>;
};
export const generateFlatIndexMetadataWithDeterministicUniversalIdentifierOrThrow =
({
flatObjectMetadata,
objectFlatFieldMetadatas,
flatIndex,
}: {
flatObjectMetadata: UniversalFlatObjectMetadata;
objectFlatFieldMetadatas: UniversalFlatFieldMetadata[];
flatIndex: FlatIndexWithoutDeterministicIdentifiers;
}): UniversalFlatIndexMetadata => {
const name = computeFlatIndexNameOrThrow({
flatObjectMetadata,
objectFlatFieldMetadatas,
universalFlatIndexFieldMetadatas:
flatIndex.universalFlatIndexFieldMetadatas,
isUnique: flatIndex.isUnique,
indexWhereClause: flatIndex.indexWhereClause,
});
const universalIdentifier = getIndexUniversalIdentifier({
applicationUniversalIdentifier:
flatObjectMetadata.applicationUniversalIdentifier,
objectUniversalIdentifier: flatObjectMetadata.universalIdentifier,
name,
});
return {
...flatIndex,
name,
universalIdentifier,
objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier,
applicationUniversalIdentifier:
flatObjectMetadata.applicationUniversalIdentifier,
universalFlatIndexFieldMetadatas:
flatIndex.universalFlatIndexFieldMetadatas.map((indexField) => ({
...indexField,
indexMetadataUniversalIdentifier: universalIdentifier,
})),
};
};
@@ -1,4 +1,15 @@
import { computeFlatIndexNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/compute-flat-index-name-or-throw.util';
import { compositeTypeDefinitions, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { generateDeterministicIndexNameV2 } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name-v2';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
@@ -14,11 +25,69 @@ export const generateFlatIndexMetadataWithNameOrThrow = ({
objectFlatFieldMetadatas,
flatIndex,
}: GenerateFlatIndexArgs): UniversalFlatIndexMetadata => {
const name = computeFlatIndexNameOrThrow({
const orderedIndexColumnNames = flatIndex.universalFlatIndexFieldMetadatas
.sort((a, b) => a.order - b.order)
.map((flatIndexField) => {
const relatedFlatFieldMetadata = objectFlatFieldMetadatas.find(
(flatFieldMetadata) =>
flatFieldMetadata.universalIdentifier ===
flatIndexField.fieldMetadataUniversalIdentifier,
);
if (!isDefined(relatedFlatFieldMetadata)) {
throw new FlatEntityMapsException(
'Could not find flat index field related field in cache',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
// Composite parent with an explicit sub-field → single sub-column.
// Composite parent without sub-field falls through to the legacy
// scalar branch below, which produces a deterministic name based on
// the parent name (the runner handles the multi-column SQL expansion
// via isIncludedInUniqueConstraint).
if (
isCompositeFieldMetadataType(relatedFlatFieldMetadata.type) &&
isDefined(flatIndexField.subFieldName)
) {
const property = compositeTypeDefinitions
.get(relatedFlatFieldMetadata.type)
?.properties.find(
(compositeProperty) =>
compositeProperty.name === flatIndexField.subFieldName,
);
if (!isDefined(property)) {
throw new FlatEntityMapsException(
`Composite sub-field "${flatIndexField.subFieldName}" not found on ${relatedFlatFieldMetadata.name}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
return computeCompositeColumnName(
{
name: relatedFlatFieldMetadata.name,
type: relatedFlatFieldMetadata.type,
},
property,
);
}
const isManyToOneRelation =
isMorphOrRelationUniversalFlatFieldMetadata(relatedFlatFieldMetadata) &&
relatedFlatFieldMetadata.universalSettings?.relationType ===
RelationType.MANY_TO_ONE;
return isManyToOneRelation
? computeMorphOrRelationFieldJoinColumnName({
name: relatedFlatFieldMetadata.name,
})
: relatedFlatFieldMetadata.name;
});
const name = generateDeterministicIndexNameV2({
flatObjectMetadata,
objectFlatFieldMetadatas,
universalFlatIndexFieldMetadatas:
flatIndex.universalFlatIndexFieldMetadatas,
orderedIndexColumnNames,
isUnique: flatIndex.isUnique,
indexWhereClause: flatIndex.indexWhereClause,
});
@@ -2,10 +2,6 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import {
getIndexViewUniversalIdentifier,
getNavigationCommandUniversalIdentifier,
} from 'twenty-shared/application';
import {
ViewKey,
ViewOpenRecordIn,
@@ -14,12 +10,15 @@ import {
} from 'twenty-shared/types';
import { fromArrayToUniqueKeyRecord, isDefined } from 'twenty-shared/utils';
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
import { v4 } from 'uuid';
import { v4, v5 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
import { buildNavigationFlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
import {
buildNavigationFlatCommandMenuItem,
NAVIGATION_COMMAND_UUID_NAMESPACE,
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
@@ -31,6 +30,9 @@ import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-meta
import { fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util';
import { fromDeleteObjectInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util';
import { fromUpdateObjectInputToFlatObjectMetadataAndRelatedFlatEntities } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata-and-related-flat-entities.util';
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';
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
import { NavigationMenuItemType } from 'src/engine/metadata-modules/navigation-menu-item/enums/navigation-menu-item-type.enum';
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
@@ -41,6 +43,7 @@ import {
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
@@ -417,8 +420,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
this.findNavigationCommandMenuItemForObject({
objectUniversalIdentifier:
flatObjectMetadataToDelete.universalIdentifier,
applicationUniversalIdentifier:
flatObjectMetadataToDelete.applicationUniversalIdentifier,
flatCommandMenuItemMaps,
}),
)
@@ -543,13 +544,28 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
},
);
const flatDefaultRecordPageLayoutsToCreate =
computeFlatDefaultRecordPageLayoutToCreate({
const flatRecordPageFieldsViewToCreate =
this.computeFlatRecordPageFieldsViewToCreate({
objectMetadata: flatObjectMetadataToCreate,
flatApplication: resolvedOwnerFlatApplication,
});
const flatRecordPageFieldsViewFieldsToCreate =
computeFlatViewFieldsToCreate({
flatApplication: resolvedOwnerFlatApplication,
objectFlatFieldMetadatas: flatFieldMetadataToCreateOnObject,
labelIdentifierFieldMetadataUniversalIdentifier:
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
viewUniversalIdentifier:
flatRecordPageFieldsViewToCreate.universalIdentifier,
excludeLabelIdentifier: true,
});
const flatDefaultRecordPageLayoutsToCreate =
this.computeFlatDefaultRecordPageLayoutToCreate({
objectMetadata: flatObjectMetadataToCreate,
flatApplication: resolvedOwnerFlatApplication,
recordPageFieldsView: flatRecordPageFieldsViewToCreate,
workspaceId,
});
@@ -565,7 +581,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
view: {
flatEntityToCreate: [
flatDefaultViewToCreate,
flatDefaultRecordPageLayoutsToCreate.recordPageFieldsView,
flatRecordPageFieldsViewToCreate,
],
flatEntityToDelete: [],
flatEntityToUpdate: [],
@@ -573,7 +589,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
viewField: {
flatEntityToCreate: [
...flatDefaultViewFieldsToCreate,
...flatDefaultRecordPageLayoutsToCreate.recordPageFieldsViewFields,
...flatRecordPageFieldsViewFieldsToCreate,
],
flatEntityToDelete: [],
flatEntityToUpdate: [],
@@ -697,10 +713,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
mainGroupByFieldMetadataUniversalIdentifier: null,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
position: 0,
universalIdentifier: getIndexViewUniversalIdentifier({
applicationUniversalIdentifier: flatApplication.universalIdentifier,
objectUniversalIdentifier: objectMetadata.universalIdentifier,
}),
universalIdentifier: v4(),
visibility: ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
isActive: true,
@@ -716,6 +729,42 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
};
}
private computeFlatRecordPageFieldsViewToCreate({
objectMetadata,
flatApplication,
}: {
flatApplication: FlatApplication;
objectMetadata: UniversalFlatObjectMetadata & { id: string };
}): UniversalFlatView & { id: string } {
return computeFlatRecordPageFieldsViewToCreate({
objectMetadata,
flatApplication,
});
}
private computeFlatDefaultRecordPageLayoutToCreate({
objectMetadata,
flatApplication,
recordPageFieldsView,
workspaceId,
}: {
flatApplication: FlatApplication;
objectMetadata: UniversalFlatObjectMetadata & { id: string };
recordPageFieldsView: UniversalFlatView & { id: string };
workspaceId: string;
}): {
pageLayouts: FlatPageLayout[];
pageLayoutTabs: FlatPageLayoutTab[];
pageLayoutWidgets: FlatPageLayoutWidget[];
} {
return computeFlatDefaultRecordPageLayoutToCreate({
objectMetadata,
flatApplication,
recordPageFieldsView,
workspaceId,
});
}
private async computeFlatNavigationMenuItemToCreate({
objectMetadata,
workspaceId,
@@ -814,29 +863,22 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
workspaceId,
position: nextPosition,
now: new Date().toISOString(),
universalIdentifier: getNavigationCommandUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier: objectMetadata.universalIdentifier,
}),
});
}
private findNavigationCommandMenuItemForObject({
objectUniversalIdentifier,
applicationUniversalIdentifier,
flatCommandMenuItemMaps,
}: {
objectUniversalIdentifier: string;
applicationUniversalIdentifier: string;
flatCommandMenuItemMaps: {
byUniversalIdentifier: Record<string, FlatCommandMenuItem | undefined>;
};
}): FlatCommandMenuItem | undefined {
const commandMenuItemUniversalIdentifier =
getNavigationCommandUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
});
const commandMenuItemUniversalIdentifier = v5(
objectUniversalIdentifier,
NAVIGATION_COMMAND_UUID_NAMESPACE,
);
return findFlatEntityByUniversalIdentifier({
flatEntityMaps: flatCommandMenuItemMaps,
@@ -877,8 +919,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
this.findNavigationCommandMenuItemForObject({
objectUniversalIdentifier:
existingFlatObjectMetadata.universalIdentifier,
applicationUniversalIdentifier:
existingFlatObjectMetadata.applicationUniversalIdentifier,
flatCommandMenuItemMaps,
});
@@ -912,8 +952,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
this.findNavigationCommandMenuItemForObject({
objectUniversalIdentifier:
existingFlatObjectMetadata.universalIdentifier,
applicationUniversalIdentifier:
existingFlatObjectMetadata.applicationUniversalIdentifier,
flatCommandMenuItemMaps,
});
@@ -40,7 +40,7 @@ const makeFieldMetadata = (
};
const flatApplication = {
universalIdentifier: '20202020-1111-4111-8111-111111111111',
universalIdentifier: 'app-uid',
id: 'app-id',
} as never;
@@ -1,5 +1,5 @@
import { getFieldUniversalIdentifier } from 'twenty-shared/application';
import { FieldMetadataType } from 'twenty-shared/types';
import { v4 } from 'uuid';
import { PARTIAL_SYSTEM_FLAT_FIELD_METADATAS } from 'src/engine/metadata-modules/object-metadata/constants/partial-system-flat-field-metadatas.constant';
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
@@ -40,17 +40,10 @@ const buildObjectSystemFlatFieldMetadatas = ({
updatedBy,
} = PARTIAL_SYSTEM_FLAT_FIELD_METADATAS;
const getSystemFieldUniversalIdentifier = (fieldName: string) =>
getFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier: objectMetadataUniversalIdentifier,
name: fieldName,
});
return {
id: {
...id,
universalIdentifier: getSystemFieldUniversalIdentifier(id.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -58,7 +51,7 @@ const buildObjectSystemFlatFieldMetadatas = ({
},
createdAt: {
...createdAt,
universalIdentifier: getSystemFieldUniversalIdentifier(createdAt.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -66,7 +59,7 @@ const buildObjectSystemFlatFieldMetadatas = ({
},
createdBy: {
...createdBy,
universalIdentifier: getSystemFieldUniversalIdentifier(createdBy.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -74,7 +67,7 @@ const buildObjectSystemFlatFieldMetadatas = ({
},
deletedAt: {
...deletedAt,
universalIdentifier: getSystemFieldUniversalIdentifier(deletedAt.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -82,7 +75,7 @@ const buildObjectSystemFlatFieldMetadatas = ({
},
position: {
...position,
universalIdentifier: getSystemFieldUniversalIdentifier(position.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -90,7 +83,7 @@ const buildObjectSystemFlatFieldMetadatas = ({
},
searchVector: {
...searchVector,
universalIdentifier: getSystemFieldUniversalIdentifier(searchVector.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -99,7 +92,7 @@ const buildObjectSystemFlatFieldMetadatas = ({
},
updatedAt: {
...updatedAt,
universalIdentifier: getSystemFieldUniversalIdentifier(updatedAt.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -107,7 +100,7 @@ const buildObjectSystemFlatFieldMetadatas = ({
},
updatedBy: {
...updatedBy,
universalIdentifier: getSystemFieldUniversalIdentifier(updatedBy.name),
universalIdentifier: v4(),
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
createdAt: now,
@@ -133,11 +126,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.TEXT,
isLabelSyncedWithName: false,
isUnique: false,
universalIdentifier: getFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier: objectMetadataUniversalIdentifier,
name: 'name',
}),
universalIdentifier: v4(),
name: 'name',
label: 'Name',
icon: 'IconAbc',
@@ -1,5 +1,7 @@
import { v4 } from 'uuid';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
import { generateFlatIndexMetadataWithDeterministicUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index-metadata-with-deterministic-universal-identifier.util';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
import { type DefaultFlatFieldForCustomObjectMaps } from 'src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type';
@@ -14,33 +16,39 @@ export const buildDefaultIndexesForCustomObject = ({
objectFlatFieldMetadatas: UniversalFlatFieldMetadata[];
defaultFlatFieldForCustomObjectMaps: DefaultFlatFieldForCustomObjectMaps;
}) => {
const createdAt = new Date().toISOString();
const tsFlatVectorIndexUniversalIdentifier = v4();
const createdAt = new Date();
const tsVectorFlatIndex = generateFlatIndexMetadataWithNameOrThrow({
objectFlatFieldMetadatas,
flatIndex: {
createdAt: createdAt.toISOString(),
universalFlatIndexFieldMetadatas: [
{
createdAt: createdAt.toISOString(),
fieldMetadataUniversalIdentifier:
defaultFlatFieldForCustomObjectMaps.fields.searchVector
.universalIdentifier,
indexMetadataUniversalIdentifier:
tsFlatVectorIndexUniversalIdentifier,
order: 0,
subFieldName: null,
updatedAt: createdAt.toISOString(),
},
],
const tsVectorFlatIndex =
generateFlatIndexMetadataWithDeterministicUniversalIdentifierOrThrow({
flatObjectMetadata,
objectFlatFieldMetadatas,
flatIndex: {
createdAt,
updatedAt: createdAt,
universalFlatIndexFieldMetadatas: [
{
createdAt,
updatedAt: createdAt,
fieldMetadataUniversalIdentifier:
defaultFlatFieldForCustomObjectMaps.fields.searchVector
.universalIdentifier,
order: 0,
subFieldName: null,
},
],
indexType: IndexType.GIN,
indexWhereClause: null,
isCustom: false,
isUnique: false,
isSystemSideEffect: true,
},
});
indexType: IndexType.GIN,
indexWhereClause: null,
isCustom: false,
isUnique: false,
isSystemSideEffect: true,
objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier,
universalIdentifier: tsFlatVectorIndexUniversalIdentifier,
updatedAt: createdAt.toISOString(),
applicationUniversalIdentifier:
flatObjectMetadata.applicationUniversalIdentifier,
},
flatObjectMetadata,
});
return {
indexes: {
@@ -1,52 +1,36 @@
import {
getPageLayoutTabUniversalIdentifier,
getPageLayoutWidgetUniversalIdentifier,
getRecordPageLayoutUniversalIdentifier,
} from 'twenty-shared/application';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.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';
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import {
TAB_PROPS,
WIDGET_PROPS,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
export const computeFlatDefaultRecordPageLayoutToCreate = ({
objectMetadata,
flatApplication,
objectFlatFieldMetadatas,
labelIdentifierFieldMetadataUniversalIdentifier,
recordPageFieldsView,
workspaceId,
}: {
flatApplication: FlatApplication;
objectMetadata: UniversalFlatObjectMetadata & { id: string };
objectFlatFieldMetadatas: UniversalFlatFieldMetadata[];
labelIdentifierFieldMetadataUniversalIdentifier: string | null;
recordPageFieldsView: UniversalFlatView & { id: string };
workspaceId: string;
}): {
pageLayouts: FlatPageLayout[];
pageLayoutTabs: FlatPageLayoutTab[];
pageLayoutWidgets: FlatPageLayoutWidget[];
recordPageFieldsView: UniversalFlatView & { id: string };
recordPageFieldsViewFields: UniversalFlatViewField[];
} => {
const now = new Date().toISOString();
const pageLayoutId = v4();
const pageLayoutUniversalIdentifier = getRecordPageLayoutUniversalIdentifier({
applicationUniversalIdentifier: flatApplication.universalIdentifier,
objectUniversalIdentifier: objectMetadata.universalIdentifier,
});
const pageLayoutUniversalIdentifier = v4();
const tabDefinitions = [
{ key: 'home' as const, widgetKey: 'fields' as const },
@@ -58,24 +42,14 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
const pageLayoutTabs: FlatPageLayoutTab[] = [];
const pageLayoutWidgets: FlatPageLayoutWidget[] = [];
let recordPageFieldsView: (UniversalFlatView & { id: string }) | undefined;
let recordPageFieldsViewFields: UniversalFlatViewField[] = [];
for (const { key, widgetKey } of tabDefinitions) {
const tabProps = TAB_PROPS[key];
const widgetProps = WIDGET_PROPS[widgetKey];
const tabId = v4();
const tabUniversalIdentifier = getPageLayoutTabUniversalIdentifier({
applicationUniversalIdentifier: flatApplication.universalIdentifier,
pageLayoutUniversalIdentifier,
title: tabProps.title,
});
const tabUniversalIdentifier = v4();
const widgetId = v4();
const widgetUniversalIdentifier = getPageLayoutWidgetUniversalIdentifier({
applicationUniversalIdentifier: flatApplication.universalIdentifier,
pageLayoutTabUniversalIdentifier: tabUniversalIdentifier,
title: widgetProps.title,
});
const widgetUniversalIdentifier = v4();
pageLayoutTabs.push({
id: tabId,
@@ -101,48 +75,31 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
const isFieldsWidget = widgetKey === 'fields';
if (isFieldsWidget) {
recordPageFieldsView = computeFlatRecordPageFieldsViewToCreate({
objectMetadata,
flatApplication,
pageLayoutWidgetUniversalIdentifier: widgetUniversalIdentifier,
});
recordPageFieldsViewFields = computeFlatViewFieldsToCreate({
flatApplication,
objectFlatFieldMetadatas,
labelIdentifierFieldMetadataUniversalIdentifier,
viewUniversalIdentifier: recordPageFieldsView.universalIdentifier,
excludeLabelIdentifier: true,
});
}
const configuration = isFieldsWidget
? {
configurationType: WidgetConfigurationType.FIELDS,
viewId: recordPageFieldsView.id,
newFieldDefaultVisibility: true,
}
: {
configurationType:
WidgetConfigurationType[
widgetKey.toUpperCase() as keyof typeof WidgetConfigurationType
],
};
const configuration =
isFieldsWidget && recordPageFieldsView
? {
configurationType: WidgetConfigurationType.FIELDS,
viewId: recordPageFieldsView.id,
newFieldDefaultVisibility: true,
}
: {
configurationType:
WidgetConfigurationType[
widgetKey.toUpperCase() as keyof typeof WidgetConfigurationType
],
};
const universalConfiguration =
isFieldsWidget && recordPageFieldsView
? {
configurationType: WidgetConfigurationType.FIELDS,
viewUniversalIdentifier: recordPageFieldsView.universalIdentifier,
newFieldDefaultVisibility: true,
}
: {
configurationType:
WidgetConfigurationType[
widgetKey.toUpperCase() as keyof typeof WidgetConfigurationType
],
};
const universalConfiguration = isFieldsWidget
? {
configurationType: WidgetConfigurationType.FIELDS,
viewUniversalIdentifier: recordPageFieldsView.universalIdentifier,
newFieldDefaultVisibility: true,
}
: {
configurationType:
WidgetConfigurationType[
widgetKey.toUpperCase() as keyof typeof WidgetConfigurationType
],
};
pageLayoutWidgets.push({
id: widgetId,
@@ -172,12 +129,6 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
});
}
if (!recordPageFieldsView) {
throw new Error(
'Record page fields widget is missing from the default record page layout',
);
}
const pageLayout: FlatPageLayout = {
id: pageLayoutId,
universalIdentifier: pageLayoutUniversalIdentifier,
@@ -200,11 +151,5 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null,
};
return {
pageLayouts: [pageLayout],
pageLayoutTabs,
pageLayoutWidgets,
recordPageFieldsView,
recordPageFieldsViewFields,
};
return { pageLayouts: [pageLayout], pageLayoutTabs, pageLayoutWidgets };
};
@@ -1,4 +1,3 @@
import { getFieldsWidgetViewUniversalIdentifier } from 'twenty-shared/application';
import {
ViewOpenRecordIn,
ViewType,
@@ -13,11 +12,9 @@ import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-m
export const computeFlatRecordPageFieldsViewToCreate = ({
objectMetadata,
flatApplication,
pageLayoutWidgetUniversalIdentifier,
}: {
flatApplication: FlatApplication;
objectMetadata: UniversalFlatObjectMetadata & { id: string };
pageLayoutWidgetUniversalIdentifier: string;
}): UniversalFlatView & { id: string } => {
const createdAt = new Date().toISOString();
@@ -43,10 +40,7 @@ export const computeFlatRecordPageFieldsViewToCreate = ({
mainGroupByFieldMetadataUniversalIdentifier: null,
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
position: 0,
universalIdentifier: getFieldsWidgetViewUniversalIdentifier({
applicationUniversalIdentifier: flatApplication.universalIdentifier,
pageLayoutWidgetUniversalIdentifier,
}),
universalIdentifier: v4(),
visibility: ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
isActive: true,
@@ -1,4 +1,4 @@
import { getViewFieldUniversalIdentifier } from 'twenty-shared/application';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant';
@@ -59,11 +59,7 @@ export const computeFlatViewFieldsToCreate = ({
createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: getViewFieldUniversalIdentifier({
applicationUniversalIdentifier: flatApplication.universalIdentifier,
viewUniversalIdentifier,
fieldMetadataUniversalIdentifier: field.universalIdentifier,
}),
universalIdentifier: v4(),
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
position: index,
@@ -1,7 +1,4 @@
import {
getNavigationCommandUniversalIdentifier,
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
} from 'twenty-shared/application';
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
@@ -81,11 +78,6 @@ export const buildStandardFlatCommandMenuItemMaps = ({
workspaceId,
position,
now,
universalIdentifier: getNavigationCommandUniversalIdentifier({
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: flatObject.universalIdentifier,
}),
});
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
@@ -1,19 +1,53 @@
import {
getCommandMenuItemUniversalIdentifier,
getGlobalCommandMenuItemUniversalIdentifier,
getGlobalObjectContextCommandMenuItemUniversalIdentifier,
getNavigationCommandUniversalIdentifier,
getRecordSelectionCommandMenuItemUniversalIdentifier,
} from '@/application/deterministic-identifier/get-command-menu-item-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getCommandMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the command label within its application', () => {
describe('getGlobalCommandMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the engineComponentKey within GLOBAL', () => {
expect(
getCommandMenuItemUniversalIdentifier({
getGlobalCommandMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
label: 'Create Record',
engineComponentKey: 'EXPORT_RECORDS',
}),
).toBe('e284adf7-5332-5253-9c94-9e6b3c57237f');
).toBe('bba78a2e-c117-52af-b25d-126702e8cf57');
});
});
describe('getGlobalObjectContextCommandMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the engineComponentKey within GLOBAL_OBJECT_CONTEXT', () => {
expect(
getGlobalObjectContextCommandMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
engineComponentKey: 'CREATE_NEW_RECORD',
}),
).toBe('77fb3fcd-ee35-50ea-9c3c-ab95cef5e0e2');
});
});
describe('getRecordSelectionCommandMenuItemUniversalIdentifier', () => {
it('includes the target object when scoped to one', () => {
expect(
getRecordSelectionCommandMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
engineComponentKey: 'COMPOSE_EMAIL',
objectUniversalIdentifier: OBJECT,
}),
).toBe('dd10a2b1-229b-572a-a887-ea20e487cfd3');
});
it('omits the object for object-agnostic record-selection commands', () => {
expect(
getRecordSelectionCommandMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
engineComponentKey: 'DELETE_RECORDS',
}),
).toBe('36b20ed1-6172-5da5-9a21-58de9c8acb22');
});
});
@@ -1,14 +1,54 @@
import { getNavigationMenuItemUniversalIdentifier } from '@/application/deterministic-identifier/get-navigation-menu-item-universal-identifier.util';
import {
getFolderNavigationMenuItemUniversalIdentifier,
getLinkNavigationMenuItemUniversalIdentifier,
getObjectNavigationMenuItemUniversalIdentifier,
getViewNavigationMenuItemUniversalIdentifier,
} from '@/application/deterministic-identifier/get-navigation-menu-item-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
const VIEW = '44444444-4444-4444-8444-444444444444';
describe('getNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the item name within its application', () => {
describe('getFolderNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the folder name within its application', () => {
expect(
getNavigationMenuItemUniversalIdentifier({
getFolderNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
name: 'Home',
name: 'Workflows',
}),
).toBe('dc0ca522-7f09-567d-a7f7-d33824495fc4');
).toBe('ca043c51-ac75-515d-902b-dd836626bba4');
});
});
describe('getObjectNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the object it targets', () => {
expect(
getObjectNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
}),
).toBe('071361bd-47c8-5513-be33-998dd287f8c7');
});
});
describe('getViewNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the view it targets', () => {
expect(
getViewNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
}),
).toBe('6504fd6d-ed38-5f34-87fc-26025130260f');
});
});
describe('getLinkNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from its target URL', () => {
expect(
getLinkNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
link: 'https://example.com',
}),
).toBe('635cef97-cc61-53a3-873e-38fecf3ecbd7');
});
});
@@ -0,0 +1,15 @@
import { getSearchFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-search-field-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getSearchFieldUniversalIdentifier', () => {
it('derives a deterministic id from the field it makes searchable', () => {
expect(
getSearchFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
fieldMetadataUniversalIdentifier: FIELD,
}),
).toBe('db4e5b93-15b6-5c83-a0b3-6c031e0ca072');
});
});
@@ -0,0 +1,16 @@
import { getViewFieldGroupUniversalIdentifier } from '@/application/deterministic-identifier/get-view-field-group-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const VIEW = '44444444-4444-4444-8444-444444444444';
describe('getViewFieldGroupUniversalIdentifier', () => {
it('derives a deterministic id from the group name within its view', () => {
expect(
getViewFieldGroupUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
name: 'General',
}),
).toBe('26350cf1-bec3-5617-b525-c0115811963e');
});
});
@@ -2,18 +2,55 @@ import { computeDeterministicUuid } from '@/application/deterministic-identifier
const NAVIGATION_COMMAND_DISCRIMINATOR = 'navigation';
// A command menu item is identified by its label within its application
// (not every command is backed by a front component).
export const getCommandMenuItemUniversalIdentifier = ({
// A command menu item is identified by its availabilityType + engineComponentKey
// (the same engineComponentKey is reused across availability types and, for
// RECORD_SELECTION, across objects). One util per availabilityType, prefixed by
// the type, with the target object added only for RECORD_SELECTION.
// GLOBAL: a command available everywhere, identified by its engineComponentKey.
export const getGlobalCommandMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
label,
engineComponentKey,
}: {
applicationUniversalIdentifier: string;
label: string;
engineComponentKey: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'commandMenuItem',
value: label,
value: `GLOBAL:${engineComponentKey}`,
applicationUniversalIdentifier,
});
// GLOBAL_OBJECT_CONTEXT: a command whose object is resolved at runtime (not stored),
// identified by its engineComponentKey.
export const getGlobalObjectContextCommandMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
engineComponentKey,
}: {
applicationUniversalIdentifier: string;
engineComponentKey: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'commandMenuItem',
value: `GLOBAL_OBJECT_CONTEXT:${engineComponentKey}`,
applicationUniversalIdentifier,
});
// RECORD_SELECTION: a command acting on selected records; the same engineComponentKey
// is replicated per object, so the target object is part of the identity
// (null/absent for the object-agnostic record-selection commands).
export const getRecordSelectionCommandMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
engineComponentKey,
objectUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
engineComponentKey: string;
objectUniversalIdentifier?: string | null;
}): string =>
computeDeterministicUuid({
entityNamespace: 'commandMenuItem',
value: `RECORD_SELECTION:${engineComponentKey}:${objectUniversalIdentifier ?? ''}`,
applicationUniversalIdentifier,
});
@@ -1,7 +1,12 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// A navigation menu item is identified by its name within its application.
export const getNavigationMenuItemUniversalIdentifier = ({
// NavigationMenuItem is a single polymorphic entity: every variant shares
// name/icon/color/position/folder, but each type is identified by a different
// target, so each variant has its own discriminator (prefixed by the type to
// keep the shared `navigationMenuItem` namespace collision-free).
// A FOLDER navigation item is identified by its name within its application.
export const getFolderNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
name,
}: {
@@ -10,6 +15,48 @@ export const getNavigationMenuItemUniversalIdentifier = ({
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: name,
value: `FOLDER:${name}`,
applicationUniversalIdentifier,
});
// An OBJECT navigation item is identified by the object it targets.
export const getObjectNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
objectUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: `OBJECT:${objectUniversalIdentifier}`,
applicationUniversalIdentifier,
});
// A VIEW navigation item is identified by the view it targets.
export const getViewNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
viewUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: `VIEW:${viewUniversalIdentifier}`,
applicationUniversalIdentifier,
});
// A LINK navigation item is identified by its target URL.
export const getLinkNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
link,
}: {
applicationUniversalIdentifier: string;
link: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: `LINK:${link}`,
applicationUniversalIdentifier,
});
@@ -0,0 +1,15 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// A search field is 1:1 with the field it makes searchable, so it is keyed by that field.
export const getSearchFieldUniversalIdentifier = ({
applicationUniversalIdentifier,
fieldMetadataUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
fieldMetadataUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'searchFieldMetadata',
value: fieldMetadataUniversalIdentifier,
applicationUniversalIdentifier,
});
@@ -0,0 +1,17 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// A view field group is identified by its name within its view.
export const getViewFieldGroupUniversalIdentifier = ({
applicationUniversalIdentifier,
viewUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'viewFieldGroup',
value: `${viewUniversalIdentifier}:${name}`,
applicationUniversalIdentifier,
});
@@ -31,7 +31,9 @@ export type { DeterministicEntityNamespace } from './deterministic-identifier/de
export { getAgentUniversalIdentifier } from './deterministic-identifier/get-agent-universal-identifier.util';
export { getApplicationVariableUniversalIdentifier } from './deterministic-identifier/get-application-variable-universal-identifier.util';
export {
getCommandMenuItemUniversalIdentifier,
getGlobalCommandMenuItemUniversalIdentifier,
getGlobalObjectContextCommandMenuItemUniversalIdentifier,
getRecordSelectionCommandMenuItemUniversalIdentifier,
getNavigationCommandUniversalIdentifier,
} from './deterministic-identifier/get-command-menu-item-universal-identifier.util';
export { getConnectionProviderUniversalIdentifier } from './deterministic-identifier/get-connection-provider-universal-identifier.util';
@@ -40,7 +42,12 @@ export { getFieldUniversalIdentifier } from './deterministic-identifier/get-fiel
export { getFrontComponentUniversalIdentifier } from './deterministic-identifier/get-front-component-universal-identifier.util';
export { getIndexUniversalIdentifier } from './deterministic-identifier/get-index-universal-identifier.util';
export { getLogicFunctionUniversalIdentifier } from './deterministic-identifier/get-logic-function-universal-identifier.util';
export { getNavigationMenuItemUniversalIdentifier } from './deterministic-identifier/get-navigation-menu-item-universal-identifier.util';
export {
getFolderNavigationMenuItemUniversalIdentifier,
getObjectNavigationMenuItemUniversalIdentifier,
getViewNavigationMenuItemUniversalIdentifier,
getLinkNavigationMenuItemUniversalIdentifier,
} from './deterministic-identifier/get-navigation-menu-item-universal-identifier.util';
export { getObjectPermissionUniversalIdentifier } from './deterministic-identifier/get-object-permission-universal-identifier.util';
export { getObjectUniversalIdentifier } from './deterministic-identifier/get-object-universal-identifier.util';
export { getPageLayoutTabUniversalIdentifier } from './deterministic-identifier/get-page-layout-tab-universal-identifier.util';
@@ -52,8 +59,10 @@ export { getPageLayoutWidgetUniversalIdentifier } from './deterministic-identifi
export { getPermissionFlagUniversalIdentifier } from './deterministic-identifier/get-permission-flag-universal-identifier.util';
export { getRolePermissionFlagUniversalIdentifier } from './deterministic-identifier/get-role-permission-flag-universal-identifier.util';
export { getRoleUniversalIdentifier } from './deterministic-identifier/get-role-universal-identifier.util';
export { getSearchFieldUniversalIdentifier } from './deterministic-identifier/get-search-field-universal-identifier.util';
export { getSelectOptionUniversalIdentifier } from './deterministic-identifier/get-select-option-universal-identifier.util';
export { getSkillUniversalIdentifier } from './deterministic-identifier/get-skill-universal-identifier.util';
export { getViewFieldGroupUniversalIdentifier } from './deterministic-identifier/get-view-field-group-universal-identifier.util';
export { getViewFieldUniversalIdentifier } from './deterministic-identifier/get-view-field-universal-identifier.util';
export { getViewFilterUniversalIdentifier } from './deterministic-identifier/get-view-filter-universal-identifier.util';
export { getViewGroupUniversalIdentifier } from './deterministic-identifier/get-view-group-universal-identifier.util';