Add command menu item entity (#17181)
This commit is contained in:
@@ -609,6 +609,26 @@ export type ClientAiModelConfig = {
|
||||
provider: ModelProvider;
|
||||
};
|
||||
|
||||
export type CommandMenuItem = {
|
||||
__typename?: 'CommandMenuItem';
|
||||
applicationId?: Maybe<Scalars['UUID']>;
|
||||
availabilityObjectMetadataId?: Maybe<Scalars['UUID']>;
|
||||
availabilityType: CommandMenuItemAvailabilityType;
|
||||
createdAt: Scalars['DateTime'];
|
||||
icon?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
isPinned: Scalars['Boolean'];
|
||||
label: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workflowVersionId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export enum CommandMenuItemAvailabilityType {
|
||||
BULK_RECORDS = 'BULK_RECORDS',
|
||||
GLOBAL = 'GLOBAL',
|
||||
SINGLE_RECORD = 'SINGLE_RECORD'
|
||||
}
|
||||
|
||||
export type ComputeStepOutputSchemaInput = {
|
||||
/** Step JSON format */
|
||||
step: Scalars['JSON'];
|
||||
@@ -836,6 +856,15 @@ export type CreateApprovedAccessDomainInput = {
|
||||
email: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateCommandMenuItemInput = {
|
||||
availabilityObjectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
availabilityType?: InputMaybe<CommandMenuItemAvailabilityType>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
isPinned?: InputMaybe<Scalars['Boolean']>;
|
||||
label: Scalars['String'];
|
||||
workflowVersionId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type CreateCronTriggerInput = {
|
||||
serverlessFunctionId: Scalars['String'];
|
||||
settings: Scalars['JSON'];
|
||||
@@ -1373,6 +1402,7 @@ export type FeatureFlagDto = {
|
||||
export enum FeatureFlagKey {
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
IS_IF_ELSE_ENABLED = 'IS_IF_ELSE_ENABLED',
|
||||
@@ -1878,6 +1908,7 @@ export type Mutation = {
|
||||
createApiKey: ApiKey;
|
||||
createApprovedAccessDomain: ApprovedAccessDomain;
|
||||
createChatThread: AgentChatThread;
|
||||
createCommandMenuItem: CommandMenuItem;
|
||||
createCoreView: CoreView;
|
||||
createCoreViewField: CoreViewField;
|
||||
createCoreViewFilter: CoreViewFilter;
|
||||
@@ -1917,6 +1948,7 @@ export type Mutation = {
|
||||
deactivateSkill: Skill;
|
||||
deactivateWorkflowVersion: Scalars['Boolean'];
|
||||
deleteApprovedAccessDomain: Scalars['Boolean'];
|
||||
deleteCommandMenuItem: CommandMenuItem;
|
||||
deleteCoreView: Scalars['Boolean'];
|
||||
deleteCoreViewField: CoreViewField;
|
||||
deleteCoreViewFilter: CoreViewFilter;
|
||||
@@ -2007,6 +2039,7 @@ export type Mutation = {
|
||||
trackAnalytics: Analytics;
|
||||
uninstallApplication: Scalars['Boolean'];
|
||||
updateApiKey?: Maybe<ApiKey>;
|
||||
updateCommandMenuItem: CommandMenuItem;
|
||||
updateCoreView: CoreView;
|
||||
updateCoreViewField: CoreViewField;
|
||||
updateCoreViewFilter: CoreViewFilter;
|
||||
@@ -2125,6 +2158,11 @@ export type MutationCreateApprovedAccessDomainArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateCommandMenuItemArgs = {
|
||||
input: CreateCommandMenuItemInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateCoreViewArgs = {
|
||||
input: CreateViewInput;
|
||||
};
|
||||
@@ -2326,6 +2364,11 @@ export type MutationDeleteApprovedAccessDomainArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteCommandMenuItemArgs = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteCoreViewArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
@@ -2756,6 +2799,11 @@ export type MutationUpdateApiKeyArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateCommandMenuItemArgs = {
|
||||
input: UpdateCommandMenuItemInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateCoreViewArgs = {
|
||||
id: Scalars['String'];
|
||||
input: UpdateViewInput;
|
||||
@@ -3391,6 +3439,8 @@ export type Query = {
|
||||
chatThreads: Array<AgentChatThread>;
|
||||
checkUserExists: CheckUserExistOutput;
|
||||
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValidOutput;
|
||||
commandMenuItem?: Maybe<CommandMenuItem>;
|
||||
commandMenuItems: Array<CommandMenuItem>;
|
||||
currentUser: User;
|
||||
currentWorkspace: Workspace;
|
||||
field: Field;
|
||||
@@ -3509,6 +3559,11 @@ export type QueryCheckWorkspaceInviteHashIsValidArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryCommandMenuItemArgs = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryFieldArgs = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
@@ -4494,6 +4549,15 @@ export type UpdateApiKeyInput = {
|
||||
revokedAt?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateCommandMenuItemInput = {
|
||||
availabilityObjectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
availabilityType?: InputMaybe<CommandMenuItemAvailabilityType>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
isPinned?: InputMaybe<Scalars['Boolean']>;
|
||||
label?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateCronTriggerInput = {
|
||||
/** The id of the cron trigger to update */
|
||||
id: Scalars['String'];
|
||||
|
||||
@@ -609,6 +609,26 @@ export type ClientAiModelConfig = {
|
||||
provider: ModelProvider;
|
||||
};
|
||||
|
||||
export type CommandMenuItem = {
|
||||
__typename?: 'CommandMenuItem';
|
||||
applicationId?: Maybe<Scalars['UUID']>;
|
||||
availabilityObjectMetadataId?: Maybe<Scalars['UUID']>;
|
||||
availabilityType: CommandMenuItemAvailabilityType;
|
||||
createdAt: Scalars['DateTime'];
|
||||
icon?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
isPinned: Scalars['Boolean'];
|
||||
label: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workflowVersionId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export enum CommandMenuItemAvailabilityType {
|
||||
BULK_RECORDS = 'BULK_RECORDS',
|
||||
GLOBAL = 'GLOBAL',
|
||||
SINGLE_RECORD = 'SINGLE_RECORD'
|
||||
}
|
||||
|
||||
export type ComputeStepOutputSchemaInput = {
|
||||
/** Step JSON format */
|
||||
step: Scalars['JSON'];
|
||||
@@ -832,6 +852,15 @@ export type CreateApprovedAccessDomainInput = {
|
||||
email: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateCommandMenuItemInput = {
|
||||
availabilityObjectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
availabilityType?: InputMaybe<CommandMenuItemAvailabilityType>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
isPinned?: InputMaybe<Scalars['Boolean']>;
|
||||
label: Scalars['String'];
|
||||
workflowVersionId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type CreateCronTriggerInput = {
|
||||
serverlessFunctionId: Scalars['String'];
|
||||
settings: Scalars['JSON'];
|
||||
@@ -1350,6 +1379,7 @@ export type FeatureFlagDto = {
|
||||
export enum FeatureFlagKey {
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
IS_IF_ELSE_ENABLED = 'IS_IF_ELSE_ENABLED',
|
||||
@@ -1853,6 +1883,7 @@ export type Mutation = {
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createApiKey: ApiKey;
|
||||
createApprovedAccessDomain: ApprovedAccessDomain;
|
||||
createCommandMenuItem: CommandMenuItem;
|
||||
createCoreView: CoreView;
|
||||
createCoreViewField: CoreViewField;
|
||||
createCoreViewFilter: CoreViewFilter;
|
||||
@@ -1889,6 +1920,7 @@ export type Mutation = {
|
||||
createWorkflowVersionStep: WorkflowVersionStepChanges;
|
||||
deactivateWorkflowVersion: Scalars['Boolean'];
|
||||
deleteApprovedAccessDomain: Scalars['Boolean'];
|
||||
deleteCommandMenuItem: CommandMenuItem;
|
||||
deleteCoreView: Scalars['Boolean'];
|
||||
deleteCoreViewField: CoreViewField;
|
||||
deleteCoreViewFilter: CoreViewFilter;
|
||||
@@ -1975,6 +2007,7 @@ export type Mutation = {
|
||||
trackAnalytics: Analytics;
|
||||
uninstallApplication: Scalars['Boolean'];
|
||||
updateApiKey?: Maybe<ApiKey>;
|
||||
updateCommandMenuItem: CommandMenuItem;
|
||||
updateCoreView: CoreView;
|
||||
updateCoreViewField: CoreViewField;
|
||||
updateCoreViewFilter: CoreViewFilter;
|
||||
@@ -2086,6 +2119,11 @@ export type MutationCreateApprovedAccessDomainArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateCommandMenuItemArgs = {
|
||||
input: CreateCommandMenuItemInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateCoreViewArgs = {
|
||||
input: CreateViewInput;
|
||||
};
|
||||
@@ -2267,6 +2305,11 @@ export type MutationDeleteApprovedAccessDomainArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteCommandMenuItemArgs = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteCoreViewArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
@@ -2676,6 +2719,11 @@ export type MutationUpdateApiKeyArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateCommandMenuItemArgs = {
|
||||
input: UpdateCommandMenuItemInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateCoreViewArgs = {
|
||||
id: Scalars['String'];
|
||||
input: UpdateViewInput;
|
||||
@@ -3297,6 +3345,8 @@ export type Query = {
|
||||
billingPortalSession: BillingSessionOutput;
|
||||
checkUserExists: CheckUserExistOutput;
|
||||
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValidOutput;
|
||||
commandMenuItem?: Maybe<CommandMenuItem>;
|
||||
commandMenuItems: Array<CommandMenuItem>;
|
||||
currentUser: User;
|
||||
currentWorkspace: Workspace;
|
||||
field: Field;
|
||||
@@ -3396,6 +3446,11 @@ export type QueryCheckWorkspaceInviteHashIsValidArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryCommandMenuItemArgs = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindOneAgentArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
@@ -4338,6 +4393,15 @@ export type UpdateApiKeyInput = {
|
||||
revokedAt?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateCommandMenuItemInput = {
|
||||
availabilityObjectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
availabilityType?: InputMaybe<CommandMenuItemAvailabilityType>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
isPinned?: InputMaybe<Scalars['Boolean']>;
|
||||
label?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateCronTriggerInput = {
|
||||
/** The id of the cron trigger to update */
|
||||
id: Scalars['String'];
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ export const useMetadataErrorHandler = () => {
|
||||
rowLevelPermissionPredicate: t`row level permission predicate`,
|
||||
rowLevelPermissionPredicateGroup: t`row level permission predicate group`,
|
||||
viewFilterGroup: t`view filter group`,
|
||||
commandMenuItem: t`command menu item`,
|
||||
frontComponent: t`front component`,
|
||||
} as const satisfies Record<AllMetadataName, string>;
|
||||
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddCommandMenuItemEntity1768503887441
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddCommandMenuItemEntity1768503887441';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."commandMenuItem_availabilitytype_enum" AS ENUM('GLOBAL', 'SINGLE_RECORD', 'BULK_RECORDS')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "core"."commandMenuItem" ("workspaceId" uuid NOT NULL, "universalIdentifier" uuid NOT NULL, "applicationId" uuid NOT NULL, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "workflowVersionId" uuid NOT NULL, "label" character varying NOT NULL, "icon" character varying, "isPinned" boolean NOT NULL DEFAULT false, "availabilityType" "core"."commandMenuItem_availabilitytype_enum" NOT NULL DEFAULT 'GLOBAL', "availabilityObjectMetadataId" uuid, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_fd076dc869e721593133fe8a007" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_a3a5976e1b580ba1086c595802" ON "core"."commandMenuItem" ("workspaceId", "universalIdentifier")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_COMMAND_MENU_ITEM_WORKFLOW_VERSION_ID_WORKSPACE_ID" ON "core"."commandMenuItem" ("workflowVersionId", "workspaceId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "FK_94947770f00413f134a1ec01dd7" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "FK_ad42dd64b117491a38120466d65" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "FK_6e050fb56a8385718123a4f8bc6" FOREIGN KEY ("availabilityObjectMetadataId") REFERENCES "core"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" DROP CONSTRAINT "FK_6e050fb56a8385718123a4f8bc6"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" DROP CONSTRAINT "FK_ad42dd64b117491a38120466d65"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" DROP CONSTRAINT "FK_94947770f00413f134a1ec01dd7"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_COMMAND_MENU_ITEM_WORKFLOW_VERSION_ID_WORKSPACE_ID"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_a3a5976e1b580ba1086c595802"`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "core"."commandMenuItem"`);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "core"."commandMenuItem_availabilitytype_enum"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
@@ -12,4 +12,5 @@ export enum FeatureFlagKey {
|
||||
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
|
||||
IS_IF_ELSE_ENABLED = 'IS_IF_ELSE_ENABLED',
|
||||
IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export enum CommandMenuItemExceptionCode {
|
||||
COMMAND_MENU_ITEM_NOT_FOUND = 'COMMAND_MENU_ITEM_NOT_FOUND',
|
||||
INVALID_COMMAND_MENU_ITEM_INPUT = 'INVALID_COMMAND_MENU_ITEM_INPUT',
|
||||
}
|
||||
|
||||
const getCommandMenuItemExceptionUserFriendlyMessage = (
|
||||
code: CommandMenuItemExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND:
|
||||
return msg`Command menu item not found.`;
|
||||
case CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT:
|
||||
return msg`Invalid command menu item input.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class CommandMenuItemException extends CustomException<CommandMenuItemExceptionCode> {
|
||||
constructor(
|
||||
message: string,
|
||||
code: CommandMenuItemExceptionCode,
|
||||
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
getCommandMenuItemExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { CommandMenuItemResolver } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.resolver';
|
||||
import { CommandMenuItemService } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.service';
|
||||
import { CommandMenuItemGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/command-menu-item/interceptors/command-menu-item-graphql-api-exception.interceptor';
|
||||
import { FlatCommandMenuItemModule } from 'src/engine/metadata-modules/flat-command-menu-item/flat-command-menu-item.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationModule,
|
||||
ApplicationModule,
|
||||
FlatCommandMenuItemModule,
|
||||
FeatureFlagModule,
|
||||
],
|
||||
providers: [
|
||||
CommandMenuItemService,
|
||||
CommandMenuItemResolver,
|
||||
CommandMenuItemGraphqlApiExceptionInterceptor,
|
||||
WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor,
|
||||
],
|
||||
exports: [CommandMenuItemService],
|
||||
})
|
||||
export class CommandMenuItemModule {}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import {
|
||||
FeatureFlagGuard,
|
||||
RequireFeatureFlag,
|
||||
} from 'src/engine/guards/feature-flag.guard';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CommandMenuItemService } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.service';
|
||||
import { CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto';
|
||||
import { CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input';
|
||||
import { UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input';
|
||||
import { CommandMenuItemGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/command-menu-item/interceptors/command-menu-item-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||
@UseInterceptors(
|
||||
WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor,
|
||||
CommandMenuItemGraphqlApiExceptionInterceptor,
|
||||
)
|
||||
@Resolver(() => CommandMenuItemDTO)
|
||||
export class CommandMenuItemResolver {
|
||||
constructor(
|
||||
private readonly commandMenuItemService: CommandMenuItemService,
|
||||
) {}
|
||||
|
||||
@Query(() => [CommandMenuItemDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
|
||||
async commandMenuItems(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<CommandMenuItemDTO[]> {
|
||||
return await this.commandMenuItemService.findAll(workspace.id);
|
||||
}
|
||||
|
||||
@Query(() => CommandMenuItemDTO, { nullable: true })
|
||||
@UseGuards(NoPermissionGuard)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
|
||||
async commandMenuItem(
|
||||
@Args('id', { type: () => UUIDScalarType }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<CommandMenuItemDTO | null> {
|
||||
return await this.commandMenuItemService.findById(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => CommandMenuItemDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
|
||||
async createCommandMenuItem(
|
||||
@Args('input') input: CreateCommandMenuItemInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<CommandMenuItemDTO> {
|
||||
return await this.commandMenuItemService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => CommandMenuItemDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
|
||||
async updateCommandMenuItem(
|
||||
@Args('input') input: UpdateCommandMenuItemInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<CommandMenuItemDTO> {
|
||||
return await this.commandMenuItemService.update(input, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => CommandMenuItemDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
|
||||
async deleteCommandMenuItem(
|
||||
@Args('id', { type: () => UUIDScalarType }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<CommandMenuItemDTO> {
|
||||
return await this.commandMenuItemService.delete(id, workspace.id);
|
||||
}
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import {
|
||||
CommandMenuItemException,
|
||||
CommandMenuItemExceptionCode,
|
||||
} from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception';
|
||||
import { type CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto';
|
||||
import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input';
|
||||
import { type UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util';
|
||||
import { fromDeleteCommandMenuItemInputToFlatCommandMenuItemOrThrow } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-delete-command-menu-item-input-to-flat-command-menu-item-or-throw.util';
|
||||
import { fromFlatCommandMenuItemToCommandMenuItemDto } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util';
|
||||
import { fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-update-command-menu-item-input-to-flat-command-menu-item-to-update-or-throw.util';
|
||||
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 { 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';
|
||||
|
||||
@Injectable()
|
||||
export class CommandMenuItemService {
|
||||
constructor(
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
async findAll(workspaceId: string): Promise<CommandMenuItemDTO[]> {
|
||||
const { flatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return Object.values(flatCommandMenuItemMaps.byId)
|
||||
.filter(isDefined)
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.map(fromFlatCommandMenuItemToCommandMenuItemDto);
|
||||
}
|
||||
|
||||
async findById(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<CommandMenuItemDTO | null> {
|
||||
const { flatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatCommandMenuItem = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(flatCommandMenuItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fromFlatCommandMenuItemToCommandMenuItemDto(flatCommandMenuItem);
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<CommandMenuItemDTO> {
|
||||
const commandMenuItem = await this.findById(id, workspaceId);
|
||||
|
||||
if (!isDefined(commandMenuItem)) {
|
||||
throw new CommandMenuItemException(
|
||||
'Command menu item not found',
|
||||
CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return commandMenuItem;
|
||||
}
|
||||
|
||||
async create(
|
||||
input: CreateCommandMenuItemInput,
|
||||
workspaceId: string,
|
||||
): Promise<CommandMenuItemDTO> {
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const flatCommandMenuItemToCreate =
|
||||
fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate({
|
||||
createCommandMenuItemInput: input,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomFlatApplication.id,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
flatEntityToCreate: [flatCommandMenuItemToCreate],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while creating command menu item',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatCommandMenuItemMaps: recomputedFlatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatCommandMenuItemToCommandMenuItemDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatCommandMenuItemToCreate.id,
|
||||
flatEntityMaps: recomputedFlatCommandMenuItemMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async update(
|
||||
input: UpdateCommandMenuItemInput,
|
||||
workspaceId: string,
|
||||
): Promise<CommandMenuItemDTO> {
|
||||
const { flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatCommandMenuItemToUpdate =
|
||||
fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow({
|
||||
flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps,
|
||||
updateCommandMenuItemInput: input,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatCommandMenuItemToUpdate],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating command menu item',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatCommandMenuItemMaps: recomputedFlatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatCommandMenuItemToCommandMenuItemDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: input.id,
|
||||
flatEntityMaps: recomputedFlatCommandMenuItemMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<CommandMenuItemDTO> {
|
||||
const { flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatCommandMenuItemToDelete =
|
||||
fromDeleteCommandMenuItemInputToFlatCommandMenuItemOrThrow({
|
||||
flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps,
|
||||
commandMenuItemId: id,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [flatCommandMenuItemToDelete],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting command menu item',
|
||||
);
|
||||
}
|
||||
|
||||
return fromFlatCommandMenuItemToCommandMenuItemDto(
|
||||
flatCommandMenuItemToDelete,
|
||||
);
|
||||
}
|
||||
|
||||
async findAllFlatCommandMenuItems(
|
||||
workspaceId: string,
|
||||
): Promise<FlatCommandMenuItem[]> {
|
||||
const { flatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return Object.values(flatCommandMenuItemMaps.byId)
|
||||
.filter(isDefined)
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
async findByWorkflowVersionId(
|
||||
workflowVersionId: string,
|
||||
workspaceId: string,
|
||||
): Promise<CommandMenuItemDTO | null> {
|
||||
const { flatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatCommandMenuItem = Object.values(
|
||||
flatCommandMenuItemMaps.byId,
|
||||
).find(
|
||||
(item) => isDefined(item) && item.workflowVersionId === workflowVersionId,
|
||||
);
|
||||
|
||||
if (!isDefined(flatCommandMenuItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fromFlatCommandMenuItemToCommandMenuItemDto(flatCommandMenuItem);
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
Field,
|
||||
HideField,
|
||||
ObjectType,
|
||||
registerEnumType,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
|
||||
registerEnumType(CommandMenuItemAvailabilityType, {
|
||||
name: 'CommandMenuItemAvailabilityType',
|
||||
});
|
||||
|
||||
@ObjectType('CommandMenuItem')
|
||||
export class CommandMenuItemDTO {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType)
|
||||
workflowVersionId: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
label: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isPinned: boolean;
|
||||
|
||||
@IsEnum(CommandMenuItemAvailabilityType)
|
||||
@Field(() => CommandMenuItemAvailabilityType)
|
||||
availabilityType: CommandMenuItemAvailabilityType;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
availabilityObjectMetadataId?: string;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
applicationId?: string;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
|
||||
@InputType()
|
||||
export class CreateCommandMenuItemInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType)
|
||||
workflowVersionId: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
label: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
isPinned?: boolean;
|
||||
|
||||
@IsEnum(CommandMenuItemAvailabilityType)
|
||||
@IsOptional()
|
||||
@Field(() => CommandMenuItemAvailabilityType, { nullable: true })
|
||||
availabilityType?: CommandMenuItemAvailabilityType;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
availabilityObjectMetadataId?: string;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
|
||||
@InputType()
|
||||
export class UpdateCommandMenuItemInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
label?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
isPinned?: boolean;
|
||||
|
||||
@IsEnum(CommandMenuItemAvailabilityType)
|
||||
@IsOptional()
|
||||
@Field(() => CommandMenuItemAvailabilityType, { nullable: true })
|
||||
availabilityType?: CommandMenuItemAvailabilityType;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
availabilityObjectMetadataId?: string;
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
type Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface';
|
||||
|
||||
export enum CommandMenuItemAvailabilityType {
|
||||
GLOBAL = 'GLOBAL',
|
||||
SINGLE_RECORD = 'SINGLE_RECORD',
|
||||
BULK_RECORDS = 'BULK_RECORDS',
|
||||
}
|
||||
|
||||
@Entity({ name: 'commandMenuItem', schema: 'core' })
|
||||
@Index('IDX_COMMAND_MENU_ITEM_WORKFLOW_VERSION_ID_WORKSPACE_ID', [
|
||||
'workflowVersionId',
|
||||
'workspaceId',
|
||||
])
|
||||
export class CommandMenuItemEntity
|
||||
extends SyncableEntityRequired
|
||||
implements Required<CommandMenuItemEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workflowVersionId: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
label: string;
|
||||
|
||||
@Column({ nullable: true, type: 'varchar' })
|
||||
icon: string | null;
|
||||
|
||||
@Column({ default: false })
|
||||
isPinned: boolean;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: CommandMenuItemAvailabilityType,
|
||||
nullable: false,
|
||||
default: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
})
|
||||
availabilityType: CommandMenuItemAvailabilityType;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
availabilityObjectMetadataId: string | null;
|
||||
|
||||
@ManyToOne(() => ObjectMetadataEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'availabilityObjectMetadataId' })
|
||||
availabilityObjectMetadata: Relation<ObjectMetadataEntity> | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
type CallHandler,
|
||||
type ExecutionContext,
|
||||
Injectable,
|
||||
type NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Observable, catchError } from 'rxjs';
|
||||
|
||||
import { commandMenuItemGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/command-menu-item/utils/command-menu-item-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class CommandMenuItemGraphqlApiExceptionInterceptor
|
||||
implements NestInterceptor
|
||||
{
|
||||
intercept(
|
||||
_context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<unknown> {
|
||||
return next
|
||||
.handle()
|
||||
.pipe(catchError(commandMenuItemGraphqlApiExceptionHandler));
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
NotFoundError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import {
|
||||
CommandMenuItemException,
|
||||
CommandMenuItemExceptionCode,
|
||||
} from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception';
|
||||
|
||||
export const commandMenuItemGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof CommandMenuItemException) {
|
||||
switch (error.code) {
|
||||
case CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND:
|
||||
throw new NotFoundError(error);
|
||||
case CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT:
|
||||
throw new UserInputError(error);
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
|
||||
export const FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES = [
|
||||
'label',
|
||||
'icon',
|
||||
'isPinned',
|
||||
'availabilityType',
|
||||
'availabilityObjectMetadataId',
|
||||
] as const satisfies (keyof FlatCommandMenuItem)[];
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { WorkspaceFlatCommandMenuItemMapCacheService } from 'src/engine/metadata-modules/flat-command-menu-item/services/workspace-flat-command-menu-item-map-cache.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([CommandMenuItemEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatCommandMenuItemMapCacheService],
|
||||
exports: [WorkspaceFlatCommandMenuItemMapCacheService],
|
||||
})
|
||||
export class FlatCommandMenuItemModule {}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
|
||||
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type FlatCommandMenuItemMaps } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item-maps.type';
|
||||
import { fromCommandMenuItemEntityToFlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('flatCommandMenuItemMaps')
|
||||
export class WorkspaceFlatCommandMenuItemMapCacheService extends WorkspaceCacheProvider<FlatCommandMenuItemMaps> {
|
||||
constructor(
|
||||
@InjectRepository(CommandMenuItemEntity)
|
||||
private readonly commandMenuItemRepository: Repository<CommandMenuItemEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatCommandMenuItemMaps> {
|
||||
const commandMenuItems = await this.commandMenuItemRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
const flatCommandMenuItemMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const commandMenuItemEntity of commandMenuItems) {
|
||||
const flatCommandMenuItem =
|
||||
fromCommandMenuItemEntityToFlatCommandMenuItem(commandMenuItemEntity);
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatCommandMenuItem,
|
||||
flatEntityMapsToMutate: flatCommandMenuItemMaps,
|
||||
});
|
||||
}
|
||||
|
||||
return flatCommandMenuItemMaps;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
|
||||
export type FlatCommandMenuItemMaps = FlatEntityMaps<FlatCommandMenuItem>;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
|
||||
|
||||
export type FlatCommandMenuItem = FlatEntityFrom<CommandMenuItemEntity>;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { type CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
|
||||
export const fromCommandMenuItemEntityToFlatCommandMenuItem = (
|
||||
commandMenuItemEntity: CommandMenuItemEntity,
|
||||
): FlatCommandMenuItem => {
|
||||
return {
|
||||
id: commandMenuItemEntity.id,
|
||||
workflowVersionId: commandMenuItemEntity.workflowVersionId,
|
||||
label: commandMenuItemEntity.label,
|
||||
icon: commandMenuItemEntity.icon,
|
||||
isPinned: commandMenuItemEntity.isPinned,
|
||||
availabilityType: commandMenuItemEntity.availabilityType,
|
||||
availabilityObjectMetadataId:
|
||||
commandMenuItemEntity.availabilityObjectMetadataId,
|
||||
workspaceId: commandMenuItemEntity.workspaceId,
|
||||
universalIdentifier: commandMenuItemEntity.universalIdentifier,
|
||||
applicationId: commandMenuItemEntity.applicationId,
|
||||
createdAt: commandMenuItemEntity.createdAt.toISOString(),
|
||||
updatedAt: commandMenuItemEntity.updatedAt.toISOString(),
|
||||
};
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
|
||||
export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({
|
||||
createCommandMenuItemInput,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
}: {
|
||||
createCommandMenuItemInput: CreateCommandMenuItemInput;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
}): FlatCommandMenuItem => {
|
||||
const id = uuidv4();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
return {
|
||||
id,
|
||||
universalIdentifier: id,
|
||||
workflowVersionId: createCommandMenuItemInput.workflowVersionId,
|
||||
label: createCommandMenuItemInput.label,
|
||||
icon: createCommandMenuItemInput.icon ?? null,
|
||||
isPinned: createCommandMenuItemInput.isPinned ?? false,
|
||||
availabilityType:
|
||||
createCommandMenuItemInput.availabilityType ??
|
||||
CommandMenuItemAvailabilityType.GLOBAL,
|
||||
availabilityObjectMetadataId:
|
||||
createCommandMenuItemInput.availabilityObjectMetadataId ?? null,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
CommandMenuItemException,
|
||||
CommandMenuItemExceptionCode,
|
||||
} from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception';
|
||||
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 { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
|
||||
export const fromDeleteCommandMenuItemInputToFlatCommandMenuItemOrThrow = ({
|
||||
flatCommandMenuItemMaps,
|
||||
commandMenuItemId,
|
||||
}: {
|
||||
flatCommandMenuItemMaps: FlatCommandMenuItemMaps;
|
||||
commandMenuItemId: string;
|
||||
}): FlatCommandMenuItem => {
|
||||
const existingFlatCommandMenuItem = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: commandMenuItemId,
|
||||
flatEntityMaps: flatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingFlatCommandMenuItem)) {
|
||||
throw new CommandMenuItemException(
|
||||
'Command menu item not found',
|
||||
CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return existingFlatCommandMenuItem;
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { type CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
|
||||
export const fromFlatCommandMenuItemToCommandMenuItemDto = (
|
||||
flatCommandMenuItem: FlatCommandMenuItem,
|
||||
): CommandMenuItemDTO => ({
|
||||
id: flatCommandMenuItem.id,
|
||||
workflowVersionId: flatCommandMenuItem.workflowVersionId,
|
||||
label: flatCommandMenuItem.label,
|
||||
icon: flatCommandMenuItem.icon ?? undefined,
|
||||
isPinned: flatCommandMenuItem.isPinned,
|
||||
availabilityType: flatCommandMenuItem.availabilityType,
|
||||
availabilityObjectMetadataId:
|
||||
flatCommandMenuItem.availabilityObjectMetadataId ?? undefined,
|
||||
workspaceId: flatCommandMenuItem.workspaceId,
|
||||
applicationId: flatCommandMenuItem.applicationId ?? undefined,
|
||||
createdAt: new Date(flatCommandMenuItem.createdAt),
|
||||
updatedAt: new Date(flatCommandMenuItem.updatedAt),
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
CommandMenuItemException,
|
||||
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 { 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 { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
|
||||
|
||||
export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow =
|
||||
({
|
||||
flatCommandMenuItemMaps,
|
||||
updateCommandMenuItemInput,
|
||||
}: {
|
||||
flatCommandMenuItemMaps: FlatCommandMenuItemMaps;
|
||||
updateCommandMenuItemInput: UpdateCommandMenuItemInput;
|
||||
}): FlatCommandMenuItem => {
|
||||
const existingFlatCommandMenuItem = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: updateCommandMenuItemInput.id,
|
||||
flatEntityMaps: flatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingFlatCommandMenuItem)) {
|
||||
throw new CommandMenuItemException(
|
||||
'Command menu item not found',
|
||||
CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const { id: _id, ...updates } = updateCommandMenuItemInput;
|
||||
|
||||
return {
|
||||
...mergeUpdateInExistingRecord({
|
||||
existing: existingFlatCommandMenuItem,
|
||||
properties: [...FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES],
|
||||
update: updates,
|
||||
}),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
+5
@@ -14,6 +14,7 @@ import { FLAT_ROLE_TARGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-module
|
||||
import { FLAT_ROLE_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-role/constants/flat-role-editable-properties.constant';
|
||||
import { FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/constants/flat-row-level-permission-predicate-group-editable-properties.constant';
|
||||
import { FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/constants/flat-row-level-permission-predicate-editable-properties.constant';
|
||||
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 { FLAT_SKILL_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-skill/constants/flat-skill-editable-properties.constant';
|
||||
import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-editable-properties.constant';
|
||||
import { FLAT_VIEW_FILTER_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter-group/constants/flat-view-filter-group-editable-properties.constant';
|
||||
@@ -141,6 +142,10 @@ export const ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY = {
|
||||
propertiesToCompare: [...FLAT_SKILL_EDITABLE_PROPERTIES],
|
||||
propertiesToStringify: [],
|
||||
},
|
||||
commandMenuItem: {
|
||||
propertiesToCompare: [...FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES],
|
||||
propertiesToStringify: [],
|
||||
},
|
||||
rowLevelPermissionPredicate: {
|
||||
propertiesToCompare: [
|
||||
...FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES,
|
||||
|
||||
+12
@@ -79,6 +79,18 @@ export const ALL_METADATA_RELATIONS = {
|
||||
},
|
||||
oneToMany: {},
|
||||
},
|
||||
commandMenuItem: {
|
||||
manyToOne: {
|
||||
workspace: null,
|
||||
application: null,
|
||||
availabilityObjectMetadata: {
|
||||
metadataName: 'objectMetadata',
|
||||
flatEntityForeignKeyAggregator: null,
|
||||
foreignKey: 'availabilityObjectMetadataId',
|
||||
},
|
||||
},
|
||||
oneToMany: {},
|
||||
},
|
||||
fieldMetadata: {
|
||||
manyToOne: {
|
||||
object: {
|
||||
|
||||
+3
@@ -62,6 +62,9 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
|
||||
role: true,
|
||||
},
|
||||
skill: {},
|
||||
commandMenuItem: {
|
||||
objectMetadata: true,
|
||||
},
|
||||
pageLayout: {
|
||||
objectMetadata: true,
|
||||
},
|
||||
|
||||
+23
-7
@@ -1,13 +1,14 @@
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
import { type CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type CronTriggerEntity } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
|
||||
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
|
||||
import { type DatabaseEventTriggerEntity } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
|
||||
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
@@ -21,6 +22,7 @@ import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
|
||||
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
|
||||
import { type FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
@@ -47,6 +49,11 @@ import {
|
||||
type DeleteAgentAction,
|
||||
type UpdateAgentAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/agent/types/workspace-migration-agent-action-builder.service';
|
||||
import {
|
||||
type CreateCommandMenuItemAction,
|
||||
type DeleteCommandMenuItemAction,
|
||||
type UpdateCommandMenuItemAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/types/workspace-migration-command-menu-item-action.type';
|
||||
import {
|
||||
type CreateCronTriggerAction,
|
||||
type DeleteCronTriggerAction,
|
||||
@@ -62,6 +69,11 @@ import {
|
||||
type DeleteFieldAction,
|
||||
type UpdateFieldAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action';
|
||||
import {
|
||||
type CreateFrontComponentAction,
|
||||
type DeleteFrontComponentAction,
|
||||
type UpdateFrontComponentAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/front-component/types/workspace-migration-front-component-action.type';
|
||||
import {
|
||||
type CreateIndexAction,
|
||||
type DeleteIndexAction,
|
||||
@@ -147,11 +159,6 @@ import {
|
||||
type DeleteViewAction,
|
||||
type UpdateViewAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view/types/workspace-migration-view-action.type';
|
||||
import {
|
||||
type CreateFrontComponentAction,
|
||||
type DeleteFrontComponentAction,
|
||||
type UpdateFrontComponentAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/front-component/types/workspace-migration-front-component-action.type';
|
||||
|
||||
export type AllFlatEntityTypesByMetadataName = {
|
||||
fieldMetadata: {
|
||||
@@ -316,6 +323,15 @@ export type AllFlatEntityTypesByMetadataName = {
|
||||
flatEntity: FlatSkill;
|
||||
entity: SkillEntity;
|
||||
};
|
||||
commandMenuItem: {
|
||||
actions: {
|
||||
create: CreateCommandMenuItemAction;
|
||||
update: UpdateCommandMenuItemAction;
|
||||
delete: DeleteCommandMenuItemAction;
|
||||
};
|
||||
flatEntity: FlatCommandMenuItem;
|
||||
entity: CommandMenuItemEntity;
|
||||
};
|
||||
pageLayout: {
|
||||
actions: {
|
||||
create: CreatePageLayoutAction;
|
||||
|
||||
+6
@@ -2,6 +2,12 @@
|
||||
|
||||
exports[`getMetadataRelatedMetadataNames should return related metadata names for agent 1`] = `[]`;
|
||||
|
||||
exports[`getMetadataRelatedMetadataNames should return related metadata names for commandMenuItem 1`] = `
|
||||
[
|
||||
"objectMetadata",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`getMetadataRelatedMetadataNames should return related metadata names for cronTrigger 1`] = `
|
||||
[
|
||||
"serverlessFunction",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { AiAgentMonitorModule } from 'src/engine/metadata-modules/ai/ai-agent-monitor/ai-agent-monitor.module';
|
||||
import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module';
|
||||
import { AiChatModule } from 'src/engine/metadata-modules/ai/ai-chat/ai-chat.module';
|
||||
import { CommandMenuItemModule } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.module';
|
||||
import { CronTriggerModule } from 'src/engine/metadata-modules/cron-trigger/cron-trigger.module';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { DatabaseEventTriggerModule } from 'src/engine/metadata-modules/database-event-trigger/database-event-trigger.module';
|
||||
@@ -29,6 +30,7 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
|
||||
ServerlessFunctionModule,
|
||||
ServerlessFunctionLayerModule,
|
||||
SkillModule,
|
||||
CommandMenuItemModule,
|
||||
AiAgentModule,
|
||||
AiAgentMonitorModule,
|
||||
AiChatModule,
|
||||
@@ -49,6 +51,7 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
|
||||
SearchFieldMetadataModule,
|
||||
ServerlessFunctionModule,
|
||||
SkillModule,
|
||||
CommandMenuItemModule,
|
||||
AiAgentModule,
|
||||
AiChatModule,
|
||||
ViewModule,
|
||||
|
||||
+1
@@ -222,6 +222,7 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false,
|
||||
IS_IF_ELSE_ENABLED: false,
|
||||
IS_SSE_DB_EVENTS_ENABLED: false,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: false,
|
||||
},
|
||||
userWorkspaceRoleMap: {},
|
||||
eventEmitterService: {
|
||||
|
||||
@@ -32,6 +32,7 @@ export const WORKSPACE_CACHE_KEYS_V2 = {
|
||||
ORMEntityMetadatas: 'orm:entity-metadatas',
|
||||
flatAgentMaps: 'flat-maps:agent',
|
||||
flatSkillMaps: 'flat-maps:skill',
|
||||
flatCommandMenuItemMaps: 'flat-maps:command-menu-item',
|
||||
flatRoleTargetByAgentIdMaps: 'flat-maps:flatRoleTargetByAgentId',
|
||||
flatPageLayoutMaps: 'flat-maps:page-layout',
|
||||
flatPageLayoutWidgetMaps: 'flat-maps:page-layout-widget',
|
||||
|
||||
+48
@@ -15,6 +15,7 @@ import {
|
||||
} from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type';
|
||||
import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration/utils/aggregate-orchestrator-actions-report.util';
|
||||
import { WorkspaceMigrationAgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/agent/workspace-migration-agent-actions-builder.service';
|
||||
import { WorkspaceMigrationCommandMenuItemActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/workspace-migration-command-menu-item-actions-builder.service';
|
||||
import { WorkspaceMigrationCronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/cron-trigger/workspace-migration-cron-trigger-action-builder.service';
|
||||
import { WorkspaceMigrationDatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/database-event-trigger/workspace-migration-database-event-trigger-actions-builder.service';
|
||||
import { WorkspaceMigrationFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service';
|
||||
@@ -56,6 +57,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
private readonly workspaceMigrationRoleActionsBuilderService: WorkspaceMigrationRoleActionsBuilderService,
|
||||
private readonly workspaceMigrationAgentActionsBuilderService: WorkspaceMigrationAgentActionsBuilderService,
|
||||
private readonly workspaceMigrationSkillActionsBuilderService: WorkspaceMigrationSkillActionsBuilderService,
|
||||
private readonly workspaceMigrationCommandMenuItemActionsBuilderService: WorkspaceMigrationCommandMenuItemActionsBuilderService,
|
||||
private readonly workspaceMigrationPageLayoutActionsBuilderService: WorkspaceMigrationPageLayoutActionsBuilderService,
|
||||
private readonly workspaceMigrationPageLayoutWidgetActionsBuilderService: WorkspaceMigrationPageLayoutWidgetActionsBuilderService,
|
||||
private readonly workspaceMigrationPageLayoutTabActionsBuilderService: WorkspaceMigrationPageLayoutTabActionsBuilderService,
|
||||
@@ -158,6 +160,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
flatRoleTargetMaps,
|
||||
flatAgentMaps,
|
||||
flatSkillMaps,
|
||||
flatCommandMenuItemMaps,
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
@@ -817,6 +820,45 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(flatCommandMenuItemMaps)) {
|
||||
const {
|
||||
from: fromFlatCommandMenuItemMaps,
|
||||
to: toFlatCommandMenuItemMaps,
|
||||
} = flatCommandMenuItemMaps;
|
||||
|
||||
const commandMenuItemResult =
|
||||
await this.workspaceMigrationCommandMenuItemActionsBuilderService.validateAndBuild(
|
||||
{
|
||||
additionalCacheDataMaps,
|
||||
from: fromFlatCommandMenuItemMaps,
|
||||
to: toFlatCommandMenuItemMaps,
|
||||
buildOptions,
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
flatObjectMetadataMaps:
|
||||
optimisticAllFlatEntityMaps.flatObjectMetadataMaps,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation(
|
||||
{
|
||||
allFlatEntityMaps: optimisticAllFlatEntityMaps,
|
||||
flatEntityMapsAndRelatedFlatEntityMaps:
|
||||
commandMenuItemResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
|
||||
if (commandMenuItemResult.status === 'fail') {
|
||||
orchestratorFailureReport.commandMenuItem.push(
|
||||
...commandMenuItemResult.errors,
|
||||
);
|
||||
} else {
|
||||
orchestratorActionsReport.commandMenuItem =
|
||||
commandMenuItemResult.actions;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(flatPageLayoutMaps)) {
|
||||
const { from: fromFlatPageLayoutMaps, to: toFlatPageLayoutMaps } =
|
||||
flatPageLayoutMaps;
|
||||
@@ -1070,6 +1112,12 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
...aggregatedOrchestratorActionsReport.skill.update,
|
||||
///
|
||||
|
||||
// Command Menu Items
|
||||
...aggregatedOrchestratorActionsReport.commandMenuItem.delete,
|
||||
...aggregatedOrchestratorActionsReport.commandMenuItem.create,
|
||||
...aggregatedOrchestratorActionsReport.commandMenuItem.update,
|
||||
///
|
||||
|
||||
// Page layouts
|
||||
...aggregatedOrchestratorActionsReport.pageLayout.delete,
|
||||
...aggregatedOrchestratorActionsReport.pageLayout.create,
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { type BaseCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-create-workspace-migration-action.type';
|
||||
import { type BaseDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-delete-workspace-migration-action.type';
|
||||
import { type BaseUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-update-workspace-migration-action.type';
|
||||
|
||||
export type CreateCommandMenuItemAction =
|
||||
BaseCreateWorkspaceMigrationAction<'commandMenuItem'>;
|
||||
|
||||
export type UpdateCommandMenuItemAction =
|
||||
BaseUpdateWorkspaceMigrationAction<'commandMenuItem'>;
|
||||
|
||||
export type DeleteCommandMenuItemAction =
|
||||
BaseDeleteWorkspaceMigrationAction<'commandMenuItem'>;
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
|
||||
import { UpdateCommandMenuItemAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/types/workspace-migration-command-menu-item-action.type';
|
||||
import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service';
|
||||
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/flat-entity-update-validation-args.type';
|
||||
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/flat-entity-validation-args.type';
|
||||
import { FlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/flat-entity-validation-result.type';
|
||||
import { FlatCommandMenuItemValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-command-menu-item-validator.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationCommandMenuItemActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem
|
||||
> {
|
||||
constructor(
|
||||
private readonly flatCommandMenuItemValidatorService: FlatCommandMenuItemValidatorService,
|
||||
) {
|
||||
super(ALL_METADATA_NAME.commandMenuItem);
|
||||
}
|
||||
|
||||
protected validateFlatEntityCreation(
|
||||
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.commandMenuItem>,
|
||||
): FlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem,
|
||||
'create'
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatCommandMenuItemValidatorService.validateFlatCommandMenuItemCreation(
|
||||
args,
|
||||
);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityToValidate: flatCommandMenuItemToValidate } = args;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
type: 'create',
|
||||
metadataName: 'commandMenuItem',
|
||||
flatEntity: flatCommandMenuItemToValidate,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected validateFlatEntityDeletion(
|
||||
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.commandMenuItem>,
|
||||
): FlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem,
|
||||
'delete'
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatCommandMenuItemValidatorService.validateFlatCommandMenuItemDeletion(
|
||||
args,
|
||||
);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityToValidate: flatCommandMenuItemToValidate } = args;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
type: 'delete',
|
||||
metadataName: 'commandMenuItem',
|
||||
entityId: flatCommandMenuItemToValidate.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected validateFlatEntityUpdate(
|
||||
args: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem
|
||||
>,
|
||||
): FlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem,
|
||||
'update'
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatCommandMenuItemValidatorService.validateFlatCommandMenuItemUpdate(
|
||||
args,
|
||||
);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityId, flatEntityUpdates } = args;
|
||||
|
||||
const updateCommandMenuItemAction: UpdateCommandMenuItemAction = {
|
||||
type: 'update',
|
||||
metadataName: 'commandMenuItem',
|
||||
entityId: flatEntityId,
|
||||
updates: flatEntityUpdates,
|
||||
};
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: updateCommandMenuItemAction,
|
||||
};
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CommandMenuItemExceptionCode } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { findFlatEntityPropertyUpdate } from 'src/engine/workspace-manager/workspace-migration/utils/find-flat-entity-property-update.util';
|
||||
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
|
||||
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
|
||||
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/flat-entity-update-validation-args.type';
|
||||
import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/flat-entity-validation-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class FlatCommandMenuItemValidatorService {
|
||||
public validateFlatCommandMenuItemCreation({
|
||||
flatEntityToValidate: flatCommandMenuItem,
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem
|
||||
>): FailedFlatEntityValidation<'commandMenuItem', 'create'> {
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatCommandMenuItem.id,
|
||||
universalIdentifier: flatCommandMenuItem.universalIdentifier,
|
||||
},
|
||||
metadataName: 'commandMenuItem',
|
||||
type: 'create',
|
||||
});
|
||||
|
||||
if (!isNonEmptyString(flatCommandMenuItem.label)) {
|
||||
validationResult.errors.push({
|
||||
code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT,
|
||||
message: t`Label is required`,
|
||||
userFriendlyMessage: msg`Label is required`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatCommandMenuItemDeletion({
|
||||
flatEntityToValidate,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatCommandMenuItemMaps: optimisticFlatCommandMenuItemMaps,
|
||||
},
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem
|
||||
>): FailedFlatEntityValidation<'commandMenuItem', 'delete'> {
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatEntityToValidate.id,
|
||||
universalIdentifier: flatEntityToValidate.universalIdentifier,
|
||||
},
|
||||
metadataName: 'commandMenuItem',
|
||||
type: 'delete',
|
||||
});
|
||||
|
||||
const existingCommandMenuItem = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatEntityToValidate.id,
|
||||
flatEntityMaps: optimisticFlatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingCommandMenuItem)) {
|
||||
validationResult.errors.push({
|
||||
code: CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND,
|
||||
message: t`Command menu item not found`,
|
||||
userFriendlyMessage: msg`Command menu item not found`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatCommandMenuItemUpdate({
|
||||
flatEntityId,
|
||||
flatEntityUpdates,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatCommandMenuItemMaps: optimisticFlatCommandMenuItemMaps,
|
||||
},
|
||||
}: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.commandMenuItem
|
||||
>): FailedFlatEntityValidation<'commandMenuItem', 'update'> {
|
||||
const fromFlatCommandMenuItem = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId,
|
||||
flatEntityMaps: optimisticFlatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatEntityId,
|
||||
universalIdentifier: fromFlatCommandMenuItem?.universalIdentifier,
|
||||
},
|
||||
metadataName: 'commandMenuItem',
|
||||
type: 'update',
|
||||
});
|
||||
|
||||
if (!isDefined(fromFlatCommandMenuItem)) {
|
||||
validationResult.errors.push({
|
||||
code: CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND,
|
||||
message: t`Command menu item not found`,
|
||||
userFriendlyMessage: msg`Command menu item not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const labelUpdate = findFlatEntityPropertyUpdate({
|
||||
flatEntityUpdates,
|
||||
property: 'label',
|
||||
});
|
||||
|
||||
if (isDefined(labelUpdate) && !isNonEmptyString(labelUpdate.to)) {
|
||||
validationResult.errors.push({
|
||||
code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT,
|
||||
message: t`Label is required`,
|
||||
userFriendlyMessage: msg`Label is required`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
}
|
||||
+3
@@ -18,6 +18,7 @@ import { FlatRouteTriggerValidatorService } from 'src/engine/workspace-manager/w
|
||||
import { FlatRowLevelPermissionPredicateGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-row-level-permission-predicate-group-validator.service';
|
||||
import { FlatRowLevelPermissionPredicateValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-row-level-permission-predicate-validator.service';
|
||||
import { FlatServerlessFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-serverless-function-validator.service';
|
||||
import { FlatCommandMenuItemValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-command-menu-item-validator.service';
|
||||
import { FlatSkillValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-skill-validator.service';
|
||||
import { FlatFrontComponentValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-front-component-validator.service';
|
||||
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-field-validator.service';
|
||||
@@ -47,6 +48,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
|
||||
FlatRoleTargetValidatorService,
|
||||
FlatAgentValidatorService,
|
||||
FlatSkillValidatorService,
|
||||
FlatCommandMenuItemValidatorService,
|
||||
FlatPageLayoutValidatorService,
|
||||
FlatPageLayoutWidgetValidatorService,
|
||||
FlatPageLayoutTabValidatorService,
|
||||
@@ -72,6 +74,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
|
||||
FlatRoleTargetValidatorService,
|
||||
FlatAgentValidatorService,
|
||||
FlatSkillValidatorService,
|
||||
FlatCommandMenuItemValidatorService,
|
||||
FlatPageLayoutValidatorService,
|
||||
FlatPageLayoutWidgetValidatorService,
|
||||
FlatPageLayoutTabValidatorService,
|
||||
|
||||
+3
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
|
||||
import { WorkspaceMigrationAgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/agent/workspace-migration-agent-actions-builder.service';
|
||||
import { WorkspaceMigrationCommandMenuItemActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/workspace-migration-command-menu-item-actions-builder.service';
|
||||
import { WorkspaceMigrationCronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/cron-trigger/workspace-migration-cron-trigger-action-builder.service';
|
||||
import { WorkspaceMigrationDatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/database-event-trigger/workspace-migration-database-event-trigger-actions-builder.service';
|
||||
import { WorkspaceMigrationFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service';
|
||||
@@ -46,6 +47,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
|
||||
WorkspaceMigrationRoleTargetActionsBuilderService,
|
||||
WorkspaceMigrationAgentActionsBuilderService,
|
||||
WorkspaceMigrationSkillActionsBuilderService,
|
||||
WorkspaceMigrationCommandMenuItemActionsBuilderService,
|
||||
WorkspaceMigrationPageLayoutActionsBuilderService,
|
||||
WorkspaceMigrationPageLayoutWidgetActionsBuilderService,
|
||||
WorkspaceMigrationPageLayoutTabActionsBuilderService,
|
||||
@@ -70,6 +72,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
|
||||
WorkspaceMigrationRoleTargetActionsBuilderService,
|
||||
WorkspaceMigrationAgentActionsBuilderService,
|
||||
WorkspaceMigrationSkillActionsBuilderService,
|
||||
WorkspaceMigrationCommandMenuItemActionsBuilderService,
|
||||
WorkspaceMigrationPageLayoutActionsBuilderService,
|
||||
WorkspaceMigrationPageLayoutWidgetActionsBuilderService,
|
||||
WorkspaceMigrationPageLayoutTabActionsBuilderService,
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { CreateCommandMenuItemAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/types/workspace-migration-command-menu-item-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class CreateCommandMenuItemActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'create',
|
||||
'commandMenuItem',
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<CreateCommandMenuItemAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { flatEntity } = action;
|
||||
|
||||
const commandMenuItemRepository =
|
||||
queryRunner.manager.getRepository<CommandMenuItemEntity>(
|
||||
CommandMenuItemEntity,
|
||||
);
|
||||
|
||||
await commandMenuItemRepository.insert({
|
||||
...flatEntity,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<CreateCommandMenuItemAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { DeleteCommandMenuItemAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/types/workspace-migration-command-menu-item-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteCommandMenuItemActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'delete',
|
||||
'commandMenuItem',
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<DeleteCommandMenuItemAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { entityId } = action;
|
||||
|
||||
const commandMenuItemRepository =
|
||||
queryRunner.manager.getRepository<CommandMenuItemEntity>(
|
||||
CommandMenuItemEntity,
|
||||
);
|
||||
|
||||
await commandMenuItemRepository.delete({ id: entityId, workspaceId });
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<DeleteCommandMenuItemAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { UpdateCommandMenuItemAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/types/workspace-migration-command-menu-item-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateCommandMenuItemActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'update',
|
||||
'commandMenuItem',
|
||||
) {
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UpdateCommandMenuItemAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { entityId, updates } = action;
|
||||
|
||||
const commandMenuItemRepository =
|
||||
queryRunner.manager.getRepository<CommandMenuItemEntity>(
|
||||
CommandMenuItemEntity,
|
||||
);
|
||||
|
||||
await commandMenuItemRepository.update(
|
||||
{ id: entityId, workspaceId },
|
||||
fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
|
||||
updates,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<UpdateCommandMenuItemAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+7
@@ -4,6 +4,9 @@ import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-sc
|
||||
import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service';
|
||||
import { DeleteAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/delete-agent-action-handler.service';
|
||||
import { UpdateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/update-agent-action-handler.service';
|
||||
import { CreateCommandMenuItemActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/create-command-menu-item-action-handler.service';
|
||||
import { DeleteCommandMenuItemActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/delete-command-menu-item-action-handler.service';
|
||||
import { UpdateCommandMenuItemActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/update-command-menu-item-action-handler.service';
|
||||
import { CreateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/cron-trigger/services/create-cron-trigger-action-handler.service';
|
||||
import { DeleteCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/cron-trigger/services/delete-cron-trigger-action-handler.service';
|
||||
import { UpdateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/cron-trigger/services/update-cron-trigger-action-handler.service';
|
||||
@@ -135,6 +138,10 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
|
||||
UpdateSkillActionHandlerService,
|
||||
DeleteSkillActionHandlerService,
|
||||
|
||||
CreateCommandMenuItemActionHandlerService,
|
||||
UpdateCommandMenuItemActionHandlerService,
|
||||
DeleteCommandMenuItemActionHandlerService,
|
||||
|
||||
CreatePageLayoutActionHandlerService,
|
||||
UpdatePageLayoutActionHandlerService,
|
||||
DeletePageLayoutActionHandlerService,
|
||||
|
||||
+3
-2
@@ -68,8 +68,9 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = <
|
||||
case 'skill':
|
||||
case 'pageLayout':
|
||||
case 'pageLayoutWidget':
|
||||
case 'frontComponent':
|
||||
case 'pageLayoutTab': {
|
||||
case 'pageLayoutTab':
|
||||
case 'commandMenuItem':
|
||||
case 'frontComponent': {
|
||||
addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: action.flatEntity,
|
||||
flatEntityAndRelatedMapsToMutate: allFlatEntityMaps,
|
||||
|
||||
+3
-2
@@ -45,8 +45,9 @@ export const optimisticallyApplyDeleteActionOnAllFlatEntityMaps = <
|
||||
case 'skill':
|
||||
case 'pageLayout':
|
||||
case 'pageLayoutWidget':
|
||||
case 'frontComponent':
|
||||
case 'pageLayoutTab': {
|
||||
case 'pageLayoutTab':
|
||||
case 'commandMenuItem':
|
||||
case 'frontComponent': {
|
||||
const flatEntityToDelete = findFlatEntityByIdInFlatEntityMapsOrThrow<
|
||||
MetadataFlatEntity<typeof action.metadataName>
|
||||
>({
|
||||
|
||||
+1
@@ -68,6 +68,7 @@ export const optimisticallyApplyUpdateActionOnAllFlatEntityMaps = <
|
||||
case 'pageLayout':
|
||||
case 'pageLayoutWidget':
|
||||
case 'pageLayoutTab':
|
||||
case 'commandMenuItem':
|
||||
case 'frontComponent': {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(
|
||||
action.metadataName,
|
||||
|
||||
+4
@@ -2,6 +2,8 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { CommandMenuItemModule } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
import { WorkflowRunnerModule } from 'src/modules/workflow/workflow-runner/workflow-runner.module';
|
||||
@@ -14,6 +16,8 @@ import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-t
|
||||
WorkflowCommonModule,
|
||||
WorkflowRunnerModule,
|
||||
AutomatedTriggerModule,
|
||||
CommandMenuItemModule,
|
||||
FeatureFlagModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]),
|
||||
],
|
||||
providers: [WorkflowTriggerWorkspaceService, WorkflowTriggerJob],
|
||||
|
||||
+102
-4
@@ -3,6 +3,10 @@ import { Injectable } from '@nestjs/common';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { type ActorMetadata } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { CommandMenuItemService } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.service';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
@@ -24,7 +28,10 @@ import {
|
||||
WorkflowTriggerException,
|
||||
WorkflowTriggerExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import {
|
||||
type WorkflowManualTrigger,
|
||||
WorkflowTriggerType,
|
||||
} from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import { assertVersionCanBeActivated } from 'src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util';
|
||||
import { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule';
|
||||
import { assertNever } from 'src/utils/assert';
|
||||
@@ -37,6 +44,8 @@ export class WorkflowTriggerWorkspaceService {
|
||||
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
|
||||
private readonly automatedTriggerWorkspaceService: AutomatedTriggerWorkspaceService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly commandMenuItemService: CommandMenuItemService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async runWorkflowVersion({
|
||||
@@ -191,7 +200,7 @@ export class WorkflowTriggerWorkspaceService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.enableTrigger(workflowVersion, workspaceId);
|
||||
await this.enableTrigger(workflow, workflowVersion, workspaceId);
|
||||
}
|
||||
|
||||
private async performDeactivationSteps(
|
||||
@@ -306,13 +315,77 @@ export class WorkflowTriggerWorkspaceService {
|
||||
}
|
||||
|
||||
private async enableTrigger(
|
||||
workflow: WorkflowWorkspaceEntity,
|
||||
workflowVersion: WorkflowVersionWorkspaceEntity,
|
||||
workspaceId: string,
|
||||
) {
|
||||
assertWorkflowVersionTriggerIsDefined(workflowVersion);
|
||||
|
||||
switch (workflowVersion.trigger.type) {
|
||||
case WorkflowTriggerType.MANUAL:
|
||||
case WorkflowTriggerType.MANUAL: {
|
||||
const isCommandMenuItemEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isCommandMenuItemEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trigger = workflowVersion.trigger as WorkflowManualTrigger;
|
||||
|
||||
const availability = trigger.settings.availability;
|
||||
|
||||
let availabilityType = CommandMenuItemAvailabilityType.GLOBAL;
|
||||
let availabilityObjectMetadataId: string | undefined;
|
||||
|
||||
if (availability) {
|
||||
switch (availability.type) {
|
||||
case 'GLOBAL':
|
||||
availabilityType = CommandMenuItemAvailabilityType.GLOBAL;
|
||||
break;
|
||||
case 'SINGLE_RECORD':
|
||||
case 'BULK_RECORDS': {
|
||||
availabilityType =
|
||||
availability.type === 'SINGLE_RECORD'
|
||||
? CommandMenuItemAvailabilityType.SINGLE_RECORD
|
||||
: CommandMenuItemAvailabilityType.BULK_RECORDS;
|
||||
|
||||
const { objectIdByNameSingular } =
|
||||
await this.workflowCommonWorkspaceService.getFlatEntityMaps(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const objectId =
|
||||
objectIdByNameSingular[availability.objectNameSingular];
|
||||
|
||||
if (!objectId) {
|
||||
throw new WorkflowTriggerException(
|
||||
`Object metadata not found for object: ${availability.objectNameSingular}`,
|
||||
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
|
||||
);
|
||||
}
|
||||
|
||||
availabilityObjectMetadataId = objectId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.commandMenuItemService.create(
|
||||
{
|
||||
workflowVersionId: workflowVersion.id,
|
||||
label: workflow.name ?? 'Manual Trigger',
|
||||
icon: trigger.settings.icon,
|
||||
availabilityType,
|
||||
availabilityObjectMetadataId,
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
case WorkflowTriggerType.WEBHOOK:
|
||||
return;
|
||||
case WorkflowTriggerType.DATABASE_EVENT: {
|
||||
@@ -361,7 +434,32 @@ export class WorkflowTriggerWorkspaceService {
|
||||
});
|
||||
|
||||
return;
|
||||
case WorkflowTriggerType.MANUAL:
|
||||
case WorkflowTriggerType.MANUAL: {
|
||||
const isCommandMenuItemEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isCommandMenuItemEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingCommandMenuItem =
|
||||
await this.commandMenuItemService.findByWorkflowVersionId(
|
||||
workflowVersion.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (existingCommandMenuItem) {
|
||||
await this.commandMenuItemService.delete(
|
||||
existingCommandMenuItem.id,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case WorkflowTriggerType.WEBHOOK:
|
||||
return;
|
||||
default:
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`CommandMenuItem creation should fail when creating with empty label 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"commandMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_COMMAND_MENU_ITEM_INPUT",
|
||||
"message": "Label is required",
|
||||
"userFriendlyMessage": "Label is required",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"id": Any<String>,
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "commandMenuItem",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 commandMenuItem",
|
||||
"summary": {
|
||||
"commandMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "Metadata validation failed",
|
||||
},
|
||||
"message": "Multiple validation errors occurred while creating command menu item",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem creation should fail when creating with missing label 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Field "label" of required type "String!" was not provided.",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem creation should fail when creating with missing workflowVersionId 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Field "workflowVersionId" of required type "UUID!" was not provided.",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem creation should fail when creating with empty workflowVersionId 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "GRAPHQL_VALIDATION_FAILED",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
"value": "",
|
||||
},
|
||||
"message": "Invalid UUID",
|
||||
"name": "ValidationError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem creation should fail when creating with invalid workflowVersionId (not a UUID) 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "GRAPHQL_VALIDATION_FAILED",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
"value": "not-a-valid-uuid",
|
||||
},
|
||||
"message": "Invalid UUID",
|
||||
"name": "ValidationError",
|
||||
}
|
||||
`;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`CommandMenuItem deletion should fail when deleting a non-existent command menu item 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "NOT_FOUND",
|
||||
"subCode": "COMMAND_MENU_ITEM_NOT_FOUND",
|
||||
"userFriendlyMessage": "Command menu item not found.",
|
||||
},
|
||||
"message": "Command menu item not found",
|
||||
"name": "NotFoundError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem deletion should fail when deleting with missing id 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Variable "$id" of required type "UUID!" was not provided.",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem deletion should fail when deleting with empty id 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "GRAPHQL_VALIDATION_FAILED",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
"value": "",
|
||||
},
|
||||
"message": "Invalid UUID",
|
||||
"name": "ValidationError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem deletion should fail when deleting with invalid id (not a UUID) 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "GRAPHQL_VALIDATION_FAILED",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
"value": "not-a-valid-uuid",
|
||||
},
|
||||
"message": "Invalid UUID",
|
||||
"name": "ValidationError",
|
||||
}
|
||||
`;
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`CommandMenuItem update should fail when updating with empty label 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"commandMenuItem": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_COMMAND_MENU_ITEM_INPUT",
|
||||
"message": "Label is required",
|
||||
"userFriendlyMessage": "Label is required",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"id": Any<String>,
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "commandMenuItem",
|
||||
"status": "fail",
|
||||
"type": "update",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 commandMenuItem",
|
||||
"summary": {
|
||||
"commandMenuItem": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "Metadata validation failed",
|
||||
},
|
||||
"message": "Multiple validation errors occurred while updating command menu item",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem update should fail when updating with missing id 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Field "id" of required type "UUID!" was not provided.",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem update should fail when updating with empty id 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "GRAPHQL_VALIDATION_FAILED",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
"value": "",
|
||||
},
|
||||
"message": "Invalid UUID",
|
||||
"name": "ValidationError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem update should fail when updating with invalid id (not a UUID) 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "GRAPHQL_VALIDATION_FAILED",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
"value": "not-a-valid-uuid",
|
||||
},
|
||||
"message": "Invalid UUID",
|
||||
"name": "ValidationError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CommandMenuItem update should fail when updating a non-existent command menu item 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "NOT_FOUND",
|
||||
"subCode": "COMMAND_MENU_ITEM_NOT_FOUND",
|
||||
"userFriendlyMessage": "Command menu item not found.",
|
||||
},
|
||||
"message": "Command menu item not found",
|
||||
"name": "NotFoundError",
|
||||
}
|
||||
`;
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input';
|
||||
|
||||
type TestContext = {
|
||||
input: CreateCommandMenuItemInput;
|
||||
};
|
||||
|
||||
const failingCommandMenuItemCreationTestCases: EachTestingContext<TestContext>[] =
|
||||
[
|
||||
{
|
||||
title: 'when creating with empty label',
|
||||
context: {
|
||||
input: {
|
||||
workflowVersionId: faker.string.uuid(),
|
||||
label: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when creating with missing label',
|
||||
context: {
|
||||
input: {
|
||||
workflowVersionId: faker.string.uuid(),
|
||||
} as CreateCommandMenuItemInput,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when creating with missing workflowVersionId',
|
||||
context: {
|
||||
input: {
|
||||
label: 'Test Label',
|
||||
} as CreateCommandMenuItemInput,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when creating with empty workflowVersionId',
|
||||
context: {
|
||||
input: {
|
||||
workflowVersionId: '',
|
||||
label: 'Test Label',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when creating with invalid workflowVersionId (not a UUID)',
|
||||
context: {
|
||||
input: {
|
||||
workflowVersionId: 'not-a-valid-uuid',
|
||||
label: 'Test Label',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('CommandMenuItem creation should fail', () => {
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: true,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: false,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(eachTestingContextFilter(failingCommandMenuItemCreationTestCases))(
|
||||
'$title',
|
||||
async ({ context }) => {
|
||||
const { errors } = await createCommandMenuItem({
|
||||
expectToFail: true,
|
||||
input: context.input,
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({
|
||||
errors,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { type DeleteCommandMenuItemFactoryInput } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item-query-factory.util';
|
||||
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
|
||||
type TestContext = {
|
||||
input: DeleteCommandMenuItemFactoryInput;
|
||||
};
|
||||
|
||||
const failingCommandMenuItemDeletionTestCases: EachTestingContext<TestContext>[] =
|
||||
[
|
||||
{
|
||||
title: 'when deleting a non-existent command menu item',
|
||||
context: {
|
||||
input: {
|
||||
id: faker.string.uuid(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when deleting with missing id',
|
||||
context: {
|
||||
input: {} as DeleteCommandMenuItemFactoryInput,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when deleting with empty id',
|
||||
context: {
|
||||
input: {
|
||||
id: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when deleting with invalid id (not a UUID)',
|
||||
context: {
|
||||
input: {
|
||||
id: 'not-a-valid-uuid',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('CommandMenuItem deletion should fail', () => {
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: true,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: false,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(eachTestingContextFilter(failingCommandMenuItemDeletionTestCases))(
|
||||
'$title',
|
||||
async ({ context }) => {
|
||||
const { errors } = await deleteCommandMenuItem({
|
||||
expectToFail: true,
|
||||
input: context.input,
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({
|
||||
errors,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
|
||||
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
|
||||
import { updateCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/update-command-menu-item.util';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { type UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input';
|
||||
|
||||
type TestContext = {
|
||||
input: (testSetup: TestSetup) => UpdateCommandMenuItemInput;
|
||||
};
|
||||
|
||||
type TestSetup = {
|
||||
testCommandMenuItemId: string;
|
||||
};
|
||||
|
||||
describe('CommandMenuItem update should fail', () => {
|
||||
let testCommandMenuItemId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: true,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: false,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const { data } = await createCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
workflowVersionId: faker.string.uuid(),
|
||||
label: 'Test Command Menu Item To Update',
|
||||
icon: 'IconOriginal',
|
||||
isPinned: false,
|
||||
},
|
||||
});
|
||||
|
||||
testCommandMenuItemId = data.createCommandMenuItem.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (testCommandMenuItemId) {
|
||||
await deleteCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: { id: testCommandMenuItemId },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const failingCommandMenuItemUpdateTestCases: EachTestingContext<TestContext>[] =
|
||||
[
|
||||
{
|
||||
title: 'when updating with empty label',
|
||||
context: {
|
||||
input: (testSetup) => ({
|
||||
id: testSetup.testCommandMenuItemId,
|
||||
label: '',
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when updating with missing id',
|
||||
context: {
|
||||
input: () =>
|
||||
({
|
||||
label: 'Updated Label',
|
||||
}) as UpdateCommandMenuItemInput,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when updating with empty id',
|
||||
context: {
|
||||
input: () => ({
|
||||
id: '',
|
||||
label: 'Updated Label',
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when updating with invalid id (not a UUID)',
|
||||
context: {
|
||||
input: () => ({
|
||||
id: 'not-a-valid-uuid',
|
||||
label: 'Updated Label',
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'when updating a non-existent command menu item',
|
||||
context: {
|
||||
input: () => ({
|
||||
id: faker.string.uuid(),
|
||||
label: 'Updated Label',
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it.each(eachTestingContextFilter(failingCommandMenuItemUpdateTestCases))(
|
||||
'$title',
|
||||
async ({ context }) => {
|
||||
const testSetup: TestSetup = {
|
||||
testCommandMenuItemId,
|
||||
};
|
||||
|
||||
const input = context.input(testSetup);
|
||||
|
||||
const { errors } = await updateCommandMenuItem({
|
||||
expectToFail: true,
|
||||
input,
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({
|
||||
errors,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
|
||||
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
|
||||
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
|
||||
describe('CommandMenuItem creation should succeed', () => {
|
||||
let createdCommandMenuItemId: string;
|
||||
let companyObjectMetadataId: string;
|
||||
let personObjectMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: true,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const { objects } = await findManyObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
filter: {},
|
||||
paging: { first: 1000 },
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
`,
|
||||
});
|
||||
|
||||
jestExpectToBeDefined(objects);
|
||||
|
||||
const companyObjectMetadata = objects.find(
|
||||
(object: { nameSingular: string }) => object.nameSingular === 'company',
|
||||
);
|
||||
const personObjectMetadata = objects.find(
|
||||
(object: { nameSingular: string }) => object.nameSingular === 'person',
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(companyObjectMetadata);
|
||||
jestExpectToBeDefined(personObjectMetadata);
|
||||
|
||||
companyObjectMetadataId = companyObjectMetadata.id;
|
||||
personObjectMetadataId = personObjectMetadata.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: false,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdCommandMenuItemId) {
|
||||
await deleteCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: { id: createdCommandMenuItemId },
|
||||
});
|
||||
createdCommandMenuItemId = undefined as unknown as string;
|
||||
}
|
||||
});
|
||||
|
||||
it('should create a basic command menu item with minimal input', async () => {
|
||||
const workflowVersionId = faker.string.uuid();
|
||||
|
||||
const { data } = await createCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
workflowVersionId,
|
||||
label: 'Test Command Menu Item',
|
||||
},
|
||||
});
|
||||
|
||||
createdCommandMenuItemId = data?.createCommandMenuItem?.id;
|
||||
|
||||
expect(data.createCommandMenuItem).toMatchObject({
|
||||
id: expect.any(String),
|
||||
workflowVersionId,
|
||||
label: 'Test Command Menu Item',
|
||||
icon: null,
|
||||
isPinned: false,
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
availabilityObjectMetadataId: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create command menu item with all optional fields', async () => {
|
||||
const workflowVersionId = faker.string.uuid();
|
||||
|
||||
const { data } = await createCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
workflowVersionId,
|
||||
label: 'Full Command Menu Item',
|
||||
icon: 'IconSparkles',
|
||||
isPinned: true,
|
||||
availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD,
|
||||
availabilityObjectMetadataId: companyObjectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
createdCommandMenuItemId = data?.createCommandMenuItem?.id;
|
||||
|
||||
expect(data.createCommandMenuItem).toMatchObject({
|
||||
id: expect.any(String),
|
||||
workflowVersionId,
|
||||
label: 'Full Command Menu Item',
|
||||
icon: 'IconSparkles',
|
||||
isPinned: true,
|
||||
availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD,
|
||||
availabilityObjectMetadataId: companyObjectMetadataId,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create command menu item with BULK_RECORDS availability', async () => {
|
||||
const workflowVersionId = faker.string.uuid();
|
||||
|
||||
const { data } = await createCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
workflowVersionId,
|
||||
label: 'Bulk Records Command',
|
||||
availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS,
|
||||
availabilityObjectMetadataId: personObjectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
createdCommandMenuItemId = data?.createCommandMenuItem?.id;
|
||||
|
||||
expect(data.createCommandMenuItem).toMatchObject({
|
||||
id: expect.any(String),
|
||||
workflowVersionId,
|
||||
label: 'Bulk Records Command',
|
||||
availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS,
|
||||
availabilityObjectMetadataId: personObjectMetadataId,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create command menu item with GLOBAL availability (default)', async () => {
|
||||
const workflowVersionId = faker.string.uuid();
|
||||
|
||||
const { data } = await createCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
workflowVersionId,
|
||||
label: 'Global Command',
|
||||
},
|
||||
});
|
||||
|
||||
createdCommandMenuItemId = data?.createCommandMenuItem?.id;
|
||||
|
||||
expect(data.createCommandMenuItem).toMatchObject({
|
||||
id: expect.any(String),
|
||||
workflowVersionId,
|
||||
label: 'Global Command',
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
});
|
||||
});
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
|
||||
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
|
||||
import { findCommandMenuItems } from 'test/integration/metadata/suites/command-menu-item/utils/find-command-menu-items.util';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
|
||||
describe('CommandMenuItem deletion should succeed', () => {
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: true,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: false,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete an existing command menu item', async () => {
|
||||
const workflowVersionId = faker.string.uuid();
|
||||
|
||||
const { data: createData } = await createCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
workflowVersionId,
|
||||
label: 'Command Menu Item To Delete',
|
||||
},
|
||||
});
|
||||
|
||||
const createdId = createData.createCommandMenuItem.id;
|
||||
|
||||
const { data: deleteData } = await deleteCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: { id: createdId },
|
||||
});
|
||||
|
||||
expect(deleteData.deleteCommandMenuItem).toMatchObject({
|
||||
id: createdId,
|
||||
label: 'Command Menu Item To Delete',
|
||||
});
|
||||
|
||||
const { data: findData } = await findCommandMenuItems({
|
||||
expectToFail: false,
|
||||
input: undefined,
|
||||
});
|
||||
|
||||
const deletedItem = findData.commandMenuItems.find(
|
||||
(item) => item.id === createdId,
|
||||
);
|
||||
|
||||
expect(deletedItem).toBeUndefined();
|
||||
});
|
||||
});
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
|
||||
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
|
||||
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
|
||||
import { updateCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/update-command-menu-item.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
|
||||
describe('CommandMenuItem update should succeed', () => {
|
||||
let createdCommandMenuItemId: string;
|
||||
let companyObjectMetadataId: string;
|
||||
let personObjectMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: true,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const { objects } = await findManyObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
filter: {},
|
||||
paging: { first: 1000 },
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
`,
|
||||
});
|
||||
|
||||
jestExpectToBeDefined(objects);
|
||||
|
||||
const companyObjectMetadata = objects.find(
|
||||
(object: { nameSingular: string }) => object.nameSingular === 'company',
|
||||
);
|
||||
const personObjectMetadata = objects.find(
|
||||
(object: { nameSingular: string }) => object.nameSingular === 'person',
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(companyObjectMetadata);
|
||||
jestExpectToBeDefined(personObjectMetadata);
|
||||
|
||||
companyObjectMetadataId = companyObjectMetadata.id;
|
||||
personObjectMetadataId = personObjectMetadata.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
value: false,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const workflowVersionId = faker.string.uuid();
|
||||
|
||||
const { data } = await createCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
workflowVersionId,
|
||||
label: 'Original Label',
|
||||
icon: 'IconOriginal',
|
||||
isPinned: false,
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
},
|
||||
});
|
||||
|
||||
createdCommandMenuItemId = data.createCommandMenuItem.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (createdCommandMenuItemId) {
|
||||
await deleteCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: { id: createdCommandMenuItemId },
|
||||
});
|
||||
createdCommandMenuItemId = undefined as unknown as string;
|
||||
}
|
||||
});
|
||||
|
||||
it('should update the label', async () => {
|
||||
const { data } = await updateCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: createdCommandMenuItemId,
|
||||
label: 'Updated Label',
|
||||
},
|
||||
});
|
||||
|
||||
expect(data.updateCommandMenuItem).toMatchObject({
|
||||
id: createdCommandMenuItemId,
|
||||
label: 'Updated Label',
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the icon', async () => {
|
||||
const { data } = await updateCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: createdCommandMenuItemId,
|
||||
icon: 'IconUpdated',
|
||||
},
|
||||
});
|
||||
|
||||
expect(data.updateCommandMenuItem).toMatchObject({
|
||||
id: createdCommandMenuItemId,
|
||||
icon: 'IconUpdated',
|
||||
});
|
||||
});
|
||||
|
||||
it('should update isPinned', async () => {
|
||||
const { data } = await updateCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: createdCommandMenuItemId,
|
||||
isPinned: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(data.updateCommandMenuItem).toMatchObject({
|
||||
id: createdCommandMenuItemId,
|
||||
isPinned: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update availabilityType and availabilityObjectMetadataId', async () => {
|
||||
const { data } = await updateCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: createdCommandMenuItemId,
|
||||
availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD,
|
||||
availabilityObjectMetadataId: companyObjectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(data.updateCommandMenuItem).toMatchObject({
|
||||
id: createdCommandMenuItemId,
|
||||
availabilityType: CommandMenuItemAvailabilityType.SINGLE_RECORD,
|
||||
availabilityObjectMetadataId: companyObjectMetadataId,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update multiple fields at once', async () => {
|
||||
const { data } = await updateCommandMenuItem({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: createdCommandMenuItemId,
|
||||
label: 'Fully Updated Label',
|
||||
icon: 'IconNew',
|
||||
isPinned: true,
|
||||
availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS,
|
||||
availabilityObjectMetadataId: personObjectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(data.updateCommandMenuItem).toMatchObject({
|
||||
id: createdCommandMenuItemId,
|
||||
label: 'Fully Updated Label',
|
||||
icon: 'IconNew',
|
||||
isPinned: true,
|
||||
availabilityType: CommandMenuItemAvailabilityType.BULK_RECORDS,
|
||||
availabilityObjectMetadataId: personObjectMetadataId,
|
||||
});
|
||||
});
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input';
|
||||
|
||||
export type CreateCommandMenuItemFactoryInput = CreateCommandMenuItemInput;
|
||||
|
||||
const DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS = `
|
||||
id
|
||||
workflowVersionId
|
||||
label
|
||||
icon
|
||||
isPinned
|
||||
availabilityType
|
||||
availabilityObjectMetadataId
|
||||
applicationId
|
||||
createdAt
|
||||
updatedAt
|
||||
`;
|
||||
|
||||
export const createCommandMenuItemQueryFactory = ({
|
||||
input,
|
||||
gqlFields = DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS,
|
||||
}: PerformMetadataQueryParams<CreateCommandMenuItemFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation CreateCommandMenuItem($input: CreateCommandMenuItemInput!) {
|
||||
createCommandMenuItem(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
type CreateCommandMenuItemFactoryInput,
|
||||
createCommandMenuItemQueryFactory,
|
||||
} from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto';
|
||||
|
||||
export const createCommandMenuItem = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: PerformMetadataQueryParams<CreateCommandMenuItemFactoryInput>): CommonResponseBody<{
|
||||
createCommandMenuItem: CommandMenuItemDTO;
|
||||
}> => {
|
||||
const graphqlOperation = createCommandMenuItemQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation, token);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItem creation should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItem creation has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
export type DeleteCommandMenuItemFactoryInput = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS = `
|
||||
id
|
||||
label
|
||||
`;
|
||||
|
||||
export const deleteCommandMenuItemQueryFactory = ({
|
||||
input,
|
||||
gqlFields = DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS,
|
||||
}: PerformMetadataQueryParams<DeleteCommandMenuItemFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation DeleteCommandMenuItem($id: UUID!) {
|
||||
deleteCommandMenuItem(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
type DeleteCommandMenuItemFactoryInput,
|
||||
deleteCommandMenuItemQueryFactory,
|
||||
} from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto';
|
||||
|
||||
export const deleteCommandMenuItem = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: PerformMetadataQueryParams<DeleteCommandMenuItemFactoryInput>): CommonResponseBody<{
|
||||
deleteCommandMenuItem: CommandMenuItemDTO;
|
||||
}> => {
|
||||
const graphqlOperation = deleteCommandMenuItemQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation, token);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItem deletion should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItem deletion has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
const DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS = `
|
||||
id
|
||||
workflowVersionId
|
||||
label
|
||||
icon
|
||||
isPinned
|
||||
availabilityType
|
||||
availabilityObjectMetadataId
|
||||
applicationId
|
||||
createdAt
|
||||
updatedAt
|
||||
`;
|
||||
|
||||
export const findCommandMenuItemsQueryFactory = ({
|
||||
gqlFields = DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS,
|
||||
}: PerformMetadataQueryParams<void>) => ({
|
||||
query: gql`
|
||||
query CommandMenuItems {
|
||||
commandMenuItems {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { findCommandMenuItemsQueryFactory } from 'test/integration/metadata/suites/command-menu-item/utils/find-command-menu-items-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto';
|
||||
|
||||
export const findCommandMenuItems = async ({
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: PerformMetadataQueryParams<void>): CommonResponseBody<{
|
||||
commandMenuItems: CommandMenuItemDTO[];
|
||||
}> => {
|
||||
const graphqlOperation = findCommandMenuItemsQueryFactory({
|
||||
input: undefined,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation, token);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItems query should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItems query has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
|
||||
import { type UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input';
|
||||
|
||||
export type UpdateCommandMenuItemFactoryInput = UpdateCommandMenuItemInput;
|
||||
|
||||
const DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS = `
|
||||
id
|
||||
workflowVersionId
|
||||
label
|
||||
icon
|
||||
isPinned
|
||||
availabilityType
|
||||
availabilityObjectMetadataId
|
||||
applicationId
|
||||
createdAt
|
||||
updatedAt
|
||||
`;
|
||||
|
||||
export const updateCommandMenuItemQueryFactory = ({
|
||||
input,
|
||||
gqlFields = DEFAULT_COMMAND_MENU_ITEM_GQL_FIELDS,
|
||||
}: PerformMetadataQueryParams<UpdateCommandMenuItemFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation UpdateCommandMenuItem($input: UpdateCommandMenuItemInput!) {
|
||||
updateCommandMenuItem(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
type UpdateCommandMenuItemFactoryInput,
|
||||
updateCommandMenuItemQueryFactory,
|
||||
} from 'test/integration/metadata/suites/command-menu-item/utils/update-command-menu-item-query-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto';
|
||||
|
||||
export const updateCommandMenuItem = async ({
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail = false,
|
||||
token,
|
||||
}: PerformMetadataQueryParams<UpdateCommandMenuItemFactoryInput>): CommonResponseBody<{
|
||||
updateCommandMenuItem: CommandMenuItemDTO;
|
||||
}> => {
|
||||
const graphqlOperation = updateCommandMenuItemQueryFactory({
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation, token);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItem update should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'CommandMenuItem update has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
@@ -20,5 +20,6 @@ export const ALL_METADATA_NAME = {
|
||||
pageLayout: 'pageLayout',
|
||||
pageLayoutWidget: 'pageLayoutWidget',
|
||||
pageLayoutTab: 'pageLayoutTab',
|
||||
commandMenuItem: 'commandMenuItem',
|
||||
frontComponent: 'frontComponent',
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user