feat(core): Initial workflow history API (#7234)
Github issue / Community forum post (link here to close automatically):
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { Authorized, RestController, Get, Middleware } from '@/decorators';
|
||||
import { WorkflowHistoryRequest } from '@/requests';
|
||||
import { Service } from 'typedi';
|
||||
import {
|
||||
HistoryVersionNotFoundError,
|
||||
SharedWorkflowNotFoundError,
|
||||
WorkflowHistoryService,
|
||||
} from './workflowHistory.service.ee';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { isWorkflowHistoryEnabled, isWorkflowHistoryLicensed } from './workflowHistoryHelper.ee';
|
||||
import { NotFoundError } from '@/ResponseHelper';
|
||||
import { paginationListQueryMiddleware } from '@/middlewares/listQuery/pagination';
|
||||
|
||||
const DEFAULT_TAKE = 20;
|
||||
|
||||
@Service()
|
||||
@Authorized()
|
||||
@RestController('/workflow-history')
|
||||
export class WorkflowHistoryController {
|
||||
constructor(private readonly historyService: WorkflowHistoryService) {}
|
||||
|
||||
@Middleware()
|
||||
workflowHistoryLicense(_req: Request, res: Response, next: NextFunction) {
|
||||
if (!isWorkflowHistoryLicensed()) {
|
||||
res.status(403);
|
||||
res.send('Workflow History license data not found');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
@Middleware()
|
||||
workflowHistoryEnabled(_req: Request, res: Response, next: NextFunction) {
|
||||
if (!isWorkflowHistoryEnabled()) {
|
||||
res.status(403);
|
||||
res.send('Workflow History is disabled');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
@Get('/workflow/:workflowId', { middlewares: [paginationListQueryMiddleware] })
|
||||
async getList(req: WorkflowHistoryRequest.GetList) {
|
||||
try {
|
||||
return await this.historyService.getList(
|
||||
req.user,
|
||||
req.params.workflowId,
|
||||
req.query.take ?? DEFAULT_TAKE,
|
||||
req.query.skip ?? 0,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof SharedWorkflowNotFoundError) {
|
||||
throw new NotFoundError('Could not find workflow');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/workflow/:workflowId/version/:versionId')
|
||||
async getVersion(req: WorkflowHistoryRequest.GetVersion) {
|
||||
try {
|
||||
return await this.historyService.getVersion(
|
||||
req.user,
|
||||
req.params.workflowId,
|
||||
req.params.versionId,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof SharedWorkflowNotFoundError) {
|
||||
throw new NotFoundError('Could not find workflow');
|
||||
} else if (e instanceof HistoryVersionNotFoundError) {
|
||||
throw new NotFoundError('Could not find version');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
|
||||
import type { User } from '@/databases/entities/User';
|
||||
import type { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
|
||||
import type { WorkflowHistory } from '@/databases/entities/WorkflowHistory';
|
||||
import { SharedWorkflowRepository } from '@/databases/repositories';
|
||||
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
||||
import { Service } from 'typedi';
|
||||
import { isWorkflowHistoryEnabled } from './workflowHistoryHelper.ee';
|
||||
|
||||
export class SharedWorkflowNotFoundError extends Error {}
|
||||
export class HistoryVersionNotFoundError extends Error {}
|
||||
|
||||
@Service()
|
||||
export class WorkflowHistoryService {
|
||||
constructor(
|
||||
private readonly workflowHistoryRepository: WorkflowHistoryRepository,
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
) {}
|
||||
|
||||
private async getSharedWorkflow(user: User, workflowId: string): Promise<SharedWorkflow | null> {
|
||||
return this.sharedWorkflowRepository.findOne({
|
||||
where: {
|
||||
...(!user.isOwner && { userId: user.id }),
|
||||
workflowId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getList(
|
||||
user: User,
|
||||
workflowId: string,
|
||||
take: number,
|
||||
skip: number,
|
||||
): Promise<Array<Omit<WorkflowHistory, 'nodes' | 'connections'>>> {
|
||||
const sharedWorkflow = await this.getSharedWorkflow(user, workflowId);
|
||||
if (!sharedWorkflow) {
|
||||
throw new SharedWorkflowNotFoundError();
|
||||
}
|
||||
return this.workflowHistoryRepository.find({
|
||||
where: {
|
||||
workflowId: sharedWorkflow.workflowId,
|
||||
},
|
||||
take,
|
||||
skip,
|
||||
select: ['workflowId', 'versionId', 'authors', 'createdAt', 'updatedAt'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async getVersion(user: User, workflowId: string, versionId: string): Promise<WorkflowHistory> {
|
||||
const sharedWorkflow = await this.getSharedWorkflow(user, workflowId);
|
||||
if (!sharedWorkflow) {
|
||||
throw new SharedWorkflowNotFoundError();
|
||||
}
|
||||
const hist = await this.workflowHistoryRepository.findOne({
|
||||
where: {
|
||||
workflowId: sharedWorkflow.workflowId,
|
||||
versionId,
|
||||
},
|
||||
});
|
||||
if (!hist) {
|
||||
throw new HistoryVersionNotFoundError();
|
||||
}
|
||||
return hist;
|
||||
}
|
||||
|
||||
async saveVersion(user: User, workflow: WorkflowEntity) {
|
||||
if (isWorkflowHistoryEnabled()) {
|
||||
await this.workflowHistoryRepository.insert({
|
||||
authors: user.firstName + ' ' + user.lastName,
|
||||
connections: workflow.connections,
|
||||
nodes: workflow.nodes,
|
||||
versionId: workflow.versionId,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,7 @@ export function isWorkflowHistoryLicensed() {
|
||||
const license = Container.get(License);
|
||||
return license.isWorkflowHistoryLicensed();
|
||||
}
|
||||
|
||||
export function isWorkflowHistoryEnabled() {
|
||||
return isWorkflowHistoryLicensed();
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export class WorkflowHistoryService {
|
||||
constructor(private readonly workflowHistoryRepository: WorkflowHistoryRepository) {}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ import { WorkflowRepository } from '@/databases/repositories';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { OwnershipService } from '@/services/ownership.service';
|
||||
import { isStringArray, isWorkflowIdValid } from '@/utils';
|
||||
import { isWorkflowHistoryLicensed } from './workflowHistory/workflowHistoryHelper.ee';
|
||||
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
|
||||
|
||||
export class WorkflowsService {
|
||||
static async getSharing(
|
||||
@@ -298,6 +300,10 @@ export class WorkflowsService {
|
||||
);
|
||||
}
|
||||
|
||||
if (isWorkflowHistoryLicensed()) {
|
||||
await Container.get(WorkflowHistoryService).saveVersion(user, shared.workflow);
|
||||
}
|
||||
|
||||
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
|
||||
|
||||
// We sadly get nothing back from "update". Neither if it updated a record
|
||||
|
||||
Reference in New Issue
Block a user