UNPKG

@ima-worldhealth/sunfish

Version:

A webapp for configuring DHIS2 email reports

276 lines (210 loc) 8.79 kB
const express = require('express'); const dayjs = require('dayjs'); const debug = require('debug')('sunfish:schedules'); const relativeTime = require('dayjs/plugin/relativeTime'); const { parseExpression } = require('cron-parser'); const db = require('../lib/db'); const attendant = require('../lib/attendant'); const executor = require('../lib/executor'); const { refreshDashboardList } = require('./dashboards'); const { refreshUserGroupList } = require('./userGroups'); dayjs.extend(relativeTime); const queries = { schedules : db.prepare(` SELECT s.id, s.subject, s.cron, g.display_name as userGroupName, s.group_id AS userGroupId, s.include_graphs, s.is_running, GROUP_CONCAT(d.display_name) as dashboards, s.paused, s.created_at FROM schedules s JOIN groups g ON s.group_id = g.id JOIN schedules_dashboards sd ON s.id = sd.schedule_id JOIN dashboards d ON sd.dashboard_id = d.id GROUP BY s.id ORDER BY s.created_at; `), schedule : db.prepare(` SELECT s.id, s.subject, s.cron, s.body, g.display_name as userGroupName, s.group_id AS userGroupId, s.include_graphs, GROUP_CONCAT(sd.dashboard_id) as dashboardIds, s.is_running, GROUP_CONCAT(d.display_name) as dashboardNames, s.paused, s.created_at FROM schedules s JOIN groups g ON s.group_id = g.id JOIN schedules_dashboards sd ON s.id = sd.schedule_id JOIN dashboards d ON sd.dashboard_id = d.id WHERE s.id = ? GROUP BY s.id ORDER BY s.created_at; `), dashboards : db.prepare('SELECT * FROM dashboards ORDER BY display_name;'), groups : db.prepare('SELECT * FROM groups ORDER BY display_name;'), }; const router = express.Router(); router.get('/', (req, res) => { const schedules = queries.schedules.all(); schedules.forEach((schedule) => { const createdLabel = dayjs(schedule.created_at).fromNow(); Object.assign(schedule, { createdLabel }); // assign the next run time by parsing the cron const parsed = parseExpression(schedule.cron); const nextRunTimeDate = parsed.next().toDate(); const nextRunTimeLabel = dayjs(nextRunTimeDate).fromNow(); const parsed2 = parseExpression(schedule.cron); const prevRunTimeDate = parsed2.prev().toDate(); const prevRunTimeLabel = dayjs(prevRunTimeDate).fromNow(); Object.assign(schedule, { nextRunTimeLabel, nextRunTime : nextRunTimeDate }); Object.assign(schedule, { prevRunTimeLabel, prevRunTime : prevRunTimeDate }); }); res.render('schedules', { schedules }); }); router.get('/create', (req, res) => { const dashboards = queries.dashboards.all(); const userGroups = queries.groups.all(); res.render('schedules/create', { userGroups, dashboards }); }); router.get('/refresh', async (req, res) => { debug('Refreshing dashboards and user groups...'); await Promise.all([ refreshDashboardList(), refreshUserGroupList(), ]); debug('Dashboards and user groups refreshed.'); res.redirect('back'); }); router.post('/create', (req, res) => { // try parsing the cron syntax. try { parseExpression(req.body.cron); } catch (e) { req.flash('error', req.t('ERRORS.CRON', { cron : req.body.cron })); res.redirect('back'); return; } try { // gather principle data const data = req.body; data.user_id = req.user.id; // coerce the ids into arrays const dashboardIds = [].concat(data['dashboard-ids']); delete data['dashboard-ids']; data.include_graphs = parseInt(data.include_graphs, 10); const createScheduleStatement = db.prepare(`INSERT INTO schedules (subject, body, group_id, cron, is_running, paused, include_graphs) VALUES (@subject, @body, @group_id, @cron, FALSE, FALSE, @include_graphs); `); const linkDashboardStatement = db.prepare('INSERT INTO schedules_dashboards (schedule_id, dashboard_id) VALUES (?, ?);'); const txn = db.transaction(() => { const { lastInsertRowid } = createScheduleStatement.run(data); dashboardIds.forEach((dashboardId) => { linkDashboardStatement.run(lastInsertRowid, dashboardId); }); }); txn(); attendant.refreshAllSchedules(); req.flash('success', req.t('SCHEDULES.CREATE_SUCCESS', data)); res.redirect('/schedules'); } catch (e) { req.flash('error', req.t('ERRORS.GENERIC', e)); res.redirect('back'); } }); router.post('/:id/edit', (req, res) => { // try parsing the cron syntax. try { parseExpression(req.body.cron); } catch (e) { req.flash('error', req.t('ERRORS.CRON', { cron : req.body.cron })); res.redirect('back'); return; } try { // gather principle data const data = req.body; data.id = req.params.id; // coerce the ids into arrays const dashboardIds = [].concat(data['dashboard-ids']); delete data['dashboard-ids']; data.include_graphs = parseInt(data.include_graphs, 10); const updateScheduleStatement = db.prepare(`UPDATE schedules SET subject = @subject, body = @body, group_id = @group_id, cron = @cron, include_graphs = @include_graphs WHERE id = @id;`); const clearDashboardStatement = db.prepare('DELETE FROM schedules_dashboards WHERE schedule_id = ?;'); const linkDashboardStatement = db.prepare('INSERT INTO schedules_dashboards (schedule_id, dashboard_id) VALUES (?, ?);'); const txn = db.transaction(() => { updateScheduleStatement.run(data); clearDashboardStatement.run(data.id); dashboardIds.forEach((dashboardId) => { linkDashboardStatement.run(data.id, dashboardId); }); }); txn(); attendant.refreshAllSchedules(); req.flash('success', req.t('SCHEDULES.EDIT_SUCCESS', data)); res.redirect('/schedules'); } catch (e) { req.flash('error', req.t('ERRORS.GENERIC', e)); res.redirect('back'); } }); router.get('/:id/details', (req, res) => { const schedule = queries.schedule.get(req.params.id); const dashboardNames = schedule.dashboardNames.split(','); const dashboardIds = schedule.dashboardIds.split(','); schedule.dashboards = dashboardNames .map((name, idx) => ({ name, id : dashboardIds[idx] })); res.render('schedules/details', { schedule }); }); router.get('/:id/edit', (req, res) => { const schedule = queries.schedule.get(req.params.id); const dashboardNames = schedule.dashboardNames.split(','); const dashboardIds = schedule.dashboardIds.split(','); schedule.dashboards = dashboardNames .map((name, idx) => ({ name, id : dashboardIds[idx] })); const dashboards = queries.dashboards.all(); const userGroups = queries.groups.all(); res.render('schedules/edit', { schedule, dashboards, userGroups }); }); router.get('/:id/delete', (req, res) => { db.prepare('DELETE FROM schedules WHERE id = ?').run(req.params.id); req.flash('success', req.t('SCHEDULES.DELETE_SUCCESS')); res.redirect('/schedules'); // queue a rescan of the fields in the database attendant.refreshAllSchedules(); }); // trigger the schedule router.get('/:id/trigger', (req, res) => { const schedule = queries.schedule.get(req.params.id); const dashboardNames = schedule.dashboardNames.split(','); const dashboardIds = schedule.dashboardIds.split(','); // make a map of name => ids of dashboards schedule.dashboards = dashboardNames .map((name, idx) => ({ name, id : dashboardIds[idx] })); executor.runScheduledTask(schedule); res.redirect('details'); }); router.get('/:id/test', async (req, res) => { const schedule = queries.schedule.get(req.params.id); res.setTimeout(5 * 60 * 1000); // timeout after a five minutes const dashboardNames = schedule.dashboardNames.split(','); const dashboardIds = schedule.dashboardIds.split(','); // make a map of name => ids of dashboards schedule.dashboards = dashboardNames .map((name, idx) => ({ name, id : dashboardIds[idx] })); const asHTML = req.query.html; const [board] = await executor.testScheduledTask(schedule, asHTML); res.sendFile(board); }); // reset the "is_running" indicator if it gets out of sync router.get('/:id/reset', (req, res) => { db.prepare('UPDATE schedules SET is_running = 0 WHERE id = ?') .run(req.params.id); req.flash('success', req.t('SCHEDULES.IS_RUNNING_RESET')); res.redirect('/schedules'); }); router.get('/:id/pause', (req, res) => { const schedule = queries.schedule.get(req.params.id); const toggle = Number(!schedule.paused); db.prepare('UPDATE schedules SET paused = ? WHERE id = ?') .run(toggle, req.params.id); req.flash('success', toggle ? req.t('SCHEDULES.PAUSE_SUCCESS') : req.t('SCHEDULES.UNPAUSE_SUCCESS')); res.redirect('/schedules'); // queue a rescan of the fields in the database attendant.refreshAllSchedules(); }); module.exports = router;