From e887cdeb5e9cf223d97318f699e1ae96a2dfcbb3 Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 24 Jan 2025 11:39:51 +0100 Subject: [PATCH] feat(task): expand reactions via parameter --- frontend/src/modelTypes/ITask.ts | 1 + frontend/src/services/taskCollection.ts | 2 +- frontend/src/views/tasks/TaskDetailView.vue | 2 +- pkg/models/task_collection.go | 8 ++- pkg/models/task_collection_test.go | 57 +++++++++++++++++++-- pkg/models/tasks.go | 24 +++++---- 6 files changed, 77 insertions(+), 17 deletions(-) diff --git a/frontend/src/modelTypes/ITask.ts b/frontend/src/modelTypes/ITask.ts index 6230f80b3..3c3bd746a 100644 --- a/frontend/src/modelTypes/ITask.ts +++ b/frontend/src/modelTypes/ITask.ts @@ -15,6 +15,7 @@ import type {IRepeatMode} from '@/types/IRepeatMode' import type {PartialWithId} from '@/types/PartialWithId' import type {ITaskReminder} from '@/modelTypes/ITaskReminder' import type {IReactionPerEntity} from '@/modelTypes/IReaction' +import type {ExpandTaskFilterParam} from '@/services/taskCollection.ts' export interface ITask extends IAbstract { id: number diff --git a/frontend/src/services/taskCollection.ts b/frontend/src/services/taskCollection.ts index 0717df6ab..f57d8e14b 100644 --- a/frontend/src/services/taskCollection.ts +++ b/frontend/src/services/taskCollection.ts @@ -4,7 +4,7 @@ import TaskModel from '@/models/task' import type {ITask} from '@/modelTypes/ITask' import BucketModel from '@/models/bucket' -export type ExpandTaskFilterParam = 'subtasks' | null +export type ExpandTaskFilterParam = 'subtasks' | 'buckets' | 'reactions' | null export interface TaskFilterParams { sort_by: ('start_date' | 'end_date' | 'due_date' | 'done' | 'id' | 'position')[], diff --git a/frontend/src/views/tasks/TaskDetailView.vue b/frontend/src/views/tasks/TaskDetailView.vue index a7b3a80f0..fabade699 100644 --- a/frontend/src/views/tasks/TaskDetailView.vue +++ b/frontend/src/views/tasks/TaskDetailView.vue @@ -735,7 +735,7 @@ watch( } try { - const loaded = await taskService.get({id}) + const loaded = await taskService.get({id}, {expand: 'reactions'}) Object.assign(task.value, loaded) attachmentStore.set(task.value.attachments) taskColor.value = task.value.hexColor diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 2fbd6deb3..c508602ae 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -53,6 +53,7 @@ type TaskCollection struct { // 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. // If set to `buckets`, the buckets of each task will be present in the response. + // If set to `reactions`, the reactions of each task will be present in the response. // You can set this multiple times with different values. Expand []TaskCollectionExpandable `query:"expand" json:"-"` @@ -66,6 +67,7 @@ type TaskCollectionExpandable string const TaskCollectionExpandSubtasks TaskCollectionExpandable = `subtasks` const TaskCollectionExpandBuckets TaskCollectionExpandable = `buckets` +const TaskCollectionExpandReactions TaskCollectionExpandable = `reactions` // Validate validates if the TaskCollectionExpandable value is valid. func (t TaskCollectionExpandable) Validate() error { @@ -74,9 +76,11 @@ func (t TaskCollectionExpandable) Validate() error { return nil case TaskCollectionExpandBuckets: return nil + case TaskCollectionExpandReactions: + return nil } - return InvalidFieldErrorWithMessage([]string{"expand"}, "Expand must be one of the following values: subtasks, buckets") + return InvalidFieldErrorWithMessage([]string{"expand"}, "Expand must be one of the following values: subtasks, buckets, reactions") } func validateTaskField(fieldName string) error { @@ -239,7 +243,7 @@ func getFilterValueForBucketFilter(filter string, view *ProjectView) (newFilter // @Param filter query string false "The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature." // @Param filter_timezone query string false "The time zone which should be used for date match (statements like "now" resolve to different actual times)" // @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`." -// @Param expand query string false "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. If set to `buckets`, the buckets of each task will be present in the response. You can set this multiple times with different values. +// @Param expand query string false "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. If set to `buckets`, the buckets of each task will be present in the response. If set to `reactions`, the reactions of each task will be present in the response. You can set this multiple times with different values. // @Security JWTKeyAuth // @Success 200 {array} models.Task "The tasks" // @Failure 500 {object} models.Message "Internal error" diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 9d2490917..2ddf7f4d9 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -105,9 +105,6 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, IsFavorite: true, - Reactions: ReactionMap{ - "👋": []*user.User{user1}, - }, Labels: []*Label{ label4, }, @@ -167,6 +164,11 @@ func TestTaskCollection_ReadAll(t *testing.T) { Created: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc), } + var task1WithReaction = &Task{} + *task1WithReaction = *task1 + task1WithReaction.Reactions = ReactionMap{ + "👋": []*user.User{user1}, + } task2 := &Task{ ID: 2, Title: "task #2 done", @@ -652,6 +654,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { FilterIncludeNulls bool Filter string + Expand []TaskCollectionExpandable + CRUDable web.CRUDable Rights web.Rights } @@ -723,6 +727,51 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, wantErr: false, }, + { + name: "ReadAll Tasks with expanded reaction", + fields: fields{ + Expand: []TaskCollectionExpandable{ + TaskCollectionExpandReactions, + }, + }, + args: defaultArgs, + want: []*Task{ + task1WithReaction, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + task11, + task12, + task15, + task16, + task17, + task18, + task19, + task20, + task21, + task22, + task23, + task24, + task25, + task26, + task27, + task28, + task29, + task30, + task31, + task32, + task33, + task35, + task39, + }, + wantErr: false, + }, { // For more sorting tests see task_collection_sort_test.go name: "sorted by done asc and id desc", @@ -1465,6 +1514,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { Filter: tt.fields.Filter, + Expand: tt.fields.Expand, + CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 40867ea8b..1cc1dc448 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -672,11 +672,6 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, vi return err } - reactions, err := getReactionsForEntityIDs(s, ReactionKindTask, taskIDs) - if err != nil { - return - } - var positionsMap = make(map[int64]*TaskPosition) if view != nil { positions, err := getPositionsForView(s, view) @@ -688,6 +683,7 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, vi } } + var reactions map[int64]ReactionMap if expand != nil { expanded := make(map[TaskCollectionExpandable]bool) for _, expandable := range expand { @@ -695,11 +691,17 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, vi continue } - if expandable == TaskCollectionExpandBuckets { + switch expandable { + case TaskCollectionExpandBuckets: err = addBucketsToTasks(s, a, taskIDs, taskMap) if err != nil { return err } + case TaskCollectionExpandReactions: + reactions, err = getReactionsForEntityIDs(s, ReactionKindTask, taskIDs) + if err != nil { + return + } } expanded[expandable] = true } @@ -722,9 +724,11 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, vi task.IsFavorite = taskFavorites[task.ID] - r, has := reactions[task.ID] - if has { - task.Reactions = r + if reactions != nil { + r, has := reactions[task.ID] + if has { + task.Reactions = r + } } p, has := positionsMap[task.ID] @@ -1677,7 +1681,7 @@ func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) { // @Accept json // @Produce json // @Param id path int true "The task ID" -// @Param expand query string false "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. If set to `buckets`, the buckets of each task will be present in the response. You can set this multiple times with different values. +// @Param expand query string false "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. If set to `buckets`, the buckets of each task will be present in the response. If set to `reactions`, the reactions of each task will be present in the response. You can set this multiple times with different values. // @Security JWTKeyAuth // @Success 200 {object} models.Task "The task" // @Failure 404 {object} models.Message "Task not found"