2022-12-06 06:55:40 -08:00
|
|
|
import { User } from '@/databases/entities/User';
|
|
|
|
import { whereClause } from '@/UserManagement/UserManagementHelper';
|
|
|
|
import express from 'express';
|
|
|
|
import { LoggerProxy } from 'n8n-workflow';
|
2022-12-21 01:46:26 -08:00
|
|
|
import * as Db from '@/Db';
|
|
|
|
import * as ResponseHelper from '@/ResponseHelper';
|
|
|
|
import type {
|
2022-12-06 06:55:40 -08:00
|
|
|
IWorkflowStatisticsCounts,
|
|
|
|
IWorkflowStatisticsDataLoaded,
|
|
|
|
IWorkflowStatisticsTimestamps,
|
2022-12-21 01:46:26 -08:00
|
|
|
} from '@/Interfaces';
|
2022-12-06 06:55:40 -08:00
|
|
|
import { StatisticsNames } from '../databases/entities/WorkflowStatistics';
|
|
|
|
import { getLogger } from '../Logger';
|
|
|
|
import { ExecutionRequest } from '../requests';
|
|
|
|
|
|
|
|
export const workflowStatsController = express.Router();
|
|
|
|
|
|
|
|
// Helper function that validates the ID, return a flag stating whether the request is allowed
|
|
|
|
async function checkWorkflowId(workflowId: string, user: User): Promise<boolean> {
|
|
|
|
// Check permissions
|
|
|
|
const shared = await Db.collections.SharedWorkflow.findOne({
|
|
|
|
relations: ['workflow'],
|
|
|
|
where: whereClause({
|
|
|
|
user,
|
|
|
|
entityType: 'workflow',
|
|
|
|
entityId: workflowId,
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!shared) {
|
2022-12-21 07:42:07 -08:00
|
|
|
LoggerProxy.verbose('User attempted to read a workflow without permissions', {
|
2022-12-06 06:55:40 -08:00
|
|
|
workflowId,
|
|
|
|
userId: user.id,
|
|
|
|
});
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialise Logger if needed
|
|
|
|
*/
|
|
|
|
workflowStatsController.use((req, res, next) => {
|
|
|
|
try {
|
|
|
|
LoggerProxy.getInstance();
|
|
|
|
} catch (error) {
|
|
|
|
LoggerProxy.init(getLogger());
|
|
|
|
}
|
|
|
|
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that the workflow ID is valid and allowed to be read by the user
|
|
|
|
*/
|
|
|
|
workflowStatsController.use(async (req: ExecutionRequest.Get, res, next) => {
|
|
|
|
const allowed = await checkWorkflowId(req.params.id, req.user);
|
|
|
|
if (allowed) {
|
|
|
|
next();
|
|
|
|
} else {
|
|
|
|
// Otherwise, make and return an error
|
|
|
|
const response = new ResponseHelper.NotFoundError(`Workflow ${req.params.id} does not exist.`);
|
|
|
|
next(response);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GET /workflow-stats/:id/counts/
|
|
|
|
*/
|
|
|
|
workflowStatsController.get(
|
|
|
|
'/:id/counts/',
|
|
|
|
ResponseHelper.send(async (req: ExecutionRequest.Get): Promise<IWorkflowStatisticsCounts> => {
|
|
|
|
// Get counts from DB
|
|
|
|
const workflowId = req.params.id;
|
|
|
|
|
|
|
|
// Find the stats for this workflow
|
|
|
|
const stats = await Db.collections.WorkflowStatistics.find({
|
|
|
|
select: ['count', 'name'],
|
|
|
|
where: {
|
|
|
|
workflowId,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const data: IWorkflowStatisticsCounts = {
|
|
|
|
productionSuccess: 0,
|
|
|
|
productionError: 0,
|
|
|
|
manualSuccess: 0,
|
|
|
|
manualError: 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
// There will be a maximum of 4 stats (currently)
|
|
|
|
stats.forEach(({ count, name }) => {
|
|
|
|
switch (name) {
|
|
|
|
case StatisticsNames.manualError:
|
|
|
|
data.manualError = count;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case StatisticsNames.manualSuccess:
|
|
|
|
data.manualSuccess = count;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case StatisticsNames.productionError:
|
|
|
|
data.productionError = count;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case StatisticsNames.productionSuccess:
|
|
|
|
data.productionSuccess = count;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GET /workflow-stats/:id/times/
|
|
|
|
*/
|
|
|
|
workflowStatsController.get(
|
|
|
|
'/:id/times/',
|
|
|
|
ResponseHelper.send(async (req: ExecutionRequest.Get): Promise<IWorkflowStatisticsTimestamps> => {
|
|
|
|
// Get times from DB
|
|
|
|
const workflowId = req.params.id;
|
|
|
|
|
|
|
|
// Find the stats for this workflow
|
|
|
|
const stats = await Db.collections.WorkflowStatistics.find({
|
|
|
|
select: ['latestEvent', 'name'],
|
|
|
|
where: {
|
|
|
|
workflowId,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const data: IWorkflowStatisticsTimestamps = {
|
|
|
|
productionSuccess: null,
|
|
|
|
productionError: null,
|
|
|
|
manualSuccess: null,
|
|
|
|
manualError: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
// There will be a maximum of 4 stats (currently)
|
|
|
|
stats.forEach(({ latestEvent, name }) => {
|
|
|
|
switch (name) {
|
|
|
|
case StatisticsNames.manualError:
|
|
|
|
data.manualError = latestEvent;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case StatisticsNames.manualSuccess:
|
|
|
|
data.manualSuccess = latestEvent;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case StatisticsNames.productionError:
|
|
|
|
data.productionError = latestEvent;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case StatisticsNames.productionSuccess:
|
|
|
|
data.productionSuccess = latestEvent;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GET /workflow-stats/:id/data-loaded/
|
|
|
|
*/
|
|
|
|
workflowStatsController.get(
|
|
|
|
'/:id/data-loaded/',
|
|
|
|
ResponseHelper.send(async (req: ExecutionRequest.Get): Promise<IWorkflowStatisticsDataLoaded> => {
|
|
|
|
// Get flag
|
|
|
|
const workflowId = req.params.id;
|
|
|
|
|
|
|
|
// Get the corresponding workflow
|
|
|
|
const workflow = await Db.collections.Workflow.findOne(workflowId);
|
|
|
|
// It will be valid if we reach this point, this is just for TS
|
|
|
|
if (!workflow) {
|
|
|
|
return { dataLoaded: false };
|
|
|
|
}
|
|
|
|
|
|
|
|
const data: IWorkflowStatisticsDataLoaded = {
|
|
|
|
dataLoaded: workflow.dataLoaded,
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}),
|
|
|
|
);
|