Compare commits
12 Commits
main
...
codex/anal
| Author | SHA1 | Date |
|---|---|---|
|
|
c0f58bb879 | |
|
|
a80d555264 | |
|
|
e1728ee0bc | |
|
|
cd5c1ba3e5 | |
|
|
27c5876c60 | |
|
|
1ff0b18080 | |
|
|
503ca607d1 | |
|
|
7c69302119 | |
|
|
0ccaf5b476 | |
|
|
3109fa4a72 | |
|
|
c5ddec0254 | |
|
|
0e621dc38a |
|
|
@ -940,10 +940,15 @@
|
|||
"default_value": "true",
|
||||
"comment": "If set to true will send an email every day with all overdue tasks at a configured time."
|
||||
},
|
||||
{
|
||||
"key": "today_tasks_reminders_enabled",
|
||||
"default_value": "false",
|
||||
"comment": "If set to true, include tasks due today in the daily overdue reminder email."
|
||||
},
|
||||
{
|
||||
"key": "overdue_tasks_reminders_time",
|
||||
"key": "today_tasks_reminders_time",
|
||||
"default_value": "9:00",
|
||||
"comment": "When to send the overdue task reminder email."
|
||||
"comment": "When to send the daily tasks reminder email."
|
||||
},
|
||||
{
|
||||
"key": "default_project_id",
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
"savedSuccess": "The settings were successfully updated.",
|
||||
"emailReminders": "Send me reminders for tasks via email",
|
||||
"overdueReminders": "Send me a summary of my undone overdue tasks every day",
|
||||
"todayReminders": "Include tasks due today in reminder email",
|
||||
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
|
||||
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
|
||||
"playSoundWhenDone": "Play a sound when marking tasks as done",
|
||||
|
|
@ -110,7 +111,7 @@
|
|||
"defaultProject": "Default project",
|
||||
"defaultView": "Default view",
|
||||
"timezone": "Time zone",
|
||||
"overdueTasksRemindersTime": "Overdue tasks reminder email time",
|
||||
"todayTasksRemindersTime": "Tasks reminder email time",
|
||||
"filterUsedOnOverview": "Saved filter used on the overview page",
|
||||
"minimumPriority": "Minimum visible task priority",
|
||||
"dateDisplay": "Date display format",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ export interface IUserSettings extends IAbstract {
|
|||
discoverableByName: boolean
|
||||
discoverableByEmail: boolean
|
||||
overdueTasksRemindersEnabled: boolean
|
||||
overdueTasksRemindersTime: undefined | string | Date
|
||||
todayTasksRemindersTime: undefined | string | Date
|
||||
todayTasksRemindersEnabled: boolean
|
||||
defaultProjectId: undefined | IProject['id']
|
||||
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
||||
timezone: string
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ export default class UserSettingsModel extends AbstractModel<IUserSettings> impl
|
|||
discoverableByName = false
|
||||
discoverableByEmail = false
|
||||
overdueTasksRemindersEnabled = true
|
||||
overdueTasksRemindersTime = undefined
|
||||
todayTasksRemindersTime = undefined
|
||||
todayTasksRemindersEnabled = false
|
||||
defaultProjectId = undefined
|
||||
weekStart = 0 as IUserSettings['weekStart']
|
||||
timezone = ''
|
||||
|
|
|
|||
|
|
@ -123,26 +123,34 @@
|
|||
{{ $t('user.settings.general.overdueReminders') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="settings.overdueTasksRemindersEnabled"
|
||||
class="field"
|
||||
>
|
||||
<label
|
||||
for="overdueTasksReminderTime"
|
||||
class="two-col"
|
||||
>
|
||||
<span>
|
||||
{{ $t('user.settings.general.overdueTasksRemindersTime') }}
|
||||
</span>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input
|
||||
id="overdueTasksReminderTime"
|
||||
v-model="settings.overdueTasksRemindersTime"
|
||||
class="input"
|
||||
type="time"
|
||||
@keyup.enter="updateSettings"
|
||||
v-model="settings.todayTasksRemindersEnabled"
|
||||
type="checkbox"
|
||||
>
|
||||
{{ $t('user.settings.general.todayReminders') }}
|
||||
</label>
|
||||
</div>
|
||||
<template v-if="settings.overdueTasksRemindersEnabled || settings.todayTasksRemindersEnabled">
|
||||
<div class="field">
|
||||
<label
|
||||
for="todayTasksReminderTime"
|
||||
class="two-col"
|
||||
>
|
||||
<span>
|
||||
{{ $t('user.settings.general.todayTasksRemindersTime') }}
|
||||
</span>
|
||||
<input
|
||||
id="todayTasksReminderTime"
|
||||
v-model="settings.todayTasksRemindersTime"
|
||||
class="input"
|
||||
type="time"
|
||||
@keyup.enter="updateSettings"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ test.describe('Home Page Task Overview', () => {
|
|||
|
||||
await updateUserSettings(apiContext, token, {
|
||||
default_project_id: project.id,
|
||||
overdue_tasks_reminders_time: '9:00',
|
||||
today_tasks_reminders_time: '9:00',
|
||||
})
|
||||
|
||||
const newTaskTitle = 'New Task'
|
||||
|
|
|
|||
|
|
@ -208,11 +208,12 @@ const (
|
|||
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
|
||||
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
|
||||
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
|
||||
DefaultSettingsTodayTasksRemindersEnabled Key = `defaultsettings.today_tasks_reminders_enabled`
|
||||
DefaultSettingsDefaultProjectID Key = `defaultsettings.default_project_id`
|
||||
DefaultSettingsWeekStart Key = `defaultsettings.week_start`
|
||||
DefaultSettingsLanguage Key = `defaultsettings.language`
|
||||
DefaultSettingsTimezone Key = `defaultsettings.timezone`
|
||||
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
|
||||
DefaultSettingsTodayTaskRemindersTime Key = `defaultsettings.today_tasks_reminders_time`
|
||||
|
||||
WebhooksEnabled Key = `webhooks.enabled`
|
||||
WebhooksTimeoutSeconds Key = `webhooks.timeoutseconds`
|
||||
|
|
@ -473,7 +474,7 @@ func InitDefaultConfig() {
|
|||
// Settings
|
||||
DefaultSettingsAvatarProvider.setDefault("initials")
|
||||
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
|
||||
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
|
||||
DefaultSettingsTodayTaskRemindersTime.setDefault("9:00")
|
||||
// Webhook
|
||||
WebhooksEnabled.setDefault(true)
|
||||
WebhooksTimeoutSeconds.setDefault(30)
|
||||
|
|
|
|||
|
|
@ -24,14 +24,3 @@
|
|||
task_id: 2
|
||||
reminder: 2019-06-01 12:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
# Task 47: two reminders, neither inside the (2018-10-01, 2018-12-10) window
|
||||
# Reminder before the window:
|
||||
- id: 6
|
||||
task_id: 47
|
||||
reminder: 2018-08-01 12:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
# Reminder after the window:
|
||||
- id: 7
|
||||
task_id: 47
|
||||
reminder: 2019-03-01 12:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
|
|
|
|||
|
|
@ -412,12 +412,15 @@
|
|||
due_date: 2023-03-01 15:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
||||
# Task due later today for reminders
|
||||
- id: 47
|
||||
title: 'task #47 with reminders outside window'
|
||||
title: 'task #47 due today'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
project_id: 1
|
||||
index: 32
|
||||
due_date: 2018-12-01 23:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 48
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
export_file_id: 1
|
||||
today_tasks_reminders_enabled: true
|
||||
-
|
||||
id: 2
|
||||
username: 'user2'
|
||||
|
|
|
|||
|
|
@ -68,7 +68,10 @@
|
|||
"task": {
|
||||
"reminder": {
|
||||
"subject": "Reminder for \"%[1]s\" (%[2]s)",
|
||||
"message": "This is a friendly reminder of the task \"%[1]s\" (%[2]s)."
|
||||
"message": "This is a friendly reminder of the task \"%[1]s\" (%[2]s).",
|
||||
"only_due_today_subject": "Tasks due today",
|
||||
"overdue_intro": "You have the following overdue tasks:",
|
||||
"today_intro": "You have the following tasks due today:"
|
||||
},
|
||||
"comment": {
|
||||
"subject": "Re: %[1]s (%[2]s)",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-present 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 License 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 License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// usersTodayTasksRemindersEnabled20250903072808 adds the today_tasks_reminders_enabled column to the users table.
|
||||
type usersTodayTasksRemindersEnabled20250903072808 struct {
|
||||
TodayTasksRemindersEnabled bool `xorm:"not null default false index"`
|
||||
}
|
||||
|
||||
func (usersTodayTasksRemindersEnabled20250903072808) TableName() string { return "users" }
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20250903072808",
|
||||
Description: "Add today_tasks_reminders_enabled setting",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(usersTodayTasksRemindersEnabled20250903072808{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error { return nil },
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-present 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 License 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 License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20250903072809",
|
||||
Description: "Rename overdue_tasks_reminders_time column to today_tasks_reminders_time",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
switch tx.Dialect().URI().DBType {
|
||||
case schemas.SQLITE:
|
||||
_, err := tx.Exec("ALTER TABLE `users` RENAME COLUMN `overdue_tasks_reminders_time` TO `today_tasks_reminders_time`")
|
||||
return err
|
||||
case schemas.MYSQL:
|
||||
_, err := tx.Exec("ALTER TABLE `users` CHANGE `overdue_tasks_reminders_time` `today_tasks_reminders_time` VARCHAR(5) NOT NULL DEFAULT '09:00'")
|
||||
return err
|
||||
default: // postgres
|
||||
_, err := tx.Exec("ALTER TABLE \"users\" RENAME COLUMN \"overdue_tasks_reminders_time\" TO \"today_tasks_reminders_time\"")
|
||||
return err
|
||||
}
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error { return nil },
|
||||
})
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
DefaultProjectID: 4,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ func TestLabel_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersEnabled: true,
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
ExportFileID: 1,
|
||||
|
|
@ -67,7 +68,7 @@ func TestLabel_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
DefaultProjectID: 4,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
|
|
@ -177,7 +178,8 @@ func TestLabel_ReadOne(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersEnabled: true,
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
ExportFileID: 1,
|
||||
|
|
@ -240,7 +242,7 @@ func TestLabel_ReadOne(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
DefaultProjectID: 4,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
|
|
|
|||
|
|
@ -295,83 +295,72 @@ func getOverdueSinceString(until time.Duration, language string) (overdueSince s
|
|||
}
|
||||
|
||||
// UndoneTaskOverdueNotification represents a UndoneTaskOverdueNotification notification
|
||||
type UndoneTaskOverdueNotification struct {
|
||||
User *user.User
|
||||
Task *Task
|
||||
Project *Project
|
||||
type DailyTasksReminderNotification struct {
|
||||
User *user.User
|
||||
OverdueTasks map[int64]*Task
|
||||
DueToday map[int64]*Task
|
||||
Projects map[int64]*Project
|
||||
}
|
||||
|
||||
// ToMail returns the mail notification for UndoneTaskOverdueNotification
|
||||
func (n *UndoneTaskOverdueNotification) ToMail(lang string) *notifications.Mail {
|
||||
until := time.Until(n.Task.DueDate).Round(1*time.Hour) * -1
|
||||
return notifications.NewMail().
|
||||
IncludeLinkToSettings(lang).
|
||||
Subject(i18n.T(lang, "notifications.task.overdue.subject", n.Task.Title, n.Project.Title)).
|
||||
Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())).
|
||||
Line(i18n.T(lang, "notifications.task.overdue.message", n.Task.Title, n.Project.Title, getOverdueSinceString(until, n.User.Language))).
|
||||
Action(i18n.T(lang, "notifications.common.actions.open_task"), config.ServicePublicURL.GetString()+"tasks/"+strconv.FormatInt(n.Task.ID, 10)).
|
||||
Line(i18n.T(lang, "notifications.common.have_nice_day"))
|
||||
}
|
||||
// ToMail returns the mail notification for DailyTasksReminderNotification
|
||||
func (n *DailyTasksReminderNotification) ToMail(lang string) *notifications.Mail {
|
||||
|
||||
// ToDB returns the UndoneTaskOverdueNotification notification in a format which can be saved in the db
|
||||
func (n *UndoneTaskOverdueNotification) ToDB() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the name of the notification
|
||||
func (n *UndoneTaskOverdueNotification) Name() string {
|
||||
return "task.undone.overdue"
|
||||
}
|
||||
|
||||
// ThreadID returns the thread ID for email threading
|
||||
func (n *UndoneTaskOverdueNotification) ThreadID() string {
|
||||
return getThreadID(n.Task.ID)
|
||||
}
|
||||
|
||||
// UndoneTasksOverdueNotification represents a UndoneTasksOverdueNotification notification
|
||||
type UndoneTasksOverdueNotification struct {
|
||||
User *user.User
|
||||
Tasks map[int64]*Task
|
||||
Projects map[int64]*Project
|
||||
}
|
||||
|
||||
// ToMail returns the mail notification for UndoneTasksOverdueNotification
|
||||
func (n *UndoneTasksOverdueNotification) ToMail(lang string) *notifications.Mail {
|
||||
|
||||
sortedTasks := make([]*Task, 0, len(n.Tasks))
|
||||
for _, task := range n.Tasks {
|
||||
sortedTasks = append(sortedTasks, task)
|
||||
sortedOverdue := make([]*Task, 0, len(n.OverdueTasks))
|
||||
for _, task := range n.OverdueTasks {
|
||||
sortedOverdue = append(sortedOverdue, task)
|
||||
}
|
||||
sort.Slice(sortedOverdue, func(i, j int) bool {
|
||||
return sortedOverdue[i].DueDate.Before(sortedOverdue[j].DueDate)
|
||||
})
|
||||
|
||||
sort.Slice(sortedTasks, func(i, j int) bool {
|
||||
return sortedTasks[i].DueDate.Before(sortedTasks[j].DueDate)
|
||||
sortedToday := make([]*Task, 0, len(n.DueToday))
|
||||
for _, task := range n.DueToday {
|
||||
sortedToday = append(sortedToday, task)
|
||||
}
|
||||
sort.Slice(sortedToday, func(i, j int) bool {
|
||||
return sortedToday[i].DueDate.Before(sortedToday[j].DueDate)
|
||||
})
|
||||
|
||||
overdueLine := ""
|
||||
for _, task := range sortedTasks {
|
||||
for _, task := range sortedOverdue {
|
||||
until := time.Until(task.DueDate).Round(1*time.Hour) * -1
|
||||
overdueLine += `* [` + task.Title + `](` + config.ServicePublicURL.GetString() + "tasks/" + strconv.FormatInt(task.ID, 10) + `) (` + n.Projects[task.ProjectID].Title + `), ` + i18n.T("notifications.task.overdue.overdue", getOverdueSinceString(until, n.User.Language)) + "\n"
|
||||
}
|
||||
|
||||
return notifications.NewMail().
|
||||
todayLine := ""
|
||||
for _, task := range sortedToday {
|
||||
todayLine += `* [` + task.Title + `](` + config.ServicePublicURL.GetString() + "tasks/" + strconv.FormatInt(task.ID, 10) + `) (` + n.Projects[task.ProjectID].Title + ")\n"
|
||||
}
|
||||
|
||||
subject := i18n.T(lang, "notifications.task.overdue.multiple_subject")
|
||||
if len(n.OverdueTasks) == 0 {
|
||||
subject = i18n.T(lang, "notifications.task.reminder.only_due_today_subject")
|
||||
}
|
||||
|
||||
m := notifications.NewMail().
|
||||
IncludeLinkToSettings(lang).
|
||||
Subject(i18n.T(lang, "notifications.task.overdue.multiple_subject")).
|
||||
Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())).
|
||||
Line(i18n.T(lang, "notifications.task.overdue.multiple_message")).
|
||||
Line(overdueLine).
|
||||
Action(i18n.T(lang, "notifications.common.actions.open_vikunja"), config.ServicePublicURL.GetString()).
|
||||
Subject(subject).
|
||||
Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName()))
|
||||
|
||||
if overdueLine != "" {
|
||||
m.Line(i18n.T(lang, "notifications.task.reminder.overdue_intro"))
|
||||
m.Line(overdueLine)
|
||||
}
|
||||
if todayLine != "" {
|
||||
m.Line(i18n.T(lang, "notifications.task.reminder.today_intro"))
|
||||
m.Line(todayLine)
|
||||
}
|
||||
|
||||
m.Action(i18n.T(lang, "notifications.common.actions.open_vikunja"), config.ServicePublicURL.GetString()).
|
||||
Line(i18n.T(lang, "notifications.common.have_nice_day"))
|
||||
return m
|
||||
}
|
||||
|
||||
// ToDB returns the UndoneTasksOverdueNotification notification in a format which can be saved in the db
|
||||
func (n *UndoneTasksOverdueNotification) ToDB() interface{} {
|
||||
return nil
|
||||
}
|
||||
// ToDB returns the DailyTasksReminderNotification notification in a format which can be saved in the db
|
||||
func (n *DailyTasksReminderNotification) ToDB() interface{} { return nil }
|
||||
|
||||
// Name returns the name of the notification
|
||||
func (n *UndoneTasksOverdueNotification) Name() string {
|
||||
return "task.undone.overdue"
|
||||
}
|
||||
func (n *DailyTasksReminderNotification) Name() string { return "task.daily.reminder" }
|
||||
|
||||
// UserMentionedInTaskNotification represents a UserMentionedInTaskNotification notification
|
||||
type UserMentionedInTaskNotification struct {
|
||||
|
|
|
|||
|
|
@ -153,7 +153,8 @@ func TestProjectUser_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersEnabled: true,
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
ExportFileID: 1,
|
||||
|
|
@ -168,7 +169,7 @@ func TestProjectUser_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
DefaultProjectID: 4,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersEnabled: true,
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
ExportFileID: 1,
|
||||
|
|
@ -55,7 +56,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersEnabled: false,
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
DefaultProjectID: 4,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
|
|
@ -67,7 +69,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersEnabled: false,
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -606,30 +609,17 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task47 := &Task{
|
||||
ID: 47,
|
||||
Title: "task #47 with reminders outside window",
|
||||
Identifier: "test1-32",
|
||||
Index: 32,
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Reminders: []*TaskReminder{
|
||||
{
|
||||
ID: 6,
|
||||
TaskID: 47,
|
||||
Reminder: time.Date(2018, 8, 1, 12, 0, 0, 0, loc),
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
},
|
||||
{
|
||||
ID: 7,
|
||||
TaskID: 47,
|
||||
Reminder: time.Date(2019, 3, 1, 12, 0, 0, 0, loc),
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
},
|
||||
},
|
||||
ID: 47,
|
||||
Title: "task #47 due today",
|
||||
Identifier: "test1-32",
|
||||
Index: 32,
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
ProjectID: 1,
|
||||
DueDate: time.Date(2018, 12, 1, 23, 0, 0, 0, loc),
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
Created: time.Date(2018, 12, 1, 1, 12, 4, 0, loc),
|
||||
Updated: time.Date(2018, 12, 1, 1, 12, 4, 0, loc),
|
||||
}
|
||||
task48 := &Task{
|
||||
ID: 48,
|
||||
|
|
@ -1002,7 +992,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
task32, // has nil dates
|
||||
task33, // has nil dates
|
||||
task39, // has nil dates
|
||||
task47, // has nil dates
|
||||
task47,
|
||||
task48, // has nil dates
|
||||
},
|
||||
wantErr: false,
|
||||
|
|
@ -1035,7 +1025,6 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
task30,
|
||||
task31,
|
||||
task33,
|
||||
task47,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
@ -1055,7 +1044,6 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
task30,
|
||||
task31,
|
||||
task33,
|
||||
task47,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
@ -1071,18 +1059,6 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filtered reminder dates should not match task with reminders outside window",
|
||||
fields: fields{
|
||||
Filter: "reminders > '2018-10-01T00:00:00+00:00' && reminders < '2018-12-10T00:00:00+00:00'",
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{
|
||||
task2,
|
||||
task27,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filtered reminder dates narrow window excludes all",
|
||||
fields: fields{
|
||||
|
|
@ -1099,8 +1075,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{
|
||||
task2, // has reminder at 2019-06-01 (> 2019-01-01)
|
||||
task47, // has reminder at 2019-03-01 (> 2019-01-01) and 2018-08-01 (< 2018-09-01)
|
||||
task2, // has reminder at 2019-06-01 (> 2019-01-01)
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
@ -1601,10 +1576,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
// The only tasks with a due date
|
||||
task6,
|
||||
task5,
|
||||
task47,
|
||||
task28,
|
||||
// The other ones don't have a due date
|
||||
task48,
|
||||
task47,
|
||||
task39,
|
||||
task33,
|
||||
task32,
|
||||
|
|
@ -1652,6 +1627,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
task7,
|
||||
task6,
|
||||
task5,
|
||||
task47,
|
||||
task28,
|
||||
},
|
||||
},
|
||||
|
|
@ -1667,6 +1643,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
},
|
||||
want: []*Task{
|
||||
task28,
|
||||
task47,
|
||||
task5,
|
||||
task6,
|
||||
task7,
|
||||
|
|
@ -1687,6 +1664,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
want: []*Task{
|
||||
task6,
|
||||
task5,
|
||||
task47,
|
||||
task28,
|
||||
task7,
|
||||
task8,
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ func getTaskUsersForTasks(s *xorm.Session, taskIDs []int64, cond builder.Cond) (
|
|||
|
||||
creators := []*userWithTask{}
|
||||
err = s.Table("tasks").
|
||||
Select("DISTINCT tasks.id AS task_id, users.id, users.name, users.username, users.email, users.email_reminders_enabled, users.overdue_tasks_reminders_enabled, users.overdue_tasks_reminders_time, users.language, users.timezone, users.created, users.updated").
|
||||
Select("DISTINCT tasks.id AS task_id, users.id, users.name, users.username, users.email, users.email_reminders_enabled, users.overdue_tasks_reminders_enabled, users.today_tasks_reminders_enabled, users.today_tasks_reminders_time, users.language, users.timezone, users.created, users.updated").
|
||||
Join("INNER", "users", "tasks.created_by_id = users.id").
|
||||
Where(builder.And(conditions...)).
|
||||
Find(&creators)
|
||||
|
|
@ -182,7 +182,7 @@ func getTaskUsersForTasks(s *xorm.Session, taskIDs []int64, cond builder.Cond) (
|
|||
|
||||
assignees := []*TaskAssigneeWithUser{}
|
||||
err = s.Table("task_assignees").
|
||||
Select("DISTINCT task_assignees.task_id, users.id, users.name, users.username, users.email, users.email_reminders_enabled, users.overdue_tasks_reminders_enabled, users.overdue_tasks_reminders_time, users.language, users.timezone, users.created, users.updated").
|
||||
Select("DISTINCT task_assignees.task_id, users.id, users.name, users.username, users.email, users.email_reminders_enabled, users.overdue_tasks_reminders_enabled, users.today_tasks_reminders_enabled, users.today_tasks_reminders_time, users.language, users.timezone, users.created, users.updated").
|
||||
Join("INNER", "users", "task_assignees.user_id = users.id").
|
||||
Where(builder.And(assigneeConds...)).
|
||||
Find(&assignees)
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func getUndoneOverdueTasks(s *xorm.Session, now time.Time, cond builder.Cond) (usersWithTasks map[int64]*userWithTasks, err error) {
|
||||
func getTasksForDailyReminder(s *xorm.Session, now time.Time, cond builder.Cond) (usersWithTasks map[int64]*userWithTasks, err error) {
|
||||
now = utils.GetTimeWithoutSeconds(now)
|
||||
nextMinute := now.Add(1 * time.Minute)
|
||||
|
||||
var tasks []*Task
|
||||
err = s.
|
||||
Where("due_date is not null AND due_date < ? AND projects.is_archived = false", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
|
||||
Where("due_date is not null AND due_date < ? AND projects.is_archived = false", nextMinute.Add(time.Hour*38).Format(dbTimeFormat)).
|
||||
Join("LEFT", "projects", "projects.id = tasks.project_id").
|
||||
And("done = false").
|
||||
Find(&tasks)
|
||||
|
|
@ -81,23 +81,32 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time, cond builder.Cond) (u
|
|||
}
|
||||
|
||||
// If it is time for that current user, add the task to their project of overdue tasks
|
||||
tm, err := time.Parse("15:04", t.User.OverdueTasksRemindersTime)
|
||||
tm, err := time.Parse("15:04", t.User.TodayTasksRemindersTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
overdueMailTime := time.Date(now.Year(), now.Month(), now.Day(), tm.Hour(), tm.Minute(), 0, 0, tz)
|
||||
isTimeForReminder := overdueMailTime.After(now) || overdueMailTime.Equal(now.In(tz))
|
||||
wasTimeForReminder := overdueMailTime.Before(nextMinute)
|
||||
taskIsOverdueInUserTimezone := overdueMailTime.After(t.Task.DueDate.In(tz))
|
||||
if isTimeForReminder && wasTimeForReminder && taskIsOverdueInUserTimezone {
|
||||
reminderTime := time.Date(now.Year(), now.Month(), now.Day(), tm.Hour(), tm.Minute(), 0, 0, tz)
|
||||
isTimeForReminder := reminderTime.After(now) || reminderTime.Equal(now.In(tz))
|
||||
wasTimeForReminder := reminderTime.Before(nextMinute)
|
||||
taskDue := t.Task.DueDate.In(tz)
|
||||
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, tz)
|
||||
if isTimeForReminder && wasTimeForReminder {
|
||||
_, exists := uts[t.User.ID]
|
||||
if !exists {
|
||||
uts[t.User.ID] = &userWithTasks{
|
||||
user: t.User,
|
||||
tasks: make(map[int64]*Task),
|
||||
user: t.User,
|
||||
overdue: make(map[int64]*Task),
|
||||
dueToday: make(map[int64]*Task),
|
||||
}
|
||||
}
|
||||
uts[t.User.ID].tasks[t.Task.ID] = t.Task
|
||||
|
||||
if t.User.OverdueTasksRemindersEnabled && reminderTime.After(taskDue) {
|
||||
uts[t.User.ID].overdue[t.Task.ID] = t.Task
|
||||
continue
|
||||
}
|
||||
if t.User.TodayTasksRemindersEnabled && taskDue.After(reminderTime) && taskDue.Before(endOfDay) {
|
||||
uts[t.User.ID].dueToday[t.Task.ID] = t.Task
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,11 +114,12 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time, cond builder.Cond) (u
|
|||
}
|
||||
|
||||
type userWithTasks struct {
|
||||
user *user.User
|
||||
tasks map[int64]*Task
|
||||
user *user.User
|
||||
overdue map[int64]*Task
|
||||
dueToday map[int64]*Task
|
||||
}
|
||||
|
||||
// RegisterOverdueReminderCron registers a function which checks once a day for tasks that are overdue and not done.
|
||||
// RegisterOverdueReminderCron registers a function which checks once a day for overdue tasks and tasks due today and sends reminders.
|
||||
func RegisterOverdueReminderCron() {
|
||||
webhookEnabled := config.WebhooksEnabled.GetBool()
|
||||
emailEnabled := config.ServiceEnableEmailReminders.GetBool() && config.MailerEnabled.GetBool()
|
||||
|
|
@ -130,12 +140,15 @@ func RegisterOverdueReminderCron() {
|
|||
|
||||
var cond builder.Cond
|
||||
if emailEnabled && !webhookEnabled {
|
||||
cond = builder.Eq{"users.overdue_tasks_reminders_enabled": true}
|
||||
cond = builder.Or(
|
||||
builder.Eq{"users.overdue_tasks_reminders_enabled": true},
|
||||
builder.Eq{"users.today_tasks_reminders_enabled": true},
|
||||
)
|
||||
}
|
||||
|
||||
uts, err := getUndoneOverdueTasks(s, now, cond)
|
||||
uts, err := getTasksForDailyReminder(s, now, cond)
|
||||
if err != nil {
|
||||
log.Errorf("[Undone Overdue Tasks Reminder] Could not get undone overdue tasks in the next minute: %s", err)
|
||||
log.Errorf("[Daily Tasks Reminder] Could not get tasks for daily reminder: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -143,80 +156,92 @@ func RegisterOverdueReminderCron() {
|
|||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Undone Overdue Tasks Reminder] Sending reminders to %d users", len(uts))
|
||||
log.Debugf("[Daily Tasks Reminder] Sending reminders to %d users", len(uts))
|
||||
|
||||
taskIDs := []int64{}
|
||||
for _, ut := range uts {
|
||||
for _, t := range ut.tasks {
|
||||
for _, t := range ut.overdue {
|
||||
taskIDs = append(taskIDs, t.ID)
|
||||
}
|
||||
for _, t := range ut.dueToday {
|
||||
taskIDs = append(taskIDs, t.ID)
|
||||
}
|
||||
}
|
||||
|
||||
projects, err := GetProjectsMapSimpleByTaskIDs(s, taskIDs)
|
||||
if err != nil {
|
||||
log.Errorf("[Undone Overdue Tasks Reminder] Could not get projects for tasks: %s", err)
|
||||
log.Errorf("[Daily Tasks Reminder] Could not get projects for tasks: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ut := range uts {
|
||||
if emailEnabled && ut.user.OverdueTasksRemindersEnabled {
|
||||
var n notifications.Notification = &UndoneTasksOverdueNotification{
|
||||
User: ut.user,
|
||||
Tasks: ut.tasks,
|
||||
Projects: projects,
|
||||
}
|
||||
|
||||
if len(ut.tasks) == 1 {
|
||||
for _, t := range ut.tasks {
|
||||
n = &UndoneTaskOverdueNotification{
|
||||
User: ut.user,
|
||||
Task: t,
|
||||
Project: projects[t.ProjectID],
|
||||
}
|
||||
// Dispatch webhook events, deduplicated by task ID across all users
|
||||
if webhookEnabled {
|
||||
dispatchedTasks := make(map[int64]bool)
|
||||
for _, ut := range uts {
|
||||
// Per-task overdue events
|
||||
for _, t := range ut.overdue {
|
||||
if dispatchedTasks[t.ID] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
err = notifications.Notify(ut.user, n, s)
|
||||
if err != nil {
|
||||
log.Errorf("[Undone Overdue Tasks Reminder] Could not notify user %d: %s", ut.user.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch webhook events
|
||||
if webhookEnabled {
|
||||
// Per-task events
|
||||
for _, t := range ut.tasks {
|
||||
dispatchedTasks[t.ID] = true
|
||||
err = events.Dispatch(&TaskOverdueEvent{
|
||||
Task: t,
|
||||
User: ut.user,
|
||||
Project: projects[t.ProjectID],
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("[Undone Overdue Tasks Reminder] Could not dispatch overdue event for task %d: %s", t.ID, err)
|
||||
log.Errorf("[Daily Tasks Reminder] Could not dispatch overdue event for task %d: %s", t.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Batch event
|
||||
err = events.Dispatch(&TasksOverdueEvent{
|
||||
Tasks: mapToSlice(ut.tasks),
|
||||
User: ut.user,
|
||||
Projects: projects,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("[Undone Overdue Tasks Reminder] Could not dispatch batch overdue event for user %d: %s", ut.user.ID, err)
|
||||
if len(ut.overdue) > 0 {
|
||||
err = events.Dispatch(&TasksOverdueEvent{
|
||||
Tasks: mapToSlice(ut.overdue),
|
||||
User: ut.user,
|
||||
Projects: projects,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("[Daily Tasks Reminder] Could not dispatch batch overdue event for user %d: %s", ut.user.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("[Undone Overdue Tasks Reminder] Sent reminder for %d tasks to user %d", len(ut.tasks), ut.user.ID)
|
||||
if !emailEnabled {
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Errorf("[Daily Tasks Reminder] Could not commit: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, ut := range uts {
|
||||
if len(ut.overdue) == 0 && len(ut.dueToday) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
n := &DailyTasksReminderNotification{
|
||||
User: ut.user,
|
||||
OverdueTasks: ut.overdue,
|
||||
DueToday: ut.dueToday,
|
||||
Projects: projects,
|
||||
}
|
||||
|
||||
err = notifications.Notify(ut.user, n, s)
|
||||
if err != nil {
|
||||
log.Errorf("[Daily Tasks Reminder] Could not notify user %d: %s", ut.user.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Daily Tasks Reminder] Sent reminder email to user %d (overdue: %d, today: %d)", ut.user.ID, len(ut.overdue), len(ut.dueToday))
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Errorf("[Undone Overdue Tasks Reminder] Could not commit: %s", err)
|
||||
log.Errorf("[Daily Tasks Reminder] Could not commit: %s", err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not register undone overdue tasks reminder cron: %s", err)
|
||||
log.Fatalf("Could not register daily tasks reminder cron: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ import (
|
|||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestGetUndoneOverDueTasks(t *testing.T) {
|
||||
func TestGetTasksForDailyReminder(t *testing.T) {
|
||||
t.Run("no undone tasks", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
|
|
@ -35,34 +35,61 @@ func TestGetUndoneOverDueTasks(t *testing.T) {
|
|||
|
||||
now, err := time.Parse(time.RFC3339Nano, "2018-01-01T01:13:00Z")
|
||||
require.NoError(t, err)
|
||||
tasks, err := getUndoneOverdueTasks(s, now, builder.Eq{"users.overdue_tasks_reminders_enabled": true})
|
||||
tasks, err := getTasksForDailyReminder(s, now, builder.Or(
|
||||
builder.Eq{"users.overdue_tasks_reminders_enabled": true},
|
||||
builder.Eq{"users.today_tasks_reminders_enabled": true},
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, tasks)
|
||||
})
|
||||
t.Run("undone overdue", func(t *testing.T) {
|
||||
t.Run("overdue and due today", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
now, err := time.Parse(time.RFC3339Nano, "2018-12-01T09:00:00Z")
|
||||
require.NoError(t, err)
|
||||
uts, err := getUndoneOverdueTasks(s, now, builder.Eq{"users.overdue_tasks_reminders_enabled": true})
|
||||
uts, err := getTasksForDailyReminder(s, now, builder.Or(
|
||||
builder.Eq{"users.overdue_tasks_reminders_enabled": true},
|
||||
builder.Eq{"users.today_tasks_reminders_enabled": true},
|
||||
))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, uts, 1)
|
||||
assert.Len(t, uts[1].tasks, 2)
|
||||
// The tasks don't always have the same order, so we only check their presence, not their position.
|
||||
var task5Present bool
|
||||
var task6Present bool
|
||||
for _, t := range uts[1].tasks {
|
||||
if t.ID == 5 {
|
||||
task5Present = true
|
||||
}
|
||||
if t.ID == 6 {
|
||||
task6Present = true
|
||||
}
|
||||
}
|
||||
assert.Truef(t, task5Present, "expected task 5 to be present but was not")
|
||||
assert.Truef(t, task6Present, "expected task 6 to be present but was not")
|
||||
assert.Len(t, uts, 1)
|
||||
assert.Len(t, uts[1].overdue, 2)
|
||||
assert.Len(t, uts[1].dueToday, 1)
|
||||
_, ok := uts[1].dueToday[47]
|
||||
assert.True(t, ok)
|
||||
|
||||
// Disable today reminders and ensure the task is not included
|
||||
_, err = s.Where("id = ?", 1).Cols("today_tasks_reminders_enabled").Update(&user.User{TodayTasksRemindersEnabled: false})
|
||||
require.NoError(t, err)
|
||||
uts, err = getTasksForDailyReminder(s, now, builder.Or(
|
||||
builder.Eq{"users.overdue_tasks_reminders_enabled": true},
|
||||
builder.Eq{"users.today_tasks_reminders_enabled": true},
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, uts[1].overdue, 2)
|
||||
assert.Empty(t, uts[1].dueToday)
|
||||
})
|
||||
t.Run("only due today", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
// disable overdue reminders, keep today reminders enabled
|
||||
_, err := s.Where("id = ?", 1).Cols("overdue_tasks_reminders_enabled").Update(&user.User{OverdueTasksRemindersEnabled: false})
|
||||
require.NoError(t, err)
|
||||
|
||||
now, err := time.Parse(time.RFC3339Nano, "2018-12-01T09:00:00Z")
|
||||
require.NoError(t, err)
|
||||
uts, err := getTasksForDailyReminder(s, now, builder.Or(
|
||||
builder.Eq{"users.overdue_tasks_reminders_enabled": true},
|
||||
builder.Eq{"users.today_tasks_reminders_enabled": true},
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, uts, 1)
|
||||
assert.Empty(t, uts[1].overdue)
|
||||
assert.Len(t, uts[1].dueToday, 1)
|
||||
})
|
||||
t.Run("done overdue", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
|
|
@ -71,7 +98,10 @@ func TestGetUndoneOverDueTasks(t *testing.T) {
|
|||
|
||||
now, err := time.Parse(time.RFC3339Nano, "2018-11-01T01:13:00Z")
|
||||
require.NoError(t, err)
|
||||
tasks, err := getUndoneOverdueTasks(s, now, builder.Eq{"users.overdue_tasks_reminders_enabled": true})
|
||||
tasks, err := getTasksForDailyReminder(s, now, builder.Or(
|
||||
builder.Eq{"users.overdue_tasks_reminders_enabled": true},
|
||||
builder.Eq{"users.today_tasks_reminders_enabled": true},
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, tasks)
|
||||
})
|
||||
|
|
@ -32,7 +32,8 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersEnabled: true,
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
ExportFileID: 1,
|
||||
|
|
@ -44,7 +45,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
DefaultProjectID: 4,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
|
|
@ -56,7 +57,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
DefaultProjectID: 4,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
|
|
@ -69,7 +70,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -81,7 +82,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -92,7 +93,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -104,7 +105,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
EmailRemindersEnabled: true,
|
||||
DiscoverableByEmail: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -115,7 +116,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -126,7 +127,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -137,7 +138,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -149,7 +150,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -162,7 +163,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
EmailRemindersEnabled: true,
|
||||
DiscoverableByName: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
@ -173,7 +174,7 @@ func TestListUsersFromProject(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
TodayTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ type UserSettings struct {
|
|||
DiscoverableByEmail bool `json:"discoverable_by_email"`
|
||||
// If enabled, the user will get an email for their overdue tasks each morning.
|
||||
OverdueTasksRemindersEnabled bool `json:"overdue_tasks_reminders_enabled"`
|
||||
// The time when the daily summary of overdue tasks will be sent via email.
|
||||
OverdueTasksRemindersTime string `json:"overdue_tasks_reminders_time" valid:"time,required"`
|
||||
// If enabled, includes tasks due later today in the overdue reminder email.
|
||||
TodayTasksRemindersEnabled bool `json:"today_tasks_reminders_enabled"`
|
||||
// The time when the daily summary of tasks will be sent via email.
|
||||
TodayTasksRemindersTime string `json:"today_tasks_reminders_time" valid:"time,required"`
|
||||
// If a task is created without a specified project this value should be used. Applies
|
||||
// to tasks made directly in API and from clients.
|
||||
DefaultProjectID int64 `json:"default_project_id"`
|
||||
|
|
@ -209,11 +211,12 @@ func UpdateGeneralUserSettings(c *echo.Context) error {
|
|||
user.DiscoverableByEmail = us.DiscoverableByEmail
|
||||
user.DiscoverableByName = us.DiscoverableByName
|
||||
user.OverdueTasksRemindersEnabled = us.OverdueTasksRemindersEnabled
|
||||
user.TodayTasksRemindersEnabled = us.TodayTasksRemindersEnabled
|
||||
user.DefaultProjectID = us.DefaultProjectID
|
||||
user.WeekStart = us.WeekStart
|
||||
user.Language = us.Language
|
||||
user.Timezone = us.Timezone
|
||||
user.OverdueTasksRemindersTime = us.OverdueTasksRemindersTime
|
||||
user.TodayTasksRemindersTime = us.TodayTasksRemindersTime
|
||||
user.FrontendSettings = us.FrontendSettings
|
||||
|
||||
_, err = user2.UpdateUser(s, user, true)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func UserShow(c *echo.Context) error {
|
|||
WeekStart: u.WeekStart,
|
||||
Language: u.Language,
|
||||
Timezone: u.Timezone,
|
||||
OverdueTasksRemindersTime: u.OverdueTasksRemindersTime,
|
||||
TodayTasksRemindersTime: u.TodayTasksRemindersTime,
|
||||
FrontendSettings: u.FrontendSettings,
|
||||
ExtraSettingsLinks: u.ExtraSettingsLinks,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -105,7 +105,8 @@ type User struct {
|
|||
DiscoverableByName bool `xorm:"bool default false index" json:"-"`
|
||||
DiscoverableByEmail bool `xorm:"bool default false index" json:"-"`
|
||||
OverdueTasksRemindersEnabled bool `xorm:"bool default true index" json:"-"`
|
||||
OverdueTasksRemindersTime string `xorm:"varchar(5) not null default '09:00'" json:"-"`
|
||||
TodayTasksRemindersTime string `xorm:"varchar(5) not null default '09:00'" json:"-"`
|
||||
TodayTasksRemindersEnabled bool `xorm:"bool default false index" json:"-"`
|
||||
DefaultProjectID int64 `xorm:"bigint null index" json:"-"`
|
||||
WeekStart int `xorm:"null" json:"-"`
|
||||
Language string `xorm:"varchar(50) null" json:"-" valid:"language"`
|
||||
|
|
@ -317,8 +318,8 @@ func getUser(s *xorm.Session, user *User, withEmail bool) (userOut *User, err er
|
|||
userOut.Email = ""
|
||||
}
|
||||
|
||||
if userOut.OverdueTasksRemindersTime == "" {
|
||||
userOut.OverdueTasksRemindersTime = "9:00"
|
||||
if userOut.TodayTasksRemindersTime == "" {
|
||||
userOut.TodayTasksRemindersTime = "9:00"
|
||||
}
|
||||
|
||||
if userOut.Status == StatusDisabled {
|
||||
|
|
@ -629,11 +630,12 @@ func UpdateUser(s *xorm.Session, user *User, forceOverride bool) (updatedUser *U
|
|||
"discoverable_by_name",
|
||||
"discoverable_by_email",
|
||||
"overdue_tasks_reminders_enabled",
|
||||
"today_tasks_reminders_enabled",
|
||||
"default_project_id",
|
||||
"week_start",
|
||||
"language",
|
||||
"timezone",
|
||||
"overdue_tasks_reminders_time",
|
||||
"today_tasks_reminders_time",
|
||||
"frontend_settings",
|
||||
"extra_settings_links",
|
||||
).
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
|
|||
user.DiscoverableByName = config.DefaultSettingsDiscoverableByName.GetBool()
|
||||
user.DiscoverableByEmail = config.DefaultSettingsDiscoverableByEmail.GetBool()
|
||||
user.OverdueTasksRemindersEnabled = config.DefaultSettingsOverdueTaskRemindersEnabled.GetBool()
|
||||
user.OverdueTasksRemindersTime = config.DefaultSettingsOverdueTaskRemindersTime.GetString()
|
||||
user.TodayTasksRemindersTime = config.DefaultSettingsTodayTaskRemindersTime.GetString()
|
||||
user.TodayTasksRemindersEnabled = config.DefaultSettingsTodayTasksRemindersEnabled.GetBool()
|
||||
user.DefaultProjectID = config.DefaultSettingsDefaultProjectID.GetInt64()
|
||||
user.WeekStart = config.DefaultSettingsWeekStart.GetInt()
|
||||
user.Timezone = config.DefaultSettingsTimezone.GetString()
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 due today","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T23:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
t.Run("by priority desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
|
||||
|
|
@ -142,7 +142,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 due today","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T23:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
// should equal duedate asc
|
||||
t.Run("by due_date", func(t *testing.T) {
|
||||
|
|
@ -153,7 +153,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by duedate desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 due today","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T23:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
// Due date without unix suffix
|
||||
t.Run("by duedate asc without suffix", func(t *testing.T) {
|
||||
|
|
@ -169,7 +169,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by duedate desc without suffix", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 due today","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T23:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
t.Run("by duedate asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
|
||||
|
|
@ -380,7 +380,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 due today","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T23:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
t.Run("by priority desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
|
||||
|
|
@ -390,7 +390,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 due today","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T23:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
// should equal duedate asc
|
||||
t.Run("by due_date", func(t *testing.T) {
|
||||
|
|
@ -401,7 +401,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by duedate desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 due today","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T23:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
t.Run("by duedate asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ func init() {
|
|||
"CreateNewProjectForUser": reflect.ValueOf(models.CreateNewProjectForUser),
|
||||
"CreateProject": reflect.ValueOf(models.CreateProject),
|
||||
"CreateSession": reflect.ValueOf(models.CreateSession),
|
||||
"DailyTasksReminderNotification": reflect.ValueOf((*models.DailyTasksReminderNotification)(nil)),
|
||||
"DeleteAllUserSessions": reflect.ValueOf(models.DeleteAllUserSessions),
|
||||
"DeleteOrphanedTaskPositions": reflect.ValueOf(models.DeleteOrphanedTaskPositions),
|
||||
"DeleteUser": reflect.ValueOf(models.DeleteUser),
|
||||
|
|
@ -501,8 +502,6 @@ func init() {
|
|||
"TeamProject": reflect.ValueOf((*models.TeamProject)(nil)),
|
||||
"TeamUser": reflect.ValueOf((*models.TeamUser)(nil)),
|
||||
"TeamWithPermission": reflect.ValueOf((*models.TeamWithPermission)(nil)),
|
||||
"UndoneTaskOverdueNotification": reflect.ValueOf((*models.UndoneTaskOverdueNotification)(nil)),
|
||||
"UndoneTasksOverdueNotification": reflect.ValueOf((*models.UndoneTasksOverdueNotification)(nil)),
|
||||
"UnsplashPhoto": reflect.ValueOf((*models.UnsplashPhoto)(nil)),
|
||||
"UpdateTaskInSavedFilterViews": reflect.ValueOf((*models.UpdateTaskInSavedFilterViews)(nil)),
|
||||
"UserDataExportRequestedEvent": reflect.ValueOf((*models.UserDataExportRequestedEvent)(nil)),
|
||||
|
|
|
|||
Loading…
Reference in New Issue