#1416: Add relative Reminders #1427

Merged
konrad merged 21 commits from ce72/api:1416_reminders into main 2023-03-27 20:07:08 +00:00
Contributor

Solution idea

  • Add Reminder-Array to Task
    • New: Reminders []*reminder.TaskReminder
    • Old and deprecated: ReminderDates []time.Time
  • TaskReminder keeps its attribute Reminder time.Time It always stores the next trigger time of the Reminder
    • Pro: Cron-Queries for finding due reminders don't need to be modified. No data migration of existing TaskReminders required.
    • Pro: Cron-Queries for finding due reminders can stay simple (they are queried frequently)
    • Pro: Clients always find the next trigger time in one place and don't need to calculate
    • Con: TaskReminder.Reminder must be updated whenever due_date, start_date or end_date of the Task is updated
    • Con: Slight Redundancy: The next trigger time is stored in attribute TaskReminder.Reminder and can be calculated from the following additional attributes as well
  • Additional Attributes:
    • TaskReminder.RelativeTo (string, one of 'due_date', 'start_date', 'end_date' or nil)
    • TaskReminder.RelativePeriod (int64, trigger in seconds before (negative) or after (positive) RelativeTo)
  • API:
"reminders": [
    {
        "reminder": "2023-02-26T14:31:13+01:00",
        "relative_to": null,
        "relative_period": null
    },
    {
        "reminder": "2023-02-28T14:31:13+01:00",
        "relative_to": "due_date",
        "releative_period": "-3600"
    }
]

Steps:

  • Rename existing task.Reminders to task.ReminderDates
  • Introduce task.Reminders as array of TaskReminder
  • Refactor existing logic to use task.ReminderDates and new task.Reminders in parallel
  • Migrate task filter from ReminderDates to new Reminders
  • Migrate logic and tests for updateDone from ReminderDates to new Reminders
  • Migrate logic and tests for migrations from ReminderDates to new Reminders
  • Add RelativePeriod and RelativeTo to TaskReminder
  • Set the reminder time in incoming relative Reminders
  • Update TaskReminder when DueDate, StartDate, EndDate change
  • Migrate current reminder dates to the new structure in db Not necessary. We can just continue to use the existing column reminder in table task_reminders.
  • Further tests and cleanup
### Solution idea - Add Reminder-Array to `Task` - New: `Reminders []*reminder.TaskReminder` - Old and deprecated: `ReminderDates []time.Time` - `TaskReminder` keeps its attribute `Reminder time.Time` It always stores the next trigger time of the Reminder - Pro: Cron-Queries for finding due reminders don't need to be modified. No data migration of existing TaskReminders required. - Pro: Cron-Queries for finding due reminders can stay simple (they are queried frequently) - Pro: Clients always find the next trigger time in one place and don't need to calculate - Con: `TaskReminder.Reminder` must be updated whenever due_date, start_date or end_date of the Task is updated - Con: Slight Redundancy: The next trigger time is stored in attribute `TaskReminder.Reminder` and can be calculated from the following additional attributes as well - Additional Attributes: - `TaskReminder.RelativeTo` (string, one of 'due_date', 'start_date', 'end_date' or nil) - `TaskReminder.RelativePeriod` (int64, trigger in seconds before (negative) or after (positive) RelativeTo) - API: ``` "reminders": [ { "reminder": "2023-02-26T14:31:13+01:00", "relative_to": null, "relative_period": null }, { "reminder": "2023-02-28T14:31:13+01:00", "relative_to": "due_date", "releative_period": "-3600" } ] ``` ### Steps: - [x] Rename existing task.Reminders to task.ReminderDates - [x] Introduce task.Reminders as array of TaskReminder - [x] Refactor existing logic to use task.ReminderDates and new task.Reminders in parallel - [x] Migrate task filter from ReminderDates to new Reminders - [x] Migrate logic and tests for updateDone from ReminderDates to new Reminders - [x] Migrate logic and tests for migrations from ReminderDates to new Reminders - [x] Add RelativePeriod and RelativeTo to TaskReminder - [x] Set the reminder time in incoming relative Reminders - [x] Update TaskReminder when DueDate, StartDate, EndDate change - [x] ~~Migrate current reminder dates to the new structure in db~~ Not necessary. We can just continue to use the existing column `reminder` in table task_reminders. - [x] Further tests and cleanup
ce72 added 1 commit 2023-03-06 08:28:15 +00:00
continuous-integration/drone/pr Build is passing Details
3dedc040db
#1416: Step 1 Rename Reminders to ReminderDates
konrad reviewed 2023-03-06 19:01:00 +00:00
@ -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" {
Owner

This is the value which consumers of the api will pass in for filtering. For consistency, this should be called

This is the value which consumers of the api will pass in for filtering. For consistency, this should be called
Owner

We'll also need a similar check to filter for the new reminders.

We'll also need a similar check to filter for the new reminders.
Author
Contributor

This is the value which consumers of the api will pass in for filtering. For consistency, this should be called

Yes, this has to be fixed. I still need to figure out how to apply the filter on an attribute in an Substructure.

> This is the value which consumers of the api will pass in for filtering. For consistency, this should be called Yes, this has to be fixed. I still need to figure out how to apply the filter on an attribute in an Substructure.
Author
Contributor

We'll also need a similar check to filter for the new reminders.

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.

> We'll also need a similar check to filter for the new reminders. 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.
Owner

I think in the end we need only one to filter by reminder trigger dates.

That's true.

> I think in the end we need only one to filter by reminder trigger dates. That's true.
Author
Contributor

This has now been fixed. Filter queries for "reminders" will be applied to task_reminders.reminder column.

This has now been fixed. Filter queries for "reminders" will be applied to task_reminders.reminder column.
ce72 marked this conversation as resolved
Owner

I like that api.

Con: TaskReminder.Reminder must be updated whenever due_date, start_date or end_date of the Task is updated

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.

I like that api. > Con: TaskReminder.Reminder must be updated whenever due_date, start_date or end_date of the Task is updated 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.
ce72 force-pushed 1416_reminders from f39a0fb120 to 85267d26bb 2023-03-06 21:28:32 +00:00 Compare
Author
Contributor

I like that api.

Con: TaskReminder.Reminder must be updated whenever due_date, start_date or end_date of the Task is updated

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.

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.

> I like that api. > > > Con: TaskReminder.Reminder must be updated whenever due_date, start_date or end_date of the Task is updated > > 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. 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.
ce72 added 1 commit 2023-03-07 07:28:09 +00:00
Owner

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.

> 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.
Owner

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.

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.
ce72 reviewed 2023-03-07 17:04:28 +00:00
@ -0,0 +24,4 @@
)
type taskReminders20230307171848 struct {
Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'" json:"reminder"`
Author
Contributor

Just json:"reminder" was added to this line. Do we need to include that in the migration script?

Just `json:"reminder"` was added to this line. Do we need to include that in the migration script?
Owner

No, the json declarations are usually ignored.

No, the json declarations are usually ignored.
ce72 marked this conversation as resolved
ce72 force-pushed 1416_reminders from d78045f333 to 7a0a7c92af 2023-03-07 17:08:57 +00:00 Compare
ce72 force-pushed 1416_reminders from 7a0a7c92af to e0487516a3 2023-03-07 17:27:58 +00:00 Compare
ce72 added 1 commit 2023-03-07 18:51:03 +00:00
continuous-integration/drone/pr Build is passing Details
d8630e9680
fix: task filter "reminders" uses new Reminder object
ce72 added 1 commit 2023-03-07 20:24:57 +00:00
ce72 force-pushed 1416_reminders from 2dff90448f to 3c4fb76554 2023-03-07 22:07:13 +00:00 Compare
ce72 added 1 commit 2023-03-07 22:45:28 +00:00
continuous-integration/drone/pr Build is passing Details
8674b6437e
fix: store reminder_period and reminder_to
ce72 added 1 commit 2023-03-08 11:44:23 +00:00
continuous-integration/drone/pr Build is passing Details
54ed002d1b
fix: add tests and cleanup
ce72 changed title from WIP: #1416: Add relative Reminders to #1416: Add relative Reminders 2023-03-08 11:45:03 +00:00
Author
Contributor

Next 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.

Next 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.
ce72 added 8 commits 2023-03-08 11:59:27 +00:00
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
a39b3aeb06
fix(deps): update github.com/vectordotdev/go-datemath digest to f3954d0
continuous-integration/drone/push Build is passing Details
9f14466dfa
fix: lint
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
29f8522de9
fix(deps): update module github.com/getsentry/sentry-go to v0.19.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
abf38defa2
fix(deps): update module github.com/spf13/afero to v1.9.5
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
a5c241758a
fix(deps): update module github.com/ulule/limiter/v3 to v3.11.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
4f62e978ef
fix(deps): update src.techknowlogick.com/xgo digest to b607086
continuous-integration/drone/pr Build is passing Details
333832293d
Merge branch 'main' into 1416_reminders
konrad reviewed 2023-03-08 13:27:41 +00:00
@ -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 `)
Owner

Why the extra space?

Why the extra space?
Author
Contributor

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?

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?
Owner

Ah okay that makes sense. Should be fine leave it like this.

Ah okay that makes sense. Should be fine leave it like this.
konrad marked this conversation as resolved
@ -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 {
Owner

What about passing only the task? (As the method is called on a task we can avoid the many parameters that way)

What about passing only the task? (As the method is called on a task we can avoid the many parameters that way)
Author
Contributor

ok

ok
ce72 marked this conversation as resolved
@ -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 {
Owner

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.

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.
Author
Contributor

ok

ok
ce72 marked this conversation as resolved
@ -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 {
Owner

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).

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).
Author
Contributor

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?

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?
Owner

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?

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?
Author
Contributor

ok

ok
ce72 marked this conversation as resolved
@ -1494,2 +1557,3 @@
// Resolve duplicates and sort them
reminderMap := make(map[int64]time.Time, len(reminders))
reminderMap := make(map[int64]TaskReminder, len(reminders))
Owner

Please use a pointer to TaskReminder.

Please use a pointer to `TaskReminder`.
Author
Contributor

ok

ok
konrad marked this conversation as resolved
@ -1508,2 +1582,2 @@
t.Reminders = reminders
if len(reminders) == 0 {
// sort reminders
sort.Slice(t.Reminders, func(i, j int) bool {
Owner

Why sort them here? Just for the updated task response?

Why sort them here? Just for the updated task response?
Author
Contributor

Yes. It simplifies the tests too.

Yes. It simplifies the tests too.
konrad marked this conversation as resolved
Owner

Nice work so far!

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.

Do you want to send a PR for that?

Nice work so far! > 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. Do you want to send a PR for that?
Author
Contributor

Nice work so far!

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.

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.

> Nice work so far! > > > 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. > > 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.
ce72 force-pushed 1416_reminders from 76442080c4 to 7f9e70cc1f 2023-03-08 15:13:19 +00:00 Compare
Author
Contributor

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"

BTW (and off-topic with respect to this PR): I found that rebasing onto https://kolaente.dev/vikunja/api/commit/9f14466dfa8660362a4e51b3c8c6810bf8d66a22#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"
ce72 added 1 commit 2023-03-08 22:11:09 +00:00
continuous-integration/drone/pr Build is passing Details
26119d585b
fix: update correct task variable
ce72 added 1 commit 2023-03-12 18:07:58 +00:00
continuous-integration/drone/pr Build is passing Details
1a2e15ece6
Merge branch 'main' into 1416_reminders
ce72 added 1 commit 2023-03-15 08:14:45 +00:00
continuous-integration/drone/pr Build is passing Details
345e94843f
chore: merge main branch
ce72 force-pushed 1416_reminders from 55b56a84c5 to 6b208cc9b8 2023-03-15 14:22:50 +00:00 Compare
ce72 force-pushed 1416_reminders from 6b208cc9b8 to 8098d66727 2023-03-15 14:43:37 +00:00 Compare
ce72 added 1 commit 2023-03-15 22:04:15 +00:00
continuous-integration/drone/pr Build is passing Details
7fe3a1ade7
fix: unset reminder date if referenced date is missing
ce72 added 1 commit 2023-03-24 20:55:32 +00:00
continuous-integration/drone/pr Build is failing Details
39fa2f1b8e
Merge branch 'main' into 1416_reminders
ce72 added 1 commit 2023-03-25 15:32:59 +00:00
continuous-integration/drone/pr Build is passing Details
06548195ce
Merge branch 'main' into 1416_reminders
Author
Contributor

Nice work so far!

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.

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.

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.

> > Nice work so far! > > > > > 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. > > > > 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. The PR for the frontend is https://kolaente.dev/vikunja/frontend/pulls/3248. The frontend and the api may be merged independently. But to see and use relative reminders the api must be merged.
konrad reviewed 2023-03-26 15:43:54 +00:00
@ -876,2 +876,4 @@
}
// ErrReminderRelativeToMissing represents an error where a task has a relative reminder without reference date
type ErrReminderRelativeToMissing struct {
Owner

Please add this error to the error.md file in the docs.

Please add this error to the error.md file in the docs.
Author
Contributor

ok

ok
konrad marked this conversation as resolved
@ -1481,0 +1518,4 @@
if !task.DueDate.IsZero() {
reminder.Reminder = task.DueDate.Add(relativeDuration)
} else {
reminder.Reminder = time.Time{}
Owner

I think we can get rid of all the else clauses and move them before the switch statements so that the logic inside the switch will override the empty reminder time.

I think we can get rid of all the `else` clauses and move them before the `switch` statements so that the logic inside the switch will override the empty reminder time.
Owner

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?

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?
Author
Contributor

I think we can get rid of all the else clauses and move them before the switch statements so that the logic inside the switch will override the empty reminder time.

Ok. Not sure if it's better, but ok.

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?

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.

> I think we can get rid of all the `else` clauses and move them before the `switch` statements so that the logic inside the switch will override the empty reminder time. Ok. Not sure if it's better, but ok. > 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? 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.
Owner

The current implementation permits users to enter a reminder relative to due date and set the due date after that.

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.

> The current implementation permits users to enter a reminder relative to due date and set the due date after that. 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.
konrad marked this conversation as resolved
ce72 added 1 commit 2023-03-26 18:14:13 +00:00
continuous-integration/drone/pr Build is passing Details
a3cb694323
feat: add validation for relative reminders
konrad reviewed 2023-03-27 07:50:59 +00:00
@ -878,0 +898,4 @@
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeReminderRelativeToMissing,
Message: "Relative reminder without relative_to",
Owner

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

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
Author
Contributor

ok

ok
konrad marked this conversation as resolved
ce72 added 1 commit 2023-03-27 10:24:43 +00:00
continuous-integration/drone/pr Build is passing Details
c49bf45cbf
feat: add validation for relative reminders
konrad approved these changes 2023-03-27 20:01:39 +00:00
konrad left a comment
Owner

Thanks!

Thanks!
konrad merged commit 3f5252dc24 into main 2023-03-27 20:07:08 +00:00
konrad deleted branch 1416_reminders 2023-03-27 20:07:08 +00:00
Sign in to join this conversation.
No reviewers
No Milestone
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: vikunja/vikunja#1427
No description provided.