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:
@@ -345,6 +345,7 @@ type CommandMenuItem {
|
||||
pageLayoutId: UUID
|
||||
universalIdentifier: UUID
|
||||
applicationId: UUID
|
||||
isActive: Boolean!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
@@ -299,6 +299,7 @@ export interface CommandMenuItem {
|
||||
pageLayoutId?: Scalars['UUID']
|
||||
universalIdentifier?: Scalars['UUID']
|
||||
applicationId?: Scalars['UUID']
|
||||
isActive: Scalars['Boolean']
|
||||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
__typename: 'CommandMenuItem'
|
||||
@@ -3205,6 +3206,7 @@ export interface CommandMenuItemGenqlSelection{
|
||||
pageLayoutId?: boolean | number
|
||||
universalIdentifier?: boolean | number
|
||||
applicationId?: boolean | number
|
||||
isActive?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
|
||||
@@ -769,6 +769,9 @@ export default {
|
||||
"applicationId": [
|
||||
3
|
||||
],
|
||||
"isActive": [
|
||||
6
|
||||
],
|
||||
"createdAt": [
|
||||
4
|
||||
],
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
@@ -30,5 +30,6 @@ export const COMMAND_MENU_ITEM_FRAGMENT = gql`
|
||||
availabilityType
|
||||
availabilityObjectMetadataId
|
||||
pageLayoutId
|
||||
isActive
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -8,6 +8,7 @@ export const createMockCommandMenuItems =
|
||||
(): CommandMenuItemFieldsFragment[] => [
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-add-to-favorites',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -26,6 +27,7 @@ export const createMockCommandMenuItems =
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-export',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -44,6 +46,7 @@ export const createMockCommandMenuItems =
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-delete',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -62,6 +65,7 @@ export const createMockCommandMenuItems =
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-go-to-people',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
|
||||
+10
-8
@@ -22,13 +22,15 @@ export const commandMenuItemsSelector = createAtomSelector<
|
||||
]),
|
||||
);
|
||||
|
||||
return commandMenuItems.map((item) => ({
|
||||
...item,
|
||||
frontComponent: isDefined(item.frontComponentId)
|
||||
? (frontComponentsById.get(item.frontComponentId) ??
|
||||
item.frontComponent ??
|
||||
null)
|
||||
: null,
|
||||
})) as CommandMenuItemFieldsFragment[];
|
||||
return commandMenuItems
|
||||
.filter((item) => item.isActive)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
frontComponent: isDefined(item.frontComponentId)
|
||||
? (frontComponentsById.get(item.frontComponentId) ??
|
||||
item.frontComponent ??
|
||||
null)
|
||||
: null,
|
||||
})) as CommandMenuItemFieldsFragment[];
|
||||
},
|
||||
});
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ const buildMockItem = (
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
availabilityObjectMetadataId: null,
|
||||
payload: null,
|
||||
isActive: true,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-go-to-people',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -24,6 +25,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-go-to-opportunities',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -42,6 +44,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-go-to-settings',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -59,6 +62,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-go-to-tasks',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -76,6 +80,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-go-to-notes',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -93,6 +98,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-search-records',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
@@ -110,6 +116,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
|
||||
},
|
||||
{
|
||||
__typename: 'CommandMenuItem',
|
||||
isActive: true,
|
||||
id: 'mock-search-records-fallback',
|
||||
workflowVersionId: null,
|
||||
frontComponentId: null,
|
||||
|
||||
+3
@@ -240,6 +240,9 @@ export class RefactorNavigationCommandsCommand extends ActiveOrSuspendedWorkspac
|
||||
availabilityObjectMetadataUniversalIdentifier: null,
|
||||
pageLayoutId: null,
|
||||
pageLayoutUniversalIdentifier: null,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
@RegisteredInstanceCommand('2.13.0', 1781253016028)
|
||||
export class CommandMenuItemOverridableEntityFastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."commandMenuItem" ADD "overrides" jsonb, ADD "isActive" boolean NOT NULL DEFAULT true',
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."commandMenuItem" DROP COLUMN "isActive", DROP COLUMN "overrides"',
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -64,6 +64,7 @@ import { DropEmailingDomainDriverColumnFastInstanceCommand } from 'src/database/
|
||||
import { ViewOverridableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-12/2-12-instance-command-fast-1781114009075-view-overridable-entity';
|
||||
import { RenameIsUiReadOnlyToIsUiEditableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781277453604-rename-is-ui-read-only-to-is-ui-editable';
|
||||
import { BackfillNonUiCreatableStandardSystemObjectsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-slow-1781277480000-backfill-non-ui-creatable-standard-system-objects';
|
||||
import { CommandMenuItemOverridableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781253016028-command-menu-item-overridable-entity';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
|
||||
@@ -130,4 +131,5 @@ export const INSTANCE_COMMANDS = [
|
||||
ViewOverridableEntityFastInstanceCommand,
|
||||
RenameIsUiReadOnlyToIsUiEditableFastInstanceCommand,
|
||||
BackfillNonUiCreatableStandardSystemObjectsSlowInstanceCommand,
|
||||
CommandMenuItemOverridableEntityFastInstanceCommand,
|
||||
];
|
||||
|
||||
+2
@@ -45,6 +45,8 @@ export const fromCommandMenuItemManifestToUniversalFlatCommandMenuItem = ({
|
||||
hotKeys: null,
|
||||
workflowVersionId: null,
|
||||
pageLayoutUniversalIdentifier: null,
|
||||
isActive: true,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+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(),
|
||||
};
|
||||
|
||||
+3
@@ -100,6 +100,9 @@ export const buildNavigationFlatCommandMenuItem = ({
|
||||
availabilityObjectMetadataUniversalIdentifier: null,
|
||||
pageLayoutId: null,
|
||||
pageLayoutUniversalIdentifier: null,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+17
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+78
@@ -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,
|
||||
);
|
||||
};
|
||||
+3
@@ -76,6 +76,9 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({
|
||||
workspaceId,
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+34
-23
@@ -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),
|
||||
};
|
||||
};
|
||||
|
||||
+50
-10
@@ -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;
|
||||
};
|
||||
|
||||
+3
@@ -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": {
|
||||
|
||||
+26
-1
@@ -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: {
|
||||
|
||||
+31
-14
@@ -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(
|
||||
|
||||
+3
@@ -91,6 +91,9 @@ export const prefillFrontComponentCommandMenuItems = async ({
|
||||
hotKeys: null,
|
||||
pageLayoutId: definition.pageLayoutId ?? null,
|
||||
pageLayoutUniversalIdentifier: definition.pageLayoutId ?? null,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+3
@@ -70,6 +70,9 @@ export const prefillWorkflowCommandMenuItems = async ({
|
||||
hotKeys: null,
|
||||
pageLayoutId: null,
|
||||
pageLayoutUniversalIdentifier: null,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+3
@@ -76,6 +76,9 @@ export const createStandardCommandMenuItemFlatMetadata = ({
|
||||
resolvedObjectMetadataUniversalIdentifier,
|
||||
pageLayoutId: null,
|
||||
pageLayoutUniversalIdentifier: null,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+3
-1
@@ -38,7 +38,9 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
|
||||
configuration: 'configuration',
|
||||
overrides: 'overrides',
|
||||
},
|
||||
commandMenuItem: {},
|
||||
commandMenuItem: {
|
||||
overrides: 'overrides',
|
||||
},
|
||||
navigationMenuItem: {},
|
||||
rolePermissionFlag: {},
|
||||
permissionFlag: {},
|
||||
|
||||
+11
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
FlatCreateCommandMenuItemAction,
|
||||
UniversalCreateCommandMenuItemAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/types/workspace-migration-command-menu-item-action.type';
|
||||
import { fromUniversalOverridesToCommandMenuItemOverrides } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/utils/from-universal-overrides-to-command-menu-item-overrides.util';
|
||||
import {
|
||||
WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
@@ -37,6 +39,14 @@ export class CreateCommandMenuItemActionHandlerService extends WorkspaceMigratio
|
||||
universalForeignKeyValues: action.flatEntity,
|
||||
});
|
||||
|
||||
const overrides = isDefined(action.flatEntity.universalOverrides)
|
||||
? fromUniversalOverridesToCommandMenuItemOverrides({
|
||||
universalOverrides: action.flatEntity.universalOverrides,
|
||||
flatObjectMetadataMaps: allFlatEntityMaps.flatObjectMetadataMaps,
|
||||
flatPageLayoutMaps: allFlatEntityMaps.flatPageLayoutMaps,
|
||||
})
|
||||
: null;
|
||||
|
||||
const emptyUniversalForeignKeyAggregators =
|
||||
getUniversalFlatEntityEmptyForeignKeyAggregators({
|
||||
metadataName: 'commandMenuItem',
|
||||
@@ -49,6 +59,7 @@ export class CreateCommandMenuItemActionHandlerService extends WorkspaceMigratio
|
||||
availabilityObjectMetadataId,
|
||||
frontComponentId,
|
||||
pageLayoutId,
|
||||
overrides,
|
||||
applicationId: flatApplication.id,
|
||||
id: action.id ?? v4(),
|
||||
workspaceId,
|
||||
|
||||
+22
-5
@@ -7,6 +7,7 @@ import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-mana
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
|
||||
import { fromUniversalOverridesToCommandMenuItemOverrides } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/utils/from-universal-overrides-to-command-menu-item-overrides.util';
|
||||
import {
|
||||
FlatUpdateCommandMenuItemAction,
|
||||
UniversalUpdateCommandMenuItemAction,
|
||||
@@ -31,11 +32,27 @@ export class UpdateCommandMenuItemActionHandlerService extends WorkspaceMigratio
|
||||
universalIdentifier: action.universalIdentifier,
|
||||
});
|
||||
|
||||
const update = resolveUniversalUpdateRelationIdentifiersToIds({
|
||||
metadataName: 'commandMenuItem',
|
||||
universalUpdate: action.update,
|
||||
allFlatEntityMaps,
|
||||
});
|
||||
const { universalOverrides, ...updateWithResolvedForeignKeys } =
|
||||
resolveUniversalUpdateRelationIdentifiersToIds({
|
||||
metadataName: 'commandMenuItem',
|
||||
universalUpdate: action.update,
|
||||
allFlatEntityMaps,
|
||||
});
|
||||
|
||||
const update =
|
||||
universalOverrides === undefined
|
||||
? updateWithResolvedForeignKeys
|
||||
: universalOverrides === null
|
||||
? { ...updateWithResolvedForeignKeys, overrides: null }
|
||||
: {
|
||||
...updateWithResolvedForeignKeys,
|
||||
overrides: fromUniversalOverridesToCommandMenuItemOverrides({
|
||||
universalOverrides,
|
||||
flatObjectMetadataMaps:
|
||||
allFlatEntityMaps.flatObjectMetadataMaps,
|
||||
flatPageLayoutMaps: allFlatEntityMaps.flatPageLayoutMaps,
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'update',
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { type FormatRecordSerializedRelationProperties } from 'twenty-shared/types';
|
||||
|
||||
import { type CommandMenuItemOverrides } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
|
||||
type UniversalCommandMenuItemOverrides =
|
||||
FormatRecordSerializedRelationProperties<CommandMenuItemOverrides>;
|
||||
|
||||
export const fromUniversalOverridesToCommandMenuItemOverrides = ({
|
||||
universalOverrides,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
}: {
|
||||
universalOverrides: UniversalCommandMenuItemOverrides;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatPageLayoutMaps: FlatEntityMaps<FlatPageLayout>;
|
||||
}): CommandMenuItemOverrides => {
|
||||
const {
|
||||
availabilityObjectMetadataUniversalIdentifier,
|
||||
pageLayoutUniversalIdentifier,
|
||||
...scalarOverrides
|
||||
} = universalOverrides;
|
||||
|
||||
const overrides: CommandMenuItemOverrides = { ...scalarOverrides };
|
||||
|
||||
if (availabilityObjectMetadataUniversalIdentifier !== undefined) {
|
||||
if (availabilityObjectMetadataUniversalIdentifier === null) {
|
||||
overrides.availabilityObjectMetadataId = null;
|
||||
} else {
|
||||
const flatObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: availabilityObjectMetadataUniversalIdentifier,
|
||||
});
|
||||
|
||||
overrides.availabilityObjectMetadataId = flatObjectMetadata?.id ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
if (pageLayoutUniversalIdentifier !== undefined) {
|
||||
if (pageLayoutUniversalIdentifier === null) {
|
||||
overrides.pageLayoutId = null;
|
||||
} else {
|
||||
const flatPageLayout =
|
||||
findFlatEntityByUniversalIdentifier<FlatPageLayout>({
|
||||
flatEntityMaps: flatPageLayoutMaps,
|
||||
universalIdentifier: pageLayoutUniversalIdentifier,
|
||||
});
|
||||
|
||||
overrides.pageLayoutId = flatPageLayout?.id ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return overrides;
|
||||
};
|
||||
+7
-5
@@ -25,6 +25,7 @@ const COMMAND_MENU_ITEM_GQL_FIELDS = `
|
||||
engineComponentKey
|
||||
label
|
||||
icon
|
||||
isActive
|
||||
payload {
|
||||
... on PathCommandMenuItemPayload {
|
||||
path
|
||||
@@ -159,7 +160,7 @@ describe('Command menu item side effect on object metadata', () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should delete the navigation command menu item when a custom object is disabled', async () => {
|
||||
it('should deactivate the navigation command menu item when a custom object is disabled', async () => {
|
||||
const {
|
||||
data: { createOneObject },
|
||||
} = await createOneObjectMetadata({
|
||||
@@ -183,7 +184,7 @@ describe('Command menu item side effect on object metadata', () => {
|
||||
itemsBeforeDisable,
|
||||
createdObjectMetadataId,
|
||||
),
|
||||
).toBeDefined();
|
||||
).toEqual(expect.objectContaining({ isActive: true }));
|
||||
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
@@ -206,10 +207,10 @@ describe('Command menu item side effect on object metadata', () => {
|
||||
itemsAfterDisable,
|
||||
createdObjectMetadataId,
|
||||
),
|
||||
).toBeUndefined();
|
||||
).toEqual(expect.objectContaining({ isActive: false }));
|
||||
});
|
||||
|
||||
it('should recreate the navigation command menu item when a disabled object is re-enabled', async () => {
|
||||
it('should reactivate the navigation command menu item when a disabled object is re-enabled', async () => {
|
||||
const {
|
||||
data: { createOneObject },
|
||||
} = await createOneObjectMetadata({
|
||||
@@ -241,7 +242,7 @@ describe('Command menu item side effect on object metadata', () => {
|
||||
itemsWhileDisabled,
|
||||
createdObjectMetadataId,
|
||||
),
|
||||
).toBeUndefined();
|
||||
).toEqual(expect.objectContaining({ isActive: false }));
|
||||
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
@@ -269,6 +270,7 @@ describe('Command menu item side effect on object metadata', () => {
|
||||
label: `Go to ${createObjectInput.labelPlural}`,
|
||||
icon: createObjectInput.icon,
|
||||
engineComponentKey: EngineComponentKey.NAVIGATION,
|
||||
isActive: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user