From 7d993e83194ab6be19af46a09815bf77eb4a7a76 Mon Sep 17 00:00:00 2001 From: ABHAMON Ronan Date: Tue, 19 Jul 2016 13:32:27 +0200 Subject: [PATCH] feat(schedules): schedules support timezones (#363) Fixes vatesfr/xo-web#1258 --- package.json | 1 + src/api.js | 7 +++++++ src/api/schedule.js | 8 ++++---- src/models/schedule.js | 5 +++-- src/utils.js | 39 +++++++++++++++++++++++-------------- src/xo-mixins/scheduling.js | 13 ++++++++----- 6 files changed, 47 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index b316718f0..7e65376d4 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "make-error": "^1", "micromatch": "^2.3.2", "minimist": "^1.2.0", + "moment-timezone": "^0.5.4", "ms": "^0.7.1", "multikey-hash": "^1.0.1", "ndjson": "^1.4.3", diff --git a/src/api.js b/src/api.js index 3f7337d08..7202da082 100644 --- a/src/api.js +++ b/src/api.js @@ -3,6 +3,7 @@ const debug = createDebug('xo:api') import getKeys from 'lodash/keys' import kindOf from 'kindof' +import moment from 'moment-timezone' import ms from 'ms' import schemaInspector from 'schema-inspector' @@ -188,6 +189,11 @@ methodSignature.description = 'returns the signature of an API method' // =================================================================== +const getServerTimezone = (tz => () => tz)(moment.tz.guess()) +getServerTimezone.description = 'return the timezone server' + +// =================================================================== + export default class Api { constructor ({ context, @@ -201,6 +207,7 @@ export default class Api { system: { getMethodsInfo, getServerVersion, + getServerTimezone, getVersion, listMethods, methodSignature diff --git a/src/api/schedule.js b/src/api/schedule.js index eb47a2d22..74c46cee1 100644 --- a/src/api/schedule.js +++ b/src/api/schedule.js @@ -17,8 +17,8 @@ get.params = { id: {type: 'string'} } -export async function create ({jobId, cron, enabled, name}) { - return /* await */ this.createSchedule(this.session.get('user_id'), {job: jobId, cron, enabled, name}) +export async function create ({ jobId, cron, enabled, name, timezone }) { + return /* await */ this.createSchedule(this.session.get('user_id'), { job: jobId, cron, enabled, name, timezone }) } create.permission = 'admin' @@ -30,8 +30,8 @@ create.params = { name: {type: 'string', optional: true} } -export async function set ({id, jobId, cron, enabled, name}) { - await this.updateSchedule(id, {job: jobId, cron, enabled, name}) +export async function set ({ id, jobId, cron, enabled, name, timezone }) { + await this.updateSchedule(id, { job: jobId, cron, enabled, name, timezone }) } set.permission = 'admin' diff --git a/src/models/schedule.js b/src/models/schedule.js index 5ae571798..0e96bcbff 100644 --- a/src/models/schedule.js +++ b/src/models/schedule.js @@ -15,13 +15,14 @@ export class Schedules extends Collection { return 'schedule:' } - create (userId, job, cron, enabled, name = undefined) { + create (userId, job, cron, enabled, name = undefined, timezone = undefined) { return this.add(new Schedule({ userId, job, cron, enabled, - name + name, + timezone })) } diff --git a/src/utils.js b/src/utils.js index bfc9aa079..0e7102404 100644 --- a/src/utils.js +++ b/src/utils.js @@ -12,6 +12,12 @@ import keys from 'lodash/keys' import kindOf from 'kindof' import multiKeyHashInt from 'multikey-hash' import xml2js from 'xml2js' + +// Moment timezone can be loaded only one time, it's a workaround to load +// the latest version because cron module uses an old version of moment which +// does not implement `guess` function for example. +import 'moment-timezone' + import { CronJob } from 'cron' import { all as pAll, @@ -439,27 +445,30 @@ export const streamToArray = (stream, { // ------------------------------------------------------------------- -export const scheduleFn = (cronPattern, fn) => { +export const scheduleFn = (cronTime, fn, timeZone) => { let running = false - const job = new CronJob(cronPattern, async () => { - if (running) { - return - } + const job = new CronJob({ + cronTime, + onTick: async () => { + if (running) { + return + } - running = true + running = true - try { - await fn() - } catch (error) { - console.error('[WARN] scheduled function:', error && error.stack || error) - } finally { - running = false - } + try { + await fn() + } catch (error) { + console.error('[WARN] scheduled function:', error && error.stack || error) + } finally { + running = false + } + }, + start: true, + timeZone }) - job.start() - return () => { job.stop() } diff --git a/src/xo-mixins/scheduling.js b/src/xo-mixins/scheduling.js index a72fd6acc..c4bb688dc 100644 --- a/src/xo-mixins/scheduling.js +++ b/src/xo-mixins/scheduling.js @@ -74,8 +74,10 @@ export default class { _enable (schedule) { const { id } = schedule - const stopSchedule = scheduleFn(schedule.cron, () => - this.xo.runJobSequence([ schedule.job ]) + const stopSchedule = scheduleFn( + schedule.cron, + () => this.xo.runJobSequence([ schedule.job ]), + schedule.timezone ) this._cronJobs[id] = stopSchedule @@ -137,8 +139,8 @@ export default class { return /* await */ this._redisSchedules.get() } - async createSchedule (userId, {job, cron, enabled, name}) { - const schedule_ = await this._redisSchedules.create(userId, job, cron, enabled, name) + async createSchedule (userId, { job, cron, enabled, name, timezone }) { + const schedule_ = await this._redisSchedules.create(userId, job, cron, enabled, name, timezone) const schedule = schedule_.properties this._add(schedule) @@ -146,13 +148,14 @@ export default class { return schedule } - async updateSchedule (id, {job, cron, enabled, name}) { + async updateSchedule (id, { job, cron, enabled, name, timezone }) { const schedule = await this._getSchedule(id) if (job) schedule.set('job', job) if (cron) schedule.set('cron', cron) if (enabled !== undefined) schedule.set('enabled', enabled) if (name !== undefined) schedule.set('name', name) + if (timezone !== undefined) schedule.set('timezone', timezone) await this._redisSchedules.save(schedule)