bull-arena
Version:
An interactive UI dashboard for Bee Queue
185 lines (162 loc) • 5.3 kB
JavaScript
const _ = require('lodash');
const {
BEE_STATES,
BULL_STATES,
BULLMQ_STATES,
} = require('../helpers/queueHelpers');
const JobHelpers = require('../helpers/jobHelpers');
function getStates(queue) {
if (queue.IS_BEE) {
return BEE_STATES;
}
if (queue.IS_BULLMQ) {
return BULLMQ_STATES;
}
return BULL_STATES;
}
/**
* Determines if the requested job state lookup is valid.
*
* @param {String} state
* @param {Object} queue Queue that contains which queue package is used (bee, bull or bullmq)
*
* @return {Boolean}
*/
function isValidState(state, queue) {
const validStates = getStates(queue);
return _.includes(validStates, state);
}
async function handler(req, res) {
if (req.params.ext === 'json') return _json(req, res);
return _html(req, res);
}
/**
* Returns the queue jobs in the requested state as a json document.
*
* @prop {Object} req express request object
* @prop {Object} res express response object
*/
async function _json(req, res) {
const {queueName, queueHost, state} = req.params;
const {Queues} = req.app.locals;
const queue = await Queues.get(queueName, queueHost);
if (!queue) return res.status(404).json({message: 'Queue not found'});
if (!isValidState(state, queue))
return res.status(400).json({message: `Invalid state requested: ${state}`});
let jobs;
if (queue.IS_BEE) {
jobs = await queue.getJobs(state, {size: 1000});
jobs = jobs.map((j) =>
_.pick(j, 'id', 'progress', 'data', 'options', 'status')
);
} else {
const words = state.split('-');
const finalStateName = words.map((word) => _.capitalize(word)).join('');
jobs = await queue[`get${finalStateName}`](0, 1000);
jobs = jobs.map((j) => j && j.toJSON());
}
const filename = `${queueName}-${state}-dump.json`;
res.setHeader('Content-disposition', `attachment; filename=${filename}`);
res.setHeader('Content-type', 'application/json');
res.write(JSON.stringify(jobs, null, 2), () => res.end());
}
/**
* Renders an html view of the queue jobs in the requested state.
*
* @prop {Object} req express request object
* @prop {Object} res express response object
*/
async function _html(req, res) {
const {queueName, queueHost, state} = req.params;
const {Queues, Flows} = req.app.locals;
const queue = await Queues.get(queueName, queueHost);
const basePath = req.baseUrl;
if (!queue)
return res.status(404).render('dashboard/templates/queueNotFound', {
basePath,
queueName,
queueHost,
});
if (!isValidState(state, queue))
return res.status(400).json({message: `Invalid state requested: ${state}`});
let jobCounts;
if (queue.IS_BEE) {
jobCounts = await queue.checkHealth();
delete jobCounts.newestJob;
} else {
jobCounts = await queue.getJobCounts();
}
const page = parseInt(req.query.page, 10) || 1;
const pageSize = parseInt(req.query.pageSize, 10) || 100;
const order = req.query.order || 'desc';
const startId = (page - 1) * pageSize;
const endId = startId + pageSize - 1;
let jobs;
if (queue.IS_BEE) {
const pageOptions = {};
if (['failed', 'succeeded'].includes(state)) {
pageOptions.size = pageSize;
} else {
pageOptions.start = startId;
pageOptions.end = endId;
}
jobs = await queue.getJobs(state, pageOptions);
// Filter out Bee jobs that have already been removed by the time the promise resolves
jobs = jobs.filter((job) => job);
} else {
const stateTypes = state === 'waiting' ? ['wait', 'paused'] : state;
jobs = await queue.getJobs(stateTypes, startId, endId, order === 'asc');
}
for (let i = 0; i < jobs.length; i++) {
if (!jobs[i]) {
jobs[i] = {
showRetryButton: false,
showPromoteButton: false,
showDeleteRepeatableButton: false,
};
} else {
const jobState = queue.IS_BEE ? jobs[i].status : await jobs[i].getState();
jobs[i].showRetryButton = !queue.IS_BEE || jobState === 'failed';
jobs[i].retryButtonText = jobState === 'failed' ? 'Retry' : 'Trigger';
jobs[i].showPromoteButton = !queue.IS_BEE && jobState === 'delayed';
jobs[i].showDeleteRepeatableButton = !queue.IS_BEE && jobs[i].opts.repeat;
jobs[i].parent = JobHelpers.getKeyProperties(jobs[i].parentKey);
}
}
let pages = _.range(page - 6, page + 7).filter((page) => page >= 1);
while (pages.length < 12) {
pages.push(_.last(pages) + 1);
}
pages = pages.filter((page) => page <= _.ceil(jobCounts[state] / pageSize));
const disablePromote = !(state === 'delayed' && !queue.IS_BEE);
const disableRetry = !(
state === 'failed' ||
(state === 'delayed' && queue.IS_BEE)
);
const disableClean = !(
state === 'failed' ||
state === 'completed' ||
!queue.IS_BULL
);
return res.render('dashboard/templates/queueJobsByState', {
basePath,
queueName,
queueHost,
state,
jobs,
jobsInStateCount: jobCounts[state],
disablePagination:
queue.IS_BEE && (state === 'succeeded' || state === 'failed'),
disableOrdering: queue.IS_BEE,
disableClean,
disablePromote,
disableRetry,
currentPage: page,
hasFlows: Flows.hasFlows(),
pages,
pageSize,
lastPage: _.last(pages),
order,
});
}
module.exports = handler;