From 1292f9a3d5c60a41d73db52be4bc05effbb1085a Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Thu, 11 May 2017 13:11:16 -0600 Subject: [PATCH] Added nextDue field --- .../tasks/POST-tasks_id_score_direction.test.js | 7 +++++++ .../integration/tasks/POST-tasks_user.test.js | 1 + .../v3/integration/tasks/PUT-tasks_id.test.js | 1 + test/api/v3/unit/libs/cron.test.js | 8 ++++++++ website/common/script/cron.js | 11 ++++++++++- website/server/controllers/api-v3/tasks.js | 17 +++++++++++++++-- website/server/libs/cron.js | 11 ++++++++++- website/server/libs/taskManager.js | 11 ++++++++++- website/server/models/task.js | 1 + 9 files changed, 63 insertions(+), 5 deletions(-) diff --git a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js index 0918780bae..3f6965e4f6 100644 --- a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js +++ b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js @@ -215,6 +215,13 @@ describe('POST /tasks/:id/score/:direction', () => { expect(task.isDue).to.equal(true); }); + it('computes nextDue', async () => { + await user.post(`/tasks/${daily._id}/score/up`); + let task = await user.get(`/tasks/${daily._id}`); + + expect(task.nextDue.length).to.eql(3); + }); + it('scores up daily even if it is already completed'); // Yes? it('scores down daily even if it is already uncompleted'); // Yes? diff --git a/test/api/v3/integration/tasks/POST-tasks_user.test.js b/test/api/v3/integration/tasks/POST-tasks_user.test.js index c1385ea93e..cb11a9f672 100644 --- a/test/api/v3/integration/tasks/POST-tasks_user.test.js +++ b/test/api/v3/integration/tasks/POST-tasks_user.test.js @@ -510,6 +510,7 @@ describe('POST /tasks/user', () => { expect(task.weeksOfMonth).to.eql([3]); expect(new Date(task.startDate)).to.eql(now); expect(task.isDue).to.be.true; + expect(task.nextDue.length).to.eql(3); }); it('creates multiple dailys', async () => { diff --git a/test/api/v3/integration/tasks/PUT-tasks_id.test.js b/test/api/v3/integration/tasks/PUT-tasks_id.test.js index ce17b9c94b..82c7dcaa75 100644 --- a/test/api/v3/integration/tasks/PUT-tasks_id.test.js +++ b/test/api/v3/integration/tasks/PUT-tasks_id.test.js @@ -404,6 +404,7 @@ describe('PUT /tasks/:id', () => { expect(savedDaily.frequency).to.eql('daily'); expect(savedDaily.everyX).to.eql(5); expect(savedDaily.isDue).to.be.false; + expect(savedDaily.nextDue.length).to.eql(3); }); it('can update checklists (replace it)', async () => { diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js index 93d164f1fd..18754398ae 100644 --- a/test/api/v3/unit/libs/cron.test.js +++ b/test/api/v3/unit/libs/cron.test.js @@ -366,6 +366,14 @@ describe('cron', () => { expect(tasksByType.dailys[0].isDue).to.be.false; }); + it('computes nextDue', () => { + tasksByType.dailys[0].frequency = 'daily'; + tasksByType.dailys[0].everyX = 5; + tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate(); + cron({user, tasksByType, daysMissed, analytics}); + expect(tasksByType.dailys[0].nextDue.length).to.eql(3); + }); + it('should add history', () => { cron({user, tasksByType, daysMissed, analytics}); expect(tasksByType.dailys[0].history).to.be.lengthOf(1); diff --git a/website/common/script/cron.js b/website/common/script/cron.js index 6e521d233c..ba632b8c81 100644 --- a/website/common/script/cron.js +++ b/website/common/script/cron.js @@ -105,7 +105,7 @@ export function shouldDo (day, dailyTask, options = {}) { let startDate = moment(dailyTask.startDate).zone(o.timezoneOffset).startOf('day'); - if (startDate > startOfDayWithCDSTime.startOf('day')) { + if (startDate > startOfDayWithCDSTime.startOf('day') && !options.nextDue) { return false; // Daily starts in the future } @@ -121,6 +121,9 @@ export function shouldDo (day, dailyTask, options = {}) { if (!dailyTask.everyX) return false; // error condition let schedule = moment(startDate).recur() .every(dailyTask.everyX).days(); + + if (options.nextDue) return schedule.next(3, 'L'); + return schedule.matches(startOfDayWithCDSTime); } else if (dailyTask.frequency === 'weekly') { let schedule = moment(startDate).recur(); @@ -131,6 +134,8 @@ export function shouldDo (day, dailyTask, options = {}) { schedule = schedule.every(daysOfTheWeek).daysOfWeek(); + if (options.nextDue) return schedule.next(3, 'L'); + return schedule.matches(startOfDayWithCDSTime); } else if (dailyTask.frequency === 'monthly') { let schedule = moment(startDate).recur(); @@ -145,12 +150,16 @@ export function shouldDo (day, dailyTask, options = {}) { schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth(); } + if (options.nextDue) return schedule.next(3, 'L'); + return schedule.matches(startOfDayWithCDSTime) && matchEveryX; } else if (dailyTask.frequency === 'yearly') { let schedule = moment(startDate).recur(); schedule = schedule.every(dailyTask.everyX).years(); + if (options.nextDue) return schedule.next(3, 'L'); + return schedule.matches(startOfDayWithCDSTime); } diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 401a432e24..f150f14b8d 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -21,6 +21,7 @@ import { import common from '../../../common'; import Bluebird from 'bluebird'; import _ from 'lodash'; +import cloneDeep from 'lodash/cloneDeep'; import logger from '../../libs/logger'; const MAX_SCORE_NOTES_LENGTH = 256; @@ -457,7 +458,13 @@ api.updateTask = { } if (sanitizedObj.type === 'daily') { - task.isDue = common.shouldDo(Date.now(), sanitizedObj, user.preferences); + let optionsForShouldDo = cloneDeep(user.preferences.toObject()); + task.isDue = common.shouldDo(Date.now(), sanitizedObj, optionsForShouldDo); + optionsForShouldDo.nextDue = true; + let nextDue = common.shouldDo(Date.now(), sanitizedObj, optionsForShouldDo); + if (nextDue && nextDue.length > 0) { + task.nextDue = nextDue; + } } let savedTask = await task.save(); @@ -590,7 +597,13 @@ api.scoreTask = { } if (task.type === 'daily') { - task.isDue = common.shouldDo(Date.now(), task, user.preferences); + let optionsForShouldDo = cloneDeep(user.preferences.toObject()); + task.isDue = common.shouldDo(Date.now(), task, optionsForShouldDo); + optionsForShouldDo.nextDue = true; + let nextDue = common.shouldDo(Date.now(), task, optionsForShouldDo); + if (nextDue && nextDue.length > 0) { + task.nextDue = nextDue; + } } let results = await Bluebird.all([ diff --git a/website/server/libs/cron.js b/website/server/libs/cron.js index 88a119c4f6..2cc319e824 100644 --- a/website/server/libs/cron.js +++ b/website/server/libs/cron.js @@ -4,6 +4,7 @@ import { model as User } from '../models/user'; import common from '../../common/'; import { preenUserHistory } from '../libs/preening'; import _ from 'lodash'; +import cloneDeep from 'lodash/cloneDeep'; import nconf from 'nconf'; const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true'; @@ -314,7 +315,15 @@ export function cron (options = {}) { value: task.value, }); task.completed = false; - task.isDue = common.shouldDo(Date.now(), task, user.preferences); + + let optionsForShouldDo = cloneDeep(user.preferences.toObject()); + task.isDue = common.shouldDo(now, task, optionsForShouldDo); + optionsForShouldDo.nextDue = true; + let nextDue = common.shouldDo(now, task, optionsForShouldDo); + + if (nextDue && nextDue.length > 0) { + task.nextDue = nextDue; + } if (completed || scheduleMisses > 0) { if (task.checklist) { diff --git a/website/server/libs/taskManager.js b/website/server/libs/taskManager.js index 5c2de7d5c4..8676b001b9 100644 --- a/website/server/libs/taskManager.js +++ b/website/server/libs/taskManager.js @@ -4,6 +4,7 @@ import { } from './errors'; import Bluebird from 'bluebird'; import _ from 'lodash'; +import cloneDeep from 'lodash/cloneDeep'; import shared from '../../common'; async function _validateTaskAlias (tasks, res) { @@ -64,7 +65,15 @@ export async function createTasks (req, res, options = {}) { newTask.userId = user._id; } - if (newTask.type === 'daily') newTask.isDue = shared.shouldDo(Date.now(), newTask, user.preferences); + if (newTask.type === 'daily') { + let optionsForShouldDo = cloneDeep(user.preferences.toObject()); + newTask.isDue = shared.shouldDo(Date.now(), newTask, optionsForShouldDo); + optionsForShouldDo.nextDue = true; + let nextDue = shared.shouldDo(Date.now(), newTask, optionsForShouldDo); + if (nextDue && nextDue.length > 0) { + newTask.nextDue = nextDue; + } + } // Validate that the task is valid and throw if it isn't // otherwise since we're saving user/challenge/group and task in parallel it could save the user/challenge/group with a tasksOrder that doens't match reality diff --git a/website/server/models/task.js b/website/server/models/task.js index 182c174ce8..c032293dc2 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -243,6 +243,7 @@ export let DailySchema = new Schema(_.defaults({ daysOfMonth: {type: [Number], default: []}, // Days of the month that the daily should repeat on weeksOfMonth: {type: [Number], default: []}, // Weeks of the month that the daily should repeat on isDue: {type: Boolean}, + nextDue: [{type: String}], }, habitDailySchema(), dailyTodoSchema()), subDiscriminatorOptions); export let daily = Task.discriminator('daily', DailySchema);