Skip to content

Commit

Permalink
Move recurring tasks back to default bucket when done
Browse files Browse the repository at this point in the history
Closes go-vikunja#347

The idea behind this is a kanban layout like this:

    | Backlog | In Progress | Done |

A recurring task is also moved into `In Progress`, however when it's
closed, it should be moved back into the default column (Backlog).

This patch effectively implements that by changing the bucket ID in the
saved view event hook for recurring tasks that were closed in the update
action. The frontend counterpart gets a condition to detect this case
and move the card to the right column in the UI.

This patch is a POC to see if this goes into the right direction, most
notably there's one part of the feature missing: I mostly care about
the Ctrl-Click case, the "move to 'Done'" case doesn't work. I'm happy
to implement that if the rest of the patch is fine for upstream.
  • Loading branch information
Ma27 committed Nov 22, 2024
1 parent ee04423 commit a2aa76f
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 9 deletions.
7 changes: 7 additions & 0 deletions frontend/src/stores/kanban.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ export const useKanbanStore = defineStore('kanban', () => {
const defaultBucketId = getDefaultBucketId(currentView)
moveTaskToBucket(task, defaultBucketId)
}

if(!task.done
&& currentTaskBucket.id !== task.bucketId
&& (task.repeatAfter !== 0 || task.repeatMode !== 0)
) {
moveTaskToBucket(task, task.bucketId)
}

setTaskInBucket(task)
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/models/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
type TaskCreatedEvent struct {
Task *Task `json:"task"`
Doer *user.User `json:"doer"`
WasDone bool `json:"was_done"`
}

// Name defines the name for TaskCreatedEvent
Expand All @@ -40,6 +41,7 @@ func (t *TaskCreatedEvent) Name() string {
type TaskUpdatedEvent struct {
Task *Task `json:"task"`
Doer *user.User `json:"doer"`
WasDone bool `json:"was_done"`
}

// Name defines the name for TaskUpdatedEvent
Expand Down Expand Up @@ -287,4 +289,4 @@ type UserDataExportRequestedEvent struct {
// Name defines the name for UserDataExportRequestedEvent
func (t *UserDataExportRequestedEvent) Name() string {
return "user.export.requested"
}
}
3 changes: 2 additions & 1 deletion pkg/models/kanban_task_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,5 +211,6 @@ func (b *TaskBucket) Update(s *xorm.Session, a web.Auth) (err error) {
return events.Dispatch(&TaskUpdatedEvent{
Task: &task,
Doer: doer,
WasDone: task.Done,
})
}
}
4 changes: 2 additions & 2 deletions pkg/models/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ func (l *UpdateTaskInSavedFilterViews) Handle(msg *message.Message) (err error)
continue
}

taskBucket, taskPosition, err := addTaskToFilter(s, filter, view, fallbackTimezone, event.Task)
taskBucket, taskPosition, err := addTaskToFilter(s, filter, view, fallbackTimezone, event.Task, event.WasDone)
if err != nil {
if IsErrInvalidFilterExpression(err) ||
IsErrInvalidTaskFilterValue(err) ||
Expand Down Expand Up @@ -1066,4 +1066,4 @@ func (s *HandleUserDataExport) Handle(msg *message.Message) (err error) {

err = sess.Commit()
return err
}
}
8 changes: 4 additions & 4 deletions pkg/models/saved_filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func (sf *SavedFilter) Delete(s *xorm.Session, _ web.Auth) error {
return err
}

func addTaskToFilter(s *xorm.Session, filter *SavedFilter, view *ProjectView, fallbackTimezone string, task *Task) (taskBucket *TaskBucket, taskPosition *TaskPosition, err error) {
func addTaskToFilter(s *xorm.Session, filter *SavedFilter, view *ProjectView, fallbackTimezone string, task *Task, wasDone bool) (taskBucket *TaskBucket, taskPosition *TaskPosition, err error) {

filterString := filter.Filters.Filter

Expand Down Expand Up @@ -348,7 +348,7 @@ func addTaskToFilter(s *xorm.Session, filter *SavedFilter, view *ProjectView, fa
if err != nil {
return nil, nil, err
}
if !taskHasBucketInView {
if !taskHasBucketInView || (taskHasBucketInView && task.isRepeating() && (task.Done || wasDone)) {
bucketID, err := getDefaultBucketID(s, view)
if err != nil {
return nil, nil, err
Expand All @@ -369,7 +369,7 @@ func addTaskToFilter(s *xorm.Session, filter *SavedFilter, view *ProjectView, fa
if err != nil {
return nil, nil, err
}
if !existingTaskPosition {
if !existingTaskPosition || (taskHasBucketInView && task.isRepeating() && (task.Done || wasDone)) {
taskPosition = &TaskPosition{
TaskID: task.ID,
ProjectViewID: view.ID,
Expand Down Expand Up @@ -576,4 +576,4 @@ func upsertRelatedTaskProperties(s *xorm.Session, logPrefix string, newTaskBucke
return
}
}
}
}
73 changes: 72 additions & 1 deletion pkg/models/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
}

views := []*ProjectView{}
if (!t.isRepeating() && t.Done != ot.Done) || t.ProjectID != ot.ProjectID {
if (t.Done != ot.Done) || t.ProjectID != ot.ProjectID {
err = s.
Where("project_id = ? AND view_kind = ? AND bucket_configuration_mode = ?",
t.ProjectID, ProjectViewKindKanban, BucketConfigurationModeManual).
Expand Down Expand Up @@ -1051,6 +1051,56 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
}
}

if t.ProjectID == ot.ProjectID && t.isRepeating() && t.Done != ot.Done {
for _, view := range views {
currentTaskBucket := &TaskBucket{}
_, err := s.Where("task_id = ? AND project_view_id = ?", t.ID, view.ID).
Get(currentTaskBucket)
if err != nil {
return err
}

var bucketID = currentTaskBucket.BucketID
var defaultBucketID, error = getDefaultBucketID(s, view)
if error != nil {
return error
}

// Nothing to do, already in default bucket.
if bucketID == defaultBucketID {
continue
}

// Task done? Move back to default
if t.Done {
bucketID = defaultBucketID
}

tb := &TaskBucket{
BucketID: bucketID,
TaskID: t.ID,
ProjectViewID: view.ID,
ProjectID: t.ProjectID,
}
err = tb.Update(s, a)
if err != nil {
return err
}

tp := TaskPosition{
TaskID: t.ID,
ProjectViewID: view.ID,
Position: calculateDefaultPosition(t.Index, t.Position),
}
err = tp.Update(s, a)
if err != nil {
return err
}
}
}

wasDone := t.Done

// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
updateDone(&ot, t)

Expand Down Expand Up @@ -1192,11 +1242,32 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
err = events.Dispatch(&TaskUpdatedEvent{
Task: t,
Doer: doer,
WasDone: wasDone,
})
if err != nil {
return err
}

if wasDone {
previousBucketID := t.BucketID
view := &ProjectView{}
_, err := s.Distinct("pv.id").
Table("project_views").
Alias("pv").
Join("INNER", "buckets", "buckets.project_view_id = pv.id").
Where(builder.Eq{"buckets.id": previousBucketID}).
Get(view)

if err != nil {
log.Warningf("Failed fetching current project view ID of task %d: %v", t.ID, err)
} else {
t.BucketID, err = getDefaultBucketID(s, view)
if err != nil {
log.Warningf("Failed refreshing bucket ID of task %d: %v", t.ID, err)
}
}
}

return updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
}

Expand Down

0 comments on commit a2aa76f

Please sign in to comment.