Skip to content

Commit

Permalink
feat: Workflow copy, export and import (#8)
Browse files Browse the repository at this point in the history
* workflow recipe. app core improves

* standard

* add db validation errors handler

* workflow unscheduler (on remove workflow)

* task destroy method.

remove everything linked to a task.

* stop throwing errors when events are not found

* workflow CRUD methods updated

* crud controllers backward compatible

* added api version control using headers

* ErrorHandler.ClientError dependency added
  • Loading branch information
facugon committed Mar 15, 2022
1 parent 269708c commit 8553a40
Show file tree
Hide file tree
Showing 17 changed files with 1,121 additions and 291 deletions.
16 changes: 8 additions & 8 deletions core/app/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ module.exports = function () {

// respond with error middleware
server.use((req, res, next) => {
res.sendError = (error, next) => {
if (error.statusCode < 500) {
res.send(error.statusCode || 400, {
statusCode: error.statusCode,
message: error.message,
errors: error.errors
res.sendError = (err, next) => {
if (err instanceof ErrorHandler.ClientError || err.statusCode < 500) {
res.send(err.statusCode || 400, {
statusCode: err.statusCode,
message: err.message,
errors: err.errors
})
} else {
logger.error(error)
logger.error(err)
const handler = new ErrorHandler()
handler.sendExceptionAlert(error, req)
handler.sendExceptionAlert(err, req)
res.send(500, 'Internal Server Error')
}
if (next) { next() }
Expand Down
314 changes: 68 additions & 246 deletions core/controllers/workflow/crud.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@

const restify = require('restify')
const graphlib = require('graphlib')
const App = require('../../app')
const TopicsConstants = require('../../constants/topics')
const logger = require('../../lib/logger')('controller:workflow')
const audit = require('../../lib/audit')
const router = require('../../router')
// const audit = require('../../lib/audit')
const Workflow = require('../../entity/workflow').Workflow
Expand All @@ -12,202 +14,99 @@ const Tag = require('../../entity/tag').Entity
const ACL = require('../../lib/acl');
const dbFilter = require('../../lib/db-filter');

const crudv2 = require('./crudv2')

module.exports = function (server) {
// const crudTopic = TopicsConstants.workflow.crud
const CRUD_TOPIC = TopicsConstants.workflow.crud

// default middlewares
var middlewares = [
server.get('/workflows',
server.auth.bearerMiddleware,
router.resolve.customerNameToEntity({required: true}),
router.ensureCustomer
]

server.get(
'/workflows',
middlewares,
router.requireCredential('viewer') ,
controller.fetch
router.resolve.customerSessionToEntity(),
router.ensureCustomer,
router.requireCredential('viewer'),
fetch
)

server.get(
'/workflows/:workflow',
middlewares,
server.get('/workflows/:workflow',
server.auth.bearerMiddleware,
router.resolve.customerSessionToEntity(),
router.ensureCustomer,
router.requireCredential('viewer'),
router.resolve.idToEntity({ param: 'workflow', required: true }),
router.ensureAllowed({ entity: { name: 'workflow' } }) ,
controller.get
(req, res, next) => {
res.send(200, req.workflow)
next()
}
)

server.post(
'/workflows',
middlewares,
router.requireCredential('admin') ,
controller.create
// audit.afterCreate('workflow',{ display: 'name' }) // cannot audit bulk creation
server.post('/workflows',
server.auth.bearerMiddleware,
router.resolve.customerSessionToEntity(),
router.ensureCustomer,
router.requireCredential('admin'),
restify.plugins.conditionalHandler([
{ version: '2.9.0', handler: crudv2.create }
]),
audit.afterCreate('workflow', { display: 'name', topic: CRUD_TOPIC })
)

server.del(
'/workflows/:workflow',
middlewares,
server.del('/workflows/:workflow',
server.auth.bearerMiddleware,
router.resolve.customerSessionToEntity(),
router.ensureCustomer,
router.requireCredential('admin'),
router.resolve.idToEntity({param: 'workflow', required: true}) ,
controller.remove
// audit.afterRemove('workflow',{ display: 'name', topic: crudTopic })
router.resolve.idToEntity({param: 'workflow', required: true}),
restify.plugins.conditionalHandler([
{ version: '1.0.0', handler: remove },
{ version: '2.9.0', handler: crudv2.remove }
]),
audit.afterRemove('workflow', { display: 'name', topic: CRUD_TOPIC })
)

server.put(
'/workflows/:workflow',
middlewares,
server.put('/workflows/:workflow',
server.auth.bearerMiddleware,
router.resolve.customerSessionToEntity(),
router.ensureCustomer,
router.requireCredential('admin'),
router.resolve.idToEntity({param: 'workflow', required: true}) ,
controller.replace
// audit.afterReplace('workflow',{ display: 'name', topic: crudTopic })
router.resolve.idToEntity({ param: 'workflow', required: true }) ,
restify.plugins.conditionalHandler([
{ version: '2.9.0', handler: crudv2.replace }
]),
audit.afterReplace('workflow', { display: 'name', topic: CRUD_TOPIC })
)
}

const controller = {
/**
*
* @method GET
*
*/
get (req, res, next) {
res.send(200, req.workflow)
next()
},
/**
*
* @method GET
*
*/
fetch (req, res, next) {
const customer = req.customer
const input = req.query

const filter = dbFilter(input,{ /** default **/ })
filter.where.customer_id = customer.id
if (!ACL.hasAccessLevel(req.user.credential,'admin')) {
// find what this user can access
filter.where.acl = req.user.email
}

Workflow.fetchBy(filter, (err, workflows) => {
if (err) return res.send(500, err)
res.send(200, workflows)
next()
})
},
/**
*
* @method DELETE
*
*/
remove (req, res, next) {
var workflow = req.workflow
workflow.remove(err => {
if (err) return res.send(500,err)

unlinkWorkflowTasks(req)
res.send(200)
})
},
/**
*
* @method PUT
*
*/
replace (req, res, next) {
replaceWorkflow(req, (err, workflow) => {
if (err) {
return res.send(err.statusCode || 500, err)
}

assignWorkflowAclToTasks(workflow, (err) => {
if (err) { return res.send(err.statusCode || 500, err) }

res.send(200, workflow)
})
})
},
//async acl (req, res, next) {
// try {
// const workflow = req.workflow
// // validate acl
// workflow.acl = req.body
// assignWorkflowAclToTasks(workflow, (err) => {
// if (err) {
// return res.send(err.statusCode || 500, err)
// }
// res.send(200, workflow)
// })
// } catch (err) {
// }
//},
/**
*
* @method POST
*
*/
create (req, res, next) {
createWorkflow(req, (err, workflow) => {
if (err) { return res.send(err.statusCode || 500, err) }

assignTasksToWorkflow(req, (err) => {
if (err) { return res.send(err.statusCode || 500, err) }

return res.send(200, workflow)
})
})
/**
*
* @method GET
*
*/
const fetch = (req, res, next) => {
const { customer, input = {} } = req

const filter = dbFilter(input, { /** default **/ })
filter.where.customer_id = customer.id
if (!ACL.hasAccessLevel(req.user.credential,'admin')) {
// find what this user can access
filter.where.acl = req.user.email
}
}

const createWorkflow = (req, next) => {
const customer = req.customer
const body = req.body
const graph = graphlib.json.read(body.graph)

validateGraph(graph, (err) => {
if (err) {
err.statusCode = 400
return next(err)
}

var workflow = new Workflow(
Object.assign({}, body, {
_type: 'Workflow',
customer: customer.id,
customer_id: customer.id,
user: req.user.id,
user_id: req.user.id
})
)

workflow.save(err => {
if (err) { logger.error('%o', err) }
req.workflow = workflow
createTags(body.tags, customer)
return next(err, workflow)
})
App.Models.Workflow.Workflow.fetchBy(filter, (err, workflows) => {
if (err) return res.send(500, err)
res.send(200, workflows)
next()
})
}

/**
*
* take all the tasks on this workflow and assign to it
*
*/
const assignTasksToWorkflow = (req, next) => {
const remove = (req, res, next) => {
const workflow = req.workflow
const graph = graphlib.json.read(workflow.graph)
workflow.remove(err => {
if (err) return res.send(500,err)

graph.nodes().forEach(id => {
let node = graph.node(id)
if (!/Event/.test(node._type)) {
App.task.assignTaskToWorkflow(id, workflow)
}
unlinkWorkflowTasks(req)
res.send(200)
})

next()
}

const unlinkWorkflowTasks = (req) => {
Expand All @@ -219,80 +118,3 @@ const unlinkWorkflowTasks = (req) => {
}
})
}

const replaceWorkflow = (req, next) => {
const workflow = req.workflow
const body = req.body
const newgraph = graphlib.json.read(body.graph)
const oldgraph = graphlib.json.read(workflow.graph)

validateGraph(newgraph, (err) => {
if (err) {
err.statusCode = 400
return next(err)
}

workflow.set(body)
workflow.save(err => {
if (err) {
logger.error('%s, %s, errors: %o',err.name, err.message, err.errors)
if (err.name === 'ValidationError') {
err.statusCode = 400
}
return next(err)
}

updateWorkflowGraphTasksDiferences(oldgraph, newgraph, workflow)
createTags(body.tags, req.customer)

next(err, workflow)
})
})
}

const createTags = (tags, customer) => {
if (tags && Array.isArray(tags)) {
Tag.create(tags, customer)
}
}

const validateGraph = (graph, next) => {
if (graph.nodes().length === 0 || graph.edges().length === 0) {
return next(new Error('invalid graph definition'))
}
return next()
}

const updateWorkflowGraphTasksDiferences = (oldgraph, newgraph, workflow) => {
newgraph.nodes().forEach(id => {
let node = oldgraph.node(id)
if (!node) { // is new
node = newgraph.node(id)
if (!/Event/.test(node._type)) {
App.task.assignTaskToWorkflow(id, workflow)
}
}
})
oldgraph.nodes().forEach(id => {
let node = newgraph.node(id)
if (!node) { // is no more in the workflow
node = oldgraph.node(id)
if (!/Event/.test(node._type)) {
App.task.unlinkTaskFromWorkflow(id)
}
}
})
}

const assignWorkflowAclToTasks = (workflow, next) => {
const graph = graphlib.json.read(workflow.graph)

graph.nodes().forEach(id => {
let node = graph.node(id)
if (!/Event/.test(node._type)) {
App.task.assignWorkflowAclToTask(id, workflow)
}
})

next()
}
Loading

0 comments on commit 8553a40

Please sign in to comment.