From abd9709291e123be18a925832e9c9200b65a80bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:21:56 +0100 Subject: [PATCH] Update Command Menu Item entity (#18391) Closes https://github.com/twentyhq/core-team-issues/issues/2256 --- .../src/generated-metadata/graphql.ts | 15 +++-- .../fragments/commandMenuItemFragment.ts | 2 + ...seCommandMenuItemFrontComponentActions.tsx | 11 +-- ...ort-label-position-to-command-menu-item.ts | 67 +++++++++++++++++++ ...o-universal-flat-command-menu-item.util.ts | 5 +- .../dtos/command-menu-item.dto.ts | 11 +++ .../dtos/create-command-menu-item.input.ts | 13 +++- .../dtos/update-command-menu-item.input.ts | 13 +++- .../entities/command-menu-item.entity.ts | 9 ++- ...-menu-item-editable-properties.constant.ts | 2 + ...m-entity-to-flat-command-menu-item.util.ts | 2 + ...o-flat-command-menu-item-to-create.util.ts | 2 + ...menu-item-to-command-menu-item-dto.util.ts | 4 ++ ...ompare-and-stringify.constant.spec.ts.snap | 2 + ...configuration-by-metadata-name.constant.ts | 10 +++ .../workflow-trigger.workspace-service.ts | 4 +- ...and-menu-item-creation.integration-spec.ts | 8 +-- ...mmand-menu-item-update.integration-spec.ts | 8 +-- .../application/frontComponentManifestType.ts | 2 +- 19 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1772643950000-add-short-label-position-to-command-menu-item.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 99663c9de5..717bbb026c 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -844,14 +844,15 @@ export type CommandMenuItem = { id: Scalars['UUID']; isPinned: Scalars['Boolean']; label: Scalars['String']; + position: Scalars['Float']; + shortLabel?: Maybe; updatedAt: Scalars['DateTime']; workflowVersionId?: Maybe; }; export enum CommandMenuItemAvailabilityType { - BULK_RECORDS = 'BULK_RECORDS', GLOBAL = 'GLOBAL', - SINGLE_RECORD = 'SINGLE_RECORD' + RECORD_SELECTION = 'RECORD_SELECTION' } export enum ConfigSource { @@ -1133,6 +1134,8 @@ export type CreateCommandMenuItemInput = { icon?: InputMaybe; isPinned?: InputMaybe; label: Scalars['String']; + position?: InputMaybe; + shortLabel?: InputMaybe; workflowVersionId?: InputMaybe; }; @@ -4849,6 +4852,8 @@ export type UpdateCommandMenuItemInput = { id: Scalars['UUID']; isPinned?: InputMaybe; label?: InputMaybe; + position?: InputMaybe; + shortLabel?: InputMaybe; }; export type UpdateFieldInput = { @@ -6105,12 +6110,12 @@ export type ListPlansQueryVariables = Exact<{ [key: string]: never; }>; export type ListPlansQuery = { __typename?: 'Query', listPlans: Array<{ __typename?: 'BillingPlan', planKey: BillingPlanKey, licensedProducts: Array<{ __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, prices?: Array<{ __typename?: 'BillingPriceLicensed', stripePriceId: string, unitAmount: number, recurringInterval: SubscriptionInterval, priceUsageType: BillingUsageType }> | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } }>, meteredProducts: Array<{ __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, prices?: Array<{ __typename?: 'BillingPriceMetered', priceUsageType: BillingUsageType, recurringInterval: SubscriptionInterval, stripePriceId: string, tiers: Array<{ __typename?: 'BillingPriceTier', flatAmount?: number | null, unitAmount?: number | null, upTo?: number | null }> }> | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } }> }> }; -export type CommandMenuItemFieldsFragment = { __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, label: string, icon?: string | null, isPinned: boolean, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }; +export type CommandMenuItemFieldsFragment = { __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, label: string, icon?: string | null, shortLabel?: string | null, position: number, isPinned: boolean, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }; export type FindManyCommandMenuItemsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyCommandMenuItemsQuery = { __typename?: 'Query', commandMenuItems: Array<{ __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, label: string, icon?: string | null, isPinned: boolean, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }> }; +export type FindManyCommandMenuItemsQuery = { __typename?: 'Query', commandMenuItems: Array<{ __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, label: string, icon?: string | null, shortLabel?: string | null, position: number, isPinned: boolean, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }> }; export type PageLayoutFragmentFragment = { __typename?: 'PageLayout', id: string, name: string, objectMetadataId?: string | null, type: PageLayoutType, defaultTabToFocusOnMobileAndSidePanelId?: string | null, createdAt: string, updatedAt: string, tabs?: Array<{ __typename?: 'PageLayoutTab', id: string, applicationId: string, title: string, icon?: string | null, position: number, layoutMode?: PageLayoutTabLayoutMode | null, pageLayoutId: string, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: string, title: string, type: WidgetType, objectMetadataId?: string | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: string, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, position?: { __typename?: 'PageLayoutWidgetCanvasPosition', layoutMode: PageLayoutTabLayoutMode } | { __typename?: 'PageLayoutWidgetGridPosition', layoutMode: PageLayoutTabLayoutMode, row: number, column: number, rowSpan: number, columnSpan: number } | { __typename?: 'PageLayoutWidgetVerticalListPosition', layoutMode: PageLayoutTabLayoutMode, index: number } | null, configuration: { __typename?: 'AggregateChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: string, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: string, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: string | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, layout: BarChartLayout, isCumulative?: boolean | null, splitMultiValueFields?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'CalendarConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'EmailsConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldRichTextConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldsConfiguration', configurationType: WidgetConfigurationType, viewId?: string | null } | { __typename?: 'FilesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FrontComponentConfiguration', configurationType: WidgetConfigurationType, frontComponentId: string } | { __typename?: 'GaugeChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', configurationType: WidgetConfigurationType, url?: string | null } | { __typename?: 'LineChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: string, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: string | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, splitMultiValueFields?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'NotesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'PieChartConfiguration', configurationType: WidgetConfigurationType, groupByFieldMetadataId: string, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, manualSortOrder?: Array | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, hideEmptyCategory?: boolean | null, splitMultiValueFields?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', configurationType: WidgetConfigurationType, body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | { __typename?: 'TasksConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'TimelineConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'ViewConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowRunConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowVersionConfiguration', configurationType: WidgetConfigurationType } }> | null }> | null }; @@ -7592,6 +7597,8 @@ export const CommandMenuItemFieldsFragmentDoc = gql` } label icon + shortLabel + position isPinned conditionalAvailabilityExpression availabilityType diff --git a/packages/twenty-front/src/modules/command-menu-item/graphql/fragments/commandMenuItemFragment.ts b/packages/twenty-front/src/modules/command-menu-item/graphql/fragments/commandMenuItemFragment.ts index fdceb5ea0d..ca80245c32 100644 --- a/packages/twenty-front/src/modules/command-menu-item/graphql/fragments/commandMenuItemFragment.ts +++ b/packages/twenty-front/src/modules/command-menu-item/graphql/fragments/commandMenuItemFragment.ts @@ -12,6 +12,8 @@ export const COMMAND_MENU_ITEM_FRAGMENT = gql` } label icon + shortLabel + position isPinned conditionalAvailabilityExpression availabilityType diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemFrontComponentActions.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemFrontComponentActions.tsx index 42e74ba35d..3aa3c12f74 100644 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemFrontComponentActions.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemFrontComponentActions.tsx @@ -193,11 +193,12 @@ export const useCommandMenuItemFrontComponentActions = ( const recordScopedItems = frontComponentItems.filter((item) => { if (!objectMatches(item)) return false; - if (item.availabilityType === CommandMenuItemAvailabilityType.SINGLE_RECORD) - return selectedRecordCount === 1; - if (item.availabilityType === CommandMenuItemAvailabilityType.BULK_RECORDS) - return selectedRecordCount >= 1; - return false; + + return ( + item.availabilityType === + CommandMenuItemAvailabilityType.RECORD_SELECTION && + selectedRecordCount >= 1 + ); }); const globalActions = globalItems.map((item, index) => diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1772643950000-add-short-label-position-to-command-menu-item.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1772643950000-add-short-label-position-to-command-menu-item.ts new file mode 100644 index 0000000000..bf9ca8c862 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1772643950000-add-short-label-position-to-command-menu-item.ts @@ -0,0 +1,67 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class AddShortLabelPositionToCommandMenuItem1772643950000 + implements MigrationInterface +{ + name = 'AddShortLabelPositionToCommandMenuItem1772643950000'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ADD "shortLabel" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ADD "position" double precision NOT NULL DEFAULT '0'`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" TYPE character varying`, + ); + await queryRunner.query( + `UPDATE "core"."commandMenuItem" SET "availabilityType" = CASE WHEN "availabilityType" IN ('SINGLE_RECORD', 'BULK_RECORDS') THEN 'RECORD_SELECTION' ELSE 'GLOBAL' END`, + ); + await queryRunner.query( + `DROP TYPE "core"."commandMenuItem_availabilitytype_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "core"."commandMenuItem_availabilitytype_enum" AS ENUM('GLOBAL', 'RECORD_SELECTION')`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" TYPE "core"."commandMenuItem_availabilitytype_enum" USING "availabilityType"::"core"."commandMenuItem_availabilitytype_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" SET DEFAULT 'GLOBAL'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" TYPE character varying`, + ); + await queryRunner.query( + `UPDATE "core"."commandMenuItem" SET "availabilityType" = CASE WHEN "availabilityType" = 'RECORD_SELECTION' THEN 'SINGLE_RECORD' ELSE 'GLOBAL' END`, + ); + await queryRunner.query( + `DROP TYPE "core"."commandMenuItem_availabilitytype_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "core"."commandMenuItem_availabilitytype_enum" AS ENUM('GLOBAL', 'SINGLE_RECORD', 'BULK_RECORDS')`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" TYPE "core"."commandMenuItem_availabilitytype_enum" USING "availabilityType"::"core"."commandMenuItem_availabilitytype_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "availabilityType" SET DEFAULT 'GLOBAL'`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" DROP COLUMN "position"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" DROP COLUMN "shortLabel"`, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/utils/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts b/packages/twenty-server/src/engine/core-modules/application/utils/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts index 6b022736aa..118187e3d4 100644 --- a/packages/twenty-server/src/engine/core-modules/application/utils/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/utils/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts @@ -8,8 +8,7 @@ const AVAILABILITY_TYPE_MAP: Record< CommandMenuItemAvailabilityType > = { GLOBAL: CommandMenuItemAvailabilityType.GLOBAL, - SINGLE_RECORD: CommandMenuItemAvailabilityType.SINGLE_RECORD, - BULK_RECORDS: CommandMenuItemAvailabilityType.BULK_RECORDS, + RECORD_SELECTION: CommandMenuItemAvailabilityType.RECORD_SELECTION, }; export const fromCommandMenuItemManifestToUniversalFlatCommandMenuItem = ({ @@ -25,6 +24,8 @@ export const fromCommandMenuItemManifestToUniversalFlatCommandMenuItem = ({ universalIdentifier: commandMenuItemManifest.universalIdentifier, applicationUniversalIdentifier, label: commandMenuItemManifest.label, + shortLabel: null, + position: 0, icon: commandMenuItemManifest.icon ?? null, isPinned: commandMenuItemManifest.isPinned ?? false, availabilityType: commandMenuItemManifest.availabilityType diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts index d2a4f29a45..50532d552c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts @@ -1,5 +1,6 @@ import { Field, + Float, HideField, ObjectType, registerEnumType, @@ -10,6 +11,7 @@ import { IsDateString, IsEnum, IsNotEmpty, + IsNumber, IsOptional, IsString, IsUUID, @@ -53,6 +55,15 @@ export class CommandMenuItemDTO { @Field({ nullable: true }) icon?: string; + @IsString() + @IsOptional() + @Field({ nullable: true }) + shortLabel?: string; + + @IsNumber() + @Field(() => Float) + position: number; + @IsBoolean() @Field() isPinned: boolean; diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts index 23721acd29..e076dce2e9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts @@ -1,9 +1,10 @@ -import { Field, InputType } from '@nestjs/graphql'; +import { Field, Float, InputType } from '@nestjs/graphql'; import { IsBoolean, IsEnum, IsNotEmpty, + IsNumber, IsOptional, IsString, IsUUID, @@ -34,6 +35,16 @@ export class CreateCommandMenuItemInput { @Field({ nullable: true }) icon?: string; + @IsString() + @IsOptional() + @Field({ nullable: true }) + shortLabel?: string; + + @IsNumber() + @IsOptional() + @Field(() => Float, { nullable: true }) + position?: number; + @IsBoolean() @IsOptional() @Field({ nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input.ts index 6bbba6cdae..d478292395 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input.ts @@ -1,9 +1,10 @@ -import { Field, InputType } from '@nestjs/graphql'; +import { Field, Float, InputType } from '@nestjs/graphql'; import { IsBoolean, IsEnum, IsNotEmpty, + IsNumber, IsOptional, IsString, IsUUID, @@ -29,6 +30,16 @@ export class UpdateCommandMenuItemInput { @Field({ nullable: true }) icon?: string; + @IsString() + @IsOptional() + @Field({ nullable: true }) + shortLabel?: string; + + @IsNumber() + @IsOptional() + @Field(() => Float, { nullable: true }) + position?: number; + @IsBoolean() @IsOptional() @Field({ nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts index 13369fb3e8..fd7e0d819b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts @@ -17,8 +17,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti export enum CommandMenuItemAvailabilityType { GLOBAL = 'GLOBAL', - SINGLE_RECORD = 'SINGLE_RECORD', - BULK_RECORDS = 'BULK_RECORDS', + RECORD_SELECTION = 'RECORD_SELECTION', } @Entity({ name: 'commandMenuItem', schema: 'core' }) @@ -63,6 +62,12 @@ export class CommandMenuItemEntity @Column({ nullable: true, type: 'varchar' }) icon: string | null; + @Column({ nullable: true, type: 'varchar' }) + shortLabel: string | null; + + @Column({ nullable: false, type: 'double precision', default: 0 }) + position: number; + @Column({ default: false }) isPinned: boolean; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/constants/flat-command-menu-item-editable-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/constants/flat-command-menu-item-editable-properties.constant.ts index cb3091b8f6..f8dc66ad95 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/constants/flat-command-menu-item-editable-properties.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/constants/flat-command-menu-item-editable-properties.constant.ts @@ -3,6 +3,8 @@ import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/fla export const FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES = [ 'label', 'icon', + 'shortLabel', + 'position', 'isPinned', 'availabilityType', 'availabilityObjectMetadataId', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts index ce018fe5e3..a5b25f461d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts @@ -63,6 +63,8 @@ export const fromCommandMenuItemEntityToFlatCommandMenuItem = ({ frontComponentId: commandMenuItemEntity.frontComponentId, label: commandMenuItemEntity.label, icon: commandMenuItemEntity.icon, + shortLabel: commandMenuItemEntity.shortLabel, + position: commandMenuItemEntity.position, isPinned: commandMenuItemEntity.isPinned, availabilityType: commandMenuItemEntity.availabilityType, availabilityObjectMetadataId: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts index e71391c802..5f92aff261 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts @@ -64,6 +64,8 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({ frontComponentUniversalIdentifier, label: createCommandMenuItemInput.label, icon: createCommandMenuItemInput.icon ?? null, + shortLabel: createCommandMenuItemInput.shortLabel ?? null, + position: createCommandMenuItemInput.position ?? 0, isPinned: createCommandMenuItemInput.isPinned ?? false, availabilityType: createCommandMenuItemInput.availabilityType ?? diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts index 865631b11c..f4e1aa0995 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts @@ -9,8 +9,12 @@ export const fromFlatCommandMenuItemToCommandMenuItemDto = ( frontComponentId: flatCommandMenuItem.frontComponentId ?? undefined, label: flatCommandMenuItem.label, icon: flatCommandMenuItem.icon ?? undefined, + shortLabel: flatCommandMenuItem.shortLabel ?? undefined, + position: flatCommandMenuItem.position, isPinned: flatCommandMenuItem.isPinned, availabilityType: flatCommandMenuItem.availabilityType, + conditionalAvailabilityExpression: + flatCommandMenuItem.conditionalAvailabilityExpression ?? undefined, availabilityObjectMetadataId: flatCommandMenuItem.availabilityObjectMetadataId ?? undefined, workspaceId: flatCommandMenuItem.workspaceId, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap index 3cf21763d9..ec98e5cb7c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap @@ -24,6 +24,8 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma "propertiesToCompare": [ "label", "icon", + "shortLabel", + "position", "isPinned", "availabilityType", "conditionalAvailabilityExpression", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index cbce5561e5..eb24674faa 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -970,6 +970,16 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { universalProperty: undefined, }, icon: { toCompare: true, toStringify: false, universalProperty: undefined }, + shortLabel: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, + position: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, isPinned: { toCompare: true, toStringify: false, diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts index 625b0a2db5..0ce5ada0d4 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts @@ -355,9 +355,7 @@ export class WorkflowTriggerWorkspaceService { case 'SINGLE_RECORD': case 'BULK_RECORDS': { availabilityType = - availability.type === 'SINGLE_RECORD' - ? CommandMenuItemAvailabilityType.SINGLE_RECORD - : CommandMenuItemAvailabilityType.BULK_RECORDS; + CommandMenuItemAvailabilityType.RECORD_SELECTION; const { objectIdByNameSingular } = await this.workflowCommonWorkspaceService.getFlatEntityMaps( diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts index 1e99becfcd..5bd661d686 100644 --- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts @@ -120,7 +120,7 @@ describe('CommandMenuItem creation should succeed', () => { label: 'Full Command Menu Item', icon: 'IconSparkles', isPinned: true, - availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: companyObjectMetadataId, }, }); @@ -133,7 +133,7 @@ describe('CommandMenuItem creation should succeed', () => { label: 'Full Command Menu Item', icon: 'IconSparkles', isPinned: true, - availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: companyObjectMetadataId, }); }); @@ -146,7 +146,7 @@ describe('CommandMenuItem creation should succeed', () => { input: { workflowVersionId, label: 'Bulk Records Command', - availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: personObjectMetadataId, }, }); @@ -157,7 +157,7 @@ describe('CommandMenuItem creation should succeed', () => { id: expect.any(String), workflowVersionId, label: 'Bulk Records Command', - availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: personObjectMetadataId, }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts index 78c5cc8aa1..2e8085b99a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts @@ -134,14 +134,14 @@ describe('CommandMenuItem update should succeed', () => { expectToFail: false, input: { id: createdCommandMenuItemId, - availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: companyObjectMetadataId, }, }); expect(data.updateCommandMenuItem).toMatchObject({ id: createdCommandMenuItemId, - availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: companyObjectMetadataId, }); }); @@ -154,7 +154,7 @@ describe('CommandMenuItem update should succeed', () => { label: 'Fully Updated Label', icon: 'IconNew', isPinned: true, - availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: personObjectMetadataId, }, }); @@ -164,7 +164,7 @@ describe('CommandMenuItem update should succeed', () => { label: 'Fully Updated Label', icon: 'IconNew', isPinned: true, - availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: personObjectMetadataId, }); }); diff --git a/packages/twenty-shared/src/application/frontComponentManifestType.ts b/packages/twenty-shared/src/application/frontComponentManifestType.ts index aa2034c01d..fcaf8d7ae3 100644 --- a/packages/twenty-shared/src/application/frontComponentManifestType.ts +++ b/packages/twenty-shared/src/application/frontComponentManifestType.ts @@ -4,7 +4,7 @@ export type CommandMenuItemManifest = SyncableEntityOptions & { label: string; icon?: string; isPinned?: boolean; - availabilityType?: 'GLOBAL' | 'SINGLE_RECORD' | 'BULK_RECORDS'; + availabilityType?: 'GLOBAL' | 'RECORD_SELECTION'; availabilityObjectUniversalIdentifier?: string; frontComponentUniversalIdentifier: string; conditionalAvailabilityExpression?: string;