diff --git a/lib/controllers/agendash.js b/lib/controllers/agendash.js index ef770e8a..a7b77fd9 100644 --- a/lib/controllers/agendash.js +++ b/lib/controllers/agendash.js @@ -1,7 +1,22 @@ "use strict"; const semver = require("semver"); const { ObjectId } = require("mongodb"); // We rely on the Agenda's "mongodb", thus our package.json lists "*" as required version +const cronstrue = require("cronstrue"); // gets a human representation of the cron interval +/** + * Get a human representation of the cron interval + * @param {string} repeatInterval - a cron interval e.g. "0 0 * * *" + * @returns {string} human representation of the cron interval e.g. "Every day at 00:00" + */ +const getHumanRepeatInterval = (repeatInterval) => { + let result = repeatInterval; + // if the repeat interval doesn't have at least 5 spaces, it's not a cron expression, in which case just return the original value + const isRepeatIntervalCronExpr = !!(repeatInterval.split(" ").length > 4); + if (isRepeatIntervalCronExpr) { + result = cronstrue.toString(repeatInterval, { verbose: true, throwExceptionOnParseError: false }); + } + return result; +} module.exports = function (agenda, options) { options = options || {}; @@ -34,7 +49,7 @@ module.exports = function (agenda, options) { }); // Options = {query = '', property = '', isObjectId = false, limit, skip} - const getJobs = function (job, state, options) { + const getJobs = async (job, state, options) => { const preMatch = {}; if (job) { preMatch.name = job; @@ -56,7 +71,7 @@ module.exports = function (agenda, options) { } const collection = agenda._collection.collection || agenda._collection; - return collection + const jobsQueryResult = await collection .aggregate([ { $match: preMatch }, { @@ -118,8 +133,17 @@ module.exports = function (agenda, options) { filtered: [{ $skip: options.skip }, { $limit: options.limit }], }, }, - ]) - .toArray(); + ]).toArray(); + + // add a human representation of the cron interval for each job + jobsQueryResult?.[0]?.filtered?.forEach((job) => { + const repeatIntervalHuman = getHumanRepeatInterval(job.job.repeatInterval); + if (repeatIntervalHuman !== job.job.repeatInterval) { // only add the human representation if it's different from the cron expression + job.job.repeatIntervalHuman = repeatIntervalHuman; + } + }); + + return jobsQueryResult; }; const getOverview = async () => { diff --git a/package.json b/package.json index 5565860d..bf0abc42 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "agenda": "^4.2.1", "body-parser": "^1.19.2", "commander": "^2.9.0", + "cronstrue": "^2.28.0", "express": "^4.17.3", "mongodb": "*", "semver": "^7.3.4" diff --git a/public/app/js/joblist.js b/public/app/js/joblist.js index 0e27f8d4..772f50df 100644 --- a/public/app/js/joblist.js +++ b/public/app/js/joblist.js @@ -142,6 +142,7 @@ const jobList = Vue.component("job-list", {