Compare commits
12 Commits
main
...
codex/anal
| Author | SHA1 | Date |
|---|---|---|
|
|
c0f58bb879 | |
|
|
a80d555264 | |
|
|
e1728ee0bc | |
|
|
cd5c1ba3e5 | |
|
|
27c5876c60 | |
|
|
1ff0b18080 | |
|
|
503ca607d1 | |
|
|
7c69302119 | |
|
|
0ccaf5b476 | |
|
|
3109fa4a72 | |
|
|
c5ddec0254 | |
|
|
0e621dc38a |
|
|
@ -941,9 +941,14 @@
|
||||||
"comment": "If set to true will send an email every day with all overdue tasks at a configured time."
|
"comment": "If set to true will send an email every day with all overdue tasks at a configured time."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "overdue_tasks_reminders_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": "today_tasks_reminders_time",
|
||||||
"default_value": "9:00",
|
"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",
|
"key": "default_project_id",
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@
|
||||||
"savedSuccess": "The settings were successfully updated.",
|
"savedSuccess": "The settings were successfully updated.",
|
||||||
"emailReminders": "Send me reminders for tasks via email",
|
"emailReminders": "Send me reminders for tasks via email",
|
||||||
"overdueReminders": "Send me a summary of my undone overdue tasks every day",
|
"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",
|
"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",
|
"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",
|
"playSoundWhenDone": "Play a sound when marking tasks as done",
|
||||||
|
|
@ -110,7 +111,7 @@
|
||||||
"defaultProject": "Default project",
|
"defaultProject": "Default project",
|
||||||
"defaultView": "Default view",
|
"defaultView": "Default view",
|
||||||
"timezone": "Time zone",
|
"timezone": "Time zone",
|
||||||
"overdueTasksRemindersTime": "Overdue tasks reminder email time",
|
"todayTasksRemindersTime": "Tasks reminder email time",
|
||||||
"filterUsedOnOverview": "Saved filter used on the overview page",
|
"filterUsedOnOverview": "Saved filter used on the overview page",
|
||||||
"minimumPriority": "Minimum visible task priority",
|
"minimumPriority": "Minimum visible task priority",
|
||||||
"dateDisplay": "Date display format",
|
"dateDisplay": "Date display format",
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ export interface IUserSettings extends IAbstract {
|
||||||
discoverableByName: boolean
|
discoverableByName: boolean
|
||||||
discoverableByEmail: boolean
|
discoverableByEmail: boolean
|
||||||
overdueTasksRemindersEnabled: boolean
|
overdueTasksRemindersEnabled: boolean
|
||||||
overdueTasksRemindersTime: undefined | string | Date
|
todayTasksRemindersTime: undefined | string | Date
|
||||||
|
todayTasksRemindersEnabled: boolean
|
||||||
defaultProjectId: undefined | IProject['id']
|
defaultProjectId: undefined | IProject['id']
|
||||||
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
||||||
timezone: string
|
timezone: string
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ export default class UserSettingsModel extends AbstractModel<IUserSettings> impl
|
||||||
discoverableByName = false
|
discoverableByName = false
|
||||||
discoverableByEmail = false
|
discoverableByEmail = false
|
||||||
overdueTasksRemindersEnabled = true
|
overdueTasksRemindersEnabled = true
|
||||||
overdueTasksRemindersTime = undefined
|
todayTasksRemindersTime = undefined
|
||||||
|
todayTasksRemindersEnabled = false
|
||||||
defaultProjectId = undefined
|
defaultProjectId = undefined
|
||||||
weekStart = 0 as IUserSettings['weekStart']
|
weekStart = 0 as IUserSettings['weekStart']
|
||||||
timezone = ''
|
timezone = ''
|
||||||
|
|
|
||||||
|
|
@ -123,26 +123,34 @@
|
||||||
{{ $t('user.settings.general.overdueReminders') }}
|
{{ $t('user.settings.general.overdueReminders') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="field">
|
||||||
v-if="settings.overdueTasksRemindersEnabled"
|
<label class="checkbox">
|
||||||
class="field"
|
<input
|
||||||
|
v-model="settings.todayTasksRemindersEnabled"
|
||||||
|
type="checkbox"
|
||||||
>
|
>
|
||||||
|
{{ $t('user.settings.general.todayReminders') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<template v-if="settings.overdueTasksRemindersEnabled || settings.todayTasksRemindersEnabled">
|
||||||
|
<div class="field">
|
||||||
<label
|
<label
|
||||||
for="overdueTasksReminderTime"
|
for="todayTasksReminderTime"
|
||||||
class="two-col"
|
class="two-col"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{{ $t('user.settings.general.overdueTasksRemindersTime') }}
|
{{ $t('user.settings.general.todayTasksRemindersTime') }}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
id="overdueTasksReminderTime"
|
id="todayTasksReminderTime"
|
||||||
v-model="settings.overdueTasksRemindersTime"
|
v-model="settings.todayTasksRemindersTime"
|
||||||
class="input"
|
class="input"
|
||||||
type="time"
|
type="time"
|
||||||
@keyup.enter="updateSettings"
|
@keyup.enter="updateSettings"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ test.describe('Home Page Task Overview', () => {
|
||||||
|
|
||||||
await updateUserSettings(apiContext, token, {
|
await updateUserSettings(apiContext, token, {
|
||||||
default_project_id: project.id,
|
default_project_id: project.id,
|
||||||
overdue_tasks_reminders_time: '9:00',
|
today_tasks_reminders_time: '9:00',
|
||||||
})
|
})
|
||||||
|
|
||||||
const newTaskTitle = 'New Task'
|
const newTaskTitle = 'New Task'
|
||||||
|
|
|
||||||
|
|
@ -208,11 +208,12 @@ const (
|
||||||
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
|
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
|
||||||
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
|
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
|
||||||
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
|
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
|
||||||
|
DefaultSettingsTodayTasksRemindersEnabled Key = `defaultsettings.today_tasks_reminders_enabled`
|
||||||
DefaultSettingsDefaultProjectID Key = `defaultsettings.default_project_id`
|
DefaultSettingsDefaultProjectID Key = `defaultsettings.default_project_id`
|
||||||
DefaultSettingsWeekStart Key = `defaultsettings.week_start`
|
DefaultSettingsWeekStart Key = `defaultsettings.week_start`
|
||||||
DefaultSettingsLanguage Key = `defaultsettings.language`
|
DefaultSettingsLanguage Key = `defaultsettings.language`
|
||||||
DefaultSettingsTimezone Key = `defaultsettings.timezone`
|
DefaultSettingsTimezone Key = `defaultsettings.timezone`
|
||||||
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
|
DefaultSettingsTodayTaskRemindersTime Key = `defaultsettings.today_tasks_reminders_time`
|
||||||
|
|
||||||
WebhooksEnabled Key = `webhooks.enabled`
|
WebhooksEnabled Key = `webhooks.enabled`
|
||||||
WebhooksTimeoutSeconds Key = `webhooks.timeoutseconds`
|
WebhooksTimeoutSeconds Key = `webhooks.timeoutseconds`
|
||||||
|
|
@ -473,7 +474,7 @@ func InitDefaultConfig() {
|
||||||
// Settings
|
// Settings
|
||||||
DefaultSettingsAvatarProvider.setDefault("initials")
|
DefaultSettingsAvatarProvider.setDefault("initials")
|
||||||
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
|
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
|
||||||
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
|
DefaultSettingsTodayTaskRemindersTime.setDefault("9:00")
|
||||||
// Webhook
|
// Webhook
|
||||||
WebhooksEnabled.setDefault(true)
|
WebhooksEnabled.setDefault(true)
|
||||||
WebhooksTimeoutSeconds.setDefault(30)
|
WebhooksTimeoutSeconds.setDefault(30)
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,3 @@
|
||||||
task_id: 2
|
task_id: 2
|
||||||
reminder: 2019-06-01 12:00:00
|
reminder: 2019-06-01 12:00:00
|
||||||
created: 2018-12-01 01:12:04
|
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
|
due_date: 2023-03-01 15:00:00
|
||||||
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
|
||||||
|
|
||||||
|
# Task due later today for reminders
|
||||||
- id: 47
|
- id: 47
|
||||||
title: 'task #47 with reminders outside window'
|
title: 'task #47 due today'
|
||||||
done: false
|
done: false
|
||||||
created_by_id: 1
|
created_by_id: 1
|
||||||
project_id: 1
|
project_id: 1
|
||||||
index: 32
|
index: 32
|
||||||
|
due_date: 2018-12-01 23:00:00
|
||||||
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: 48
|
- id: 48
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
updated: 2018-12-02 15:13:12
|
updated: 2018-12-02 15:13:12
|
||||||
created: 2018-12-01 15:13:12
|
created: 2018-12-01 15:13:12
|
||||||
export_file_id: 1
|
export_file_id: 1
|
||||||
|
today_tasks_reminders_enabled: true
|
||||||
-
|
-
|
||||||
id: 2
|
id: 2
|
||||||
username: 'user2'
|
username: 'user2'
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,10 @@
|
||||||
"task": {
|
"task": {
|
||||||
"reminder": {
|
"reminder": {
|
||||||
"subject": "Reminder for \"%[1]s\" (%[2]s)",
|
"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": {
|
"comment": {
|
||||||
"subject": "Re: %[1]s (%[2]s)",
|
"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",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
DefaultProjectID: 4,
|
DefaultProjectID: 4,
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,8 @@ func TestLabel_ReadAll(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersEnabled: true,
|
||||||
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
ExportFileID: 1,
|
ExportFileID: 1,
|
||||||
|
|
@ -67,7 +68,7 @@ func TestLabel_ReadAll(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
DefaultProjectID: 4,
|
DefaultProjectID: 4,
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
|
|
@ -177,7 +178,8 @@ func TestLabel_ReadOne(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersEnabled: true,
|
||||||
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
ExportFileID: 1,
|
ExportFileID: 1,
|
||||||
|
|
@ -240,7 +242,7 @@ func TestLabel_ReadOne(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
DefaultProjectID: 4,
|
DefaultProjectID: 4,
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
|
|
|
||||||
|
|
@ -295,83 +295,72 @@ func getOverdueSinceString(until time.Duration, language string) (overdueSince s
|
||||||
}
|
}
|
||||||
|
|
||||||
// UndoneTaskOverdueNotification represents a UndoneTaskOverdueNotification notification
|
// UndoneTaskOverdueNotification represents a UndoneTaskOverdueNotification notification
|
||||||
type UndoneTaskOverdueNotification struct {
|
type DailyTasksReminderNotification struct {
|
||||||
User *user.User
|
User *user.User
|
||||||
Task *Task
|
OverdueTasks map[int64]*Task
|
||||||
Project *Project
|
DueToday map[int64]*Task
|
||||||
}
|
|
||||||
|
|
||||||
// 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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
Projects map[int64]*Project
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToMail returns the mail notification for UndoneTasksOverdueNotification
|
// ToMail returns the mail notification for DailyTasksReminderNotification
|
||||||
func (n *UndoneTasksOverdueNotification) ToMail(lang string) *notifications.Mail {
|
func (n *DailyTasksReminderNotification) ToMail(lang string) *notifications.Mail {
|
||||||
|
|
||||||
sortedTasks := make([]*Task, 0, len(n.Tasks))
|
sortedOverdue := make([]*Task, 0, len(n.OverdueTasks))
|
||||||
for _, task := range n.Tasks {
|
for _, task := range n.OverdueTasks {
|
||||||
sortedTasks = append(sortedTasks, task)
|
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 {
|
sortedToday := make([]*Task, 0, len(n.DueToday))
|
||||||
return sortedTasks[i].DueDate.Before(sortedTasks[j].DueDate)
|
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 := ""
|
overdueLine := ""
|
||||||
for _, task := range sortedTasks {
|
for _, task := range sortedOverdue {
|
||||||
until := time.Until(task.DueDate).Round(1*time.Hour) * -1
|
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"
|
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 := ""
|
||||||
IncludeLinkToSettings(lang).
|
for _, task := range sortedToday {
|
||||||
Subject(i18n.T(lang, "notifications.task.overdue.multiple_subject")).
|
todayLine += `* [` + task.Title + `](` + config.ServicePublicURL.GetString() + "tasks/" + strconv.FormatInt(task.ID, 10) + `) (` + n.Projects[task.ProjectID].Title + ")\n"
|
||||||
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()).
|
|
||||||
Line(i18n.T(lang, "notifications.common.have_nice_day"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDB returns the UndoneTasksOverdueNotification notification in a format which can be saved in the db
|
subject := i18n.T(lang, "notifications.task.overdue.multiple_subject")
|
||||||
func (n *UndoneTasksOverdueNotification) ToDB() interface{} {
|
if len(n.OverdueTasks) == 0 {
|
||||||
return nil
|
subject = i18n.T(lang, "notifications.task.reminder.only_due_today_subject")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m := notifications.NewMail().
|
||||||
|
IncludeLinkToSettings(lang).
|
||||||
|
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 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
|
// Name returns the name of the notification
|
||||||
func (n *UndoneTasksOverdueNotification) Name() string {
|
func (n *DailyTasksReminderNotification) Name() string { return "task.daily.reminder" }
|
||||||
return "task.undone.overdue"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserMentionedInTaskNotification represents a UserMentionedInTaskNotification notification
|
// UserMentionedInTaskNotification represents a UserMentionedInTaskNotification notification
|
||||||
type UserMentionedInTaskNotification struct {
|
type UserMentionedInTaskNotification struct {
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,8 @@ func TestProjectUser_ReadAll(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersEnabled: true,
|
||||||
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
ExportFileID: 1,
|
ExportFileID: 1,
|
||||||
|
|
@ -168,7 +169,7 @@ func TestProjectUser_ReadAll(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
DefaultProjectID: 4,
|
DefaultProjectID: 4,
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersEnabled: true,
|
||||||
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
ExportFileID: 1,
|
ExportFileID: 1,
|
||||||
|
|
@ -55,7 +56,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersEnabled: false,
|
||||||
|
TodayTasksRemindersTime: "09:00",
|
||||||
DefaultProjectID: 4,
|
DefaultProjectID: 4,
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
|
|
@ -67,7 +69,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersEnabled: false,
|
||||||
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -607,29 +610,16 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
task47 := &Task{
|
task47 := &Task{
|
||||||
ID: 47,
|
ID: 47,
|
||||||
Title: "task #47 with reminders outside window",
|
Title: "task #47 due today",
|
||||||
Identifier: "test1-32",
|
Identifier: "test1-32",
|
||||||
Index: 32,
|
Index: 32,
|
||||||
CreatedByID: 1,
|
CreatedByID: 1,
|
||||||
CreatedBy: user1,
|
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),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ProjectID: 1,
|
ProjectID: 1,
|
||||||
|
DueDate: time.Date(2018, 12, 1, 23, 0, 0, 0, loc),
|
||||||
RelatedTasks: map[RelationKind][]*Task{},
|
RelatedTasks: map[RelationKind][]*Task{},
|
||||||
Created: time.Unix(1543626724, 0).In(loc),
|
Created: time.Date(2018, 12, 1, 1, 12, 4, 0, loc),
|
||||||
Updated: time.Unix(1543626724, 0).In(loc),
|
Updated: time.Date(2018, 12, 1, 1, 12, 4, 0, loc),
|
||||||
}
|
}
|
||||||
task48 := &Task{
|
task48 := &Task{
|
||||||
ID: 48,
|
ID: 48,
|
||||||
|
|
@ -1002,7 +992,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
task32, // has nil dates
|
task32, // has nil dates
|
||||||
task33, // has nil dates
|
task33, // has nil dates
|
||||||
task39, // has nil dates
|
task39, // has nil dates
|
||||||
task47, // has nil dates
|
task47,
|
||||||
task48, // has nil dates
|
task48, // has nil dates
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
|
@ -1035,7 +1025,6 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
task30,
|
task30,
|
||||||
task31,
|
task31,
|
||||||
task33,
|
task33,
|
||||||
task47,
|
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
|
@ -1055,7 +1044,6 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
task30,
|
task30,
|
||||||
task31,
|
task31,
|
||||||
task33,
|
task33,
|
||||||
task47,
|
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
|
@ -1071,18 +1059,6 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantErr: false,
|
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",
|
name: "filtered reminder dates narrow window excludes all",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
|
|
@ -1100,7 +1076,6 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
args: defaultArgs,
|
args: defaultArgs,
|
||||||
want: []*Task{
|
want: []*Task{
|
||||||
task2, // has reminder at 2019-06-01 (> 2019-01-01)
|
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)
|
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
|
@ -1601,10 +1576,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
// The only tasks with a due date
|
// The only tasks with a due date
|
||||||
task6,
|
task6,
|
||||||
task5,
|
task5,
|
||||||
|
task47,
|
||||||
task28,
|
task28,
|
||||||
// The other ones don't have a due date
|
// The other ones don't have a due date
|
||||||
task48,
|
task48,
|
||||||
task47,
|
|
||||||
task39,
|
task39,
|
||||||
task33,
|
task33,
|
||||||
task32,
|
task32,
|
||||||
|
|
@ -1652,6 +1627,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
task7,
|
task7,
|
||||||
task6,
|
task6,
|
||||||
task5,
|
task5,
|
||||||
|
task47,
|
||||||
task28,
|
task28,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1667,6 +1643,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: []*Task{
|
want: []*Task{
|
||||||
task28,
|
task28,
|
||||||
|
task47,
|
||||||
task5,
|
task5,
|
||||||
task6,
|
task6,
|
||||||
task7,
|
task7,
|
||||||
|
|
@ -1687,6 +1664,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
want: []*Task{
|
want: []*Task{
|
||||||
task6,
|
task6,
|
||||||
task5,
|
task5,
|
||||||
|
task47,
|
||||||
task28,
|
task28,
|
||||||
task7,
|
task7,
|
||||||
task8,
|
task8,
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ func getTaskUsersForTasks(s *xorm.Session, taskIDs []int64, cond builder.Cond) (
|
||||||
|
|
||||||
creators := []*userWithTask{}
|
creators := []*userWithTask{}
|
||||||
err = s.Table("tasks").
|
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").
|
Join("INNER", "users", "tasks.created_by_id = users.id").
|
||||||
Where(builder.And(conditions...)).
|
Where(builder.And(conditions...)).
|
||||||
Find(&creators)
|
Find(&creators)
|
||||||
|
|
@ -182,7 +182,7 @@ func getTaskUsersForTasks(s *xorm.Session, taskIDs []int64, cond builder.Cond) (
|
||||||
|
|
||||||
assignees := []*TaskAssigneeWithUser{}
|
assignees := []*TaskAssigneeWithUser{}
|
||||||
err = s.Table("task_assignees").
|
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").
|
Join("INNER", "users", "task_assignees.user_id = users.id").
|
||||||
Where(builder.And(assigneeConds...)).
|
Where(builder.And(assigneeConds...)).
|
||||||
Find(&assignees)
|
Find(&assignees)
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,13 @@ import (
|
||||||
"xorm.io/xorm"
|
"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)
|
now = utils.GetTimeWithoutSeconds(now)
|
||||||
nextMinute := now.Add(1 * time.Minute)
|
nextMinute := now.Add(1 * time.Minute)
|
||||||
|
|
||||||
var tasks []*Task
|
var tasks []*Task
|
||||||
err = s.
|
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").
|
Join("LEFT", "projects", "projects.id = tasks.project_id").
|
||||||
And("done = false").
|
And("done = false").
|
||||||
Find(&tasks)
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
overdueMailTime := time.Date(now.Year(), now.Month(), now.Day(), tm.Hour(), tm.Minute(), 0, 0, tz)
|
reminderTime := time.Date(now.Year(), now.Month(), now.Day(), tm.Hour(), tm.Minute(), 0, 0, tz)
|
||||||
isTimeForReminder := overdueMailTime.After(now) || overdueMailTime.Equal(now.In(tz))
|
isTimeForReminder := reminderTime.After(now) || reminderTime.Equal(now.In(tz))
|
||||||
wasTimeForReminder := overdueMailTime.Before(nextMinute)
|
wasTimeForReminder := reminderTime.Before(nextMinute)
|
||||||
taskIsOverdueInUserTimezone := overdueMailTime.After(t.Task.DueDate.In(tz))
|
taskDue := t.Task.DueDate.In(tz)
|
||||||
if isTimeForReminder && wasTimeForReminder && taskIsOverdueInUserTimezone {
|
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, tz)
|
||||||
|
if isTimeForReminder && wasTimeForReminder {
|
||||||
_, exists := uts[t.User.ID]
|
_, exists := uts[t.User.ID]
|
||||||
if !exists {
|
if !exists {
|
||||||
uts[t.User.ID] = &userWithTasks{
|
uts[t.User.ID] = &userWithTasks{
|
||||||
user: t.User,
|
user: t.User,
|
||||||
tasks: make(map[int64]*Task),
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,10 +115,11 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time, cond builder.Cond) (u
|
||||||
|
|
||||||
type userWithTasks struct {
|
type userWithTasks struct {
|
||||||
user *user.User
|
user *user.User
|
||||||
tasks map[int64]*Task
|
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() {
|
func RegisterOverdueReminderCron() {
|
||||||
webhookEnabled := config.WebhooksEnabled.GetBool()
|
webhookEnabled := config.WebhooksEnabled.GetBool()
|
||||||
emailEnabled := config.ServiceEnableEmailReminders.GetBool() && config.MailerEnabled.GetBool()
|
emailEnabled := config.ServiceEnableEmailReminders.GetBool() && config.MailerEnabled.GetBool()
|
||||||
|
|
@ -130,12 +140,15 @@ func RegisterOverdueReminderCron() {
|
||||||
|
|
||||||
var cond builder.Cond
|
var cond builder.Cond
|
||||||
if emailEnabled && !webhookEnabled {
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,80 +156,92 @@ func RegisterOverdueReminderCron() {
|
||||||
return
|
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{}
|
taskIDs := []int64{}
|
||||||
for _, ut := range uts {
|
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)
|
taskIDs = append(taskIDs, t.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
projects, err := GetProjectsMapSimpleByTaskIDs(s, taskIDs)
|
projects, err := GetProjectsMapSimpleByTaskIDs(s, taskIDs)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ut := range uts {
|
// Dispatch webhook events, deduplicated by task ID across all users
|
||||||
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],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if webhookEnabled {
|
||||||
// Per-task events
|
dispatchedTasks := make(map[int64]bool)
|
||||||
for _, t := range ut.tasks {
|
for _, ut := range uts {
|
||||||
|
// Per-task overdue events
|
||||||
|
for _, t := range ut.overdue {
|
||||||
|
if dispatchedTasks[t.ID] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dispatchedTasks[t.ID] = true
|
||||||
err = events.Dispatch(&TaskOverdueEvent{
|
err = events.Dispatch(&TaskOverdueEvent{
|
||||||
Task: t,
|
Task: t,
|
||||||
User: ut.user,
|
User: ut.user,
|
||||||
Project: projects[t.ProjectID],
|
Project: projects[t.ProjectID],
|
||||||
})
|
})
|
||||||
if err != nil {
|
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
|
// Batch event
|
||||||
|
if len(ut.overdue) > 0 {
|
||||||
err = events.Dispatch(&TasksOverdueEvent{
|
err = events.Dispatch(&TasksOverdueEvent{
|
||||||
Tasks: mapToSlice(ut.tasks),
|
Tasks: mapToSlice(ut.overdue),
|
||||||
User: ut.user,
|
User: ut.user,
|
||||||
Projects: projects,
|
Projects: projects,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("[Undone Overdue Tasks Reminder] Could not dispatch batch overdue event for user %d: %s", ut.user.ID, err)
|
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 {
|
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 {
|
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"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetUndoneOverDueTasks(t *testing.T) {
|
func TestGetTasksForDailyReminder(t *testing.T) {
|
||||||
t.Run("no undone tasks", func(t *testing.T) {
|
t.Run("no undone tasks", func(t *testing.T) {
|
||||||
db.LoadAndAssertFixtures(t)
|
db.LoadAndAssertFixtures(t)
|
||||||
s := db.NewSession()
|
s := db.NewSession()
|
||||||
|
|
@ -35,34 +35,61 @@ func TestGetUndoneOverDueTasks(t *testing.T) {
|
||||||
|
|
||||||
now, err := time.Parse(time.RFC3339Nano, "2018-01-01T01:13:00Z")
|
now, err := time.Parse(time.RFC3339Nano, "2018-01-01T01:13:00Z")
|
||||||
require.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, tasks)
|
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)
|
db.LoadAndAssertFixtures(t)
|
||||||
s := db.NewSession()
|
s := db.NewSession()
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
now, err := time.Parse(time.RFC3339Nano, "2018-12-01T09:00:00Z")
|
now, err := time.Parse(time.RFC3339Nano, "2018-12-01T09:00:00Z")
|
||||||
require.NoError(t, err)
|
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.NoError(t, err)
|
||||||
require.Len(t, uts, 1)
|
assert.Len(t, uts, 1)
|
||||||
assert.Len(t, uts[1].tasks, 2)
|
assert.Len(t, uts[1].overdue, 2)
|
||||||
// The tasks don't always have the same order, so we only check their presence, not their position.
|
assert.Len(t, uts[1].dueToday, 1)
|
||||||
var task5Present bool
|
_, ok := uts[1].dueToday[47]
|
||||||
var task6Present bool
|
assert.True(t, ok)
|
||||||
for _, t := range uts[1].tasks {
|
|
||||||
if t.ID == 5 {
|
// Disable today reminders and ensure the task is not included
|
||||||
task5Present = true
|
_, err = s.Where("id = ?", 1).Cols("today_tasks_reminders_enabled").Update(&user.User{TodayTasksRemindersEnabled: false})
|
||||||
}
|
require.NoError(t, err)
|
||||||
if t.ID == 6 {
|
uts, err = getTasksForDailyReminder(s, now, builder.Or(
|
||||||
task6Present = true
|
builder.Eq{"users.overdue_tasks_reminders_enabled": true},
|
||||||
}
|
builder.Eq{"users.today_tasks_reminders_enabled": true},
|
||||||
}
|
))
|
||||||
assert.Truef(t, task5Present, "expected task 5 to be present but was not")
|
require.NoError(t, err)
|
||||||
assert.Truef(t, task6Present, "expected task 6 to be present but was not")
|
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) {
|
t.Run("done overdue", func(t *testing.T) {
|
||||||
db.LoadAndAssertFixtures(t)
|
db.LoadAndAssertFixtures(t)
|
||||||
|
|
@ -71,7 +98,10 @@ func TestGetUndoneOverDueTasks(t *testing.T) {
|
||||||
|
|
||||||
now, err := time.Parse(time.RFC3339Nano, "2018-11-01T01:13:00Z")
|
now, err := time.Parse(time.RFC3339Nano, "2018-11-01T01:13:00Z")
|
||||||
require.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, tasks)
|
assert.Empty(t, tasks)
|
||||||
})
|
})
|
||||||
|
|
@ -32,7 +32,8 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersEnabled: true,
|
||||||
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
ExportFileID: 1,
|
ExportFileID: 1,
|
||||||
|
|
@ -44,7 +45,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
DefaultProjectID: 4,
|
DefaultProjectID: 4,
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
|
|
@ -56,7 +57,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
DefaultProjectID: 4,
|
DefaultProjectID: 4,
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
|
|
@ -69,7 +70,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +82,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +93,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +105,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
DiscoverableByEmail: true,
|
DiscoverableByEmail: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +116,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +127,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +138,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +150,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +163,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
DiscoverableByName: true,
|
DiscoverableByName: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +174,7 @@ func TestListUsersFromProject(t *testing.T) {
|
||||||
Issuer: "local",
|
Issuer: "local",
|
||||||
EmailRemindersEnabled: true,
|
EmailRemindersEnabled: true,
|
||||||
OverdueTasksRemindersEnabled: true,
|
OverdueTasksRemindersEnabled: true,
|
||||||
OverdueTasksRemindersTime: "09:00",
|
TodayTasksRemindersTime: "09:00",
|
||||||
Created: testCreatedTime,
|
Created: testCreatedTime,
|
||||||
Updated: testUpdatedTime,
|
Updated: testUpdatedTime,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,10 @@ type UserSettings struct {
|
||||||
DiscoverableByEmail bool `json:"discoverable_by_email"`
|
DiscoverableByEmail bool `json:"discoverable_by_email"`
|
||||||
// If enabled, the user will get an email for their overdue tasks each morning.
|
// If enabled, the user will get an email for their overdue tasks each morning.
|
||||||
OverdueTasksRemindersEnabled bool `json:"overdue_tasks_reminders_enabled"`
|
OverdueTasksRemindersEnabled bool `json:"overdue_tasks_reminders_enabled"`
|
||||||
// The time when the daily summary of overdue tasks will be sent via email.
|
// If enabled, includes tasks due later today in the overdue reminder email.
|
||||||
OverdueTasksRemindersTime string `json:"overdue_tasks_reminders_time" valid:"time,required"`
|
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
|
// If a task is created without a specified project this value should be used. Applies
|
||||||
// to tasks made directly in API and from clients.
|
// to tasks made directly in API and from clients.
|
||||||
DefaultProjectID int64 `json:"default_project_id"`
|
DefaultProjectID int64 `json:"default_project_id"`
|
||||||
|
|
@ -209,11 +211,12 @@ func UpdateGeneralUserSettings(c *echo.Context) error {
|
||||||
user.DiscoverableByEmail = us.DiscoverableByEmail
|
user.DiscoverableByEmail = us.DiscoverableByEmail
|
||||||
user.DiscoverableByName = us.DiscoverableByName
|
user.DiscoverableByName = us.DiscoverableByName
|
||||||
user.OverdueTasksRemindersEnabled = us.OverdueTasksRemindersEnabled
|
user.OverdueTasksRemindersEnabled = us.OverdueTasksRemindersEnabled
|
||||||
|
user.TodayTasksRemindersEnabled = us.TodayTasksRemindersEnabled
|
||||||
user.DefaultProjectID = us.DefaultProjectID
|
user.DefaultProjectID = us.DefaultProjectID
|
||||||
user.WeekStart = us.WeekStart
|
user.WeekStart = us.WeekStart
|
||||||
user.Language = us.Language
|
user.Language = us.Language
|
||||||
user.Timezone = us.Timezone
|
user.Timezone = us.Timezone
|
||||||
user.OverdueTasksRemindersTime = us.OverdueTasksRemindersTime
|
user.TodayTasksRemindersTime = us.TodayTasksRemindersTime
|
||||||
user.FrontendSettings = us.FrontendSettings
|
user.FrontendSettings = us.FrontendSettings
|
||||||
|
|
||||||
_, err = user2.UpdateUser(s, user, true)
|
_, err = user2.UpdateUser(s, user, true)
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ func UserShow(c *echo.Context) error {
|
||||||
WeekStart: u.WeekStart,
|
WeekStart: u.WeekStart,
|
||||||
Language: u.Language,
|
Language: u.Language,
|
||||||
Timezone: u.Timezone,
|
Timezone: u.Timezone,
|
||||||
OverdueTasksRemindersTime: u.OverdueTasksRemindersTime,
|
TodayTasksRemindersTime: u.TodayTasksRemindersTime,
|
||||||
FrontendSettings: u.FrontendSettings,
|
FrontendSettings: u.FrontendSettings,
|
||||||
ExtraSettingsLinks: u.ExtraSettingsLinks,
|
ExtraSettingsLinks: u.ExtraSettingsLinks,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,8 @@ type User struct {
|
||||||
DiscoverableByName bool `xorm:"bool default false index" json:"-"`
|
DiscoverableByName bool `xorm:"bool default false index" json:"-"`
|
||||||
DiscoverableByEmail bool `xorm:"bool default false index" json:"-"`
|
DiscoverableByEmail bool `xorm:"bool default false index" json:"-"`
|
||||||
OverdueTasksRemindersEnabled bool `xorm:"bool default true 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:"-"`
|
DefaultProjectID int64 `xorm:"bigint null index" json:"-"`
|
||||||
WeekStart int `xorm:"null" json:"-"`
|
WeekStart int `xorm:"null" json:"-"`
|
||||||
Language string `xorm:"varchar(50) null" json:"-" valid:"language"`
|
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 = ""
|
userOut.Email = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if userOut.OverdueTasksRemindersTime == "" {
|
if userOut.TodayTasksRemindersTime == "" {
|
||||||
userOut.OverdueTasksRemindersTime = "9:00"
|
userOut.TodayTasksRemindersTime = "9:00"
|
||||||
}
|
}
|
||||||
|
|
||||||
if userOut.Status == StatusDisabled {
|
if userOut.Status == StatusDisabled {
|
||||||
|
|
@ -629,11 +630,12 @@ func UpdateUser(s *xorm.Session, user *User, forceOverride bool) (updatedUser *U
|
||||||
"discoverable_by_name",
|
"discoverable_by_name",
|
||||||
"discoverable_by_email",
|
"discoverable_by_email",
|
||||||
"overdue_tasks_reminders_enabled",
|
"overdue_tasks_reminders_enabled",
|
||||||
|
"today_tasks_reminders_enabled",
|
||||||
"default_project_id",
|
"default_project_id",
|
||||||
"week_start",
|
"week_start",
|
||||||
"language",
|
"language",
|
||||||
"timezone",
|
"timezone",
|
||||||
"overdue_tasks_reminders_time",
|
"today_tasks_reminders_time",
|
||||||
"frontend_settings",
|
"frontend_settings",
|
||||||
"extra_settings_links",
|
"extra_settings_links",
|
||||||
).
|
).
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
|
||||||
user.DiscoverableByName = config.DefaultSettingsDiscoverableByName.GetBool()
|
user.DiscoverableByName = config.DefaultSettingsDiscoverableByName.GetBool()
|
||||||
user.DiscoverableByEmail = config.DefaultSettingsDiscoverableByEmail.GetBool()
|
user.DiscoverableByEmail = config.DefaultSettingsDiscoverableByEmail.GetBool()
|
||||||
user.OverdueTasksRemindersEnabled = config.DefaultSettingsOverdueTaskRemindersEnabled.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.DefaultProjectID = config.DefaultSettingsDefaultProjectID.GetInt64()
|
||||||
user.WeekStart = config.DefaultSettingsWeekStart.GetInt()
|
user.WeekStart = config.DefaultSettingsWeekStart.GetInt()
|
||||||
user.Timezone = config.DefaultSettingsTimezone.GetString()
|
user.Timezone = config.DefaultSettingsTimezone.GetString()
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ func TestTaskCollection(t *testing.T) {
|
||||||
t.Run("by priority", func(t *testing.T) {
|
t.Run("by priority", func(t *testing.T) {
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
|
||||||
require.NoError(t, err)
|
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) {
|
t.Run("by priority desc", func(t *testing.T) {
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
|
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) {
|
t.Run("by priority asc", func(t *testing.T) {
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
|
||||||
require.NoError(t, err)
|
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
|
// should equal duedate asc
|
||||||
t.Run("by due_date", func(t *testing.T) {
|
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) {
|
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)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||||
require.NoError(t, err)
|
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
|
// Due date without unix suffix
|
||||||
t.Run("by duedate asc without suffix", func(t *testing.T) {
|
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) {
|
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)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||||
require.NoError(t, err)
|
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) {
|
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)
|
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) {
|
t.Run("by priority", func(t *testing.T) {
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
|
||||||
require.NoError(t, err)
|
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) {
|
t.Run("by priority desc", func(t *testing.T) {
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
|
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) {
|
t.Run("by priority asc", func(t *testing.T) {
|
||||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
|
||||||
require.NoError(t, err)
|
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
|
// should equal duedate asc
|
||||||
t.Run("by due_date", func(t *testing.T) {
|
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) {
|
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)
|
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
|
||||||
require.NoError(t, err)
|
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) {
|
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)
|
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),
|
"CreateNewProjectForUser": reflect.ValueOf(models.CreateNewProjectForUser),
|
||||||
"CreateProject": reflect.ValueOf(models.CreateProject),
|
"CreateProject": reflect.ValueOf(models.CreateProject),
|
||||||
"CreateSession": reflect.ValueOf(models.CreateSession),
|
"CreateSession": reflect.ValueOf(models.CreateSession),
|
||||||
|
"DailyTasksReminderNotification": reflect.ValueOf((*models.DailyTasksReminderNotification)(nil)),
|
||||||
"DeleteAllUserSessions": reflect.ValueOf(models.DeleteAllUserSessions),
|
"DeleteAllUserSessions": reflect.ValueOf(models.DeleteAllUserSessions),
|
||||||
"DeleteOrphanedTaskPositions": reflect.ValueOf(models.DeleteOrphanedTaskPositions),
|
"DeleteOrphanedTaskPositions": reflect.ValueOf(models.DeleteOrphanedTaskPositions),
|
||||||
"DeleteUser": reflect.ValueOf(models.DeleteUser),
|
"DeleteUser": reflect.ValueOf(models.DeleteUser),
|
||||||
|
|
@ -501,8 +502,6 @@ func init() {
|
||||||
"TeamProject": reflect.ValueOf((*models.TeamProject)(nil)),
|
"TeamProject": reflect.ValueOf((*models.TeamProject)(nil)),
|
||||||
"TeamUser": reflect.ValueOf((*models.TeamUser)(nil)),
|
"TeamUser": reflect.ValueOf((*models.TeamUser)(nil)),
|
||||||
"TeamWithPermission": reflect.ValueOf((*models.TeamWithPermission)(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)),
|
"UnsplashPhoto": reflect.ValueOf((*models.UnsplashPhoto)(nil)),
|
||||||
"UpdateTaskInSavedFilterViews": reflect.ValueOf((*models.UpdateTaskInSavedFilterViews)(nil)),
|
"UpdateTaskInSavedFilterViews": reflect.ValueOf((*models.UpdateTaskInSavedFilterViews)(nil)),
|
||||||
"UserDataExportRequestedEvent": reflect.ValueOf((*models.UserDataExportRequestedEvent)(nil)),
|
"UserDataExportRequestedEvent": reflect.ValueOf((*models.UserDataExportRequestedEvent)(nil)),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue