mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-03-12 01:59:34 -05:00
feat: expand buckets
This commit is contained in:
@@ -38,7 +38,7 @@ type Bucket struct {
|
||||
// The project view this bucket belongs to.
|
||||
ProjectViewID int64 `xorm:"bigint not null" json:"project_view_id" param:"view"`
|
||||
// All tasks which belong to this bucket.
|
||||
Tasks []*Task `xorm:"-" json:"tasks"`
|
||||
Tasks []*Task `xorm:"-" json:"tasks,omitempty"`
|
||||
|
||||
// How many tasks can be at the same time on this board max
|
||||
Limit int64 `xorm:"default 0" json:"limit" minimum:"0" valid:"range(0|9223372036854775807)"`
|
||||
@@ -271,7 +271,7 @@ func GetTasksInBucketsForView(s *xorm.Session, view *ProjectView, projects []*Pr
|
||||
taskMap[t.ID] = t
|
||||
}
|
||||
|
||||
err = addMoreInfoToTasks(s, taskMap, auth, view)
|
||||
err = addMoreInfoToTasks(s, taskMap, auth, view, opts.expand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
|
||||
"code.vikunja.io/api/pkg/web"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@@ -175,6 +175,41 @@ var FavoritesPseudoProject = Project{
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /projects [get]
|
||||
func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
|
||||
prs, resultCount, totalItems, err := getAllRawProjects(s, a, search, page, perPage, p.IsArchived)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Add project details (favorite state, among other things)
|
||||
err = addProjectDetails(s, prs, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.Expand == ProjectExpandableRights {
|
||||
var doer *user.User
|
||||
doer, err = user.GetFromAuth(a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = addMaxRightToProjects(s, prs, doer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, pr := range prs {
|
||||
pr.MaxRight = RightUnknown
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Putting it all together
|
||||
|
||||
return prs, resultCount, totalItems, err
|
||||
}
|
||||
|
||||
func getAllRawProjects(s *xorm.Session, a web.Auth, search string, page int, perPage int, isArchived bool) (projects []*Project, resultCount int, totalItems int64, err error) {
|
||||
// Check if we're dealing with a share auth
|
||||
shareAuth, ok := a.(*LinkSharing)
|
||||
if ok {
|
||||
@@ -202,7 +237,7 @@ func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
|
||||
user: doer,
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
getArchived: p.IsArchived,
|
||||
getArchived: isArchived,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
@@ -220,27 +255,6 @@ func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
|
||||
prs = append(prs, savedFiltersProject...)
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Add project details (favorite state, among other things)
|
||||
err = addProjectDetails(s, prs, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.Expand == ProjectExpandableRights {
|
||||
err = addMaxRightToProjects(s, prs, doer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, pr := range prs {
|
||||
pr.MaxRight = RightUnknown
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Putting it all together
|
||||
|
||||
return prs, resultCount, totalItems, err
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ type TaskCollection struct {
|
||||
// If set to `subtasks`, Vikunja will fetch only tasks which do not have subtasks and then in a
|
||||
// second step, will fetch all of these subtasks. This may result in more tasks than the
|
||||
// pagination limit being returned, but all subtasks will be present in the response.
|
||||
Expand TaskCollectionExpandable `query:"expand" json:"-"`
|
||||
// If set to `buckets`, the buckets of each task will be present in the response.
|
||||
// You can set this multiple times with different values.
|
||||
Expand []TaskCollectionExpandable `query:"expand" json:"-"`
|
||||
|
||||
isSavedFilter bool
|
||||
|
||||
@@ -63,15 +65,18 @@ type TaskCollection struct {
|
||||
type TaskCollectionExpandable string
|
||||
|
||||
const TaskCollectionExpandSubtasks TaskCollectionExpandable = `subtasks`
|
||||
const TaskCollectionExpandBuckets TaskCollectionExpandable = `buckets`
|
||||
|
||||
// Validate validates if the TaskCollectionExpandable value is valid.
|
||||
func (t TaskCollectionExpandable) Validate() error {
|
||||
switch t {
|
||||
case TaskCollectionExpandSubtasks:
|
||||
return nil
|
||||
case TaskCollectionExpandBuckets:
|
||||
return nil
|
||||
}
|
||||
|
||||
return InvalidFieldErrorWithMessage([]string{"expand"}, "Expand must be one of the following values: subtasks")
|
||||
return InvalidFieldErrorWithMessage([]string{"expand"}, "Expand must be one of the following values: subtasks, buckets")
|
||||
}
|
||||
|
||||
func validateTaskField(fieldName string) error {
|
||||
@@ -341,9 +346,11 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
err = tf.Expand.Validate()
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
for _, expandValue := range tf.Expand {
|
||||
err = expandValue.Validate()
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
opts.search = search
|
||||
|
||||
@@ -319,7 +319,15 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||
distinct += ", task_positions.position"
|
||||
}
|
||||
|
||||
if opts.expand == TaskCollectionExpandSubtasks {
|
||||
var expandSubtasks = false
|
||||
for _, expandable := range opts.expand {
|
||||
if expandable == TaskCollectionExpandSubtasks {
|
||||
expandSubtasks = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if expandSubtasks {
|
||||
cond = builder.And(cond, builder.IsNull{"task_relations.id"})
|
||||
}
|
||||
|
||||
@@ -340,7 +348,7 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||
if joinTaskBuckets {
|
||||
query = query.Join("LEFT", "task_buckets", "task_buckets.task_id = tasks.id")
|
||||
}
|
||||
if opts.expand == TaskCollectionExpandSubtasks {
|
||||
if expandSubtasks {
|
||||
query = query.Join("LEFT", "task_relations", "tasks.id = task_relations.task_id and task_relations.relation_kind = 'parenttask'")
|
||||
}
|
||||
|
||||
@@ -354,7 +362,7 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||
}
|
||||
|
||||
// fetch subtasks when expanding
|
||||
if opts.expand == TaskCollectionExpandSubtasks {
|
||||
if expandSubtasks {
|
||||
subtasks := []*Task{}
|
||||
|
||||
taskIDs := []int64{}
|
||||
@@ -407,7 +415,7 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||
if joinTaskBuckets {
|
||||
queryCount = queryCount.Join("LEFT", "task_buckets", "task_buckets.task_id = tasks.id")
|
||||
}
|
||||
if opts.expand == TaskCollectionExpandSubtasks {
|
||||
if expandSubtasks {
|
||||
queryCount = queryCount.Join("LEFT", "task_relations", "tasks.id = task_relations.task_id and task_relations.relation_kind = 'parenttask'")
|
||||
}
|
||||
totalCount, err = queryCount.
|
||||
|
||||
@@ -119,6 +119,9 @@ type Task struct {
|
||||
// Can be used to move a task between buckets. In that case, the new bucket must be in the same view as the old one.
|
||||
BucketID int64 `xorm:"-" json:"bucket_id"`
|
||||
|
||||
// All buckets across all views this task is part of. Only present when fetching tasks with the `expand` parameter set to `buckets`.
|
||||
Buckets []*Bucket `xorm:"-" json:"buckets,omitempty"`
|
||||
|
||||
// The position of the task - any task project can be sorted as usual by this parameter.
|
||||
// When accessing tasks via views with buckets, this is primarily used to sort them based on a range.
|
||||
// Positions are always saved per view. They will automatically be set if you request the tasks through a view
|
||||
@@ -185,7 +188,7 @@ type taskSearchOptions struct {
|
||||
filterTimezone string
|
||||
isSavedFilter bool
|
||||
projectIDs []int64
|
||||
expand TaskCollectionExpandable
|
||||
expand []TaskCollectionExpandable
|
||||
}
|
||||
|
||||
// ReadAll is a dummy function to still have that endpoint documented
|
||||
@@ -345,7 +348,7 @@ func getTasksForProjects(s *xorm.Session, projects []*Project, a web.Auth, opts
|
||||
taskMap[t.ID] = t
|
||||
}
|
||||
|
||||
err = addMoreInfoToTasks(s, taskMap, a, view)
|
||||
err = addMoreInfoToTasks(s, taskMap, a, view, opts.expand)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
@@ -422,7 +425,7 @@ func GetTasksByUIDs(s *xorm.Session, uids []string, a web.Auth) (tasks []*Task,
|
||||
taskMap[t.ID] = t
|
||||
}
|
||||
|
||||
err = addMoreInfoToTasks(s, taskMap, a, nil)
|
||||
err = addMoreInfoToTasks(s, taskMap, a, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -561,9 +564,59 @@ func addRelatedTasksToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]
|
||||
return
|
||||
}
|
||||
|
||||
func addBucketsToTasks(s *xorm.Session, a web.Auth, taskIDs []int64, taskMap map[int64]*Task) (err error) {
|
||||
if len(taskIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
taskBuckets := []*TaskBucket{}
|
||||
err = s.
|
||||
In("task_id", taskIDs).
|
||||
Find(&taskBuckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We need to fetch all projects for that user to make sure they only
|
||||
// get to see buckets that they have permission to see.
|
||||
projectIDs := []int64{}
|
||||
allProjects, _, _, err := getAllRawProjects(s, a, "", 0, -1, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, project := range allProjects {
|
||||
projectIDs = append(projectIDs, project.ID)
|
||||
}
|
||||
|
||||
buckets := make(map[int64]*Bucket)
|
||||
err = s.
|
||||
Where(builder.In("id", builder.Select("bucket_id").
|
||||
From("task_buckets").
|
||||
Where(builder.In("task_id", taskIDs)))).
|
||||
And(builder.In("project_view_id", builder.Select("id").
|
||||
From("project_views").
|
||||
Where(builder.In("project_id", projectIDs)))).
|
||||
Find(&buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tb := range taskBuckets {
|
||||
if taskMap[tb.TaskID].Buckets == nil {
|
||||
taskMap[tb.TaskID].Buckets = []*Bucket{}
|
||||
}
|
||||
if bucket, exists := buckets[tb.BucketID]; exists {
|
||||
taskMap[tb.TaskID].Buckets = append(taskMap[tb.TaskID].Buckets, bucket)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function takes a map with pointers and returns a slice with pointers to tasks
|
||||
// It adds more stuff like assignees/labels/etc to a bunch of tasks
|
||||
func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, view *ProjectView) (err error) {
|
||||
func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, view *ProjectView, expand []TaskCollectionExpandable) (err error) {
|
||||
|
||||
// No need to iterate over users and stuff if the project doesn't have tasks
|
||||
if len(taskMap) == 0 {
|
||||
@@ -632,6 +685,23 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, vi
|
||||
}
|
||||
}
|
||||
|
||||
if expand != nil {
|
||||
expanded := make(map[TaskCollectionExpandable]bool)
|
||||
for _, expandable := range expand {
|
||||
if expanded[expandable] {
|
||||
continue
|
||||
}
|
||||
|
||||
if expandable == TaskCollectionExpandBuckets {
|
||||
err = addBucketsToTasks(s, a, taskIDs, taskMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
expanded[expandable] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add all objects to their tasks
|
||||
for _, task := range taskMap {
|
||||
|
||||
@@ -1618,7 +1688,8 @@ func (t *Task) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||
taskMap := make(map[int64]*Task, 1)
|
||||
taskMap[t.ID] = t
|
||||
|
||||
err = addMoreInfoToTasks(s, taskMap, a, nil)
|
||||
// TODO add expand here as well
|
||||
err = addMoreInfoToTasks(s, taskMap, a, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
err = addMoreInfoToTasks(s, tasks, &user.User{ID: 1}, nil)
|
||||
err = addMoreInfoToTasks(s, tasks, &user.User{ID: 1}, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch more task info: %s", err.Error())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user