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:
+29
-3
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -105,6 +105,9 @@ export class CommandMenuItemDTO {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
applicationId?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: false })
|
||||
isActive: boolean;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
+29
-2
@@ -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');
|
||||
|
||||
+1
@@ -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(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user