Add db event trigger and cron trigger to migration v2 (#14772)

## Context
Add DB events triggers and Cron triggers to migration v2 builder/runner
(and adding corresponding services/resolvers)
This commit is contained in:
Weiko
2025-09-30 11:14:18 +02:00
committed by GitHub
parent 123202df41
commit 68b21d2942
82 changed files with 3340 additions and 57 deletions
@@ -720,6 +720,16 @@ export type CreateApprovedAccessDomainInput = {
email: Scalars['String'];
};
export type CreateCronTriggerInput = {
serverlessFunctionId: Scalars['String'];
settings: Scalars['JSON'];
};
export type CreateDatabaseEventTriggerInput = {
serverlessFunctionId: Scalars['String'];
settings: Scalars['JSON'];
};
export type CreateDraftFromWorkflowVersionInput = {
/** Workflow ID */
workflowId: Scalars['UUID'];
@@ -926,6 +936,19 @@ export type CreateWorkflowVersionStepInput = {
workflowVersionId: Scalars['UUID'];
};
export type CronTrigger = {
__typename?: 'CronTrigger';
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
serverlessFunctionId: Scalars['String'];
settings: Scalars['JSON'];
updatedAt: Scalars['DateTime'];
};
export type CronTriggerIdInput = {
id: Scalars['String'];
};
export type CursorPaging = {
/** Paginate after opaque cursor */
after?: InputMaybe<Scalars['ConnectionCursor']>;
@@ -946,6 +969,19 @@ export enum DatabaseEventAction {
UPDATED = 'UPDATED'
}
export type DatabaseEventTrigger = {
__typename?: 'DatabaseEventTrigger';
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
serverlessFunctionId: Scalars['String'];
settings: Scalars['JSON'];
updatedAt: Scalars['DateTime'];
};
export type DatabaseEventTriggerIdInput = {
id: Scalars['String'];
};
export type DateFilter = {
eq?: InputMaybe<Scalars['Date']>;
gt?: InputMaybe<Scalars['Date']>;
@@ -1597,6 +1633,8 @@ export type Mutation = {
createObjectEvent: Analytics;
createOneAgent: Agent;
createOneAppToken: AppToken;
createOneCronTrigger: CronTrigger;
createOneDatabaseEventTrigger: DatabaseEventTrigger;
createOneField: Field;
createOneObject: Object;
createOneRemoteServer: RemoteServer;
@@ -1623,6 +1661,8 @@ export type Mutation = {
deleteEmailingDomain: Scalars['Boolean'];
deleteFile: File;
deleteOneAgent: Agent;
deleteOneCronTrigger: CronTrigger;
deleteOneDatabaseEventTrigger: DatabaseEventTrigger;
deleteOneField: Field;
deleteOneObject: Object;
deleteOneRemoteServer: RemoteServer;
@@ -1704,6 +1744,8 @@ export type Mutation = {
updateDatabaseConfigVariable: Scalars['Boolean'];
updateLabPublicFeatureFlag: FeatureFlagDto;
updateOneAgent: Agent;
updateOneCronTrigger: CronTrigger;
updateOneDatabaseEventTrigger: DatabaseEventTrigger;
updateOneField: Field;
updateOneObject: Object;
updateOneRemoteServer: RemoteServer;
@@ -1877,6 +1919,16 @@ export type MutationCreateOneAppTokenArgs = {
};
export type MutationCreateOneCronTriggerArgs = {
input: CreateCronTriggerInput;
};
export type MutationCreateOneDatabaseEventTriggerArgs = {
input: CreateDatabaseEventTriggerInput;
};
export type MutationCreateOneFieldArgs = {
input: CreateOneFieldMetadataInput;
};
@@ -2002,6 +2054,16 @@ export type MutationDeleteOneAgentArgs = {
};
export type MutationDeleteOneCronTriggerArgs = {
input: CronTriggerIdInput;
};
export type MutationDeleteOneDatabaseEventTriggerArgs = {
input: DatabaseEventTriggerIdInput;
};
export type MutationDeleteOneFieldArgs = {
input: DeleteOneFieldInput;
};
@@ -2398,6 +2460,16 @@ export type MutationUpdateOneAgentArgs = {
};
export type MutationUpdateOneCronTriggerArgs = {
input: UpdateCronTriggerInput;
};
export type MutationUpdateOneDatabaseEventTriggerArgs = {
input: UpdateDatabaseEventTriggerInput;
};
export type MutationUpdateOneFieldArgs = {
input: UpdateOneFieldMetadataInput;
};
@@ -2873,10 +2945,14 @@ export type Query = {
findAgentHandoffs: Array<AgentHandoffDto>;
findDistantTablesWithStatus: Array<RemoteTable>;
findManyAgents: Array<Agent>;
findManyCronTriggers: Array<CronTrigger>;
findManyDatabaseEventTriggers: Array<DatabaseEventTrigger>;
findManyPublicDomains: Array<PublicDomain>;
findManyRemoteServersByType: Array<RemoteServer>;
findManyServerlessFunctions: Array<ServerlessFunction>;
findOneAgent: Agent;
findOneCronTrigger: CronTrigger;
findOneDatabaseEventTrigger: DatabaseEventTrigger;
findOneRemoteServerById: RemoteServer;
findOneServerlessFunction: ServerlessFunction;
findWorkspaceFromInviteHash: Workspace;
@@ -3007,6 +3083,16 @@ export type QueryFindOneAgentArgs = {
};
export type QueryFindOneCronTriggerArgs = {
input: CronTriggerIdInput;
};
export type QueryFindOneDatabaseEventTriggerArgs = {
input: DatabaseEventTriggerIdInput;
};
export type QueryFindOneRemoteServerByIdArgs = {
input: RemoteServerIdInput;
};
@@ -3712,6 +3798,16 @@ export type UpdateApiKeyDto = {
revokedAt?: InputMaybe<Scalars['String']>;
};
export type UpdateCronTriggerInput = {
id: Scalars['String'];
settings: Scalars['JSON'];
};
export type UpdateDatabaseEventTriggerInput = {
id: Scalars['String'];
settings: Scalars['JSON'];
};
export type UpdateFieldInput = {
defaultValue?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
@@ -890,6 +890,15 @@ export type CreateWorkflowVersionStepInput = {
workflowVersionId: Scalars['UUID'];
};
export type CronTrigger = {
__typename?: 'CronTrigger';
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
serverlessFunctionId: Scalars['String'];
settings: Scalars['JSON'];
updatedAt: Scalars['DateTime'];
};
export type CursorPaging = {
/** Paginate after opaque cursor */
after?: InputMaybe<Scalars['ConnectionCursor']>;
@@ -910,6 +919,15 @@ export enum DatabaseEventAction {
UPDATED = 'UPDATED'
}
export type DatabaseEventTrigger = {
__typename?: 'DatabaseEventTrigger';
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
serverlessFunctionId: Scalars['String'];
settings: Scalars['JSON'];
updatedAt: Scalars['DateTime'];
};
export type DateFilter = {
eq?: InputMaybe<Scalars['Date']>;
gt?: InputMaybe<Scalars['Date']>;
@@ -3,7 +3,9 @@ import { Logger } from '@nestjs/common';
import { Command, CommandRunner } from 'nest-commander';
import { CleanupOrphanedFilesCronCommand } from 'src/engine/core-modules/file/crons/commands/cleanup-orphaned-files.cron.command';
import { CronTriggerCronCommand } from 'src/engine/metadata-modules/trigger/crons/commands/cron-trigger.cron.command';
import { CheckPublicDomainsValidRecordsCronCommand } from 'src/engine/core-modules/public-domain/crons/commands/check-public-domains-valid-records.cron.command';
import { CheckCustomDomainValidRecordsCronCommand } from 'src/engine/core-modules/workspace/crons/commands/check-custom-domain-valid-records.cron.command';
import { CronTriggerCronCommand } from 'src/engine/metadata-modules/cron-trigger/crons/commands/cron-trigger.cron.command';
import { CleanOnboardingWorkspacesCronCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.cron.command';
import { CleanSuspendedWorkspacesCronCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.cron.command';
import { CalendarEventListFetchCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command';
@@ -16,8 +18,6 @@ import { WorkflowCleanWorkflowRunsCronCommand } from 'src/modules/workflow/workf
import { WorkflowHandleStaledRunsCronCommand } from 'src/modules/workflow/workflow-runner/workflow-run-queue/cron/command/workflow-handle-staled-runs.cron.command';
import { WorkflowRunEnqueueCronCommand } from 'src/modules/workflow/workflow-runner/workflow-run-queue/cron/command/workflow-run-enqueue.cron.command';
import { WorkflowCronTriggerCronCommand } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/commands/workflow-cron-trigger.cron.command';
import { CheckCustomDomainValidRecordsCronCommand } from 'src/engine/core-modules/workspace/crons/commands/check-custom-domain-valid-records.cron.command';
import { CheckPublicDomainsValidRecordsCronCommand } from 'src/engine/core-modules/public-domain/crons/commands/check-public-domains-valid-records.cron.command';
@Command({
name: 'cron:register:all',
@@ -9,11 +9,14 @@ import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public-domain.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.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';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { TriggerModule } from 'src/engine/metadata-modules/trigger/trigger.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { DevSeederModule } from 'src/engine/workspace-manager/dev-seeder/dev-seeder.module';
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
@@ -22,8 +25,6 @@ import { CalendarEventImportManagerModule } from 'src/modules/calendar/calendar-
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
import { WorkflowRunQueueModule } from 'src/modules/workflow/workflow-runner/workflow-run-queue/workflow-run-queue.module';
import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.module';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public-domain.module';
@Module({
imports: [
@@ -46,7 +47,8 @@ import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public
WorkspaceCacheStorageModule,
ApiKeyModule,
FeatureFlagModule,
TriggerModule,
CronTriggerModule,
DatabaseEventTriggerModule,
WorkspaceCleanerModule,
PublicDomainModule,
],
@@ -10,17 +10,17 @@ import { type ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/ty
import { type ObjectRecordNonDestructiveEvent } from 'src/engine/core-modules/event-emitter/types/object-record-non-destructive-event';
import { type ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { type ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
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 { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { UpsertTimelineActivityFromInternalEvent } from 'src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job';
import { CallWebhookJobsJob } from 'src/engine/core-modules/webhook/jobs/call-webhook-jobs.job';
import { type ObjectRecordEventForWebhook } from 'src/engine/core-modules/webhook/types/object-record-event-for-webhook.type';
import { CallDatabaseEventTriggerJobsJob } from 'src/engine/metadata-modules/trigger/jobs/call-database-event-trigger-jobs.job';
import { CallDatabaseEventTriggerJobsJob } from 'src/engine/metadata-modules/database-event-trigger/jobs/call-database-event-trigger-jobs.job';
import { SubscriptionsService } from 'src/engine/subscriptions/subscriptions.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { UpsertTimelineActivityFromInternalEvent } from 'src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job';
@Injectable()
export class EntityEventsToDbListener {
@@ -6,4 +6,6 @@ export const ALL_FLAT_ENTITY_MAPS_PROPERTIES = [
'flatViewMaps',
'flatIndexMaps',
'flatServerlessFunctionMaps',
'flatDatabaseEventTriggerMaps',
'flatCronTriggerMaps',
] as const satisfies (keyof AllFlatEntityMaps)[];
@@ -10,4 +10,6 @@ export const EMPTY_ALL_FLAT_ENTITY_MAPS = {
flatViewFieldMaps: EMPTY_FLAT_ENTITY_MAPS,
flatViewMaps: EMPTY_FLAT_ENTITY_MAPS,
flatServerlessFunctionMaps: EMPTY_FLAT_ENTITY_MAPS,
flatCronTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
flatDatabaseEventTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
} as const satisfies AllFlatEntityMaps;
@@ -1,5 +1,7 @@
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.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 FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
@@ -11,4 +13,6 @@ export type AllFlatEntitiesByMetadataEngineName = {
viewField: FlatViewField;
index: FlatIndexMetadata;
serverlessFunction: FlatServerlessFunction;
cronTrigger: FlatCronTrigger;
databaseEventTrigger: FlatDatabaseEventTrigger;
};
@@ -14,11 +14,15 @@ import { EmailModule } from 'src/engine/core-modules/email/email.module';
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module';
import { UserModule } from 'src/engine/core-modules/user/user.module';
import { WebhookJobModule } from 'src/engine/core-modules/webhook/jobs/webhook-job.module';
import { HandleWorkspaceMemberDeletedJob } from 'src/engine/core-modules/workspace/handle-workspace-member-deleted.job';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.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';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
import { CleanOnboardingWorkspacesJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-onboarding-workspaces.job';
import { CleanSuspendedWorkspacesJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-suspended-workspaces.job';
@@ -31,10 +35,7 @@ import { FavoriteModule } from 'src/modules/favorite/favorite.module';
import { MessagingModule } from 'src/modules/messaging/messaging.module';
import { TimelineJobModule } from 'src/modules/timeline/jobs/timeline-job.module';
import { TimelineActivityModule } from 'src/modules/timeline/timeline-activity.module';
import { WebhookJobModule } from 'src/engine/core-modules/webhook/jobs/webhook-job.module';
import { WorkflowModule } from 'src/modules/workflow/workflow.module';
import { TriggerModule } from 'src/engine/metadata-modules/trigger/trigger.module';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
@Module({
imports: [
@@ -62,7 +63,8 @@ import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless
WorkspaceCleanerModule,
SubscriptionsModule,
AuditJobModule,
TriggerModule,
CronTriggerModule,
DatabaseEventTriggerModule,
ServerlessFunctionModule,
],
providers: [
@@ -0,0 +1,5 @@
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
export const FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES = [
'settings',
] as const satisfies (keyof FlatCronTrigger)[];
@@ -0,0 +1,6 @@
import { FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/cron-trigger/constants/flat-cron-trigger-editable-properties.constant';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
export const FLAT_CRON_TRIGGER_PROPERTIES_TO_COMPARE = [
...FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES,
] as const satisfies (keyof FlatCronTrigger)[];
@@ -0,0 +1,33 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { CronTriggerCronCommand } from 'src/engine/metadata-modules/cron-trigger/crons/commands/cron-trigger.cron.command';
import { CronTriggerCronJob } from 'src/engine/metadata-modules/cron-trigger/crons/jobs/cron-trigger.cron.job';
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { CronTriggerResolver } from 'src/engine/metadata-modules/cron-trigger/resolvers/cron-trigger.resolver';
import { CronTriggerV2Service } from 'src/engine/metadata-modules/cron-trigger/services/cron-trigger-v2.service';
import { WorkspaceFlatCronTriggerMapCacheService } from 'src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace, CronTrigger]),
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceMigrationV2Module,
],
providers: [
CronTriggerCronJob,
CronTriggerCronCommand,
CronTriggerResolver,
CronTriggerV2Service,
WorkspaceFlatCronTriggerMapCacheService,
],
exports: [
CronTriggerCronCommand,
CronTriggerV2Service,
WorkspaceFlatCronTriggerMapCacheService,
],
})
export class CronTriggerModule {}
@@ -6,7 +6,7 @@ import { MessageQueueService } from 'src/engine/core-modules/message-queue/servi
import {
CRON_TRIGGER_CRON_PATTERN,
CronTriggerCronJob,
} from 'src/engine/metadata-modules/trigger/crons/jobs/cron-trigger.cron.job';
} from 'src/engine/metadata-modules/cron-trigger/crons/jobs/cron-trigger.cron.job';
@Command({
name: 'cron:trigger:start-cron-trigger',
description:
@@ -10,7 +10,7 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { CronTrigger } from 'src/engine/metadata-modules/trigger/entities/cron-trigger.entity';
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import {
ServerlessFunctionTriggerJob,
@@ -0,0 +1,19 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { CronTriggerSettings } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
@InputType()
export class CreateCronTriggerInput {
@IsObject()
@IsNotEmpty()
@Field(() => GraphQLJSON)
settings: CronTriggerSettings;
@IsUUID()
@IsNotEmpty()
@Field()
serverlessFunctionId: string;
}
@@ -0,0 +1,11 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsUUID } from 'class-validator';
@InputType()
export class CronTriggerIdInput {
@IsUUID()
@IsNotEmpty()
@Field(() => String)
id: string;
}
@@ -0,0 +1,35 @@
import { Field, HideField, ObjectType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { IsDateString, IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { CronTriggerSettings } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
@ObjectType('CronTrigger')
export class CronTriggerDTO {
@IsUUID()
@IsNotEmpty()
@IDField(() => UUIDScalarType)
id: string;
@IsObject()
@Field(() => GraphQLJSON)
settings: CronTriggerSettings;
@HideField()
workspaceId: string;
@IsUUID()
@Field()
serverlessFunctionId: string;
@IsDateString()
@Field()
createdAt: Date;
@IsDateString()
@Field()
updatedAt: Date;
}
@@ -0,0 +1,19 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { CronTriggerSettings } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
@InputType()
export class UpdateCronTriggerInput {
@IsUUID()
@IsNotEmpty()
@Field(() => String)
id: string;
@IsObject()
@IsNotEmpty()
@Field(() => GraphQLJSON)
settings: CronTriggerSettings;
}
@@ -2,22 +2,27 @@ import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
Relation,
Index,
UpdateDateColumn,
} from 'typeorm';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { CronTriggerEntityRelationProperties } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
export type CronTriggerSettings = {
pattern: string;
};
export const CRON_TRIGGER_ENTITY_RELATION_PROPERTIES = [
'serverlessFunction',
] as const satisfies readonly CronTriggerEntityRelationProperties[];
@Entity({ name: 'cronTrigger', schema: 'core' })
@Index('IDX_CRON_TRIGGER_WORKSPACE_ID', ['workspaceId'])
export class CronTrigger extends SyncableEntity {
@@ -35,6 +40,9 @@ export class CronTrigger extends SyncableEntity {
@JoinColumn({ name: 'serverlessFunctionId' })
serverlessFunction: Relation<ServerlessFunctionEntity>;
@Column({ nullable: true, type: 'uuid' })
serverlessFunctionId: string | null;
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@@ -0,0 +1,12 @@
import { CustomException } from 'src/utils/custom-exception';
export class CronTriggerException extends CustomException {
constructor(message: string, code: CronTriggerExceptionCode) {
super(message, code);
}
}
export enum CronTriggerExceptionCode {
CRON_TRIGGER_NOT_FOUND = 'CRON_TRIGGER_NOT_FOUND',
CRON_TRIGGER_ALREADY_EXIST = 'CRON_TRIGGER_ALREADY_EXIST',
}
@@ -0,0 +1,99 @@
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input';
import { CronTriggerIdInput } from 'src/engine/metadata-modules/cron-trigger/dtos/cron-trigger-id.input';
import { CronTriggerDTO } from 'src/engine/metadata-modules/cron-trigger/dtos/cron-trigger.dto';
import { UpdateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/update-cron-trigger.input';
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { CronTriggerV2Service } from 'src/engine/metadata-modules/cron-trigger/services/cron-trigger-v2.service';
import { cronTriggerGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/cron-trigger/utils/cron-trigger-graphql-api-exception-handler.util';
@UseGuards(WorkspaceAuthGuard)
@Resolver()
@UsePipes(ResolverValidationPipe)
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
export class CronTriggerResolver {
constructor(
private readonly cronTriggerV2Service: CronTriggerV2Service,
@InjectRepository(CronTrigger)
private readonly cronTriggerRepository: Repository<CronTrigger>,
) {}
@Query(() => CronTriggerDTO)
async findOneCronTrigger(
@Args('input') { id }: CronTriggerIdInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.cronTriggerRepository.findOneOrFail({
where: {
id,
workspaceId,
},
});
} catch (error) {
cronTriggerGraphQLApiExceptionHandler(error);
}
}
@Query(() => [CronTriggerDTO])
async findManyCronTriggers(@AuthWorkspace() { id: workspaceId }: Workspace) {
try {
return await this.cronTriggerRepository.find({
where: { workspaceId },
});
} catch (error) {
cronTriggerGraphQLApiExceptionHandler(error);
}
}
@Mutation(() => CronTriggerDTO)
async deleteOneCronTrigger(
@Args('input') input: CronTriggerIdInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.cronTriggerV2Service.destroyOne({
destroyCronTriggerInput: input,
workspaceId,
});
} catch (error) {
cronTriggerGraphQLApiExceptionHandler(error);
}
}
@Mutation(() => CronTriggerDTO)
async updateOneCronTrigger(
@Args('input')
input: UpdateCronTriggerInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.cronTriggerV2Service.updateOne(input, workspaceId);
} catch (error) {
cronTriggerGraphQLApiExceptionHandler(error);
}
}
@Mutation(() => CronTriggerDTO)
async createOneCronTrigger(
@Args('input')
input: CreateCronTriggerInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.cronTriggerV2Service.createOne(input, workspaceId);
} catch (error) {
cronTriggerGraphQLApiExceptionHandler(error);
}
}
}
@@ -0,0 +1,222 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { CreateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input';
import { CronTriggerIdInput } from 'src/engine/metadata-modules/cron-trigger/dtos/cron-trigger-id.input';
import { UpdateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/update-cron-trigger.input';
import {
CronTriggerException,
CronTriggerExceptionCode,
} from 'src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception';
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { fromCreateCronTriggerInputToFlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/utils/from-create-cron-trigger-input-to-flat-cron-trigger.util';
import { fromUpdateCronTriggerInputToFlatCronTriggerToUpdateOrThrow } from 'src/engine/metadata-modules/cron-trigger/utils/from-update-cron-trigger-input-to-flat-cron-trigger-to-update-or-throw.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@Injectable()
export class CronTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {}
async createOne(
cronTriggerInput: CreateCronTriggerInput,
workspaceId: string,
) {
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatCronTriggerMaps'],
},
);
const existingFlatCronTriggerMaps = flatEntityMaps.flatCronTriggerMaps;
const flatCronTriggerToCreate = fromCreateCronTriggerInputToFlatCronTrigger(
{
createCronTriggerInput: cronTriggerInput,
workspaceId,
},
);
const toFlatCronTriggerMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatCronTriggerToCreate,
flatEntityMaps: existingFlatCronTriggerMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatCronTriggerMaps: {
from: existingFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: false,
},
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while creating cron trigger',
);
}
const { flatCronTriggerMaps: recomputedExistingFlatCronTriggerMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatCronTriggerMaps'],
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatCronTriggerToCreate.id,
flatEntityMaps: recomputedExistingFlatCronTriggerMaps,
});
}
async updateOne(
cronTriggerInput: UpdateCronTriggerInput,
workspaceId: string,
) {
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatCronTriggerMaps'],
},
);
const existingFlatCronTriggerMaps = flatEntityMaps.flatCronTriggerMaps;
const optimisticallyUpdatedFlatCronTrigger =
fromUpdateCronTriggerInputToFlatCronTriggerToUpdateOrThrow({
flatCronTriggerMaps: existingFlatCronTriggerMaps,
updateCronTriggerInput: cronTriggerInput,
});
const fromFlatCronTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [optimisticallyUpdatedFlatCronTrigger.id],
flatEntityMaps: existingFlatCronTriggerMaps,
});
const toFlatCronTriggerMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatCronTrigger,
flatEntityMaps: fromFlatCronTriggerMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatCronTriggerMaps: {
from: existingFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: false,
},
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while updating cron trigger',
);
}
const { flatCronTriggerMaps: recomputedExistingFlatCronTriggerMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatCronTriggerMaps'],
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: optimisticallyUpdatedFlatCronTrigger.id,
flatEntityMaps: recomputedExistingFlatCronTriggerMaps,
});
}
async destroyOne({
destroyCronTriggerInput,
workspaceId,
}: {
destroyCronTriggerInput: CronTriggerIdInput;
workspaceId: string;
}): Promise<FlatCronTrigger> {
const { flatCronTriggerMaps: existingFlatCronTriggerMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatCronTriggerMaps'],
},
);
const existingFlatCronTrigger =
existingFlatCronTriggerMaps.byId[destroyCronTriggerInput.id];
if (!isDefined(existingFlatCronTrigger)) {
throw new CronTriggerException(
'Cron trigger to destroy not found',
CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND,
);
}
const fromFlatCronTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingFlatCronTrigger.id],
flatEntityMaps: existingFlatCronTriggerMaps,
});
const toFlatCronTriggerMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatCronTriggerMaps,
entityToDeleteId: existingFlatCronTrigger.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatCronTriggerMaps: {
from: fromFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: true,
},
workspaceId,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while destroying cron trigger',
);
}
return existingFlatCronTrigger;
}
}
@@ -0,0 +1,69 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import {
CRON_TRIGGER_ENTITY_RELATION_PROPERTIES,
CronTrigger,
} from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@Injectable()
@WorkspaceFlatMapCache('flatCronTriggerMaps')
export class WorkspaceFlatCronTriggerMapCacheService extends WorkspaceFlatMapCacheService<
FlatEntityMaps<FlatCronTrigger>
> {
constructor(
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
cacheStorageService: CacheStorageService,
@InjectRepository(CronTrigger)
private readonly cronTriggerRepository: Repository<CronTrigger>,
) {
super(cacheStorageService);
}
protected async computeFlatMap({
workspaceId,
}: {
workspaceId: string;
}): Promise<FlatEntityMaps<FlatCronTrigger>> {
const cronTriggers = await this.cronTriggerRepository.find({
where: {
workspaceId,
},
});
const flatCronTriggerMaps = cronTriggers.reduce<
FlatEntityMaps<FlatCronTrigger>
>((flatEntityMaps, cronTrigger) => {
const flatCronTrigger = {
...removePropertiesFromRecord(cronTrigger, [
...CRON_TRIGGER_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier: cronTrigger.universalIdentifier ?? cronTrigger.id,
} satisfies FlatCronTrigger;
return {
byId: {
...flatEntityMaps.byId,
[flatCronTrigger.id]: flatCronTrigger,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatCronTrigger.universalIdentifier]: flatCronTrigger.id,
},
};
}, EMPTY_FLAT_ENTITY_MAPS);
return flatCronTriggerMaps;
}
}
@@ -0,0 +1,6 @@
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
export type FlatCronTriggerPropertiesToCompare = keyof Pick<
FlatCronTrigger,
'settings'
>;
@@ -0,0 +1,17 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
export type CronTriggerEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<
CronTrigger,
ServerlessFunctionEntity | Workspace
>;
export type FlatCronTrigger = Omit<
CronTrigger,
CronTriggerEntityRelationProperties
> & {
universalIdentifier: string;
};
@@ -0,0 +1,69 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { FLAT_CRON_TRIGGER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/cron-trigger/constants/flat-cron-trigger-properties-to-compare.constant';
import { type CronTriggerSettings } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { type FlatCronTriggerPropertiesToCompare } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger-properties-to-compare.type';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { type UpdateCronTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
type GetWorkspaceMigrationUpdateCronTriggerActionArgs = FromTo<
FlatCronTrigger,
'FlatCronTrigger'
>;
export const compareTwoFlatCronTrigger = ({
fromFlatCronTrigger,
toFlatCronTrigger,
}: GetWorkspaceMigrationUpdateCronTriggerActionArgs) => {
const transformMetadataForComparisonParameters = {
shouldIgnoreProperty: (property: string) =>
!FLAT_CRON_TRIGGER_PROPERTIES_TO_COMPARE.includes(
property as FlatCronTriggerPropertiesToCompare,
),
propertiesToStringify: ['settings'] as const,
};
const fromCompare = transformMetadataForComparison(
fromFlatCronTrigger,
transformMetadataForComparisonParameters,
);
const toCompare = transformMetadataForComparison(
toFlatCronTrigger,
transformMetadataForComparisonParameters,
);
const flatCronTriggerDifferences = diff(fromCompare, toCompare);
return flatCronTriggerDifferences.flatMap<
UpdateCronTriggerAction['updates'][number]
>((difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatCronTriggerPropertiesToCompare;
if (property === 'settings') {
return {
from: parseJson(oldValue) as CronTriggerSettings,
to: parseJson(value) as CronTriggerSettings,
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occurs, we should only provide null never undefined and so on
return [];
}
}
});
};
@@ -0,0 +1,23 @@
import {
ConflictError,
NotFoundError,
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import {
CronTriggerException,
CronTriggerExceptionCode,
} from 'src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception';
export const cronTriggerGraphQLApiExceptionHandler = (error: Error): void => {
if (error instanceof CronTriggerException) {
switch (error.code) {
case CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND:
throw new NotFoundError('Cron trigger not found');
case CronTriggerExceptionCode.CRON_TRIGGER_ALREADY_EXIST:
throw new ConflictError('Cron trigger already exists');
default:
throw error;
}
}
throw error;
};
@@ -0,0 +1,9 @@
import { CronTriggerException } from 'src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception';
export const cronTriggerGraphqlApiExceptionHandler = (error: Error) => {
if (error instanceof CronTriggerException) {
throw error;
}
throw error;
};
@@ -0,0 +1,24 @@
import { v4 as uuidv4 } from 'uuid';
import { type CreateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
export const fromCreateCronTriggerInputToFlatCronTrigger = ({
createCronTriggerInput,
workspaceId,
}: {
createCronTriggerInput: CreateCronTriggerInput;
workspaceId: string;
}): FlatCronTrigger => {
const now = new Date();
return {
id: uuidv4(),
universalIdentifier: uuidv4(),
settings: createCronTriggerInput.settings,
serverlessFunctionId: createCronTriggerInput.serverlessFunctionId,
workspaceId,
createdAt: now,
updatedAt: now,
};
};
@@ -0,0 +1,31 @@
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type UpdateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/update-cron-trigger.input';
import {
CronTriggerException,
CronTriggerExceptionCode,
} from 'src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
export const fromUpdateCronTriggerInputToFlatCronTriggerToUpdateOrThrow = ({
flatCronTriggerMaps,
updateCronTriggerInput,
}: {
flatCronTriggerMaps: FlatEntityMaps<FlatCronTrigger>;
updateCronTriggerInput: UpdateCronTriggerInput;
}): FlatCronTrigger => {
const existingFlatCronTrigger =
flatCronTriggerMaps.byId[updateCronTriggerInput.id];
if (!existingFlatCronTrigger) {
throw new CronTriggerException(
'Cron trigger not found',
CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND,
);
}
return {
...existingFlatCronTrigger,
settings: updateCronTriggerInput.settings,
updatedAt: new Date(),
};
};
@@ -0,0 +1,5 @@
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
export const FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES = [
'settings',
] as const satisfies (keyof FlatDatabaseEventTrigger)[];
@@ -0,0 +1,6 @@
import { FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-editable-properties.constant';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
export const FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE = [
...FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES,
] as const satisfies (keyof FlatDatabaseEventTrigger)[];
@@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { CallDatabaseEventTriggerJobsJob } from 'src/engine/metadata-modules/database-event-trigger/jobs/call-database-event-trigger-jobs.job';
import { DatabaseEventTriggerResolver } from 'src/engine/metadata-modules/database-event-trigger/resolvers/database-event-trigger.resolver';
import { DatabaseEventTriggerV2Service } from 'src/engine/metadata-modules/database-event-trigger/services/database-event-trigger-v2.service';
import { WorkspaceFlatDatabaseEventTriggerMapCacheService } from 'src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace, DatabaseEventTrigger]),
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceMigrationV2Module,
],
providers: [
CallDatabaseEventTriggerJobsJob,
DatabaseEventTriggerV2Service,
DatabaseEventTriggerResolver,
WorkspaceFlatDatabaseEventTriggerMapCacheService,
],
exports: [DatabaseEventTriggerV2Service],
})
export class DatabaseEventTriggerModule {}
@@ -0,0 +1,19 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { DatabaseEventTriggerSettings } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
@InputType()
export class CreateDatabaseEventTriggerInput {
@IsObject()
@IsNotEmpty()
@Field(() => GraphQLJSON)
settings: DatabaseEventTriggerSettings;
@IsUUID()
@IsNotEmpty()
@Field()
serverlessFunctionId: string;
}
@@ -0,0 +1,11 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsUUID } from 'class-validator';
@InputType()
export class DatabaseEventTriggerIdInput {
@IsUUID()
@IsNotEmpty()
@Field()
id: string;
}
@@ -0,0 +1,35 @@
import { Field, HideField, ObjectType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { IsDateString, IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { DatabaseEventTriggerSettings } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
@ObjectType('DatabaseEventTrigger')
export class DatabaseEventTriggerDTO {
@IsUUID()
@IsNotEmpty()
@IDField(() => UUIDScalarType)
id: string;
@IsObject()
@Field(() => GraphQLJSON)
settings: DatabaseEventTriggerSettings;
@HideField()
workspaceId: string;
@IsUUID()
@Field()
serverlessFunctionId: string;
@IsDateString()
@Field()
createdAt: Date;
@IsDateString()
@Field()
updatedAt: Date;
}
@@ -0,0 +1,19 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { DatabaseEventTriggerSettings } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
@InputType()
export class UpdateDatabaseEventTriggerInput {
@IsUUID()
@IsNotEmpty()
@Field()
id: string;
@IsObject()
@IsNotEmpty()
@Field(() => GraphQLJSON)
settings: DatabaseEventTriggerSettings;
}
@@ -2,25 +2,33 @@ import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
Relation,
Index,
UpdateDateColumn,
} from 'typeorm';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { DatabaseEventTriggerEntityRelationProperties } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
export type DatabaseEventTriggerSettings = {
eventName: string;
};
@Entity({ name: 'databaseEventTrigger', schema: 'core' })
export const DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES = [
'serverlessFunction',
] as const satisfies readonly DatabaseEventTriggerEntityRelationProperties[];
@Entity('databaseEventTrigger')
@Index('IDX_DATABASE_EVENT_TRIGGER_WORKSPACE_ID', ['workspaceId'])
export class DatabaseEventTrigger extends SyncableEntity {
export class DatabaseEventTrigger
extends SyncableEntity
implements Required<DatabaseEventTrigger>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -35,6 +43,9 @@ export class DatabaseEventTrigger extends SyncableEntity {
@JoinColumn({ name: 'serverlessFunctionId' })
serverlessFunction: Relation<ServerlessFunctionEntity>;
@Column({ nullable: true, type: 'uuid' })
serverlessFunctionId: string | null;
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@@ -0,0 +1,12 @@
import { CustomException } from 'src/utils/custom-exception';
export class DatabaseEventTriggerException extends CustomException {
constructor(message: string, code: DatabaseEventTriggerExceptionCode) {
super(message, code);
}
}
export enum DatabaseEventTriggerExceptionCode {
DATABASE_EVENT_TRIGGER_NOT_FOUND = 'DATABASE_EVENT_TRIGGER_NOT_FOUND',
DATABASE_EVENT_TRIGGER_ALREADY_EXIST = 'DATABASE_EVENT_TRIGGER_ALREADY_EXIST',
}
@@ -2,18 +2,18 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import type { ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/types/object-record-event.event';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import type { ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/types/object-record-event.event';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/trigger/entities/database-event-trigger.entity';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import {
ServerlessFunctionTriggerJob,
ServerlessFunctionTriggerJobData,
} from 'src/engine/metadata-modules/serverless-function/jobs/serverless-function-trigger.job';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
@Processor(MessageQueue.triggerQueue)
export class CallDatabaseEventTriggerJobsJob {
@@ -0,0 +1,107 @@
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input';
import { DatabaseEventTriggerIdInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/database-event-trigger-id.input';
import { DatabaseEventTriggerDTO } from 'src/engine/metadata-modules/database-event-trigger/dtos/database-event-trigger.dto';
import { UpdateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/update-database-event-trigger.input';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { DatabaseEventTriggerV2Service } from 'src/engine/metadata-modules/database-event-trigger/services/database-event-trigger-v2.service';
import { databaseEventTriggerGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/database-event-trigger/utils/database-event-trigger-graphql-api-exception-handler.utils';
@UseGuards(WorkspaceAuthGuard)
@Resolver()
@UsePipes(ResolverValidationPipe)
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
export class DatabaseEventTriggerResolver {
constructor(
private readonly databaseEventTriggerV2Service: DatabaseEventTriggerV2Service,
@InjectRepository(DatabaseEventTrigger)
private readonly databaseEventTriggerRepository: Repository<DatabaseEventTrigger>,
) {}
@Query(() => DatabaseEventTriggerDTO)
async findOneDatabaseEventTrigger(
@Args('input') { id }: DatabaseEventTriggerIdInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.databaseEventTriggerRepository.findOneOrFail({
where: {
id,
workspaceId,
},
});
} catch (error) {
databaseEventTriggerGraphQLApiExceptionHandler(error);
}
}
@Query(() => [DatabaseEventTriggerDTO])
async findManyDatabaseEventTriggers(
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.databaseEventTriggerRepository.find({
where: { workspaceId },
});
} catch (error) {
databaseEventTriggerGraphQLApiExceptionHandler(error);
}
}
@Mutation(() => DatabaseEventTriggerDTO)
async deleteOneDatabaseEventTrigger(
@Args('input') input: DatabaseEventTriggerIdInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.databaseEventTriggerV2Service.destroyOne({
destroyDatabaseEventTriggerInput: input,
workspaceId,
});
} catch (error) {
databaseEventTriggerGraphQLApiExceptionHandler(error);
}
}
@Mutation(() => DatabaseEventTriggerDTO)
async updateOneDatabaseEventTrigger(
@Args('input')
input: UpdateDatabaseEventTriggerInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.databaseEventTriggerV2Service.updateOne(
input,
workspaceId,
);
} catch (error) {
databaseEventTriggerGraphQLApiExceptionHandler(error);
}
}
@Mutation(() => DatabaseEventTriggerDTO)
async createOneDatabaseEventTrigger(
@Args('input')
input: CreateDatabaseEventTriggerInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
try {
return await this.databaseEventTriggerV2Service.createOne(
input,
workspaceId,
);
} catch (error) {
databaseEventTriggerGraphQLApiExceptionHandler(error);
}
}
}
@@ -0,0 +1,239 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { CreateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input';
import { DatabaseEventTriggerIdInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/database-event-trigger-id.input';
import { UpdateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/update-database-event-trigger.input';
import {
DatabaseEventTriggerException,
DatabaseEventTriggerExceptionCode,
} from 'src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception';
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/utils/from-create-database-event-trigger-input-to-flat-database-event-trigger.util';
import { fromUpdateDatabaseEventTriggerInputToFlatDatabaseEventTriggerToUpdateOrThrow } from 'src/engine/metadata-modules/database-event-trigger/utils/from-update-database-event-trigger-input-to-flat-database-event-trigger-to-update-or-throw.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@Injectable()
export class DatabaseEventTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {}
async createOne(
databaseEventTriggerInput: CreateDatabaseEventTriggerInput,
workspaceId: string,
) {
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatDatabaseEventTriggerMaps'],
},
);
const existingFlatDatabaseEventTriggerMaps =
flatEntityMaps.flatDatabaseEventTriggerMaps;
const flatDatabaseEventTriggerToCreate =
fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger({
createDatabaseEventTriggerInput: databaseEventTriggerInput,
workspaceId,
});
const toFlatDatabaseEventTriggerMaps = addFlatEntityToFlatEntityMapsOrThrow(
{
flatEntity: flatDatabaseEventTriggerToCreate,
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
},
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatDatabaseEventTriggerMaps: {
from: existingFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: false,
},
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while creating database event trigger',
);
}
const {
flatDatabaseEventTriggerMaps:
recomputedExistingFlatDatabaseEventTriggerMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatDatabaseEventTriggerMaps'],
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatDatabaseEventTriggerToCreate.id,
flatEntityMaps: recomputedExistingFlatDatabaseEventTriggerMaps,
});
}
async updateOne(
databaseEventTriggerInput: UpdateDatabaseEventTriggerInput,
workspaceId: string,
) {
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatDatabaseEventTriggerMaps'],
},
);
const existingFlatDatabaseEventTriggerMaps =
flatEntityMaps.flatDatabaseEventTriggerMaps;
const optimisticallyUpdatedFlatDatabaseEventTrigger =
fromUpdateDatabaseEventTriggerInputToFlatDatabaseEventTriggerToUpdateOrThrow(
{
flatDatabaseEventTriggerMaps: existingFlatDatabaseEventTriggerMaps,
updateDatabaseEventTriggerInput: databaseEventTriggerInput,
},
);
const fromFlatDatabaseEventTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [optimisticallyUpdatedFlatDatabaseEventTrigger.id],
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
});
const toFlatDatabaseEventTriggerMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatDatabaseEventTrigger,
flatEntityMaps: fromFlatDatabaseEventTriggerMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatDatabaseEventTriggerMaps: {
from: existingFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: false,
},
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while updating database event trigger',
);
}
const {
flatDatabaseEventTriggerMaps:
recomputedExistingFlatDatabaseEventTriggerMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatDatabaseEventTriggerMaps'],
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: optimisticallyUpdatedFlatDatabaseEventTrigger.id,
flatEntityMaps: recomputedExistingFlatDatabaseEventTriggerMaps,
});
}
async destroyOne({
destroyDatabaseEventTriggerInput,
workspaceId,
}: {
destroyDatabaseEventTriggerInput: DatabaseEventTriggerIdInput;
workspaceId: string;
}): Promise<FlatDatabaseEventTrigger> {
const {
flatDatabaseEventTriggerMaps: existingFlatDatabaseEventTriggerMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatDatabaseEventTriggerMaps'],
},
);
const existingFlatDatabaseEventTrigger =
existingFlatDatabaseEventTriggerMaps.byId[
destroyDatabaseEventTriggerInput.id
];
if (!isDefined(existingFlatDatabaseEventTrigger)) {
throw new DatabaseEventTriggerException(
'Database event trigger to destroy not found',
DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND,
);
}
const fromFlatDatabaseEventTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingFlatDatabaseEventTrigger.id],
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
});
const toFlatDatabaseEventTriggerMaps =
deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatDatabaseEventTriggerMaps,
entityToDeleteId: existingFlatDatabaseEventTrigger.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatDatabaseEventTriggerMaps: {
from: fromFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: true,
},
workspaceId,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while destroying database event trigger',
);
}
return existingFlatDatabaseEventTrigger;
}
}
@@ -0,0 +1,71 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import {
DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES,
DatabaseEventTrigger,
} from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@Injectable()
@WorkspaceFlatMapCache('flatDatabaseEventTriggerMaps')
export class WorkspaceFlatDatabaseEventTriggerMapCacheService extends WorkspaceFlatMapCacheService<
FlatEntityMaps<FlatDatabaseEventTrigger>
> {
constructor(
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
cacheStorageService: CacheStorageService,
@InjectRepository(DatabaseEventTrigger)
private readonly databaseEventTriggerRepository: Repository<DatabaseEventTrigger>,
) {
super(cacheStorageService);
}
protected async computeFlatMap({
workspaceId,
}: {
workspaceId: string;
}): Promise<FlatEntityMaps<FlatDatabaseEventTrigger>> {
const databaseEventTriggers =
await this.databaseEventTriggerRepository.find({
where: {
workspaceId,
},
});
const flatDatabaseEventTriggerMaps = databaseEventTriggers.reduce<
FlatEntityMaps<FlatDatabaseEventTrigger>
>((flatEntityMaps, databaseEventTrigger) => {
const flatDatabaseEventTrigger = {
...removePropertiesFromRecord(databaseEventTrigger, [
...DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier: databaseEventTrigger.universalIdentifier ?? '',
} satisfies FlatDatabaseEventTrigger;
return {
byId: {
...flatEntityMaps.byId,
[flatDatabaseEventTrigger.id]: flatDatabaseEventTrigger,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatDatabaseEventTrigger.universalIdentifier]:
flatDatabaseEventTrigger.id,
},
};
}, EMPTY_FLAT_ENTITY_MAPS);
return flatDatabaseEventTriggerMaps;
}
}
@@ -0,0 +1,4 @@
import { type FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-properties-to-compare.constant';
export type FlatDatabaseEventTriggerPropertiesToCompare =
(typeof FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE)[number];
@@ -0,0 +1,17 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
export type DatabaseEventTriggerEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<
DatabaseEventTrigger,
ServerlessFunctionEntity | Workspace
>;
export type FlatDatabaseEventTrigger = Omit<
DatabaseEventTrigger,
DatabaseEventTriggerEntityRelationProperties
> & {
universalIdentifier: string;
};
@@ -0,0 +1,69 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-properties-to-compare.constant';
import { type DatabaseEventTriggerSettings } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { type FlatDatabaseEventTriggerPropertiesToCompare } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger-properties-to-compare.type';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { type UpdateDatabaseEventTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
type GetWorkspaceMigrationUpdateDatabaseEventTriggerActionArgs = FromTo<
FlatDatabaseEventTrigger,
'FlatDatabaseEventTrigger'
>;
export const compareTwoFlatDatabaseEventTrigger = ({
fromFlatDatabaseEventTrigger,
toFlatDatabaseEventTrigger,
}: GetWorkspaceMigrationUpdateDatabaseEventTriggerActionArgs) => {
const transformMetadataForComparisonParameters = {
shouldIgnoreProperty: (property: string) =>
!FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE.includes(
property as FlatDatabaseEventTriggerPropertiesToCompare,
),
propertiesToStringify: ['settings'] as const,
};
const fromCompare = transformMetadataForComparison(
fromFlatDatabaseEventTrigger,
transformMetadataForComparisonParameters,
);
const toCompare = transformMetadataForComparison(
toFlatDatabaseEventTrigger,
transformMetadataForComparisonParameters,
);
const flatDatabaseEventTriggerDifferences = diff(fromCompare, toCompare);
return flatDatabaseEventTriggerDifferences.flatMap<
UpdateDatabaseEventTriggerAction['updates'][number]
>((difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatDatabaseEventTriggerPropertiesToCompare;
if (property === 'settings') {
return {
from: parseJson(oldValue) as DatabaseEventTriggerSettings,
to: parseJson(value) as DatabaseEventTriggerSettings,
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occurs, we should only provide null never undefined and so on
return [];
}
}
});
};
@@ -0,0 +1,25 @@
import {
ConflictError,
NotFoundError,
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import {
DatabaseEventTriggerException,
DatabaseEventTriggerExceptionCode,
} from 'src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception';
export const databaseEventTriggerGraphQLApiExceptionHandler = (
error: Error,
): void => {
if (error instanceof DatabaseEventTriggerException) {
switch (error.code) {
case DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND:
throw new NotFoundError('Database event trigger not found');
case DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_ALREADY_EXIST:
throw new ConflictError('Database event trigger already exists');
default:
throw error;
}
}
throw error;
};
@@ -0,0 +1,24 @@
import { v4 as uuidV4 } from 'uuid';
import { type CreateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
export const fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger = ({
createDatabaseEventTriggerInput,
workspaceId,
}: {
createDatabaseEventTriggerInput: CreateDatabaseEventTriggerInput;
workspaceId: string;
}): FlatDatabaseEventTrigger => {
const now = new Date();
return {
id: uuidV4(),
universalIdentifier: uuidV4(),
settings: createDatabaseEventTriggerInput.settings,
serverlessFunctionId: createDatabaseEventTriggerInput.serverlessFunctionId,
workspaceId,
createdAt: now,
updatedAt: now,
};
};
@@ -0,0 +1,32 @@
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type UpdateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/update-database-event-trigger.input';
import {
DatabaseEventTriggerException,
DatabaseEventTriggerExceptionCode,
} from 'src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
export const fromUpdateDatabaseEventTriggerInputToFlatDatabaseEventTriggerToUpdateOrThrow =
({
flatDatabaseEventTriggerMaps,
updateDatabaseEventTriggerInput,
}: {
flatDatabaseEventTriggerMaps: FlatEntityMaps<FlatDatabaseEventTrigger>;
updateDatabaseEventTriggerInput: UpdateDatabaseEventTriggerInput;
}): FlatDatabaseEventTrigger => {
const existingFlatDatabaseEventTrigger =
flatDatabaseEventTriggerMaps.byId[updateDatabaseEventTriggerInput.id];
if (!existingFlatDatabaseEventTrigger) {
throw new DatabaseEventTriggerException(
'Database event trigger not found',
DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND,
);
}
return {
...existingFlatDatabaseEventTrigger,
settings: updateDatabaseEventTriggerInput.settings,
updatedAt: new Date(),
};
};
@@ -35,6 +35,8 @@ export const throwOnFieldInputTranspilationsError: ThrowOnFieldInputTranspilatio
},
],
serverlessFunction: [],
databaseEventTrigger: [],
cronTrigger: [],
},
status: 'fail',
},
@@ -1,17 +1,19 @@
import { Module } from '@nestjs/common';
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.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';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { RemoteServerModule } from 'src/engine/metadata-modules/remote-server/remote-server.module';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { RouteModule } from 'src/engine/metadata-modules/route/route.module';
import { SearchFieldMetadataModule } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.module';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { RouteModule } from 'src/engine/metadata-modules/route/route.module';
@Module({
imports: [
@@ -27,6 +29,8 @@ import { RouteModule } from 'src/engine/metadata-modules/route/route.module';
RoleModule,
PermissionsModule,
RouteModule,
CronTriggerModule,
DatabaseEventTriggerModule,
],
providers: [],
exports: [
@@ -12,10 +12,10 @@ import {
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { Route } from 'src/engine/metadata-modules/route/route.entity';
import { ServerlessFunctionEntityRelationProperties } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
import { CronTrigger } from 'src/engine/metadata-modules/trigger/entities/cron-trigger.entity';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/trigger/entities/database-event-trigger.entity';
import { InputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/input-schema.type';
const DEFAULT_SERVERLESS_TIMEOUT_SECONDS = 300; // 5 minutes
@@ -1,8 +1,8 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { type Route } from 'src/engine/metadata-modules/route/route.entity';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type CronTrigger } from 'src/engine/metadata-modules/trigger/entities/cron-trigger.entity';
import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/trigger/entities/database-event-trigger.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
export type ServerlessFunctionEntityRelationProperties =
@@ -1,22 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CronTriggerCronCommand } from 'src/engine/metadata-modules/trigger/crons/commands/cron-trigger.cron.command';
import { CronTriggerCronJob } from 'src/engine/metadata-modules/trigger/crons/jobs/cron-trigger.cron.job';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { CronTrigger } from 'src/engine/metadata-modules/trigger/entities/cron-trigger.entity';
import { CallDatabaseEventTriggerJobsJob } from 'src/engine/metadata-modules/trigger/jobs/call-database-event-trigger-jobs.job';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/trigger/entities/database-event-trigger.entity';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace, CronTrigger, DatabaseEventTrigger]),
],
providers: [
CronTriggerCronJob,
CronTriggerCronCommand,
CallDatabaseEventTriggerJobsJob,
],
exports: [CronTriggerCronCommand],
})
export class TriggerModule {}
@@ -16,4 +16,6 @@ export const EMPTY_ORCHESTRATOR_ACTIONS_REPORT = {
viewField: emptyCreatedDeletedUpdated,
fieldMetadata: emptyCreatedDeletedUpdated,
serverlessFunction: emptyCreatedDeletedUpdated,
databaseEventTrigger: emptyCreatedDeletedUpdated,
cronTrigger: emptyCreatedDeletedUpdated,
} as const satisfies OrchestratorActionsReport;
@@ -11,6 +11,8 @@ export const fromWorkspaceMigrationBuilderExceptionToValidationResponseError = (
invalidViewField: 0,
invalidIndex: 0,
invalidServerlessFunction: 0,
invalidDatabaseEventTrigger: 0,
invalidCronTrigger: 0,
totalErrors: 0,
},
errors: {
@@ -19,6 +21,8 @@ export const fromWorkspaceMigrationBuilderExceptionToValidationResponseError = (
view: [],
viewField: [],
serverlessFunction: [],
databaseEventTrigger: [],
cronTrigger: [],
},
};
@@ -11,6 +11,8 @@ import {
WorkspaceMigrationOrchestratorFailedResult,
WorkspaceMigrationOrchestratorSuccessfulResult,
} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
import { WorkspaceMigrationV2IndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
@@ -29,6 +31,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationV2ViewActionsBuilderService: WorkspaceMigrationV2ViewActionsBuilderService,
private readonly workspaceMigrationV2ViewFieldActionsBuilderService: WorkspaceMigrationV2ViewFieldActionsBuilderService,
private readonly workspaceMigrationV2ServerlessFunctionActionsBuilderService: WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
private readonly workspaceMigrationV2DatabaseEventTriggerActionsBuilderService: WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService,
private readonly workspaceMigrationV2CronTriggerActionsBuilderService: WorkspaceMigrationV2CronTriggerActionsBuilderService,
) {}
private setupOptimisticCache({
@@ -80,6 +84,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
viewField: [],
index: [],
serverlessFunction: [],
databaseEventTrigger: [],
cronTrigger: [],
};
const optimisticAllFlatEntityMaps = this.setupOptimisticCache({
@@ -92,6 +98,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatViewMaps,
flatIndexMaps,
flatServerlessFunctionMaps,
flatDatabaseEventTriggerMaps,
flatCronTriggerMaps,
} = fromToAllFlatEntityMaps;
if (isDefined(flatObjectMetadataMaps)) {
@@ -222,6 +230,59 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatDatabaseEventTriggerMaps)) {
const {
from: fromFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
} = flatDatabaseEventTriggerMaps;
const databaseEventTriggerResult =
await this.workspaceMigrationV2DatabaseEventTriggerActionsBuilderService.validateAndBuild(
{
from: fromFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {} as AllFlatEntityMaps,
},
);
optimisticAllFlatEntityMaps.flatDatabaseEventTriggerMaps =
databaseEventTriggerResult.optimisticFlatEntityMaps;
if (databaseEventTriggerResult.status === 'fail') {
orchestratorFailureReport.databaseEventTrigger.push(
...databaseEventTriggerResult.errors,
);
} else {
orchestratorActionsReport.databaseEventTrigger =
databaseEventTriggerResult.actions;
}
}
if (isDefined(flatCronTriggerMaps)) {
const { from: fromFlatCronTriggerMaps, to: toFlatCronTriggerMaps } =
flatCronTriggerMaps;
const cronTriggerResult =
await this.workspaceMigrationV2CronTriggerActionsBuilderService.validateAndBuild(
{
from: fromFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {} as AllFlatEntityMaps,
},
);
optimisticAllFlatEntityMaps.flatCronTriggerMaps =
cronTriggerResult.optimisticFlatEntityMaps;
if (cronTriggerResult.status === 'fail') {
orchestratorFailureReport.cronTrigger.push(...cronTriggerResult.errors);
} else {
orchestratorActionsReport.cronTrigger = cronTriggerResult.actions;
}
}
const allErrors = Object.values(orchestratorFailureReport);
if (allErrors.some((report) => report.length > 0)) {
@@ -266,6 +327,18 @@ export class WorkspaceMigrationBuildOrchestratorService {
...orchestratorActionsReport.serverlessFunction.created,
...orchestratorActionsReport.serverlessFunction.updated,
///
// Database event triggers
...orchestratorActionsReport.databaseEventTrigger.deleted,
...orchestratorActionsReport.databaseEventTrigger.created,
...orchestratorActionsReport.databaseEventTrigger.updated,
///
// Cron triggers
...orchestratorActionsReport.cronTrigger.deleted,
...orchestratorActionsReport.cronTrigger.created,
...orchestratorActionsReport.cronTrigger.updated,
///
],
workspaceId,
},
@@ -0,0 +1,154 @@
import { Injectable } from '@nestjs/common';
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { compareTwoFlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/utils/compare-two-flat-cron-trigger.util';
import {
FlatEntityUpdateValidationArgs,
FlatEntityValidationArgs,
FlatEntityValidationReturnType,
WorkspaceEntityMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import {
UpdateCronTriggerAction,
WorkspaceMigrationCronTriggerActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import {
CronTriggerRelatedFlatEntityMaps,
FlatCronTriggerValidatorService,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
@Injectable()
export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
FlatCronTrigger,
WorkspaceMigrationCronTriggerActionV2,
CronTriggerRelatedFlatEntityMaps
> {
constructor(
private readonly flatCronTriggerValidatorService: FlatCronTriggerValidatorService,
) {
super();
}
protected async validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatCronTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatCronTriggerMaps,
}: FlatEntityValidationArgs<
FlatCronTrigger,
CronTriggerRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationCronTriggerActionV2,
FlatCronTrigger
>
> {
const validationResult =
await this.flatCronTriggerValidatorService.validateFlatCronTriggerCreation(
{
flatCronTriggerToValidate,
optimisticFlatCronTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: {
type: 'create_cron_trigger',
cronTrigger: flatCronTriggerToValidate,
},
};
}
protected async validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatCronTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatCronTriggerMaps,
}: FlatEntityValidationArgs<
FlatCronTrigger,
CronTriggerRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationCronTriggerActionV2,
FlatCronTrigger
>
> {
const validationResult =
this.flatCronTriggerValidatorService.validateFlatCronTriggerDeletion({
flatCronTriggerToValidate,
optimisticFlatCronTriggerMaps,
dependencyOptimisticFlatEntityMaps,
});
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: {
type: 'delete_cron_trigger',
cronTriggerId: flatCronTriggerToValidate.id,
},
};
}
protected async validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate: { from: fromFlatCronTrigger, to: toFlatCronTrigger },
optimisticFlatEntityMaps: optimisticFlatCronTriggerMaps,
}: FlatEntityUpdateValidationArgs<
FlatCronTrigger,
CronTriggerRelatedFlatEntityMaps
>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationCronTriggerActionV2,
FlatCronTrigger
>
| undefined
> {
const cronTriggerUpdatedProperties = compareTwoFlatCronTrigger({
fromFlatCronTrigger,
toFlatCronTrigger,
});
if (cronTriggerUpdatedProperties.length === 0) {
return undefined;
}
const validationResult =
this.flatCronTriggerValidatorService.validateFlatCronTriggerUpdate({
flatCronTriggerToValidate: toFlatCronTrigger,
optimisticFlatCronTriggerMaps,
dependencyOptimisticFlatEntityMaps,
});
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const updateCronTriggerAction: UpdateCronTriggerAction = {
type: 'update_cron_trigger',
cronTriggerId: toFlatCronTrigger.id,
updates: cronTriggerUpdatedProperties,
};
return {
status: 'success',
action: updateCronTriggerAction,
};
}
}
@@ -0,0 +1,162 @@
import { Injectable } from '@nestjs/common';
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { compareTwoFlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/utils/compare-two-flat-database-event-trigger.util';
import {
FlatEntityUpdateValidationArgs,
FlatEntityValidationArgs,
FlatEntityValidationReturnType,
WorkspaceEntityMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import {
UpdateDatabaseEventTriggerAction,
WorkspaceMigrationDatabaseEventTriggerActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import {
DatabaseEventTriggerRelatedFlatEntityMaps,
FlatDatabaseEventTriggerValidatorService,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
@Injectable()
export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
FlatDatabaseEventTrigger,
WorkspaceMigrationDatabaseEventTriggerActionV2,
DatabaseEventTriggerRelatedFlatEntityMaps
> {
constructor(
private readonly flatDatabaseEventTriggerValidatorService: FlatDatabaseEventTriggerValidatorService,
) {
super();
}
protected async validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatDatabaseEventTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatDatabaseEventTriggerMaps,
}: FlatEntityValidationArgs<
FlatDatabaseEventTrigger,
DatabaseEventTriggerRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationDatabaseEventTriggerActionV2,
FlatDatabaseEventTrigger
>
> {
const validationResult =
await this.flatDatabaseEventTriggerValidatorService.validateFlatDatabaseEventTriggerCreation(
{
flatDatabaseEventTriggerToValidate,
optimisticFlatDatabaseEventTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: {
type: 'create_database_event_trigger',
databaseEventTrigger: flatDatabaseEventTriggerToValidate,
},
};
}
protected async validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatDatabaseEventTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatDatabaseEventTriggerMaps,
}: FlatEntityValidationArgs<
FlatDatabaseEventTrigger,
DatabaseEventTriggerRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationDatabaseEventTriggerActionV2,
FlatDatabaseEventTrigger
>
> {
const validationResult =
this.flatDatabaseEventTriggerValidatorService.validateFlatDatabaseEventTriggerDeletion(
{
flatDatabaseEventTriggerToValidate,
optimisticFlatDatabaseEventTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: {
type: 'delete_database_event_trigger',
databaseEventTriggerId: flatDatabaseEventTriggerToValidate.id,
},
};
}
protected async validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate: {
from: fromFlatDatabaseEventTrigger,
to: toFlatDatabaseEventTrigger,
},
optimisticFlatEntityMaps: optimisticFlatDatabaseEventTriggerMaps,
}: FlatEntityUpdateValidationArgs<
FlatDatabaseEventTrigger,
DatabaseEventTriggerRelatedFlatEntityMaps
>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationDatabaseEventTriggerActionV2,
FlatDatabaseEventTrigger
>
| undefined
> {
const databaseEventTriggerUpdatedProperties =
compareTwoFlatDatabaseEventTrigger({
fromFlatDatabaseEventTrigger,
toFlatDatabaseEventTrigger,
});
if (databaseEventTriggerUpdatedProperties.length === 0) {
return undefined;
}
const validationResult =
this.flatDatabaseEventTriggerValidatorService.validateFlatDatabaseEventTriggerUpdate(
{
flatDatabaseEventTriggerToValidate: toFlatDatabaseEventTrigger,
optimisticFlatDatabaseEventTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const updateDatabaseEventTriggerAction: UpdateDatabaseEventTriggerAction = {
type: 'update_database_event_trigger',
databaseEventTriggerId: toFlatDatabaseEventTrigger.id,
updates: databaseEventTriggerUpdatedProperties,
};
return {
status: 'success',
action: updateDatabaseEventTriggerAction,
};
}
}
@@ -1,3 +1,5 @@
import { type WorkspaceMigrationCronTriggerActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import { type WorkspaceMigrationDatabaseEventTriggerActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import { type WorkspaceMigrationFieldActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type WorkspaceMigrationIndexActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
import { type WorkspaceMigrationObjectActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
@@ -11,7 +13,9 @@ export type WorkspaceMigrationActionV2 =
| WorkspaceMigrationIndexActionV2
| WorkspaceMigrationViewActionV2
| WorkspaceMigrationViewFieldActionV2
| WorkspaceMigrationServerlessFunctionActionV2;
| WorkspaceMigrationServerlessFunctionActionV2
| WorkspaceMigrationDatabaseEventTriggerActionV2
| WorkspaceMigrationCronTriggerActionV2;
export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type'];
export type ExtractAction<T extends WorkspaceMigrationActionTypeV2> = Extract<
@@ -0,0 +1,31 @@
import { type FlatCronTriggerPropertiesToCompare } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger-properties-to-compare.type';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
export type CreateCronTriggerAction = {
type: 'create_cron_trigger';
cronTrigger: FlatCronTrigger;
};
export type UpdateCronTriggerAction = {
type: 'update_cron_trigger';
cronTriggerId: string;
updates: Array<
{
[P in FlatCronTriggerPropertiesToCompare]: PropertyUpdate<
FlatCronTrigger,
P
>;
}[FlatCronTriggerPropertiesToCompare]
>;
};
export type DeleteCronTriggerAction = {
type: 'delete_cron_trigger';
cronTriggerId: string;
};
export type WorkspaceMigrationCronTriggerActionV2 =
| CreateCronTriggerAction
| UpdateCronTriggerAction
| DeleteCronTriggerAction;
@@ -0,0 +1,31 @@
import { type FlatDatabaseEventTriggerPropertiesToCompare } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger-properties-to-compare.type';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
export type CreateDatabaseEventTriggerAction = {
type: 'create_database_event_trigger';
databaseEventTrigger: FlatDatabaseEventTrigger;
};
export type UpdateDatabaseEventTriggerAction = {
type: 'update_database_event_trigger';
databaseEventTriggerId: string;
updates: Array<
{
[P in FlatDatabaseEventTriggerPropertiesToCompare]: PropertyUpdate<
FlatDatabaseEventTrigger,
P
>;
}[FlatDatabaseEventTriggerPropertiesToCompare]
>;
};
export type DeleteDatabaseEventTriggerAction = {
type: 'delete_database_event_trigger';
databaseEventTriggerId: string;
};
export type WorkspaceMigrationDatabaseEventTriggerActionV2 =
| CreateDatabaseEventTriggerAction
| UpdateDatabaseEventTriggerAction
| DeleteDatabaseEventTriggerAction;
@@ -0,0 +1,107 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { CronTriggerExceptionCode } from 'src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception';
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export type CronTriggerRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatCronTriggerMaps'
>;
type CronTriggerValidationArgs = {
flatCronTriggerToValidate: FlatCronTrigger;
optimisticFlatCronTriggerMaps: FlatEntityMaps<FlatCronTrigger>;
dependencyOptimisticFlatEntityMaps: CronTriggerRelatedFlatEntityMaps;
};
// TODO: validate settings integrity
@Injectable()
export class FlatCronTriggerValidatorService {
constructor() {}
public validateFlatCronTriggerUpdate({
flatCronTriggerToValidate: updatedFlatCronTrigger,
optimisticFlatCronTriggerMaps,
}: CronTriggerValidationArgs): FailedFlatEntityValidation<FlatCronTrigger> {
const errors = [];
const existingFlatCronTrigger =
optimisticFlatCronTriggerMaps.byId[updatedFlatCronTrigger.id];
if (!isDefined(existingFlatCronTrigger)) {
errors.push({
code: CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND,
message: t`Cron trigger not found`,
userFriendlyMessage: t`Cron trigger not found`,
});
}
return {
type: 'update_cron_trigger',
errors,
flatEntityMinimalInformation: {
id: updatedFlatCronTrigger.id,
},
};
}
public validateFlatCronTriggerDeletion({
flatCronTriggerToValidate: { id: cronTriggerIdToDelete },
optimisticFlatCronTriggerMaps,
}: CronTriggerValidationArgs): FailedFlatEntityValidation<FlatCronTrigger> {
const errors = [];
const existingFlatCronTrigger =
optimisticFlatCronTriggerMaps.byId[cronTriggerIdToDelete];
if (!isDefined(existingFlatCronTrigger)) {
errors.push({
code: CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND,
message: t`Cron trigger not found`,
userFriendlyMessage: t`Cron trigger not found`,
});
}
return {
type: 'delete_cron_trigger',
errors,
flatEntityMinimalInformation: {
id: cronTriggerIdToDelete,
},
};
}
public async validateFlatCronTriggerCreation({
flatCronTriggerToValidate,
optimisticFlatCronTriggerMaps,
}: CronTriggerValidationArgs): Promise<
FailedFlatEntityValidation<FlatCronTrigger>
> {
const errors = [];
if (
isDefined(
optimisticFlatCronTriggerMaps.byId[flatCronTriggerToValidate.id],
)
) {
errors.push({
code: CronTriggerExceptionCode.CRON_TRIGGER_ALREADY_EXIST,
message: t`Cron trigger with same id already exists`,
userFriendlyMessage: t`Cron trigger already exists`,
});
}
return {
type: 'create_cron_trigger',
errors,
flatEntityMinimalInformation: {
id: flatCronTriggerToValidate.id,
},
};
}
}
@@ -0,0 +1,113 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { DatabaseEventTriggerExceptionCode } from 'src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception';
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export type DatabaseEventTriggerRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatDatabaseEventTriggerMaps'
>;
type DatabaseEventTriggerValidationArgs = {
flatDatabaseEventTriggerToValidate: FlatDatabaseEventTrigger;
optimisticFlatDatabaseEventTriggerMaps: FlatEntityMaps<FlatDatabaseEventTrigger>;
dependencyOptimisticFlatEntityMaps: DatabaseEventTriggerRelatedFlatEntityMaps;
};
// TODO: validate settings integrity
@Injectable()
export class FlatDatabaseEventTriggerValidatorService {
constructor() {}
public validateFlatDatabaseEventTriggerUpdate({
flatDatabaseEventTriggerToValidate: updatedFlatDatabaseEventTrigger,
optimisticFlatDatabaseEventTriggerMaps,
}: DatabaseEventTriggerValidationArgs): FailedFlatEntityValidation<FlatDatabaseEventTrigger> {
const errors = [];
const existingFlatDatabaseEventTrigger =
optimisticFlatDatabaseEventTriggerMaps.byId[
updatedFlatDatabaseEventTrigger.id
];
if (!isDefined(existingFlatDatabaseEventTrigger)) {
errors.push({
code: DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND,
message: t`Database event trigger not found`,
userFriendlyMessage: t`Database event trigger not found`,
});
}
return {
type: 'update_database_event_trigger',
errors,
flatEntityMinimalInformation: {
id: updatedFlatDatabaseEventTrigger.id,
},
};
}
public validateFlatDatabaseEventTriggerDeletion({
flatDatabaseEventTriggerToValidate: { id: databaseEventTriggerIdToDelete },
optimisticFlatDatabaseEventTriggerMaps,
}: DatabaseEventTriggerValidationArgs): FailedFlatEntityValidation<FlatDatabaseEventTrigger> {
const errors = [];
const existingFlatDatabaseEventTrigger =
optimisticFlatDatabaseEventTriggerMaps.byId[
databaseEventTriggerIdToDelete
];
if (!isDefined(existingFlatDatabaseEventTrigger)) {
errors.push({
code: DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND,
message: t`Database event trigger not found`,
userFriendlyMessage: t`Database event trigger not found`,
});
}
return {
type: 'delete_database_event_trigger',
errors,
flatEntityMinimalInformation: {
id: databaseEventTriggerIdToDelete,
},
};
}
public async validateFlatDatabaseEventTriggerCreation({
flatDatabaseEventTriggerToValidate,
optimisticFlatDatabaseEventTriggerMaps,
}: DatabaseEventTriggerValidationArgs): Promise<
FailedFlatEntityValidation<FlatDatabaseEventTrigger>
> {
const errors = [];
if (
isDefined(
optimisticFlatDatabaseEventTriggerMaps.byId[
flatDatabaseEventTriggerToValidate.id
],
)
) {
errors.push({
code: DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_ALREADY_EXIST,
message: t`Database event trigger with same id already exists`,
userFriendlyMessage: t`Database event trigger already exists`,
});
}
return {
type: 'create_database_event_trigger',
errors,
flatEntityMinimalInformation: {
id: flatDatabaseEventTriggerToValidate.id,
},
};
}
}
@@ -4,6 +4,8 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/services/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2IndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
@@ -11,6 +13,8 @@ import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
import { FlatServerlessFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service';
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service';
@@ -29,9 +33,13 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2ViewFieldActionsBuilderService,
WorkspaceMigrationV2IndexActionsBuilderService,
WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService,
WorkspaceMigrationV2CronTriggerActionsBuilderService,
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatServerlessFunctionValidatorService,
FlatDatabaseEventTriggerValidatorService,
FlatCronTriggerValidatorService,
],
exports: [
WorkspaceMigrationBuilderV2Service,
@@ -39,6 +47,8 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2IndexActionsBuilderService,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService,
WorkspaceMigrationV2CronTriggerActionsBuilderService,
],
})
export class WorkspaceMigrationBuilderV2Module {}
@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { CreateCronTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateCronTriggerActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_cron_trigger',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateCronTriggerAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { cronTrigger } = action;
const cronTriggerRepository =
queryRunner.manager.getRepository<CronTrigger>(CronTrigger);
await cronTriggerRepository.insert({
...cronTrigger,
workspaceId,
});
}
}
@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { DeleteCronTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteCronTriggerActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_cron_trigger',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteCronTriggerAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { cronTriggerId } = action;
const cronTriggerRepository =
queryRunner.manager.getRepository<CronTrigger>(CronTrigger);
await cronTriggerRepository.delete({
id: cronTriggerId,
workspaceId,
});
}
}
@@ -0,0 +1,32 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { UpdateCronTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
@Injectable()
export class UpdateCronTriggerActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_cron_trigger',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateCronTriggerAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { cronTriggerId } = action;
const cronTriggerRepository =
queryRunner.manager.getRepository<CronTrigger>(CronTrigger);
await cronTriggerRepository.update(
cronTriggerId,
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
);
}
}
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { CreateDatabaseEventTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateDatabaseEventTriggerActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_database_event_trigger',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateDatabaseEventTriggerAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { databaseEventTrigger } = action;
const databaseEventTriggerRepository =
queryRunner.manager.getRepository<DatabaseEventTrigger>(
DatabaseEventTrigger,
);
await databaseEventTriggerRepository.insert({
...databaseEventTrigger,
workspaceId,
});
}
}
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { DeleteDatabaseEventTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteDatabaseEventTriggerActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_database_event_trigger',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteDatabaseEventTriggerAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { databaseEventTriggerId } = action;
const databaseEventTriggerRepository =
queryRunner.manager.getRepository<DatabaseEventTrigger>(
DatabaseEventTrigger,
);
await databaseEventTriggerRepository.delete({
id: databaseEventTriggerId,
workspaceId,
});
}
}
@@ -0,0 +1,34 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { UpdateDatabaseEventTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
@Injectable()
export class UpdateDatabaseEventTriggerActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_database_event_trigger',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateDatabaseEventTriggerAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { databaseEventTriggerId } = action;
const databaseEventTriggerRepository =
queryRunner.manager.getRepository<DatabaseEventTrigger>(
DatabaseEventTrigger,
);
await databaseEventTriggerRepository.update(
databaseEventTriggerId,
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
);
}
}
@@ -1,6 +1,12 @@
import { Module } from '@nestjs/common';
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
import { CreateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/create-cron-trigger-action-handler.service';
import { DeleteCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/delete-cron-trigger-action-handler.service';
import { UpdateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/update-cron-trigger-action-handler.service';
import { CreateDatabaseEventTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/database-event-trigger/services/create-database-event-trigger-action-handler.service';
import { DeleteDatabaseEventTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/database-event-trigger/services/delete-database-event-trigger-action-handler.service';
import { UpdateDatabaseEventTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/database-event-trigger/services/update-database-event-trigger-action-handler.service';
import { CreateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service';
import { DeleteFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service';
import { UpdateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service';
@@ -44,6 +50,14 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
CreateServerlessFunctionActionHandlerService,
DeleteServerlessFunctionActionHandlerService,
UpdateServerlessFunctionActionHandlerService,
CreateDatabaseEventTriggerActionHandlerService,
DeleteDatabaseEventTriggerActionHandlerService,
UpdateDatabaseEventTriggerActionHandlerService,
CreateCronTriggerActionHandlerService,
DeleteCronTriggerActionHandlerService,
UpdateCronTriggerActionHandlerService,
],
})
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
@@ -5,6 +5,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [],
"serverlessFunction": [],
@@ -30,6 +32,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -49,6 +53,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [],
"serverlessFunction": [],
@@ -74,6 +80,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -5,6 +5,8 @@ exports[`createOne FieldMetadataService name/label sync should return an error w
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -30,6 +32,8 @@ exports[`createOne FieldMetadataService name/label sync should return an error w
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -6,6 +6,8 @@ exports[`updateOne failing update should not update a field name if it is not sy
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -30,6 +32,8 @@ exports[`updateOne failing update should not update a field name if it is not sy
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -51,6 +55,8 @@ exports[`updateOne failing update should throw if the field name is not availabl
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -81,6 +87,8 @@ exports[`updateOne failing update should throw if the field name is not availabl
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -5,6 +5,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -77,6 +79,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -96,6 +100,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -121,6 +127,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -140,6 +148,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -160,6 +170,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -179,6 +191,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -199,6 +213,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -218,6 +234,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -265,6 +283,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -284,6 +304,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -325,6 +347,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -344,6 +368,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -385,6 +411,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -404,6 +432,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -451,6 +481,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -470,6 +502,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -498,6 +532,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -517,6 +553,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -545,6 +583,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -564,6 +604,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -591,6 +633,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -610,6 +654,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -630,6 +676,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -649,6 +697,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -669,6 +719,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -688,6 +740,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -708,6 +762,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -727,6 +783,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -750,6 +808,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -769,6 +829,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -790,6 +852,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -809,6 +873,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -829,6 +895,8 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -6,6 +6,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -31,6 +33,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -52,6 +56,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -77,6 +83,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -98,6 +106,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -129,6 +139,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -150,6 +162,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -175,6 +189,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -196,6 +212,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -227,6 +245,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -248,6 +268,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -274,6 +296,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -295,6 +319,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -321,6 +347,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -342,6 +370,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -367,6 +397,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -388,6 +420,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -429,6 +463,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -450,6 +486,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -476,6 +514,8 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -497,6 +537,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -538,6 +580,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -559,6 +603,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -600,6 +646,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -621,6 +669,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -668,6 +718,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -689,6 +741,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -730,6 +784,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -751,6 +807,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -798,6 +856,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -819,6 +879,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -845,6 +907,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -866,6 +930,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -892,6 +958,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -913,6 +981,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -938,6 +1008,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -959,6 +1031,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -984,6 +1058,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -1005,6 +1081,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -1031,6 +1109,8 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -6,6 +6,8 @@ exports[`Field metadata relation update should fail relation when name is change
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -45,6 +47,8 @@ exports[`Field metadata relation update should fail relation when name is change
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -66,6 +70,8 @@ exports[`Field metadata relation update should fail relation when name is not in
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -129,6 +135,8 @@ exports[`Field metadata relation update should fail relation when name is not in
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -5,6 +5,8 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -297,6 +299,8 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -316,6 +320,8 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -608,6 +614,8 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -627,6 +635,8 @@ exports[`Object metadata creation should fail v2 when labelPlural is empty 1`] =
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -919,6 +929,8 @@ exports[`Object metadata creation should fail v2 when labelPlural is empty 1`] =
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -938,6 +950,8 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -1230,6 +1244,8 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -1249,6 +1265,8 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -1541,6 +1559,8 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -1560,6 +1580,8 @@ exports[`Object metadata creation should fail v2 when labelSingular is empty 1`]
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -1852,6 +1874,8 @@ exports[`Object metadata creation should fail v2 when labelSingular is empty 1`]
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -1871,6 +1895,8 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] =
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -2164,6 +2190,8 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] =
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -2183,6 +2211,8 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -2476,6 +2506,8 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -2495,6 +2527,8 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -2867,6 +2901,8 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -2886,6 +2922,8 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -3178,6 +3216,8 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -3197,6 +3237,8 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -3489,6 +3531,8 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -3508,6 +3552,8 @@ exports[`Object metadata creation should fail v2 when namePlural is an empty str
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -3805,6 +3851,8 @@ exports[`Object metadata creation should fail v2 when namePlural is an empty str
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -3824,6 +3872,8 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -4121,6 +4171,8 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -4140,6 +4192,8 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -4547,6 +4601,8 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -4566,6 +4622,8 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -4973,6 +5031,8 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -4992,6 +5052,8 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -5364,6 +5426,8 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -5383,6 +5447,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -5755,6 +5821,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -5774,6 +5842,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is an empty s
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -6181,6 +6251,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is an empty s
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -6200,6 +6272,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -6607,6 +6681,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -6626,6 +6702,8 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] =
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -6919,6 +6997,8 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] =
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -6938,6 +7018,8 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [
{
"errors": [
@@ -7231,6 +7313,8 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -6,6 +6,8 @@ exports[`Object metadata update should fail when labelIdentifier is not a TEXT o
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -31,6 +33,8 @@ exports[`Object metadata update should fail when labelIdentifier is not a TEXT o
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -52,6 +56,8 @@ exports[`Object metadata update should fail when labelIdentifier is not a known
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -77,6 +83,8 @@ exports[`Object metadata update should fail when labelIdentifier is not a known
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,
@@ -114,6 +122,8 @@ exports[`Object metadata update should fail when labelIdentifier is null 1`] = `
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"cronTrigger": [],
"databaseEventTrigger": [],
"index": [],
"objectMetadata": [
{
@@ -138,6 +148,8 @@ exports[`Object metadata update should fail when labelIdentifier is null 1`] = `
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidServerlessFunction": 0,