-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tweak(datatrakWeb): RN-1400: save task due date in unix time #5838
Changes from 17 commits
d17e6e5
f268001
e774d1e
d2c881f
a09e807
098c1bf
31f1828
45e3fef
388f467
c320a4f
7ecc324
9a41766
3f4fe4a
eb05952
046ed8d
6b84aab
65998dc
3faa50e
d5f7ac7
e1b914a
d0f75fd
406ed91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd | ||
*/ | ||
import keyBy from 'lodash.keyby'; | ||
import { formatInTimeZone } from 'date-fns-tz'; | ||
import { ChangeHandler } from './ChangeHandler'; | ||
|
||
const getAnswerWrapper = (config, answers) => { | ||
|
@@ -71,6 +72,7 @@ export class TaskCreationHandler extends ChangeHandler { | |
|
||
for (const response of changedResponses) { | ||
const sr = await models.surveyResponse.findById(response.id); | ||
const { timezone } = sr; | ||
const questions = await getQuestions(models, response.survey_id); | ||
|
||
const taskQuestions = questions.filter(question => question.type === 'Task'); | ||
|
@@ -84,7 +86,7 @@ export class TaskCreationHandler extends ChangeHandler { | |
for (const taskQuestion of taskQuestions) { | ||
const config = taskQuestion.config.task; | ||
const getAnswer = getAnswerWrapper(config, answers); | ||
|
||
if ( | ||
!config || | ||
getAnswer('shouldCreateTask') === null || | ||
|
@@ -100,12 +102,29 @@ export class TaskCreationHandler extends ChangeHandler { | |
: getAnswer('entityId'); | ||
const surveyId = await getSurveyId(models, config); | ||
|
||
const dueDateAnswer = getAnswer('dueDate'); | ||
|
||
let dueDate = null; | ||
if (dueDateAnswer) { | ||
// Convert the due date to the timezone of the survey response and set the time to the last second of the day | ||
const dateInTimezone = formatInTimeZone( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a nice way of getting the end of day value for the timezone |
||
dueDateAnswer, | ||
timezone, | ||
"yyyy-MM-dd'T23:59:59'XXX", | ||
); | ||
|
||
// Convert the date to a timestamp | ||
const timestamp = new Date(dateInTimezone).getTime(); | ||
|
||
dueDate = timestamp; | ||
} | ||
|
||
await models.task.create({ | ||
initial_request_id: response.id, | ||
survey_id: surveyId, | ||
entity_id: entityId, | ||
assignee_id: getAnswer('assignee'), | ||
due_date: getAnswer('dueDate'), | ||
due_date: dueDate, | ||
status: 'to_do', | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use strict'; | ||
|
||
var dbm; | ||
var type; | ||
var seed; | ||
|
||
/** | ||
* We receive the dbmigrate dependency from dbmigrate initially. | ||
* This enables us to not have to rely on NODE_PATH. | ||
*/ | ||
exports.setup = function (options, seedLink) { | ||
dbm = options.dbmigrate; | ||
type = dbm.dataType; | ||
seed = seedLink; | ||
}; | ||
|
||
exports.up = async function (db) { | ||
return db.runSql(` | ||
ALTER TABLE task | ||
RENAME COLUMN due_date TO old_due_date; | ||
|
||
ALTER TABLE task | ||
ADD COLUMN due_date DOUBLE PRECISION; | ||
|
||
UPDATE task | ||
SET due_date = (EXTRACT(EPOCH FROM old_due_date) * 1000)::DOUBLE PRECISION; | ||
|
||
ALTER TABLE task | ||
DROP COLUMN old_due_date; | ||
`); | ||
}; | ||
|
||
exports.down = function (db) { | ||
return db.runSql(` | ||
ALTER TABLE task | ||
RENAME COLUMN due_date TO old_due_date; | ||
|
||
ALTER TABLE task | ||
ADD COLUMN due_date TIMESTAMP WITH TIME ZONE; | ||
|
||
UPDATE task | ||
SET due_date = to_timestamp(CAST(old_due_date AS DOUBLE PRECISION) / 1000); | ||
|
||
ALTER TABLE task | ||
DROP COLUMN old_due_date; | ||
`); | ||
}; | ||
|
||
exports._meta = { | ||
version: 1, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
* Tupaia | ||
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd | ||
*/ | ||
import { sub } from 'date-fns'; | ||
import { Request } from 'express'; | ||
import { Route } from '@tupaia/server-boilerplate'; | ||
import { parse } from 'cookie'; | ||
|
@@ -35,7 +36,7 @@ const DEFAULT_PAGE_SIZE = 20; | |
|
||
type FormattedFilters = Record<string, any>; | ||
|
||
const EQUALITY_FILTERS = ['due_date', 'survey.project_id', 'task_status']; | ||
const EQUALITY_FILTERS = ['survey.project_id', 'task_status']; | ||
|
||
const getFilterSettings = (cookieString: string) => { | ||
const cookies = parse(cookieString); | ||
|
@@ -54,6 +55,19 @@ export class TasksRoute extends Route<TasksRequest> { | |
|
||
filters.forEach(({ id, value }) => { | ||
if (value === '' || value === undefined || value === null) return; | ||
if (id === 'due_date') { | ||
// set the time to the end of the day to get the full range of the day, and apply milliseconds to ensure the range is inclusive | ||
const endDateObj = new Date(value); | ||
// subtract 23 hours, 59 minutes, 59 seconds to get the start of the day. This is because the filters always send the end of the day, and we need a range to handle the values being saved in the database as unix timestamps based on the user's timezone. | ||
const startDate = sub(endDateObj, { hours: 23, minutes: 59, seconds: 59 }).getTime(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we use the date-fns endOfDay function here to make this more readable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried using the |
||
const endDate = endDateObj.getTime(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this just the same value as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yes, this would be from when I was debugging There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I was wrong, this is correct as |
||
this.filters[id] = { | ||
comparator: 'BETWEEN', | ||
comparisonValue: [startDate, endDate], | ||
}; | ||
|
||
return; | ||
} | ||
|
||
if (EQUALITY_FILTERS.includes(id)) { | ||
this.filters[id] = value; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,7 @@ import { stripTimezoneFromDate } from '@tupaia/utils'; | |
type Input = Partial<DatatrakWebTaskChangeRequest.ReqBody> & | ||
Partial<Pick<Task, 'entity_id' | 'survey_id' | 'status'>>; | ||
|
||
type Output = Partial<Omit<Task, 'due_date'>> & { | ||
due_date?: string | null; | ||
type Output = Partial<Task> & { | ||
comment?: string; | ||
}; | ||
|
||
|
@@ -28,13 +27,9 @@ export const formatTaskChanges = (task: Input) => { | |
taskDetails.due_date = null; | ||
} else if (dueDate) { | ||
// apply status and due date only if not a repeating task | ||
// set due date to end of day | ||
const endOfDay = new Date(new Date(dueDate).setHours(23, 59, 59, 999)); | ||
const unix = new Date(dueDate).getTime(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So it looks like every time we want to save a value to the due date col we will have to convert the date to the unix timestamp by calling getTime and then most times we want to read the value, we will have to convert it back using new Date(). I wonder if it would be possible to do that conversion at the model level so that we always get or send a date object or date iso string when working with the Task. In previous ORMs I've used that is a pretty common thing to do. @rohan-bes do you think there is a nice way to do this with our models? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While this would be great, I don't think we have a pattern of doing this anywhere else. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep that sounds like a good improvement to me 👍 |
||
|
||
// strip timezone from date so that the returned date is always in the user's timezone | ||
const withoutTimezone = stripTimezoneFromDate(endOfDay); | ||
|
||
taskDetails.due_date = withoutTimezone; | ||
taskDetails.due_date = unix; | ||
taskDetails.repeat_schedule = null; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it just be easier if we didn't have all these tests to worry about and maintain ;)