CommandMenuItem overridable entity (#21486)

## Context
Second PR of the overridable-entities track (after #21436 for views):
command menu items become overridable so that edits on non-owned items
are stored as overrides instead of mutating the row, and
deletion/deactivation becomes reversible.

 ## What this does

- `CommandMenuItemEntity` now extends
`OverridableEntity<CommandMenuItemOverrides>` (adds `isActive` +
`overrides`). All editable properties are overridable for now (to
discuss).
- **Update**: mutations on a command item not owned by the caller
(standard items) are written into `overrides`; reads merge them in the
DTO. The command palette edit mode (pin,
reorder, shortLabel) now preserves standard values, "Reset label to
default" gains true post-save semantics.
- **Delete**: protected items are deactivated (`isActive = false`)
instead of deleted; custom items still hard-delete.
- **Object deactivate/enable toggle**: now flips `isActive` on the
command item (merged into the main migration call) instead of
delete/recreate; a create-if-missing fallback covers legacy deactivated
objects.
- **Front**: inactive command items are filtered out of the palette
selector.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21486?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Weiko
2026-06-13 14:21:27 +02:00
committed by GitHub
parent 036d9a2bcf
commit f63f053444
34 changed files with 481 additions and 78 deletions
@@ -26,6 +26,7 @@ import { fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow } fr
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
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 { isCallerOverridingEntity } from 'src/engine/metadata-modules/utils/is-caller-overriding-entity.util';
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
@@ -198,6 +199,10 @@ export class CommandMenuItemService {
updateCommandMenuItemInput: input,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatPageLayoutMaps: existingFlatPageLayoutMaps,
callerApplicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
workspaceCustomApplicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
});
const validateAndBuildResult =
@@ -260,14 +265,33 @@ export class CommandMenuItemService {
commandMenuItemId: id,
});
const shouldDeactivate = isCallerOverridingEntity({
callerApplicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
entityApplicationUniversalIdentifier:
flatCommandMenuItemToDelete.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
});
const deactivatedFlatCommandMenuItem = {
...flatCommandMenuItemToDelete,
isActive: false,
updatedAt: new Date().toISOString(),
};
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
commandMenuItem: {
flatEntityToCreate: [],
flatEntityToDelete: [flatCommandMenuItemToDelete],
flatEntityToUpdate: [],
flatEntityToDelete: shouldDeactivate
? []
: [flatCommandMenuItemToDelete],
flatEntityToUpdate: shouldDeactivate
? [deactivatedFlatCommandMenuItem]
: [],
},
},
workspaceId,
@@ -285,7 +309,9 @@ export class CommandMenuItemService {
}
return fromFlatCommandMenuItemToCommandMenuItemDto(
flatCommandMenuItemToDelete,
shouldDeactivate
? deactivatedFlatCommandMenuItem
: flatCommandMenuItemToDelete,
);
}
@@ -105,6 +105,9 @@ export class CommandMenuItemDTO {
@Field(() => UUIDScalarType, { nullable: true })
applicationId?: string;
@Field(() => Boolean, { nullable: false })
isActive: boolean;
@IsDateString()
@Field()
createdAt: Date;
@@ -10,14 +10,29 @@ import {
type Relation,
UpdateDateColumn,
} from 'typeorm';
import { type SerializedRelation } from 'twenty-shared/types';
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
import { type CommandMenuItemPayload } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item-payload.union';
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';
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { OverridableEntity } from 'src/engine/workspace-manager/types/overridable-entity';
export type CommandMenuItemOverrides = {
label?: string;
icon?: string | null;
shortLabel?: string | null;
position?: number;
isPinned?: boolean;
hotKeys?: string[] | null;
availabilityType?: CommandMenuItemAvailabilityType;
availabilityObjectMetadataId?: SerializedRelation | null;
engineComponentKey?: EngineComponentKey;
pageLayoutId?: SerializedRelation | null;
};
@Entity({ name: 'commandMenuItem', schema: 'core' })
@Index('IDX_COMMAND_MENU_ITEM_WORKFLOW_VERSION_ID_WORKSPACE_ID', [
@@ -40,7 +55,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti
`("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'NAVIGATION' AND "payload" IS NOT NULL AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER', 'NAVIGATION') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "payload" IS NULL)`,
)
export class CommandMenuItemEntity
extends SyncableEntity
extends OverridableEntity<CommandMenuItemOverrides>
implements Required<CommandMenuItemEntity>
{
@PrimaryGeneratedColumn('uuid')
@@ -120,3 +135,15 @@ export class CommandMenuItemEntity
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
}
const COMMAND_MENU_ITEM_OVERRIDABLE_COLUMNS_UPGRADE_COMMAND_NAME =
'2.13.0_CommandMenuItemOverridableEntityFastInstanceCommand_1781253016028';
WasIntroducedInUpgrade({
upgradeCommandName:
COMMAND_MENU_ITEM_OVERRIDABLE_COLUMNS_UPGRADE_COMMAND_NAME,
})(CommandMenuItemEntity.prototype, 'overrides');
WasIntroducedInUpgrade({
upgradeCommandName:
COMMAND_MENU_ITEM_OVERRIDABLE_COLUMNS_UPGRADE_COMMAND_NAME,
})(CommandMenuItemEntity.prototype, 'isActive');
@@ -34,6 +34,7 @@ const baseCommandMenuItem = {
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
payload: { objectMetadataItemId: 'obj-id-1' },
workspaceId: 'ws-id-1',
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
};
@@ -100,6 +100,9 @@ export const buildNavigationFlatCommandMenuItem = ({
availabilityObjectMetadataUniversalIdentifier: null,
pageLayoutId: null,
pageLayoutUniversalIdentifier: null,
isActive: true,
overrides: null,
universalOverrides: null,
createdAt: now,
updatedAt: now,
};
@@ -1,5 +1,6 @@
import { isDefined } from 'twenty-shared/utils';
import { fromCommandMenuItemOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-overrides-to-universal-overrides.util';
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
import {
FlatEntityMapsException,
@@ -74,6 +75,19 @@ export const fromCommandMenuItemEntityToFlatCommandMenuItem = ({
}
}
const universalOverrides = isDefined(commandMenuItemEntity.overrides)
? fromCommandMenuItemOverridesToUniversalOverrides({
overrides: commandMenuItemEntity.overrides,
objectMetadataUniversalIdentifierById: Object.fromEntries(
objectMetadataIdToUniversalIdentifierMap.entries(),
),
pageLayoutUniversalIdentifierById: Object.fromEntries(
pageLayoutIdToUniversalIdentifierMap.entries(),
),
shouldThrowOnMissingIdentifier: false,
})
: null;
return {
id: commandMenuItemEntity.id,
workflowVersionId: commandMenuItemEntity.workflowVersionId,
@@ -101,5 +115,8 @@ export const fromCommandMenuItemEntityToFlatCommandMenuItem = ({
frontComponentUniversalIdentifier,
pageLayoutId: commandMenuItemEntity.pageLayoutId,
pageLayoutUniversalIdentifier,
isActive: commandMenuItemEntity.isActive,
overrides: commandMenuItemEntity.overrides,
universalOverrides,
};
};
@@ -0,0 +1,78 @@
import { type FormatRecordSerializedRelationProperties } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type CommandMenuItemOverrides } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
type UniversalCommandMenuItemOverrides =
FormatRecordSerializedRelationProperties<CommandMenuItemOverrides>;
const COMMAND_MENU_ITEM_OVERRIDES_FOREIGN_KEYS = [
{
foreignKey: 'availabilityObjectMetadataId',
universalProperty: 'availabilityObjectMetadataUniversalIdentifier',
mapName: 'objectMetadata',
},
{
foreignKey: 'pageLayoutId',
universalProperty: 'pageLayoutUniversalIdentifier',
mapName: 'pageLayout',
},
] as const;
export const fromCommandMenuItemOverridesToUniversalOverrides = ({
overrides,
objectMetadataUniversalIdentifierById,
pageLayoutUniversalIdentifierById,
shouldThrowOnMissingIdentifier = true,
}: {
overrides: CommandMenuItemOverrides;
objectMetadataUniversalIdentifierById: Partial<Record<string, string>>;
pageLayoutUniversalIdentifierById: Partial<Record<string, string>>;
shouldThrowOnMissingIdentifier?: boolean;
}): UniversalCommandMenuItemOverrides => {
const {
availabilityObjectMetadataId: _availabilityObjectMetadataId,
pageLayoutId: _pageLayoutId,
...scalarOverrides
} = overrides;
const universalIdentifierByIdByMapName = {
objectMetadata: objectMetadataUniversalIdentifierById,
pageLayout: pageLayoutUniversalIdentifierById,
};
return COMMAND_MENU_ITEM_OVERRIDES_FOREIGN_KEYS.reduce<UniversalCommandMenuItemOverrides>(
(acc, { foreignKey, universalProperty, mapName }) => {
const foreignKeyValue = overrides[foreignKey];
if (foreignKeyValue === undefined) {
return acc;
}
if (foreignKeyValue === null) {
return { ...acc, [universalProperty]: null };
}
const universalIdentifier =
universalIdentifierByIdByMapName[mapName][foreignKeyValue];
if (!isDefined(universalIdentifier)) {
if (shouldThrowOnMissingIdentifier) {
throw new FlatEntityMapsException(
`${mapName} universal identifier not found for id: ${foreignKeyValue}`,
FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND,
);
}
return { ...acc, [universalProperty]: null };
}
return { ...acc, [universalProperty]: universalIdentifier };
},
scalarOverrides,
);
};
@@ -76,6 +76,9 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({
workspaceId,
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
isActive: true,
overrides: null,
universalOverrides: null,
createdAt: now,
updatedAt: now,
};
@@ -3,26 +3,37 @@ import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-comma
export const fromFlatCommandMenuItemToCommandMenuItemDto = (
flatCommandMenuItem: FlatCommandMenuItem,
): CommandMenuItemDTO => ({
id: flatCommandMenuItem.id,
workflowVersionId: flatCommandMenuItem.workflowVersionId ?? undefined,
frontComponentId: flatCommandMenuItem.frontComponentId ?? undefined,
engineComponentKey: flatCommandMenuItem.engineComponentKey,
label: flatCommandMenuItem.label,
icon: flatCommandMenuItem.icon ?? undefined,
shortLabel: flatCommandMenuItem.shortLabel ?? undefined,
position: flatCommandMenuItem.position,
isPinned: flatCommandMenuItem.isPinned,
payload: flatCommandMenuItem.payload ?? undefined,
hotKeys: flatCommandMenuItem.hotKeys ?? undefined,
availabilityType: flatCommandMenuItem.availabilityType,
conditionalAvailabilityExpression:
flatCommandMenuItem.conditionalAvailabilityExpression ?? undefined,
availabilityObjectMetadataId:
flatCommandMenuItem.availabilityObjectMetadataId ?? undefined,
pageLayoutId: flatCommandMenuItem.pageLayoutId ?? undefined,
workspaceId: flatCommandMenuItem.workspaceId,
applicationId: flatCommandMenuItem.applicationId ?? undefined,
createdAt: new Date(flatCommandMenuItem.createdAt),
updatedAt: new Date(flatCommandMenuItem.updatedAt),
});
): CommandMenuItemDTO => {
const effectiveFlatCommandMenuItem = {
...flatCommandMenuItem,
...(flatCommandMenuItem.overrides ?? {}),
};
return {
id: effectiveFlatCommandMenuItem.id,
workflowVersionId:
effectiveFlatCommandMenuItem.workflowVersionId ?? undefined,
frontComponentId:
effectiveFlatCommandMenuItem.frontComponentId ?? undefined,
engineComponentKey: effectiveFlatCommandMenuItem.engineComponentKey,
label: effectiveFlatCommandMenuItem.label,
icon: effectiveFlatCommandMenuItem.icon ?? undefined,
shortLabel: effectiveFlatCommandMenuItem.shortLabel ?? undefined,
position: effectiveFlatCommandMenuItem.position,
isPinned: effectiveFlatCommandMenuItem.isPinned,
payload: effectiveFlatCommandMenuItem.payload ?? undefined,
hotKeys: effectiveFlatCommandMenuItem.hotKeys ?? undefined,
availabilityType: effectiveFlatCommandMenuItem.availabilityType,
conditionalAvailabilityExpression:
effectiveFlatCommandMenuItem.conditionalAvailabilityExpression ??
undefined,
availabilityObjectMetadataId:
effectiveFlatCommandMenuItem.availabilityObjectMetadataId ?? undefined,
pageLayoutId: effectiveFlatCommandMenuItem.pageLayoutId ?? undefined,
workspaceId: effectiveFlatCommandMenuItem.workspaceId,
applicationId: effectiveFlatCommandMenuItem.applicationId ?? undefined,
isActive: effectiveFlatCommandMenuItem.isActive,
createdAt: new Date(effectiveFlatCommandMenuItem.createdAt),
updatedAt: new Date(effectiveFlatCommandMenuItem.updatedAt),
};
};
@@ -5,12 +5,16 @@ import {
CommandMenuItemExceptionCode,
} from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception';
import { type UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input';
import { type CommandMenuItemOverrides } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
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_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-command-menu-item/constants/flat-command-menu-item-editable-properties.constant';
import { type FlatCommandMenuItemMaps } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item-maps.type';
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
import { fromCommandMenuItemOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-overrides-to-universal-overrides.util';
import { isCallerOverridingEntity } from 'src/engine/metadata-modules/utils/is-caller-overriding-entity.util';
import { sanitizeOverridableEntityInput } from 'src/engine/metadata-modules/utils/sanitize-overridable-entity-input.util';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow =
@@ -19,9 +23,13 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow
updateCommandMenuItemInput,
flatObjectMetadataMaps,
flatPageLayoutMaps,
callerApplicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
}: {
flatCommandMenuItemMaps: FlatCommandMenuItemMaps;
updateCommandMenuItemInput: UpdateCommandMenuItemInput;
callerApplicationUniversalIdentifier: string;
workspaceCustomApplicationUniversalIdentifier: string;
} & Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps' | 'flatPageLayoutMaps'
@@ -40,22 +48,41 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow
const { id: _id, ...updates } = updateCommandMenuItemInput;
const flatCommandMenuItemToUpdate = {
...mergeUpdateInExistingRecord({
existing: existingFlatCommandMenuItem,
properties: [...FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES],
update: updates,
}),
const shouldOverride = isCallerOverridingEntity({
callerApplicationUniversalIdentifier,
entityApplicationUniversalIdentifier:
existingFlatCommandMenuItem.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
});
const { overrides, updatedEditableProperties } =
sanitizeOverridableEntityInput({
metadataName: 'commandMenuItem',
existingFlatEntity: existingFlatCommandMenuItem,
updatedEditableProperties: updates,
shouldOverride,
});
const mergedRecord = mergeUpdateInExistingRecord({
existing: existingFlatCommandMenuItem,
properties: [...FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES],
update: updatedEditableProperties,
});
const flatCommandMenuItemToUpdate: FlatCommandMenuItem = {
...mergedRecord,
// sanitizeOverridableEntityInput returns a loosely-typed Record overrides
overrides: overrides as FlatCommandMenuItem['overrides'],
updatedAt: new Date().toISOString(),
};
if (updates.availabilityObjectMetadataId !== undefined) {
if (updatedEditableProperties.availabilityObjectMetadataId !== undefined) {
const { availabilityObjectMetadataUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'commandMenuItem',
foreignKeyValues: {
availabilityObjectMetadataId:
flatCommandMenuItemToUpdate.availabilityObjectMetadataId,
mergedRecord.availabilityObjectMetadataId,
},
flatEntityMaps: { flatObjectMetadataMaps },
});
@@ -64,12 +91,12 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow
availabilityObjectMetadataUniversalIdentifier;
}
if (updates.pageLayoutId !== undefined) {
if (updatedEditableProperties.pageLayoutId !== undefined) {
const { pageLayoutUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'commandMenuItem',
foreignKeyValues: {
pageLayoutId: flatCommandMenuItemToUpdate.pageLayoutId,
pageLayoutId: mergedRecord.pageLayoutId,
},
flatEntityMaps: { flatPageLayoutMaps },
});
@@ -78,5 +105,18 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow
pageLayoutUniversalIdentifier;
}
if (isDefined(overrides)) {
flatCommandMenuItemToUpdate.universalOverrides =
fromCommandMenuItemOverridesToUniversalOverrides({
overrides: overrides as CommandMenuItemOverrides,
objectMetadataUniversalIdentifierById:
flatObjectMetadataMaps.universalIdentifierById,
pageLayoutUniversalIdentifierById:
flatPageLayoutMaps.universalIdentifierById,
});
} else {
flatCommandMenuItemToUpdate.universalOverrides = null;
}
return flatCommandMenuItemToUpdate;
};
@@ -41,10 +41,13 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
"engineComponentKey",
"payload",
"hotKeys",
"isActive",
"universalOverrides",
],
"propertiesToStringify": [
"payload",
"hotKeys",
"universalOverrides",
],
},
"connectionProvider": {
@@ -1096,27 +1096,37 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
icon: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
icon: { toCompare: true, toStringify: false, universalProperty: undefined },
shortLabel: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
position: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
isPinned: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
availabilityType: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
conditionalAvailabilityExpression: {
toCompare: true,
@@ -1127,6 +1137,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toCompare: true,
toStringify: false,
universalProperty: 'availabilityObjectMetadataUniversalIdentifier',
isOverridable: true,
},
createdAt: {
toCompare: false,
@@ -1147,6 +1158,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
payload: {
toCompare: true,
@@ -1157,6 +1169,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toCompare: true,
toStringify: true,
universalProperty: undefined,
isOverridable: true,
},
workflowVersionId: {
toCompare: false,
@@ -1167,6 +1180,18 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toCompare: false,
toStringify: false,
universalProperty: 'pageLayoutUniversalIdentifier',
isOverridable: true,
},
isActive: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: false,
},
overrides: {
toCompare: true,
toStringify: true,
universalProperty: 'universalOverrides',
},
},
navigationMenuItem: {
@@ -138,7 +138,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
isDefined(existingFlatObjectMetadata) &&
existingFlatObjectMetadata.isActive;
const { commandMenuItemsToCreate, commandMenuItemsToDelete } =
const { commandMenuItemsToCreate, commandMenuItemsToUpdate } =
this.computeCommandMenuItemChangesForActiveToggle({
isBeingEnabled,
isBeingDisabled,
@@ -175,6 +175,11 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
flatEntityToDelete: [],
flatEntityToUpdate: flatViewFieldsToUpdate,
},
commandMenuItem: {
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: commandMenuItemsToUpdate,
},
},
workspaceId,
isSystemBuild: false,
@@ -190,18 +195,14 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
);
}
const hasCommandMenuItemChanges =
commandMenuItemsToCreate.length > 0 ||
commandMenuItemsToDelete.length > 0;
if (hasCommandMenuItemChanges) {
if (commandMenuItemsToCreate.length > 0) {
const commandMenuItemMigrationResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
commandMenuItem: {
flatEntityToCreate: commandMenuItemsToCreate,
flatEntityToDelete: commandMenuItemsToDelete,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
@@ -919,12 +920,14 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
applicationId: string;
}): {
commandMenuItemsToCreate: FlatCommandMenuItem[];
commandMenuItemsToDelete: FlatCommandMenuItem[];
commandMenuItemsToUpdate: FlatCommandMenuItem[];
} {
if (!isDefined(existingFlatObjectMetadata)) {
return { commandMenuItemsToCreate: [], commandMenuItemsToDelete: [] };
return { commandMenuItemsToCreate: [], commandMenuItemsToUpdate: [] };
}
const now = new Date().toISOString();
if (isBeingEnabled) {
const existingCommandMenuItem =
this.findNavigationCommandMenuItemForObject({
@@ -943,28 +946,42 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
flatCommandMenuItemMaps,
}),
],
commandMenuItemsToDelete: [],
commandMenuItemsToUpdate: [],
};
}
if (!existingCommandMenuItem.isActive) {
return {
commandMenuItemsToCreate: [],
commandMenuItemsToUpdate: [
{ ...existingCommandMenuItem, isActive: true, updatedAt: now },
],
};
}
}
if (isBeingDisabled) {
const commandMenuItemToDelete =
const commandMenuItemToDeactivate =
this.findNavigationCommandMenuItemForObject({
objectUniversalIdentifier:
existingFlatObjectMetadata.universalIdentifier,
flatCommandMenuItemMaps,
});
if (isDefined(commandMenuItemToDelete)) {
if (
isDefined(commandMenuItemToDeactivate) &&
commandMenuItemToDeactivate.isActive
) {
return {
commandMenuItemsToCreate: [],
commandMenuItemsToDelete: [commandMenuItemToDelete],
commandMenuItemsToUpdate: [
{ ...commandMenuItemToDeactivate, isActive: false, updatedAt: now },
],
};
}
}
return { commandMenuItemsToCreate: [], commandMenuItemsToDelete: [] };
return { commandMenuItemsToCreate: [], commandMenuItemsToUpdate: [] };
}
public async findOneWithinWorkspace(