Assignees optimizations #47

Merged
konrad merged 10 commits from enhancement/assignees-performance into master 2019-01-08 19:13:07 +00:00
6 changed files with 233 additions and 114 deletions
Showing only changes of commit 46558e74f7 - Show all commits

View File

@ -151,6 +151,26 @@ func (l *List) GetSimpleByID() (err error) {
return
}
// GetListSimplByTaskID gets a list by a task id
func GetListSimplByTaskID(taskID int64) (l *List, err error) {
// We need to re-init our list object, because otherwise xorm creates a "where for every item in that list object,
// leading to not finding anything if the id is good, but for example the title is different.
exists, err := x.
Select("lists.*").
Join("INNER", "tasks", "list.id = tasks.list_id").
Where("tasks.id = ?", taskID).
Get(l)
if err != nil {
return
}
if !exists {
return &List{}, ErrListDoesNotExist{ID: l.ID}
}
return
}
// Gets the lists only, without any tasks or so
func getRawListsForUser(search string, u *User, page int) (lists []*List, err error) {
fullUser, err := GetUserByID(u.ID)

View File

@ -0,0 +1,162 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 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 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models
import "code.vikunja.io/web"
// ListTaskAssginee represents an assignment of a user to a task
type ListTaskAssginee struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"-"`
TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"`
UserID int64 `xorm:"int(11) INDEX not null" json:"user_id" param:"user"`
Created int64 `xorm:"created"`
web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"`
}
// TableName makes a pretty table name
func (ListTaskAssginee) TableName() string {
return "task_assignees"
}
// ListTaskAssigneeWithUser is a helper type to deal with user joins
type ListTaskAssigneeWithUser struct {
TaskID int64
User `xorm:"extends"`
}
func getRawTaskAssigneesForTasks(taskIDs []int64) (taskAssignees []*ListTaskAssigneeWithUser, err error) {
taskAssignees = []*ListTaskAssigneeWithUser{nil}
err = x.Table("task_assignees").
Select("task_id, users.*").
In("task_id", taskIDs).
Join("INNER", "users", "task_assignees.user_id = users.id").
Find(&taskAssignees)
return
}
// Create or update a bunch of task assignees
func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) {
// If we don't have any new assignees, delete everything right away. Saves us some hassle.
if len(assignees) == 0 && len(t.Assignees) > 0 {
_, err = x.Where("task_id = ?", t.ID).
Delete(ListTaskAssginee{})
return err
}
// If we didn't change anything (from 0 to zero) don't do anything.
if len(assignees) == 0 && len(t.Assignees) == 0 {
return nil
}
// Make a hashmap of the new assignees for easier comparison
newAssignees := make(map[int64]*User, len(assignees))
for _, newAssignee := range assignees {
newAssignees[newAssignee.ID] = newAssignee
}
// Get old assignees to delete
var found bool
var assigneesToDelete []int64
oldAssignees := make(map[int64]*User, len(t.Assignees))
for _, oldAssignee := range t.Assignees {
found = false
if newAssignees[oldAssignee.ID] != nil {
found = true // If a new assignee is already in the list with old assignees
}
// Put all assignees which are only on the old list to the trash
if !found {
assigneesToDelete = append(assigneesToDelete, oldAssignee.ID)
}
oldAssignees[oldAssignee.ID] = oldAssignee
}
// Delete all assignees not passed
if len(assigneesToDelete) > 0 {
_, err = x.In("user_id", assigneesToDelete).
And("task_id = ?", t.ID).
Delete(ListTaskAssginee{})
if err != nil {
return err
}
}
// Get the list to perform later checks
list := List{ID: t.ListID}
err = list.GetSimpleByID()
if err != nil {
return
}
// Loop through our users and add them
for _, u := range assignees {
// Check if the user is already assigned and assign him only if not
if oldAssignees[u.ID] != nil {
// continue outer loop
continue
}
// Add the new assignee
err = t.addNewAssigneeByID(u.ID, &list)
if err != nil {
return err
}
}
return
}
// Delete a task assignee
func (la *ListTaskAssginee) Delete() (err error) {
_, err = x.Delete(&ListTaskAssginee{TaskID: la.TaskID, UserID: la.UserID})
return
}
// Create adds a new assignee to a task
func (la *ListTaskAssginee) Create(a web.Auth) (err error) {
// Get the list to perform later checks
list, err := GetListSimplByTaskID(la.TaskID)
if err != nil {
return
}
task := &ListTask{ID: la.TaskID}
return task.addNewAssigneeByID(la.UserID, list)
}
func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) {
// Check if the user exists and has access to the list
newAssignee, err := GetUserByID(newAssigneeID)
if err != nil {
return err
}
if !list.CanRead(&newAssignee) {
return ErrUserDoesNotHaveAccessToList{list.ID, newAssigneeID}
}
_, err = x.Insert(ListTaskAssginee{
TaskID: t.ID,
UserID: newAssigneeID,
})
return
}

View File

@ -0,0 +1,42 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 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 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models
import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/web"
)
// CanCreate checks if a user can add a new assignee
func (la *ListTaskAssginee) CanCreate(a web.Auth) bool {
return la.canDoListTaskAssingee(a)
}
// CanDelete checks if a user can delete an assignee
func (la *ListTaskAssginee) CanDelete(a web.Auth) bool {
return la.canDoListTaskAssingee(a)
}
func (la *ListTaskAssginee) canDoListTaskAssingee(a web.Auth) bool {
// Check if the current user can edit the list
list, err := GetListSimplByTaskID(la.TaskID)
if err != nil {
log.Log.Errorf("Error during canDoListTaskAssingee for ListTaskAssginee: %v", err)
return false
}
return list.CanCreate(a)
}

View File

@ -76,25 +76,6 @@ func (ListTask) TableName() string {
return "tasks"
}
// ListTaskAssginee represents an assignment of a user to a task
type ListTaskAssginee struct {
ID int64 `xorm:"int(11) autoincr not null unique pk"`
TaskID int64 `xorm:"int(11) INDEX not null"`
UserID int64 `xorm:"int(11) INDEX not null"`
Created int64 `xorm:"created"`
}
// TableName makes a pretty table name
func (ListTaskAssginee) TableName() string {
return "task_assignees"
}
// ListTaskAssigneeWithUser is a helper type to deal with user joins
type ListTaskAssigneeWithUser struct {
TaskID int64
User `xorm:"extends"`
}
// GetTasksByListID gets all todotasks for a list
func GetTasksByListID(listID int64) (tasks []*ListTask, err error) {
// make a map so we can put in a lot of other stuff more easily
@ -175,16 +156,6 @@ func GetTasksByListID(listID int64) (tasks []*ListTask, err error) {
return
}
func getRawTaskAssigneesForTasks(taskIDs []int64) (taskAssignees []*ListTaskAssigneeWithUser, err error) {
taskAssignees = []*ListTaskAssigneeWithUser{nil}
err = x.Table("task_assignees").
Select("task_id, users.*").
In("task_id", taskIDs).
Join("INNER", "users", "task_assignees.user_id = users.id").
Find(&taskAssignees)
return
}
func getTaskByIDSimple(taskID int64) (task ListTask, err error) {
if taskID < 1 {
return ListTask{}, ErrListTaskDoesNotExist{taskID}

View File

@ -143,88 +143,3 @@ func updateDone(oldTask *ListTask, newTask *ListTask) {
newTask.Done = false
}
}
// Create a bunch of task assignees
func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) {
// If we don't have any new assignees, delete everything right away. Saves us some hassle.
if len(assignees) == 0 && len(t.Assignees) > 0 {
_, err = x.Where("task_id = ?", t.ID).
Delete(ListTaskAssginee{})
return err
}
// If we didn't change anything (from 0 to zero) don't do anything.
if len(assignees) == 0 && len(t.Assignees) == 0 {
return nil
}
// Make a hashmap of the new assignees for easier comparison
newAssignees := make(map[int64]*User, len(assignees))
for _, newAssignee := range assignees {
newAssignees[newAssignee.ID] = newAssignee
}
// Get old assignees to delete
var found bool
var assigneesToDelete []int64
oldAssignees := make(map[int64]*User, len(t.Assignees))
for _, oldAssignee := range t.Assignees {
found = false
if newAssignees[oldAssignee.ID] != nil {
found = true // If a new assignee is already in the list with old assignees
}
// Put all assignees which are only on the old list to the trash
if !found {
assigneesToDelete = append(assigneesToDelete, oldAssignee.ID)
}
oldAssignees[oldAssignee.ID] = oldAssignee
}
// Delete all assignees not passed
if len(assigneesToDelete) > 0 {
_, err = x.In("user_id", assigneesToDelete).
And("task_id = ?", t.ID).
Delete(ListTaskAssginee{})
if err != nil {
return err
}
}
// Get the list to perform later checks
list := List{ID: t.ListID}
err = list.GetSimpleByID()
if err != nil {
return
}
// Loop through our users and add them
for _, u := range assignees {
// Check if the user is already assigned and assign him only if not
if oldAssignees[u.ID] != nil {
// continue outer loop
continue
}
// Check if the user exists and has access to the list
newAssignee, err := GetUserByID(u.ID)
if err != nil {
return err
}
if !list.CanRead(&newAssignee) {
return ErrUserDoesNotHaveAccessToList{list.ID, u.ID}
}
_, err = x.Insert(ListTaskAssginee{
TaskID: t.ID,
UserID: u.ID,
})
if err != nil {
return err
}
}
return
}

View File

@ -241,6 +241,15 @@ func RegisterRoutes(e *echo.Echo) {
}
a.POST("/tasks/bulk", bulkTaskHandler.UpdateWeb)
assigneeTaskHandler := &handler.WebHandler{
EmptyStruct: func() handler.CObject {
return &models.ListTaskAssginee{}
},
}
a.PUT("/tasks/:listtask/assignees", assigneeTaskHandler.CreateWeb)
a.DELETE("/tasks/:listtask/assignees/:user", assigneeTaskHandler.DeleteWeb)
a.GET("/tasks/:listtask/labelassigneess", assigneeTaskHandler.ReadAllWeb)
labelTaskHandler := &handler.WebHandler{
EmptyStruct: func() handler.CObject {
return &models.LabelTask{}