diff --git a/pkg/db/fixtures/task_reminders.yml b/pkg/db/fixtures/task_reminders.yml index b3c158fbc..d3d023e9b 100644 --- a/pkg/db/fixtures/task_reminders.yml +++ b/pkg/db/fixtures/task_reminders.yml @@ -6,6 +6,8 @@ task_id: 27 reminder: 2018-12-01 01:13:44 created: 2018-12-01 01:12:04 + relative_to: 'start_date' + relative_period: -3600 - id: 3 task_id: 2 reminder: 2018-12-01 01:13:44 diff --git a/pkg/db/fixtures/tasks.yml b/pkg/db/fixtures/tasks.yml index 10e81b2f4..d3d4f5ad2 100644 --- a/pkg/db/fixtures/tasks.yml +++ b/pkg/db/fixtures/tasks.yml @@ -244,7 +244,7 @@ created: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04 - id: 27 - title: 'task #27 with reminders' + title: 'task #27 with reminders and start_date' done: false created_by_id: 1 list_id: 1 @@ -252,6 +252,7 @@ bucket_id: 1 created: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04 + start_date: 2018-11-30 22:25:24 - id: 28 title: 'task #28 with repeat after' done: false diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 5dbb36991..d14d7d4ef 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -42,14 +42,14 @@ func TestTaskCollection(t *testing.T) { assert.NoError(t, err) // Not using assert.Equal to avoid having the tests break every time we add new fixtures assert.Contains(t, rec.Body.String(), `task #1`) - assert.Contains(t, rec.Body.String(), `task #2`) - assert.Contains(t, rec.Body.String(), `task #3`) - assert.Contains(t, rec.Body.String(), `task #4`) - assert.Contains(t, rec.Body.String(), `task #5`) - 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`) + assert.Contains(t, rec.Body.String(), `task #2 `) + assert.Contains(t, rec.Body.String(), `task #3 `) + assert.Contains(t, rec.Body.String(), `task #4 `) + assert.Contains(t, rec.Body.String(), `task #5 `) + 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 `) assert.Contains(t, rec.Body.String(), `task #10`) assert.Contains(t, rec.Body.String(), `task #11`) assert.Contains(t, rec.Body.String(), `task #12`) @@ -75,14 +75,14 @@ func TestTaskCollection(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, urlParams) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.NotContains(t, rec.Body.String(), `task #5`) - assert.Contains(t, rec.Body.String(), `task #6`) - assert.NotContains(t, rec.Body.String(), `task #7`) - assert.NotContains(t, rec.Body.String(), `task #8`) - assert.NotContains(t, rec.Body.String(), `task #9`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.NotContains(t, rec.Body.String(), `task #5 `) + assert.Contains(t, rec.Body.String(), `task #6 `) + assert.NotContains(t, rec.Body.String(), `task #7 `) + assert.NotContains(t, rec.Body.String(), `task #8 `) + assert.NotContains(t, rec.Body.String(), `task #9 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -93,14 +93,14 @@ func TestTaskCollection(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"tASk #6"}}, urlParams) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.NotContains(t, rec.Body.String(), `task #5`) - assert.Contains(t, rec.Body.String(), `task #6`) - assert.NotContains(t, rec.Body.String(), `task #7`) - assert.NotContains(t, rec.Body.String(), `task #8`) - assert.NotContains(t, rec.Body.String(), `task #9`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.NotContains(t, rec.Body.String(), `task #5 `) + assert.Contains(t, rec.Body.String(), `task #6 `) + assert.NotContains(t, rec.Body.String(), `task #7 `) + assert.NotContains(t, rec.Body.String(), `task #8 `) + assert.NotContains(t, rec.Body.String(), `task #9 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -190,14 +190,14 @@ func TestTaskCollection(t *testing.T) { ) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.Contains(t, rec.Body.String(), `task #5`) - 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`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.Contains(t, rec.Body.String(), `task #5 `) + 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 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -215,14 +215,14 @@ func TestTaskCollection(t *testing.T) { ) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.NotContains(t, rec.Body.String(), `task #5`) - assert.NotContains(t, rec.Body.String(), `task #6`) - assert.Contains(t, rec.Body.String(), `task #7`) - assert.NotContains(t, rec.Body.String(), `task #8`) - assert.Contains(t, rec.Body.String(), `task #9`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.NotContains(t, rec.Body.String(), `task #5 `) + assert.NotContains(t, rec.Body.String(), `task #6 `) + assert.Contains(t, rec.Body.String(), `task #7 `) + assert.NotContains(t, rec.Body.String(), `task #8 `) + assert.Contains(t, rec.Body.String(), `task #9 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -255,14 +255,14 @@ func TestTaskCollection(t *testing.T) { ) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.Contains(t, rec.Body.String(), `task #5`) - assert.Contains(t, rec.Body.String(), `task #6`) - assert.Contains(t, rec.Body.String(), `task #7`) - assert.NotContains(t, rec.Body.String(), `task #8`) - assert.Contains(t, rec.Body.String(), `task #9`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.Contains(t, rec.Body.String(), `task #5 `) + assert.Contains(t, rec.Body.String(), `task #6 `) + assert.Contains(t, rec.Body.String(), `task #7 `) + assert.NotContains(t, rec.Body.String(), `task #8 `) + assert.Contains(t, rec.Body.String(), `task #9 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -291,14 +291,14 @@ func TestTaskCollection(t *testing.T) { ) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.Contains(t, rec.Body.String(), `task #5`) - 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`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.Contains(t, rec.Body.String(), `task #5 `) + 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 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -314,14 +314,14 @@ func TestTaskCollection(t *testing.T) { assert.NoError(t, err) // Not using assert.Equal to avoid having the tests break every time we add new fixtures assert.Contains(t, rec.Body.String(), `task #1`) - assert.Contains(t, rec.Body.String(), `task #2`) - assert.Contains(t, rec.Body.String(), `task #3`) - assert.Contains(t, rec.Body.String(), `task #4`) - assert.Contains(t, rec.Body.String(), `task #5`) - 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`) + assert.Contains(t, rec.Body.String(), `task #2 `) + assert.Contains(t, rec.Body.String(), `task #3 `) + assert.Contains(t, rec.Body.String(), `task #4 `) + assert.Contains(t, rec.Body.String(), `task #5 `) + 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 `) assert.Contains(t, rec.Body.String(), `task #10`) assert.Contains(t, rec.Body.String(), `task #11`) assert.Contains(t, rec.Body.String(), `task #12`) @@ -347,14 +347,14 @@ func TestTaskCollection(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.NotContains(t, rec.Body.String(), `task #5`) - assert.Contains(t, rec.Body.String(), `task #6`) - assert.NotContains(t, rec.Body.String(), `task #7`) - assert.NotContains(t, rec.Body.String(), `task #8`) - assert.NotContains(t, rec.Body.String(), `task #9`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.NotContains(t, rec.Body.String(), `task #5 `) + assert.Contains(t, rec.Body.String(), `task #6 `) + assert.NotContains(t, rec.Body.String(), `task #7 `) + assert.NotContains(t, rec.Body.String(), `task #8 `) + assert.NotContains(t, rec.Body.String(), `task #9 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -417,14 +417,14 @@ func TestTaskCollection(t *testing.T) { ) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.Contains(t, rec.Body.String(), `task #5`) - 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`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.Contains(t, rec.Body.String(), `task #5 `) + 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 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) @@ -442,14 +442,14 @@ func TestTaskCollection(t *testing.T) { ) assert.NoError(t, err) assert.NotContains(t, rec.Body.String(), `task #1`) - assert.NotContains(t, rec.Body.String(), `task #2`) - assert.NotContains(t, rec.Body.String(), `task #3`) - assert.NotContains(t, rec.Body.String(), `task #4`) - assert.NotContains(t, rec.Body.String(), `task #5`) - assert.NotContains(t, rec.Body.String(), `task #6`) - assert.Contains(t, rec.Body.String(), `task #7`) - assert.NotContains(t, rec.Body.String(), `task #8`) - assert.Contains(t, rec.Body.String(), `task #9`) + assert.NotContains(t, rec.Body.String(), `task #2 `) + assert.NotContains(t, rec.Body.String(), `task #3 `) + assert.NotContains(t, rec.Body.String(), `task #4 `) + assert.NotContains(t, rec.Body.String(), `task #5 `) + assert.NotContains(t, rec.Body.String(), `task #6 `) + assert.Contains(t, rec.Body.String(), `task #7 `) + assert.NotContains(t, rec.Body.String(), `task #8 `) + assert.Contains(t, rec.Body.String(), `task #9 `) assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #12`) diff --git a/pkg/integrations/task_test.go b/pkg/integrations/task_test.go index f2cc3eb97..c575dfb6e 100644 --- a/pkg/integrations/task_test.go +++ b/pkg/integrations/task_test.go @@ -117,24 +117,24 @@ func TestTask(t *testing.T) { assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`) }) t.Run("Reminders", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminders": [{"Reminder": "2020-02-10T10:00:00Z"},{"Reminder": "2020-02-11T10:00:00Z"}]}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminders": [{"reminder": "2020-02-10T10:00:00Z"},{"reminder": "2020-02-11T10:00:00Z"}]}`) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `"reminders":[`) - assert.Contains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"}`) - assert.Contains(t, rec.Body.String(), `{"Reminder":"2020-02-11T10:00:00Z"}`) + assert.Contains(t, rec.Body.String(), `{"reminder":"2020-02-10T10:00:00Z"`) + assert.Contains(t, rec.Body.String(), `{"reminder":"2020-02-11T10:00:00Z"`) assert.NotContains(t, rec.Body.String(), `"reminders":null`) }) t.Run("Reminders unset to empty array", func(t *testing.T) { rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminders": []}`) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `"reminders":null`) - assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"}`) + assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`) }) t.Run("Reminders unset to null", func(t *testing.T) { rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminders": null}`) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `"reminder_dates":null`) - assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"}`) + assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`) }) t.Run("Repeat after", func(t *testing.T) { rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"repeat_after":3600}`) diff --git a/pkg/migration/20230307171848.go b/pkg/migration/20230307171848.go new file mode 100644 index 000000000..2a55751ec --- /dev/null +++ b/pkg/migration/20230307171848.go @@ -0,0 +1,44 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type taskReminders20230307171848 struct { + RelativePeriod int64 `xorm:"bigint null" json:"relative_period"` + RelativeTo string `xorm:"varchar(50) null" json:"relative_to,omitempty"` +} + +func (taskReminders20230307171848) TableName() string { + return "task_reminders" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20230307171848", + Description: "Add relative period to task reminders", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync2(taskReminders20230307171848{}) + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index e8a65e27f..5091dfbb7 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -480,7 +480,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { } task27 := &Task{ ID: 27, - Title: "task #27 with reminders", + Title: "task #27 with reminders and start_date", Identifier: "test1-12", Index: 12, CreatedByID: 1, @@ -497,12 +497,15 @@ func TestTaskCollection_ReadAll(t *testing.T) { Created: time.Unix(1543626724, 0).In(loc), }, { - ID: 2, - TaskID: 27, - Reminder: time.Unix(1543626824, 0).In(loc), - Created: time.Unix(1543626724, 0).In(loc), + ID: 2, + TaskID: 27, + Reminder: time.Unix(1543626824, 0).In(loc), + Created: time.Unix(1543626724, 0).In(loc), + RelativePeriod: -3600, + RelativeTo: "start_date", }, }, + StartDate: time.Unix(1543616724, 0).In(loc), ListID: 1, BucketID: 1, RelatedTasks: map[RelationKind][]*Task{}, @@ -1268,7 +1271,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { return } - t.Errorf("Test %s, Task.ReadAll() = %v, want %v, \ndiff: %v", tt.name, got, tt.want, diff) + t.Errorf("Test %s, Task.ReadAll() = %v, \nwant %v, \ndiff: %v", tt.name, got, tt.want, diff) } }) } diff --git a/pkg/models/task_reminder.go b/pkg/models/task_reminder.go index 56d7180bb..dba9c3cb4 100644 --- a/pkg/models/task_reminder.go +++ b/pkg/models/task_reminder.go @@ -33,13 +33,27 @@ import ( "code.vikunja.io/api/pkg/user" ) +// ReminderRelation represents the task attribute which the period based reminder relates to +type ReminderRelation string + +// All valid ReminderRelations +const ( + DueDate ReminderRelation = `due_date` + StartDate ReminderRelation = `start_date` + EndDate ReminderRelation = `end_date` +) + // TaskReminder holds a reminder on a task type TaskReminder struct { ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` TaskID int64 `xorm:"bigint not null INDEX" json:"-"` // The absolute time when the user wants to be reminded of the task. - Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'"` + Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'" json:"reminder"` Created time.Time `xorm:"created not null" json:"-"` + // A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date, positive after. + RelativePeriod int64 `xorm:"bigint null" json:"relative_period"` + // The name of the date field to which the relative period refers to. + RelativeTo ReminderRelation `xorm:"varchar(50) null" json:"relative_to"` } // TableName returns a pretty table name diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index fdb6f8492..5a8a19b93 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -973,14 +973,12 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err } } - // Update the reminders - // - // Deprecated: the if clause can be removed if ReminderDates will be removed + // Deprecated: the if clause can be removed when ReminderDates will be removed if t.ReminderDates != nil { - if err := t.updateRemindersFromReminderDates(t.ReminderDates); err != nil { - return err - } + t.updateRemindersFromReminderDates(t.ReminderDates) } + + // Update the reminders if err := t.updateReminders(s, t.Reminders); err != nil { return err } @@ -1068,12 +1066,11 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { return err } - // Deprecated: This statement can be removed if ReminderDates will be removed + // Deprecated: This statement can be removed when ReminderDates will be removed if t.ReminderDates != nil { - if err := t.updateRemindersFromReminderDates(t.ReminderDates); err != nil { - return err - } + t.updateRemindersFromReminderDates(t.ReminderDates) } + // Update the reminders if err := ot.updateReminders(s, t.Reminders); err != nil { return err @@ -1505,18 +1502,24 @@ func updateDone(oldTask *Task, newTask *Task) { } // Deprecated: will be removed when ReminderDates are removed from Task. -// For now the method just creates Taskeminder Objects from the ReminderDates -func (t *Task) updateRemindersFromReminderDates(reminders []time.Time) (err error) { +// For now the method just creates Taskeminder objects from the ReminderDates +func (t *Task) updateRemindersFromReminderDates(reminderDates []time.Time) { + // if the client still sends old reminder_dates, then these will overwrite the absolute triggers + // of the Reminders, sent by the client. We assume that clients still sending old reminder_dates do not + // understand the new reminders. - for _, reminder := range reminders { - t.Reminders = append(t.Reminders, &TaskReminder{TaskID: t.ID, Reminder: reminder}) + // remove absolute triggers in Reminders + updatedReminders := make([]*TaskReminder, 0) + for _, reminder := range t.Reminders { + if reminder.RelativeTo != "" { + updatedReminders = append(updatedReminders, reminder) + } } - - t.ReminderDates = reminders - if len(reminders) == 0 { - t.ReminderDates = nil + // append absolute triggers from ReminderDates + for _, reminderDate := range reminderDates { + updatedReminders = append(updatedReminders, &TaskReminder{TaskID: t.ID, Reminder: reminderDate}) } - return + t.Reminders = updatedReminders } // Removes all old reminders and adds the new ones. This is a lot easier and less buggy than diff --git a/pkg/modules/migration/ticktick/ticktick.go b/pkg/modules/migration/ticktick/ticktick.go index 0870ae87d..91bbf9972 100644 --- a/pkg/modules/migration/ticktick/ticktick.go +++ b/pkg/modules/migration/ticktick/ticktick.go @@ -147,8 +147,11 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace } if !t.DueDate.IsZero() && t.Reminder > 0 { - task.Task.ReminderDates = []time.Time{ - t.DueDate.Add(t.Reminder * -1), + task.Task.Reminders = []*models.TaskReminder{ + { + RelativeTo: models.DueDate, + RelativePeriod: int64((t.Reminder * -1).Seconds()), + }, } } diff --git a/pkg/modules/migration/ticktick/ticktick_test.go b/pkg/modules/migration/ticktick/ticktick_test.go index 1638b3769..1ee2341ac 100644 --- a/pkg/modules/migration/ticktick/ticktick_test.go +++ b/pkg/modules/migration/ticktick/ticktick_test.go @@ -101,7 +101,8 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) { {Title: "label1"}, {Title: "label2"}, }) - //assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].ReminderDates, tickTickTasks[0].) // TODO + assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Reminders[0].RelativeTo, models.ReminderRelation("due_date")) + assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Reminders[0].RelativePeriod, int64(-24*3600)) assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Position, tickTickTasks[0].Order) assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Done, false) @@ -127,7 +128,8 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) { {Title: "label2"}, {Title: "other label"}, }) - //assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].ReminderDates, tickTickTasks[0].) // TODO + assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Reminders[0].RelativeTo, models.ReminderRelation("due_date")) + assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Reminders[0].RelativePeriod, int64(-24*3600)) assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Position, tickTickTasks[2].Order) assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Done, false) diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index dd934ae38..7d70fee0c 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -8309,6 +8309,19 @@ const docTemplate = `{ "RelationKindCopiedTo" ] }, + "models.ReminderRelation": { + "type": "string", + "enum": [ + "due_date", + "start_date", + "end_date" + ], + "x-enum-varnames": [ + "DueDate", + "StartDate", + "EndDate" + ] + }, "models.Right": { "type": "integer", "enum": [ @@ -8707,6 +8720,18 @@ const docTemplate = `{ "models.TaskReminder": { "type": "object", "properties": { + "relative_period": { + "description": "A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date, positive after.", + "type": "integer" + }, + "relative_to": { + "description": "The name of the date field to which the relative period refers to.", + "allOf": [ + { + "$ref": "#/definitions/models.ReminderRelation" + } + ] + }, "reminder": { "description": "The absolute time when the user wants to be reminded of the task.", "type": "string" diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index 00ac515b7..0e09adecc 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -8301,6 +8301,19 @@ "RelationKindCopiedTo" ] }, + "models.ReminderRelation": { + "type": "string", + "enum": [ + "due_date", + "start_date", + "end_date" + ], + "x-enum-varnames": [ + "DueDate", + "StartDate", + "EndDate" + ] + }, "models.Right": { "type": "integer", "enum": [ @@ -8699,6 +8712,18 @@ "models.TaskReminder": { "type": "object", "properties": { + "relative_period": { + "description": "A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date, positive after.", + "type": "integer" + }, + "relative_to": { + "description": "The name of the date field to which the relative period refers to.", + "allOf": [ + { + "$ref": "#/definitions/models.ReminderRelation" + } + ] + }, "reminder": { "description": "The absolute time when the user wants to be reminded of the task.", "type": "string" diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 9811d4ca2..8dbd01881 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -602,6 +602,16 @@ definitions: - RelationKindFollows - RelationKindCopiedFrom - RelationKindCopiedTo + models.ReminderRelation: + enum: + - due_date + - start_date + - end_date + type: string + x-enum-varnames: + - DueDate + - StartDate + - EndDate models.Right: enum: - 0 @@ -904,6 +914,15 @@ definitions: type: object models.TaskReminder: properties: + relative_period: + description: A period in seconds relative to another date argument. Negative + values mean the reminder triggers before the date, positive after. + type: integer + relative_to: + allOf: + - $ref: '#/definitions/models.ReminderRelation' + description: The name of the date field to which the relative period refers + to. reminder: description: The absolute time when the user wants to be reminded of the task. type: string