Get tasks between a date range (#41)

This commit is contained in:
konrad 2018-12-22 18:06:14 +00:00 committed by Gitea
parent 784b890f70
commit 5a93475be9
10 changed files with 356 additions and 52 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
.idea/ .idea/
.idea/*/* .idea/*/*
.idea/httpRequests
config.yml config.yml
config.yaml config.yaml
*.db *.db

View file

@ -255,6 +255,9 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
### Later/Nice to have ### Later/Nice to have
* [x] Deps nach mod umziehen * [x] Deps nach mod umziehen
* [x] Start/Enddatum für Tasks
* [x] Timeline/Calendar view -> Dazu tasks die in einem Bestimmten Bereich due sind, macht dann das Frontend
* [x] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat"
* [ ] Websockets * [ ] Websockets
* Nur lesend? (-> Updates wie bisher) * Nur lesend? (-> Updates wie bisher)
* sollen den geupdaten Kram an alle anderen user schicken * sollen den geupdaten Kram an alle anderen user schicken
@ -268,15 +271,12 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
* [ ] Mgl. zum Accountlöschen haben (so richtig krass mit emailverifiezierung und dass alle Privaten Listen gelöscht werden und man alle geteilten entweder wem übertragen muss oder auf privat stellen) * [ ] Mgl. zum Accountlöschen haben (so richtig krass mit emailverifiezierung und dass alle Privaten Listen gelöscht werden und man alle geteilten entweder wem übertragen muss oder auf privat stellen)
* [ ] IMAP-Integration -> Man schickt eine email an Vikunja und es macht daraus dann nen task -> Achtung missbrauchsmöglichkeiten * [ ] IMAP-Integration -> Man schickt eine email an Vikunja und es macht daraus dann nen task -> Achtung missbrauchsmöglichkeiten
* [ ] In und Out webhooks, mit Templates vom Payload * [ ] In und Out webhooks, mit Templates vom Payload
* [x] Start/Enddatum für Tasks
* [ ] Timeline/Calendar view -> Dazu tasks die in einem Bestimmten Bereich due sind, macht dann das Frontend
* [ ] "Smart Lists", Listen nach bestimmten Kriterien gefiltert -> nur UI? * [ ] "Smart Lists", Listen nach bestimmten Kriterien gefiltert -> nur UI?
* [ ] "Performance-Statistik" -> Wie viele Tasks man in bestimmten Zeiträumen so geschafft hat etc * [ ] "Performance-Statistik" -> Wie viele Tasks man in bestimmten Zeiträumen so geschafft hat etc
* [ ] Activity Feed, so à la "der und der hat das und das gemacht etc" * [ ] Activity Feed, so à la "der und der hat das und das gemacht etc"
* [ ] Assignees * [ ] Assignees
* [ ] Attachments * [ ] Attachments
* [ ] Labels * [ ] Labels
* [ ] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat"
* [ ] Task-Templates innerhalb namespaces und Listen (-> Mehrere, die auswählbar sind) * [ ] Task-Templates innerhalb namespaces und Listen (-> Mehrere, die auswählbar sind)
* [ ] Bulk-edit -> Transactions * [ ] Bulk-edit -> Transactions
* [ ] Ein Task muss von mehreren Assignees abgehakt werden bis er als done markiert wird * [ ] Ein Task muss von mehreren Assignees abgehakt werden bis er als done markiert wird

View file

@ -118,6 +118,12 @@ Authorization: Bearer {{auth_token}}
### ###
# Get all pending tasks in a range
GET http://localhost:8080/api/v1/tasks/all/dueadateasc/1546784000/1548784000
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks in caldav # Get all pending tasks in caldav
GET http://localhost:8080/api/v1/tasks/caldav GET http://localhost:8080/api/v1/tasks/caldav
#Authorization: Bearer {{auth_token}} #Authorization: Bearer {{auth_token}}
@ -125,10 +131,10 @@ GET http://localhost:8080/api/v1/tasks/caldav
### ###
# Update a task # Update a task
POST http://localhost:8080/api/v1/tasks/32 POST http://localhost:8080/api/v1/tasks/3491
Authorization: Bearer {{auth_token}} Authorization: Bearer {{auth_token}}
Content-Type: application/json Content-Type: application/json
{"done":true} {"startDate":1546804000, "endDate": 1546805000}
### ###

View file

@ -39,3 +39,25 @@
created: 1543626724 created: 1543626724
updated: 1543626724 updated: 1543626724
due_date_unix: 1543616724 due_date_unix: 1543616724
- id: 7
text: 'task #7 with start date'
created_by_id: 1
list_id: 1
created: 1543626724
updated: 1543626724
start_date_unix: 1544600000
- id: 8
text: 'task #8 with end date'
created_by_id: 1
list_id: 1
created: 1543626724
updated: 1543626724
end_date_unix: 1544700000
- id: 9
text: 'task #9 with start and end date'
created_by_id: 1
list_id: 1
created: 1543626724
updated: 1543626724
start_date_unix: 1544600000
end_date_unix: 1544700000

View file

@ -6,7 +6,10 @@
package models package models
import "code.vikunja.io/web" import (
"code.vikunja.io/web"
"time"
)
// SortBy declares constants to sort // SortBy declares constants to sort
type SortBy int type SortBy int
@ -73,11 +76,11 @@ func (lt *ListTask) ReadAll(search string, a web.Auth, page int) (interface{}, e
sortby = SortTasksByUnsorted sortby = SortTasksByUnsorted
} }
return GetTasksByUser(search, u, page, sortby) return GetTasksByUser(search, u, page, sortby, time.Unix(lt.StartDateSortUnix, 0), time.Unix(lt.EndDateSortUnix, 0))
} }
//GetTasksByUser returns all tasks for a user //GetTasksByUser returns all tasks for a user
func GetTasksByUser(search string, u *User, page int, sortby SortBy) (tasks []*ListTask, err error) { func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate time.Time, endDate time.Time) (tasks []*ListTask, err error) {
// Get all lists // Get all lists
lists, err := getRawListsForUser("", u, page) lists, err := getRawListsForUser("", u, page)
if err != nil { if err != nil {
@ -103,8 +106,36 @@ func GetTasksByUser(search string, u *User, page int, sortby SortBy) (tasks []*L
} }
// Then return all tasks for that lists // Then return all tasks for that lists
if err := x.In("list_id", listIDs).Where("text LIKE ?", "%"+search+"%").OrderBy(orderby).Find(&tasks); err != nil { if startDate.Unix() != 0 || endDate.Unix() != 0 {
return nil, err
startDateUnix := time.Now().Unix()
if startDate.Unix() != 0 {
startDateUnix = startDate.Unix()
}
endDateUnix := time.Now().Unix()
if endDate.Unix() != 0 {
endDateUnix = endDate.Unix()
}
if err := x.In("list_id", listIDs).
Where("text LIKE ?", "%"+search+"%").
And("((due_date_unix BETWEEN ? AND ?) OR "+
"(start_date_unix BETWEEN ? and ?) OR "+
"(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix).
And("(parent_task_id = 0 OR parent_task_id IS NULL)").
OrderBy(orderby).
Find(&tasks); err != nil {
return nil, err
}
} else {
if err := x.In("list_id", listIDs).
Where("text LIKE ?", "%"+search+"%").
And("(parent_task_id = 0 OR parent_task_id IS NULL)").
OrderBy(orderby).
Find(&tasks); err != nil {
return nil, err
}
} }
return tasks, err return tasks, err

View file

@ -69,6 +69,34 @@ func sortTasksForTesting(by SortBy) (tasks []*ListTask) {
Updated: 1543626724, Updated: 1543626724,
DueDateUnix: 1543616724, DueDateUnix: 1543616724,
}, },
{
ID: 7,
Text: "task #7 with start date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
},
{
ID: 8,
Text: "task #8 with end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
EndDateUnix: 1544700000,
},
{
ID: 9,
Text: "task #9 with start and end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
EndDateUnix: 1544700000,
},
} }
switch by { switch by {
@ -95,24 +123,26 @@ func sortTasksForTesting(by SortBy) (tasks []*ListTask) {
func TestListTask_ReadAll(t *testing.T) { func TestListTask_ReadAll(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
Text string Text string
Description string Description string
Done bool Done bool
DueDateUnix int64 DueDateUnix int64
RemindersUnix []int64 RemindersUnix []int64
CreatedByID int64 CreatedByID int64
ListID int64 ListID int64
RepeatAfter int64 RepeatAfter int64
ParentTaskID int64 ParentTaskID int64
Priority int64 Priority int64
Sorting string Sorting string
Subtasks []*ListTask StartDateSortUnix int64
Created int64 EndDateSortUnix int64
Updated int64 Subtasks []*ListTask
CreatedBy User Created int64
CRUDable web.CRUDable Updated int64
Rights web.Rights CreatedBy User
CRUDable web.CRUDable
Rights web.Rights
} }
type args struct { type args struct {
search string search string
@ -160,7 +190,89 @@ func TestListTask_ReadAll(t *testing.T) {
a: &User{ID: 1}, a: &User{ID: 1},
page: 0, page: 0,
}, },
want: sortTasksForTesting(SortTasksByPriorityAsc), want: []*ListTask{
{
ID: 1,
Text: "task #1",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
},
{
ID: 2,
Text: "task #2 done",
Done: true,
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
},
{
ID: 5,
Text: "task #5 higher due date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
DueDateUnix: 1543636724,
},
{
ID: 6,
Text: "task #6 lower due date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
DueDateUnix: 1543616724,
},
{
ID: 7,
Text: "task #7 with start date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
},
{
ID: 8,
Text: "task #8 with end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
EndDateUnix: 1544700000,
},
{
ID: 9,
Text: "task #9 with start and end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
EndDateUnix: 1544700000,
},
{
ID: 4,
Text: "task #4 low prio",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
Priority: 1,
},
{
ID: 3,
Text: "task #3 high prio",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
Priority: 100,
},
},
wantErr: false, wantErr: false,
}, },
{ {
@ -177,7 +289,7 @@ func TestListTask_ReadAll(t *testing.T) {
wantErr: false, wantErr: false,
}, },
{ {
name: "ReadAll ListTasks sorted by due date default (desc)", name: "ReadAll ListTasks sorted by due date default desc",
fields: fields{ fields: fields{
Sorting: "dueadate", Sorting: "dueadate",
}, },
@ -215,28 +327,131 @@ func TestListTask_ReadAll(t *testing.T) {
want: sortTasksForTesting(SortTasksByDueDateDesc), want: sortTasksForTesting(SortTasksByDueDateDesc),
wantErr: false, wantErr: false,
}, },
{
name: "ReadAll ListTasks with range",
fields: fields{
StartDateSortUnix: 1544500000,
EndDateSortUnix: 1544600000,
},
args: args{
search: "",
a: &User{ID: 1},
page: 0,
},
want: []*ListTask{
{
ID: 7,
Text: "task #7 with start date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
},
{
ID: 9,
Text: "task #9 with start and end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
EndDateUnix: 1544700000,
},
},
wantErr: false,
},
{
name: "ReadAll ListTasks with range",
fields: fields{
StartDateSortUnix: 1544700000,
EndDateSortUnix: 1545000000,
},
args: args{
search: "",
a: &User{ID: 1},
page: 0,
},
want: []*ListTask{
{
ID: 8,
Text: "task #8 with end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
EndDateUnix: 1544700000,
},
{
ID: 9,
Text: "task #9 with start and end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
EndDateUnix: 1544700000,
},
},
wantErr: false,
},
{
name: "ReadAll ListTasks with range without end date",
fields: fields{
StartDateSortUnix: 1544700000,
},
args: args{
search: "",
a: &User{ID: 1},
page: 0,
},
want: []*ListTask{
{
ID: 8,
Text: "task #8 with end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
EndDateUnix: 1544700000,
},
{
ID: 9,
Text: "task #9 with start and end date",
CreatedByID: 1,
ListID: 1,
Created: 1543626724,
Updated: 1543626724,
StartDateUnix: 1544600000,
EndDateUnix: 1544700000,
},
},
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
lt := &ListTask{ lt := &ListTask{
ID: tt.fields.ID, ID: tt.fields.ID,
Text: tt.fields.Text, Text: tt.fields.Text,
Description: tt.fields.Description, Description: tt.fields.Description,
Done: tt.fields.Done, Done: tt.fields.Done,
DueDateUnix: tt.fields.DueDateUnix, DueDateUnix: tt.fields.DueDateUnix,
RemindersUnix: tt.fields.RemindersUnix, RemindersUnix: tt.fields.RemindersUnix,
CreatedByID: tt.fields.CreatedByID, CreatedByID: tt.fields.CreatedByID,
ListID: tt.fields.ListID, ListID: tt.fields.ListID,
RepeatAfter: tt.fields.RepeatAfter, RepeatAfter: tt.fields.RepeatAfter,
ParentTaskID: tt.fields.ParentTaskID, ParentTaskID: tt.fields.ParentTaskID,
Priority: tt.fields.Priority, Priority: tt.fields.Priority,
Sorting: tt.fields.Sorting, Sorting: tt.fields.Sorting,
Subtasks: tt.fields.Subtasks, StartDateSortUnix: tt.fields.StartDateSortUnix,
Created: tt.fields.Created, EndDateSortUnix: tt.fields.EndDateSortUnix,
Updated: tt.fields.Updated, Subtasks: tt.fields.Subtasks,
CreatedBy: tt.fields.CreatedBy, Created: tt.fields.Created,
CRUDable: tt.fields.CRUDable, Updated: tt.fields.Updated,
Rights: tt.fields.Rights, CreatedBy: tt.fields.CreatedBy,
CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights,
} }
got, err := lt.ReadAll(tt.args.search, tt.args.a, tt.args.page) got, err := lt.ReadAll(tt.args.search, tt.args.a, tt.args.page)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -245,6 +460,20 @@ func TestListTask_ReadAll(t *testing.T) {
} }
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ListTask.ReadAll() = %v, want %v", got, tt.want) t.Errorf("ListTask.ReadAll() = %v, want %v", got, tt.want)
/*fmt.Println("Got:")
gotslice := got.([]*ListTask)
for _, g := range gotslice {
fmt.Println(g.Priority, g.Text)
//fmt.Println(g.StartDateUnix)
//fmt.Println(g.EndDateUnix)
}
fmt.Println("Want:")
wantslice := tt.want.([]*ListTask)
for _, w := range wantslice {
fmt.Println(w.Priority, w.Text)
//fmt.Println(w.StartDateUnix)
//fmt.Println(w.EndDateUnix)
}*/
} }
}) })
} }

View file

@ -34,10 +34,13 @@ type ListTask struct {
RepeatAfter int64 `xorm:"int(11) INDEX" json:"repeatAfter"` RepeatAfter int64 `xorm:"int(11) INDEX" json:"repeatAfter"`
ParentTaskID int64 `xorm:"int(11) INDEX" json:"parentTaskID"` ParentTaskID int64 `xorm:"int(11) INDEX" json:"parentTaskID"`
Priority int64 `xorm:"int(11)" json:"priority"` Priority int64 `xorm:"int(11)" json:"priority"`
Sorting string `xorm:"-" json:"-" param:"sort"` // Parameter to sort by
StartDateUnix int64 `xorm:"int(11) INDEX" json:"startDate"` StartDateUnix int64 `xorm:"int(11) INDEX" json:"startDate"`
EndDateUnix int64 `xorm:"int(11) INDEX" json:"endDate"` EndDateUnix int64 `xorm:"int(11) INDEX" json:"endDate"`
Sorting string `xorm:"-" json:"-" param:"sort"` // Parameter to sort by
StartDateSortUnix int64 `xorm:"-" json:"-" param:"startdatefilter"`
EndDateSortUnix int64 `xorm:"-" json:"-" param:"enddatefilter"`
Subtasks []*ListTask `xorm:"-" json:"subtasks"` Subtasks []*ListTask `xorm:"-" json:"subtasks"`
Created int64 `xorm:"created" json:"created"` Created int64 `xorm:"created" json:"created"`

View file

@ -114,7 +114,18 @@ func (i *ListTask) Update() (err error) {
ot.Done = false ot.Done = false
} }
_, err = x.ID(i.ID).Cols("text", "description", "done", "due_date_unix", "reminders_unix", "repeat_after").Update(ot) _, err = x.ID(i.ID).
Cols("text",
"description",
"done",
"due_date_unix",
"reminders_unix",
"repeat_after",
"parent_task_id",
"priority",
"start_date_unix",
"end_date_unix").
Update(ot)
*i = ot *i = ot
return return
} }

View file

@ -52,7 +52,7 @@ func Caldav(c echo.Context) error {
} }
// Get all tasks for that user // Get all tasks for that user
tasks, err := models.GetTasksByUser("", &u, -1, models.SortTasksByUnsorted) tasks, err := models.GetTasksByUser("", &u, -1, models.SortTasksByUnsorted, time.Now(), time.Now().Add(24*356*time.Hour))
if err != nil { if err != nil {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }

View file

@ -218,6 +218,7 @@ func RegisterRoutes(e *echo.Echo) {
a.PUT("/lists/:list", taskHandler.CreateWeb) a.PUT("/lists/:list", taskHandler.CreateWeb)
a.GET("/tasks/all", taskHandler.ReadAllWeb) a.GET("/tasks/all", taskHandler.ReadAllWeb)
a.GET("/tasks/all/:sort", taskHandler.ReadAllWeb) a.GET("/tasks/all/:sort", taskHandler.ReadAllWeb)
a.GET("/tasks/all/:sort/:startdatefilter/:enddatefilter", taskHandler.ReadAllWeb)
a.DELETE("/tasks/:listtask", taskHandler.DeleteWeb) a.DELETE("/tasks/:listtask", taskHandler.DeleteWeb)
a.POST("/tasks/:listtask", taskHandler.UpdateWeb) a.POST("/tasks/:listtask", taskHandler.UpdateWeb)