#1416: Add relative Reminders #1427
No reviewers
Labels
No Label
dependencies
duplicate
help wanted
invalid
kind/bug
kind/feature
needs reproduction
question
security
wontfix
No Milestone
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vikunja/vikunja#1427
Loading…
Reference in New Issue
No description provided.
Delete Branch "ce72/api:1416_reminders"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Solution idea
Task
Reminders []*reminder.TaskReminder
ReminderDates []time.Time
TaskReminder
keeps its attributeReminder time.Time
It always stores the next trigger time of the ReminderTaskReminder.Reminder
must be updated whenever due_date, start_date or end_date of the Task is updatedTaskReminder.Reminder
and can be calculated from the following additional attributes as wellTaskReminder.RelativeTo
(string, one of 'due_date', 'start_date', 'end_date' or nil)TaskReminder.RelativePeriod
(int64, trigger in seconds before (negative) or after (positive) RelativeTo)Steps:
Migrate current reminder dates to the new structure in dbNot necessary. We can just continue to use the existing columnreminder
in table task_reminders.@ -335,3 +335,3 @@
// To still find tasks with nil values, we exclude 0s when comparing with >/< values.
for _, f := range opts.filters {
if f.field == "reminders" {
if f.field == "ReminderDates" {
This is the value which consumers of the api will pass in for filtering. For consistency, this should be called
We'll also need a similar check to filter for the new reminders.
Yes, this has to be fixed. I still need to figure out how to apply the filter on an attribute in an Substructure.
I think in the end we need only one to filter by reminder trigger dates. And this filter has still to be migrated from Task.ReminderDates to the new Task.Reminders.
That's true.
This has now been fixed. Filter queries for "reminders" will be applied to task_reminders.reminder column.
I like that api.
I wonder if it would actually be easier to only store the relative amount and check in the reminder cron. I think it has other advantages (like the api always returning the next trigger date) when it is stored precomputed.
f39a0fb120
to85267d26bb
We need to be able to store absolute and relative dates anyway. Most of the time the absolute dates will be relevant for querying, filtering (or am I missing something?). I am not sure if I understand your idea here.
You are 100% correct. I didn't think that comment through, as you put it, it makes more sense.
I wonder if maybe we should just remove the
reminder_dates
altogether? (after migrating the old reminder dates to the new structure, which we need to do anyway)That would be a breaking change, but we're still < 1.0, so it should be fine.
@ -0,0 +24,4 @@
)
type taskReminders20230307171848 struct {
Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'" json:"reminder"`
Just
json:"reminder"
was added to this line. Do we need to include that in the migration script?No, the json declarations are usually ignored.
d78045f333
to7a0a7c92af
7a0a7c92af
toe0487516a3
2dff90448f
to3c4fb76554
WIP: #1416: Add relative Remindersto #1416: Add relative RemindersNext steps:
If this one gets merged, then the server will support the deprecated API with reminder_dates as array of timestamps by default.
The frontend should soon be enabled to allow users to either select absolute reminder times from the date picker like today. Alternatively they may select one of 'due_date', 'start_date' or 'end_date' and enter a time period (days, hours, minutes etc.) before/after the selected date. The period may be 0. These values should be sent to the server in the new reminder array.
The frontend must not return reminder_dates if the new reminder array should be used.
@ -53,0 +49,4 @@
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
Why the extra space?
Because you are basing assertions on comparing parts of json responses. Thus expected="task #2" matches "task #2" and "task #20"...
I can revert most of the changes though. Should I?
Ah okay that makes sense. Should be fine leave it like this.
@ -967,2 +980,3 @@
// Update the reminders
if err := t.updateReminders(s, t.Reminders); err != nil {
if err := t.updateReminders(s, t.Reminders, t.DueDate, t.StartDate, t.EndDate); err != nil {
What about passing only the task? (As the method is called on a task we can avoid the many parameters that way)
ok
@ -1051,2 +1073,3 @@
// Update the reminders
if err := ot.updateReminders(s, t.Reminders); err != nil {
if err := ot.updateReminders(s, t.Reminders, t.DueDate, t.StartDate, t.EndDate); err != nil {
How does the other function override the new reminders if both are sent?
Can we maybe move the logic into
updateReminders
alltogether? I feel like this is now duplicated in too many places.ok
@ -1481,0 +1523,4 @@
func updateRelativeReminderDates(reminders []*TaskReminder, dueDate time.Time, startDate time.Time, endDate time.Time) {
for _, reminder := range reminders {
relativeDuration := time.Duration(reminder.RelativePeriod) * time.Second
switch reminder.RelativeTo {
We should add a validation so that it's not possible to create a relative reminder without telling it what it's relative to (probably not here).
That would not break anything. There would just be a useless half-of-a-reminder (which maybe could be edited later). Where should we add a validation, if?
Since there's no dedicated endpoint just for reminders, I think right here is a good place for a validation. I think
updateRelativeReminderDates
is called when creating or updating a task?ok
@ -1494,2 +1557,3 @@
// Resolve duplicates and sort them
reminderMap := make(map[int64]time.Time, len(reminders))
reminderMap := make(map[int64]TaskReminder, len(reminders))
Please use a pointer to
TaskReminder
.ok
@ -1508,2 +1582,2 @@
t.Reminders = reminders
if len(reminders) == 0 {
// sort reminders
sort.Slice(t.Reminders, func(i, j int) bool {
Why sort them here? Just for the updated task response?
Yes. It simplifies the tests too.
Nice work so far!
Do you want to send a PR for that?
Thanks, I'm not sure I am able to do that properly. Will look at it and report back. If somebody else would be willing to do just go for it.
76442080c4
to7f9e70cc1f
BTW (and off-topic with respect to this PR): I found that rebasing onto
9f14466dfa (diff-5216e549ae97355f978bbe2e2887f15a97c51010)
reproducible breaks list_duplicate_test.go on my machine (Windows PC).The error is from here: https://kolaente.dev/vikunja/api/src/branch/main/pkg/models/list_duplicate.go#L196
"UNIQUE constraint failed: link_shares.hash"
55b56a84c5
to6b208cc9b8
6b208cc9b8
to8098d66727
The PR for the frontend is vikunja/frontend#3248.
The frontend and the api may be merged independently. But to see and use relative reminders the api must be merged.
@ -876,2 +876,4 @@
}
// ErrReminderRelativeToMissing represents an error where a task has a relative reminder without reference date
type ErrReminderRelativeToMissing struct {
Please add this error to the error.md file in the docs.
ok
@ -1481,0 +1518,4 @@
if !task.DueDate.IsZero() {
reminder.Reminder = task.DueDate.Add(relativeDuration)
} else {
reminder.Reminder = time.Time{}
I think we can get rid of all the
else
clauses and move them before theswitch
statements so that the logic inside the switch will override the empty reminder time.Also, what should happen in case the reminder date is an empty
time.Time
? Isn't that an invalid case and should be filtered out? Or better, fail with an error message so that the user knows why it ignores the reminder?Ok. Not sure if it's better, but ok.
The current implementation permits users to enter a reminder relative to due date and set the due date after that. I don't think we should ban this use case and I don't think the api should be too restrictive here.
An empty reminder date is still valid (actually it has fired at 01.01.0001) and it may be updated to a current value, if the due_date is entered later. In any case the situation is always visible at the frontend, because inactive reminders are rendered in red. So I would strongly vote against an additional validation.
Okay, that makes a lot of sense. I didn't think of that. If we make this clear in the frontend it is fine to leave it the way it currently is.
@ -878,0 +898,4 @@
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeReminderRelativeToMissing,
Message: "Relative reminder without relative_to",
Since this is the message that gets shown to the user, please change this to something like `Please provide what the reminder date is relative to
ok
Thanks!