From d1863964b0032942dc99cbfff06d06e81476e790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Mon, 2 Nov 2020 16:44:14 +0300 Subject: [PATCH 1/4] NCI-Agency/anet#3068: Add a biweekly implementation Add a choice of monday to the dictionary --- anet-dictionary.yml | 7 +++++++ client/src/periodUtils.js | 34 ++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/anet-dictionary.yml b/anet-dictionary.yml index 870fa25405..0df61b7bdc 100644 --- a/anet-dictionary.yml +++ b/anet-dictionary.yml @@ -166,6 +166,13 @@ dictionary: label: weekly biweekly: label: biweekly + # There are two possible ways to arrange biweekly periods + # For example, if 1 Jan is Monday: + # First way: (1 Jan - 14 Jan) => (15 Jan - 28 Jan) + # Second way: (8 Jan - 21 Jan ) => (22 Jan - 4 Feb) + # So you need to pick one of two mondays in a biweekly period, depending on your use case, pick a monday which suits your schedule + # Periods will be arranged based on that monday + selectedMonday: "2020-06-01" semimonthly: label: semimonthly monthly: diff --git a/client/src/periodUtils.js b/client/src/periodUtils.js index a32ba3d20e..d4bc45b8dd 100644 --- a/client/src/periodUtils.js +++ b/client/src/periodUtils.js @@ -2,6 +2,7 @@ import moment from "moment" import PropTypes from "prop-types" import React from "react" import { momentObj } from "react-moment-proptypes" +import Settings from "settings" const ASSESSMENT_PERIOD_DATE_FORMAT = "YYYY-MM-DD" @@ -46,17 +47,30 @@ export const PERIOD_FACTORIES = { start: date.clone().subtract(offset, "weeks").startOf("week"), end: date.clone().subtract(offset, "weeks").endOf("week") }), - // FIXME: biweekly calculation should be changed, first agree on what it means - [RECURRENCE_TYPE.BIWEEKLY]: (date, offset) => ({ - start: date - .clone() - .subtract(2 * offset, "weeks") - .startOf("week"), - end: date + [RECURRENCE_TYPE.BIWEEKLY]: (date, offset) => { + const originMondayStr = + Settings.fields.task.customFields.assessments.objectFields.recurrence + .choices.biweekly.selectedMonday + const originMonday = moment(originMondayStr) + const curWeekMonday = date.startOf("isoWeek") + + const diffInWeeks = originMonday.diff(curWeekMonday, "weeks") + // current biweekly period's start has to be even number of weeks apart from origin + const curBiweeklyStart = + diffInWeeks % 2 === 0 + ? curWeekMonday + : curWeekMonday.clone().subtract(1, "weeks") + + const curBiweeklyEnd = curBiweeklyStart .clone() - .subtract(2 * offset, "weeks") - .endOf("week") - }), + .endOf("isoWeek") + .add(1, "week") + + return { + start: curBiweeklyStart.clone().subtract(2 * offset, "weeks"), + end: curBiweeklyEnd.clone().subtract(2 * offset, "weeks") + } + }, // for more context read: https://github.com/NCI-Agency/anet/pull/3272#discussion_r515826676 [RECURRENCE_TYPE.SEMIMONTHLY]: (date, offset) => { // With first half we mean first half of a month From c3a3d3ef47ccb11df9e4a3688b46714efd282e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Tue, 3 Nov 2020 11:17:55 +0300 Subject: [PATCH 2/4] NCI-Agency/anet#3068: Add week type selection to the dictionary --- anet-dictionary.yml | 4 +++- client/src/periodUtils.js | 26 ++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/anet-dictionary.yml b/anet-dictionary.yml index 0df61b7bdc..45c5bcd42e 100644 --- a/anet-dictionary.yml +++ b/anet-dictionary.yml @@ -164,6 +164,8 @@ dictionary: label: daily weekly: label: weekly + # start of the week Monday(true) or Sunday(false) ? + startsWithMonday: true biweekly: label: biweekly # There are two possible ways to arrange biweekly periods @@ -172,7 +174,7 @@ dictionary: # Second way: (8 Jan - 21 Jan ) => (22 Jan - 4 Feb) # So you need to pick one of two mondays in a biweekly period, depending on your use case, pick a monday which suits your schedule # Periods will be arranged based on that monday - selectedMonday: "2020-06-01" + referenceMonday: "2020-06-01" semimonthly: label: semimonthly monthly: diff --git a/client/src/periodUtils.js b/client/src/periodUtils.js index d4bc45b8dd..054bb2c38e 100644 --- a/client/src/periodUtils.js +++ b/client/src/periodUtils.js @@ -37,6 +37,15 @@ const PERIOD_FORMAT = { START_LONG: "D MMMM YYYY", END_LONG: "D MMMM YYYY" } +const weeksStartsWithMonday = + Settings.fields.task.customFields.assessments.objectFields.recurrence.choices + .weekly.startsWithMonday + +const weekType = weeksStartsWithMonday ? "isoWeek" : "week" + +const refMondayForBiweekly = + Settings.fields.task.customFields.assessments.objectFields.recurrence.choices + .biweekly.referenceMonday export const PERIOD_FACTORIES = { [RECURRENCE_TYPE.DAILY]: (date, offset) => ({ @@ -44,17 +53,14 @@ export const PERIOD_FACTORIES = { end: date.clone().subtract(offset, "days").endOf("day") }), [RECURRENCE_TYPE.WEEKLY]: (date, offset) => ({ - start: date.clone().subtract(offset, "weeks").startOf("week"), - end: date.clone().subtract(offset, "weeks").endOf("week") + start: date.clone().subtract(offset, "weeks").startOf(weekType), + end: date.clone().subtract(offset, "weeks").endOf(weekType) }), [RECURRENCE_TYPE.BIWEEKLY]: (date, offset) => { - const originMondayStr = - Settings.fields.task.customFields.assessments.objectFields.recurrence - .choices.biweekly.selectedMonday - const originMonday = moment(originMondayStr) - const curWeekMonday = date.startOf("isoWeek") + const refMonday = moment(refMondayForBiweekly) + const curWeekMonday = date.startOf(weekType) - const diffInWeeks = originMonday.diff(curWeekMonday, "weeks") + const diffInWeeks = refMonday.diff(curWeekMonday, "weeks") // current biweekly period's start has to be even number of weeks apart from origin const curBiweeklyStart = diffInWeeks % 2 === 0 @@ -63,8 +69,8 @@ export const PERIOD_FACTORIES = { const curBiweeklyEnd = curBiweeklyStart .clone() - .endOf("isoWeek") - .add(1, "week") + .endOf(weekType) + .add(1, "weeks") return { start: curBiweeklyStart.clone().subtract(2 * offset, "weeks"), From 040796f5d06cce6f6354b22c1ac316303a17e76f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Mon, 9 Nov 2020 10:17:07 +0300 Subject: [PATCH 3/4] NCI-Agency/anet#3068: Revert dictionary changes, hard code ref monday into Js Also revert isoWeek changes to weekly period, that might break old records --- anet-dictionary.yml | 9 --------- client/src/periodUtils.js | 19 +++++++------------ 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/anet-dictionary.yml b/anet-dictionary.yml index 45c5bcd42e..870fa25405 100644 --- a/anet-dictionary.yml +++ b/anet-dictionary.yml @@ -164,17 +164,8 @@ dictionary: label: daily weekly: label: weekly - # start of the week Monday(true) or Sunday(false) ? - startsWithMonday: true biweekly: label: biweekly - # There are two possible ways to arrange biweekly periods - # For example, if 1 Jan is Monday: - # First way: (1 Jan - 14 Jan) => (15 Jan - 28 Jan) - # Second way: (8 Jan - 21 Jan ) => (22 Jan - 4 Feb) - # So you need to pick one of two mondays in a biweekly period, depending on your use case, pick a monday which suits your schedule - # Periods will be arranged based on that monday - referenceMonday: "2020-06-01" semimonthly: label: semimonthly monthly: diff --git a/client/src/periodUtils.js b/client/src/periodUtils.js index 054bb2c38e..83743eb84d 100644 --- a/client/src/periodUtils.js +++ b/client/src/periodUtils.js @@ -2,7 +2,6 @@ import moment from "moment" import PropTypes from "prop-types" import React from "react" import { momentObj } from "react-moment-proptypes" -import Settings from "settings" const ASSESSMENT_PERIOD_DATE_FORMAT = "YYYY-MM-DD" @@ -37,15 +36,10 @@ const PERIOD_FORMAT = { START_LONG: "D MMMM YYYY", END_LONG: "D MMMM YYYY" } -const weeksStartsWithMonday = - Settings.fields.task.customFields.assessments.objectFields.recurrence.choices - .weekly.startsWithMonday -const weekType = weeksStartsWithMonday ? "isoWeek" : "week" +const weekType = "isoWeek" -const refMondayForBiweekly = - Settings.fields.task.customFields.assessments.objectFields.recurrence.choices - .biweekly.referenceMonday +const refMondayForBiweekly = "2021-01-04" // lets select 1st monday of 2021 export const PERIOD_FACTORIES = { [RECURRENCE_TYPE.DAILY]: (date, offset) => ({ @@ -53,15 +47,16 @@ export const PERIOD_FACTORIES = { end: date.clone().subtract(offset, "days").endOf("day") }), [RECURRENCE_TYPE.WEEKLY]: (date, offset) => ({ - start: date.clone().subtract(offset, "weeks").startOf(weekType), - end: date.clone().subtract(offset, "weeks").endOf(weekType) + start: date.clone().subtract(offset, "weeks").startOf("week"), + end: date.clone().subtract(offset, "weeks").endOf("week") }), [RECURRENCE_TYPE.BIWEEKLY]: (date, offset) => { - const refMonday = moment(refMondayForBiweekly) + // every biweekly period's start is even number of weeks apart from reference monday + const refMonday = moment(refMondayForBiweekly).startOf(weekType) const curWeekMonday = date.startOf(weekType) const diffInWeeks = refMonday.diff(curWeekMonday, "weeks") - // current biweekly period's start has to be even number of weeks apart from origin + // current biweekly period's start has to be even number of weeks apart from reference monday const curBiweeklyStart = diffInWeeks % 2 === 0 ? curWeekMonday From e5869a671525613a8a9cd17e8c41fe0e6f0f2a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Tue, 10 Nov 2020 09:08:10 +0300 Subject: [PATCH 4/4] NCI-Agency/anet#3068: Add unit tests for biweekly period calculations --- client/src/periodUtils.js | 4 +-- client/tests/utils/periodUtils.test.js | 42 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/client/src/periodUtils.js b/client/src/periodUtils.js index 83743eb84d..0a01c7de92 100644 --- a/client/src/periodUtils.js +++ b/client/src/periodUtils.js @@ -53,7 +53,7 @@ export const PERIOD_FACTORIES = { [RECURRENCE_TYPE.BIWEEKLY]: (date, offset) => { // every biweekly period's start is even number of weeks apart from reference monday const refMonday = moment(refMondayForBiweekly).startOf(weekType) - const curWeekMonday = date.startOf(weekType) + const curWeekMonday = date.clone().startOf(weekType) const diffInWeeks = refMonday.diff(curWeekMonday, "weeks") // current biweekly period's start has to be even number of weeks apart from reference monday @@ -64,8 +64,8 @@ export const PERIOD_FACTORIES = { const curBiweeklyEnd = curBiweeklyStart .clone() - .endOf(weekType) .add(1, "weeks") + .endOf(weekType) return { start: curBiweeklyStart.clone().subtract(2 * offset, "weeks"), diff --git a/client/tests/utils/periodUtils.test.js b/client/tests/utils/periodUtils.test.js index d2ad5e0f54..4c572dbb8e 100644 --- a/client/tests/utils/periodUtils.test.js +++ b/client/tests/utils/periodUtils.test.js @@ -16,6 +16,10 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ semiAnnually: { start: "2019-07-01", end: "2019-12-31" + }, + biweekly: { + start: "2019-12-09", + end: "2019-12-22" } } }, @@ -31,6 +35,10 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ semiAnnually: { start: "2018-07-01", end: "2018-12-31" + }, + biweekly: { + start: "2019-12-09", + end: "2019-12-22" } } }, @@ -46,6 +54,10 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ semiAnnually: { start: "2019-07-01", end: "2019-12-31" + }, + biweekly: { + start: "2020-02-03", + end: "2020-02-16" } } }, @@ -61,6 +73,10 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ semiAnnually: { start: "2020-07-01", end: "2020-12-31" + }, + biweekly: { + start: "2020-03-02", + end: "2020-03-15" } } }, @@ -76,6 +92,10 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ semiAnnually: { start: "2021-01-01", end: "2021-06-30" + }, + biweekly: { + start: "2021-01-04", + end: "2021-01-17" } } }, @@ -91,6 +111,10 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ semiAnnually: { start: "2019-07-01", end: "2019-12-31" + }, + biweekly: { + start: "2020-05-25", + end: "2020-06-07" } } }, @@ -106,6 +130,10 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ semiAnnually: { start: "2021-07-01", end: "2021-12-31" + }, + biweekly: { + start: "2020-07-20", + end: "2020-08-02" } } } @@ -113,6 +141,20 @@ const EXAMPLE_INPUT_DATES_AND_OFFSETS = [ // test with index so that if it fails we know which date from the array describe("For period creation utility", () => { + it("We should get the correct biweekly periods given example input", () => { + EXAMPLE_INPUT_DATES_AND_OFFSETS.forEach((input, index) => { + const period = PERIOD_FACTORIES[RECURRENCE_TYPE.BIWEEKLY]( + moment(input.date), + input.offset + ) + expect(prefix(index) + period.start.format(FORMAT)).toEqual( + prefix(index) + input.expectedPeriods.biweekly.start + ) + expect(prefix(index) + period.end.format(FORMAT)).toEqual( + prefix(index) + input.expectedPeriods.biweekly.end + ) + }) + }) it("We should get the correct semimonthly periods given example input", () => { EXAMPLE_INPUT_DATES_AND_OFFSETS.forEach((input, index) => { const period = PERIOD_FACTORIES[RECURRENCE_TYPE.SEMIMONTHLY](