998 workflow restore (#12417)
Add a post hook to restore workflow sub-entities
This commit is contained in:
@ -1,9 +1,4 @@
|
||||
import {
|
||||
Field,
|
||||
HideField,
|
||||
ObjectType,
|
||||
registerEnumType,
|
||||
} from '@nestjs/graphql';
|
||||
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Authorize,
|
||||
@ -13,7 +8,6 @@ import {
|
||||
import {
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsString,
|
||||
@ -22,14 +16,8 @@ import {
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ServerlessFunctionSyncStatus } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { InputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/input-schema.type';
|
||||
|
||||
registerEnumType(ServerlessFunctionSyncStatus, {
|
||||
name: 'ServerlessFunctionSyncStatus',
|
||||
description: 'SyncStatus of the serverlessFunction',
|
||||
});
|
||||
|
||||
@ObjectType('ServerlessFunction')
|
||||
@Authorize({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -75,11 +63,6 @@ export class ServerlessFunctionDTO {
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
latestVersionInputSchema: InputSchema;
|
||||
|
||||
@IsEnum(ServerlessFunctionSyncStatus)
|
||||
@IsNotEmpty()
|
||||
@Field(() => ServerlessFunctionSyncStatus)
|
||||
syncStatus: ServerlessFunctionSyncStatus;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
@ -2,7 +2,9 @@ import {
|
||||
Check,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
@ -11,17 +13,12 @@ import { InputSchema } from 'src/modules/workflow/workflow-builder/workflow-sche
|
||||
|
||||
const DEFAULT_SERVERLESS_TIMEOUT_SECONDS = 300; // 5 minutes
|
||||
|
||||
export enum ServerlessFunctionSyncStatus {
|
||||
NOT_READY = 'NOT_READY',
|
||||
BUILDING = 'BUILDING',
|
||||
READY = 'READY',
|
||||
}
|
||||
|
||||
export enum ServerlessFunctionRuntime {
|
||||
NODE18 = 'nodejs18.x',
|
||||
}
|
||||
|
||||
@Entity('serverlessFunction')
|
||||
@Index('IDX_SERVERLESS_FUNCTION_ID_DELETED_AT', ['id', 'deletedAt'])
|
||||
export class ServerlessFunctionEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
@ -51,14 +48,6 @@ export class ServerlessFunctionEntity {
|
||||
@Column({ nullable: true })
|
||||
layerVersion: number;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
default: ServerlessFunctionSyncStatus.NOT_READY,
|
||||
type: 'enum',
|
||||
enum: ServerlessFunctionSyncStatus,
|
||||
})
|
||||
syncStatus: ServerlessFunctionSyncStatus;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@ -67,4 +56,7 @@ export class ServerlessFunctionEntity {
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@DeleteDateColumn({ type: 'timestamptz' })
|
||||
deletedAt?: Date;
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { serverlessFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver()
|
||||
@ -32,6 +33,8 @@ export class ServerlessFunctionResolver {
|
||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||
@InjectRepository(FeatureFlag, 'core')
|
||||
private readonly featureFlagRepository: Repository<FeatureFlag>,
|
||||
@InjectRepository(ServerlessFunctionEntity, 'metadata')
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
) {}
|
||||
|
||||
async checkFeatureFlag(workspaceId: string) {
|
||||
@ -57,9 +60,11 @@ export class ServerlessFunctionResolver {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.findOneOrFail({
|
||||
id,
|
||||
workspaceId,
|
||||
return await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
|
||||
@ -25,10 +25,7 @@ import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.se
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||
import {
|
||||
ServerlessFunctionEntity,
|
||||
ServerlessFunctionSyncStatus,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
@ -51,29 +48,6 @@ export class ServerlessFunctionService {
|
||||
return this.serverlessFunctionRepository.findBy(where);
|
||||
}
|
||||
|
||||
async findOneOrFail({
|
||||
workspaceId,
|
||||
id,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
}) {
|
||||
const serverlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneBy({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!serverlessFunction) {
|
||||
throw new ServerlessFunctionException(
|
||||
`Function does not exist`,
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return serverlessFunction;
|
||||
}
|
||||
|
||||
async hasServerlessFunctionPublishedVersion(serverlessFunctionId: string) {
|
||||
return await this.serverlessFunctionRepository.exists({
|
||||
where: {
|
||||
@ -88,10 +62,13 @@ export class ServerlessFunctionService {
|
||||
id: string,
|
||||
version: string,
|
||||
): Promise<{ [filePath: string]: string } | undefined> {
|
||||
const serverlessFunction = await this.findOneOrFail({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
const serverlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const folderPath = getServerlessFolder({
|
||||
@ -129,10 +106,13 @@ export class ServerlessFunctionService {
|
||||
): Promise<ServerlessExecuteResult> {
|
||||
await this.throttleExecution(workspaceId);
|
||||
|
||||
const functionToExecute = await this.findOneOrFail({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
const functionToExecute =
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const resultServerlessFunction = await this.serverlessService.execute(
|
||||
functionToExecute,
|
||||
@ -158,10 +138,13 @@ export class ServerlessFunctionService {
|
||||
}
|
||||
|
||||
async publishOneServerlessFunction(id: string, workspaceId: string) {
|
||||
const existingServerlessFunction = await this.findOneOrFail({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(existingServerlessFunction.latestVersion)) {
|
||||
const latestCode = await this.getServerlessFunctionSourceCode(
|
||||
@ -222,19 +205,25 @@ export class ServerlessFunctionService {
|
||||
async deleteOneServerlessFunction({
|
||||
id,
|
||||
workspaceId,
|
||||
isHardDeletion = true,
|
||||
softDelete = false,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
isHardDeletion?: boolean;
|
||||
softDelete?: boolean;
|
||||
}) {
|
||||
const existingServerlessFunction = await this.findOneOrFail({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (isHardDeletion) {
|
||||
await this.serverlessFunctionRepository.delete(id);
|
||||
if (softDelete) {
|
||||
await this.serverlessFunctionRepository.softDelete({ id });
|
||||
} else {
|
||||
await this.serverlessFunctionRepository.delete({ id });
|
||||
await this.fileStorageService.delete({
|
||||
folderPath: getServerlessFolder({
|
||||
serverlessFunction: existingServerlessFunction,
|
||||
@ -247,14 +236,21 @@ export class ServerlessFunctionService {
|
||||
return existingServerlessFunction;
|
||||
}
|
||||
|
||||
async restoreOneServerlessFunction(id: string) {
|
||||
await this.serverlessFunctionRepository.restore({ id });
|
||||
}
|
||||
|
||||
async updateOneServerlessFunction(
|
||||
serverlessFunctionInput: UpdateServerlessFunctionInput,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const existingServerlessFunction = await this.findOneOrFail({
|
||||
id: serverlessFunctionInput.id,
|
||||
workspaceId,
|
||||
});
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id: serverlessFunctionInput.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
await this.serverlessFunctionRepository.update(
|
||||
existingServerlessFunction.id,
|
||||
@ -316,13 +312,13 @@ export class ServerlessFunctionService {
|
||||
serverlessFunctionInput: CreateServerlessFunctionInput,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const serverlessFunctionToCreate =
|
||||
await this.serverlessFunctionRepository.create({
|
||||
const serverlessFunctionToCreate = this.serverlessFunctionRepository.create(
|
||||
{
|
||||
...serverlessFunctionInput,
|
||||
workspaceId,
|
||||
layerVersion: LAST_LAYER_VERSION,
|
||||
syncStatus: ServerlessFunctionSyncStatus.NOT_READY,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const createdServerlessFunction =
|
||||
await this.serverlessFunctionRepository.save(serverlessFunctionToCreate);
|
||||
@ -359,10 +355,13 @@ export class ServerlessFunctionService {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverlessFunction = await this.findOneOrFail({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
const serverlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
await this.fileStorageService.copy({
|
||||
from: {
|
||||
|
||||
Reference in New Issue
Block a user