#1416: Step 3a Add new fields to TaskReminder
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
cernst 2023-03-07 17:54:21 +01:00
parent 4a41a8a6ca
commit e0487516a3
13 changed files with 265 additions and 124 deletions

View File

@ -6,6 +6,8 @@
task_id: 27 task_id: 27
reminder: 2018-12-01 01:13:44 reminder: 2018-12-01 01:13:44
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
relative_to: 'start_date'
relative_period: -3600
- id: 3 - id: 3
task_id: 2 task_id: 2
reminder: 2018-12-01 01:13:44 reminder: 2018-12-01 01:13:44

View File

@ -244,7 +244,7 @@
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
- id: 27 - id: 27
title: 'task #27 with reminders' title: 'task #27 with reminders and start_date'
done: false done: false
created_by_id: 1 created_by_id: 1
list_id: 1 list_id: 1
@ -252,6 +252,7 @@
bucket_id: 1 bucket_id: 1
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
start_date: 2018-11-30 22:25:24
- id: 28 - id: 28
title: 'task #28 with repeat after' title: 'task #28 with repeat after'
done: false done: false

View File

@ -42,14 +42,14 @@ func TestTaskCollection(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Not using assert.Equal to avoid having the tests break every time we add new fixtures // 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 #1`)
assert.Contains(t, rec.Body.String(), `task #2`) assert.Contains(t, rec.Body.String(), `task #2 `)
assert.Contains(t, rec.Body.String(), `task #3`) assert.Contains(t, rec.Body.String(), `task #3 `)
assert.Contains(t, rec.Body.String(), `task #4`) assert.Contains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5`) assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8`) assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.Contains(t, rec.Body.String(), `task #10`) assert.Contains(t, rec.Body.String(), `task #10`)
assert.Contains(t, rec.Body.String(), `task #11`) assert.Contains(t, rec.Body.String(), `task #11`)
assert.Contains(t, rec.Body.String(), `task #12`) 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) rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5`) assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.NotContains(t, rec.Body.String(), `task #7`) assert.NotContains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8`) assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.NotContains(t, rec.Body.String(), `task #9`) assert.NotContains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) 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) rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"tASk #6"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5`) assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.NotContains(t, rec.Body.String(), `task #7`) assert.NotContains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8`) assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.NotContains(t, rec.Body.String(), `task #9`) assert.NotContains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)
@ -190,14 +190,14 @@ func TestTaskCollection(t *testing.T) {
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5`) assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8`) assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)
@ -215,14 +215,14 @@ func TestTaskCollection(t *testing.T) {
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5`) assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.NotContains(t, rec.Body.String(), `task #6`) assert.NotContains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8`) assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)
@ -255,14 +255,14 @@ func TestTaskCollection(t *testing.T) {
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5`) assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8`) assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)
@ -291,14 +291,14 @@ func TestTaskCollection(t *testing.T) {
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5`) assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8`) assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)
@ -314,14 +314,14 @@ func TestTaskCollection(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Not using assert.Equal to avoid having the tests break every time we add new fixtures // 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 #1`)
assert.Contains(t, rec.Body.String(), `task #2`) assert.Contains(t, rec.Body.String(), `task #2 `)
assert.Contains(t, rec.Body.String(), `task #3`) assert.Contains(t, rec.Body.String(), `task #3 `)
assert.Contains(t, rec.Body.String(), `task #4`) assert.Contains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5`) assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8`) assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.Contains(t, rec.Body.String(), `task #10`) assert.Contains(t, rec.Body.String(), `task #10`)
assert.Contains(t, rec.Body.String(), `task #11`) assert.Contains(t, rec.Body.String(), `task #11`)
assert.Contains(t, rec.Body.String(), `task #12`) 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) rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5`) assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.NotContains(t, rec.Body.String(), `task #7`) assert.NotContains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8`) assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.NotContains(t, rec.Body.String(), `task #9`) assert.NotContains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)
@ -417,14 +417,14 @@ func TestTaskCollection(t *testing.T) {
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5`) assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6`) assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8`) assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)
@ -442,14 +442,14 @@ func TestTaskCollection(t *testing.T) {
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`) assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3`) assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4`) assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5`) assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.NotContains(t, rec.Body.String(), `task #6`) assert.NotContains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7`) assert.Contains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8`) assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9`) assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #10`) assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`) assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`) assert.NotContains(t, rec.Body.String(), `task #12`)

View File

@ -117,24 +117,24 @@ func TestTask(t *testing.T) {
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`) assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
}) })
t.Run("Reminders", func(t *testing.T) { 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.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":[`) 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-10T10:00:00Z"`)
assert.Contains(t, rec.Body.String(), `{"Reminder":"2020-02-11T10:00:00Z"}`) assert.Contains(t, rec.Body.String(), `{"reminder":"2020-02-11T10:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"reminders":null`) assert.NotContains(t, rec.Body.String(), `"reminders":null`)
}) })
t.Run("Reminders unset to empty array", func(t *testing.T) { t.Run("Reminders unset to empty array", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminders": []}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminders": []}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":null`) 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) { t.Run("Reminders unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminders": null}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminders": null}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`) 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) { t.Run("Repeat after", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"repeat_after":3600}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"repeat_after":3600}`)

View File

@ -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 <https://www.gnu.org/licenses/>.
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
},
})
}

View File

@ -480,7 +480,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
} }
task27 := &Task{ task27 := &Task{
ID: 27, ID: 27,
Title: "task #27 with reminders", Title: "task #27 with reminders and start_date",
Identifier: "test1-12", Identifier: "test1-12",
Index: 12, Index: 12,
CreatedByID: 1, CreatedByID: 1,
@ -497,12 +497,15 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
}, },
{ {
ID: 2, ID: 2,
TaskID: 27, TaskID: 27,
Reminder: time.Unix(1543626824, 0).In(loc), Reminder: time.Unix(1543626824, 0).In(loc),
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
RelativePeriod: -3600,
RelativeTo: "start_date",
}, },
}, },
StartDate: time.Unix(1543616724, 0).In(loc),
ListID: 1, ListID: 1,
BucketID: 1, BucketID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
@ -1268,7 +1271,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
return 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)
} }
}) })
} }

View File

@ -33,13 +33,27 @@ import (
"code.vikunja.io/api/pkg/user" "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 // TaskReminder holds a reminder on a task
type TaskReminder struct { type TaskReminder struct {
ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"`
TaskID int64 `xorm:"bigint not null INDEX" json:"-"` TaskID int64 `xorm:"bigint not null INDEX" json:"-"`
// The absolute time when the user wants to be reminded of the task. // 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:"-"` 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 // TableName returns a pretty table name

View File

@ -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 when ReminderDates will be removed
//
// Deprecated: the if clause can be removed if ReminderDates will be removed
if t.ReminderDates != nil { if t.ReminderDates != nil {
if err := t.updateRemindersFromReminderDates(t.ReminderDates); err != nil { t.updateRemindersFromReminderDates(t.ReminderDates)
return err
}
} }
// Update the reminders
if err := t.updateReminders(s, t.Reminders); err != nil { if err := t.updateReminders(s, t.Reminders); err != nil {
return err return err
} }
@ -1068,12 +1066,11 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
return err 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 t.ReminderDates != nil {
if err := t.updateRemindersFromReminderDates(t.ReminderDates); err != nil { t.updateRemindersFromReminderDates(t.ReminderDates)
return err
}
} }
// Update the reminders // Update the reminders
if err := ot.updateReminders(s, t.Reminders); err != nil { if err := ot.updateReminders(s, t.Reminders); err != nil {
return err return err
@ -1505,18 +1502,24 @@ func updateDone(oldTask *Task, newTask *Task) {
} }
// Deprecated: will be removed when ReminderDates are removed from Task. // Deprecated: will be removed when ReminderDates are removed from Task.
// For now the method just creates Taskeminder Objects from the ReminderDates // For now the method just creates Taskeminder objects from the ReminderDates
func (t *Task) updateRemindersFromReminderDates(reminders []time.Time) (err error) { 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 { // remove absolute triggers in Reminders
t.Reminders = append(t.Reminders, &TaskReminder{TaskID: t.ID, Reminder: reminder}) updatedReminders := make([]*TaskReminder, 0)
for _, reminder := range t.Reminders {
if reminder.RelativeTo != "" {
updatedReminders = append(updatedReminders, reminder)
}
} }
// append absolute triggers from ReminderDates
t.ReminderDates = reminders for _, reminderDate := range reminderDates {
if len(reminders) == 0 { updatedReminders = append(updatedReminders, &TaskReminder{TaskID: t.ID, Reminder: reminderDate})
t.ReminderDates = nil
} }
return t.Reminders = updatedReminders
} }
// Removes all old reminders and adds the new ones. This is a lot easier and less buggy than // Removes all old reminders and adds the new ones. This is a lot easier and less buggy than

View File

@ -147,8 +147,11 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
} }
if !t.DueDate.IsZero() && t.Reminder > 0 { if !t.DueDate.IsZero() && t.Reminder > 0 {
task.Task.ReminderDates = []time.Time{ task.Task.Reminders = []*models.TaskReminder{
t.DueDate.Add(t.Reminder * -1), {
RelativeTo: models.DueDate,
RelativePeriod: int64((t.Reminder * -1).Seconds()),
},
} }
} }

View File

@ -101,7 +101,8 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
{Title: "label1"}, {Title: "label1"},
{Title: "label2"}, {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].Position, tickTickTasks[0].Order)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Done, false) assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Done, false)
@ -127,7 +128,8 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
{Title: "label2"}, {Title: "label2"},
{Title: "other label"}, {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].Position, tickTickTasks[2].Order)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Done, false) assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Done, false)

View File

@ -8309,6 +8309,19 @@ const docTemplate = `{
"RelationKindCopiedTo" "RelationKindCopiedTo"
] ]
}, },
"models.ReminderRelation": {
"type": "string",
"enum": [
"due_date",
"start_date",
"end_date"
],
"x-enum-varnames": [
"DueDate",
"StartDate",
"EndDate"
]
},
"models.Right": { "models.Right": {
"type": "integer", "type": "integer",
"enum": [ "enum": [
@ -8707,6 +8720,18 @@ const docTemplate = `{
"models.TaskReminder": { "models.TaskReminder": {
"type": "object", "type": "object",
"properties": { "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": { "reminder": {
"description": "The absolute time when the user wants to be reminded of the task.", "description": "The absolute time when the user wants to be reminded of the task.",
"type": "string" "type": "string"

View File

@ -8301,6 +8301,19 @@
"RelationKindCopiedTo" "RelationKindCopiedTo"
] ]
}, },
"models.ReminderRelation": {
"type": "string",
"enum": [
"due_date",
"start_date",
"end_date"
],
"x-enum-varnames": [
"DueDate",
"StartDate",
"EndDate"
]
},
"models.Right": { "models.Right": {
"type": "integer", "type": "integer",
"enum": [ "enum": [
@ -8699,6 +8712,18 @@
"models.TaskReminder": { "models.TaskReminder": {
"type": "object", "type": "object",
"properties": { "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": { "reminder": {
"description": "The absolute time when the user wants to be reminded of the task.", "description": "The absolute time when the user wants to be reminded of the task.",
"type": "string" "type": "string"

View File

@ -602,6 +602,16 @@ definitions:
- RelationKindFollows - RelationKindFollows
- RelationKindCopiedFrom - RelationKindCopiedFrom
- RelationKindCopiedTo - RelationKindCopiedTo
models.ReminderRelation:
enum:
- due_date
- start_date
- end_date
type: string
x-enum-varnames:
- DueDate
- StartDate
- EndDate
models.Right: models.Right:
enum: enum:
- 0 - 0
@ -904,6 +914,15 @@ definitions:
type: object type: object
models.TaskReminder: models.TaskReminder:
properties: 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: reminder:
description: The absolute time when the user wants to be reminded of the task. description: The absolute time when the user wants to be reminded of the task.
type: string type: string