Add support for migrating task comments
This commit is contained in:
parent
9569265405
commit
1773fdf73d
|
@ -26,7 +26,7 @@ import (
|
||||||
"github.com/laurent22/ical-go"
|
"github.com/laurent22/ical-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*models.Task) string {
|
func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*models.TaskWithComments) string {
|
||||||
|
|
||||||
// Make caldav todos from Vikunja todos
|
// Make caldav todos from Vikunja todos
|
||||||
var caldavtodos []*Todo
|
var caldavtodos []*Todo
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"code.vikunja.io/api/pkg/config"
|
"code.vikunja.io/api/pkg/config"
|
||||||
"code.vikunja.io/api/pkg/cron"
|
"code.vikunja.io/api/pkg/cron"
|
||||||
"code.vikunja.io/api/pkg/db"
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
@ -27,8 +28,6 @@ import (
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
"code.vikunja.io/api/pkg/utils"
|
"code.vikunja.io/api/pkg/utils"
|
||||||
"code.vikunja.io/api/pkg/version"
|
"code.vikunja.io/api/pkg/version"
|
||||||
|
|
||||||
"archive/zip"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -146,7 +145,7 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
|
||||||
nn.Lists = append(nn.Lists, &ListWithTasksAndBuckets{
|
nn.Lists = append(nn.Lists, &ListWithTasksAndBuckets{
|
||||||
List: *l,
|
List: *l,
|
||||||
BackgroundFileID: l.BackgroundFileID,
|
BackgroundFileID: l.BackgroundFileID,
|
||||||
Tasks: []*Task{},
|
Tasks: []*TaskWithComments{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +180,25 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
taskMap := make(map[int64]*TaskWithComments, len(tasks))
|
||||||
for _, t := range tasks {
|
for _, t := range tasks {
|
||||||
listMap[t.ListID].Tasks = append(listMap[t.ListID].Tasks, t)
|
taskMap[t.ID] = &TaskWithComments{
|
||||||
|
Task: *t,
|
||||||
|
}
|
||||||
|
listMap[t.ListID].Tasks = append(listMap[t.ListID].Tasks, taskMap[t.ID])
|
||||||
|
}
|
||||||
|
|
||||||
|
comments := []*TaskComment{}
|
||||||
|
err = s.
|
||||||
|
Join("LEFT", "tasks", "tasks.id = task_comments.task_id").
|
||||||
|
In("tasks.list_id", listIDs).
|
||||||
|
Find(&comments)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range comments {
|
||||||
|
taskMap[c.TaskID].Comments = append(taskMap[c.TaskID].Comments, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets := []*Bucket{}
|
buckets := []*Bucket{}
|
||||||
|
|
|
@ -82,7 +82,7 @@ type List struct {
|
||||||
type ListWithTasksAndBuckets struct {
|
type ListWithTasksAndBuckets struct {
|
||||||
List
|
List
|
||||||
// An array of tasks which belong to the list.
|
// An array of tasks which belong to the list.
|
||||||
Tasks []*Task `xorm:"-" json:"tasks"`
|
Tasks []*TaskWithComments `xorm:"-" json:"tasks"`
|
||||||
// Only used for migration.
|
// Only used for migration.
|
||||||
Buckets []*Bucket `xorm:"-" json:"buckets"`
|
Buckets []*Bucket `xorm:"-" json:"buckets"`
|
||||||
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
|
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
|
||||||
|
|
|
@ -129,6 +129,11 @@ type Task struct {
|
||||||
web.Rights `xorm:"-" json:"-"`
|
web.Rights `xorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TaskWithComments struct {
|
||||||
|
Task
|
||||||
|
Comments []*TaskComment
|
||||||
|
}
|
||||||
|
|
||||||
// TableName returns the table name for listtasks
|
// TableName returns the table name for listtasks
|
||||||
func (Task) TableName() string {
|
func (Task) TableName() string {
|
||||||
return "tasks"
|
return "tasks"
|
||||||
|
|
|
@ -129,7 +129,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||||
|
|
||||||
// Create all tasks
|
// Create all tasks
|
||||||
for _, t := range tasks {
|
for _, t := range tasks {
|
||||||
setBucketOrDefault(t)
|
setBucketOrDefault(&t.Task)
|
||||||
|
|
||||||
t.ListID = l.ID
|
t.ListID = l.ID
|
||||||
err = t.Create(s, user)
|
err = t.Create(s, user)
|
||||||
|
@ -221,6 +221,15 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||||
}
|
}
|
||||||
log.Debugf("[creating structure] Associated task %d with label %d", t.ID, lb.ID)
|
log.Debugf("[creating structure] Associated task %d with label %d", t.ID, lb.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, comment := range t.Comments {
|
||||||
|
comment.TaskID = t.ID
|
||||||
|
err = comment.Create(s, user)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("[creating structure] Created new comment %d", comment.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All tasks brought their own bucket with them, therefore the newly created default bucket is just extra space
|
// All tasks brought their own bucket with them, therefore the newly created default bucket is just extra space
|
||||||
|
|
|
@ -342,7 +342,7 @@ func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Tasks = append(list.Tasks, task)
|
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
|
||||||
log.Debugf("[Microsoft Todo Migration] Done converted %d tasks", len(l.Tasks))
|
log.Debugf("[Microsoft Todo Migration] Done converted %d tasks", len(l.Tasks))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -264,7 +264,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
||||||
lists := make(map[int64]*models.ListWithTasksAndBuckets, len(sync.Projects))
|
lists := make(map[int64]*models.ListWithTasksAndBuckets, len(sync.Projects))
|
||||||
|
|
||||||
// A map for all vikunja tasks with the todoist task id as key to find them easily and add more data
|
// A map for all vikunja tasks with the todoist task id as key to find them easily and add more data
|
||||||
tasks := make(map[int64]*models.Task, len(sync.Items))
|
tasks := make(map[int64]*models.TaskWithComments, len(sync.Items))
|
||||||
|
|
||||||
// A map for all vikunja labels with the todoist id as key to find them easier
|
// A map for all vikunja labels with the todoist id as key to find them easier
|
||||||
labels := make(map[int64]*models.Label, len(sync.Labels))
|
labels := make(map[int64]*models.Label, len(sync.Labels))
|
||||||
|
@ -307,11 +307,13 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range sync.Items {
|
for _, i := range sync.Items {
|
||||||
task := &models.Task{
|
task := &models.TaskWithComments{
|
||||||
Title: i.Content,
|
Task: models.Task{
|
||||||
Created: i.DateAdded.In(config.GetTimeZone()),
|
Title: i.Content,
|
||||||
Done: i.Checked == 1,
|
Created: i.DateAdded.In(config.GetTimeZone()),
|
||||||
BucketID: i.SectionID,
|
Done: i.Checked == 1,
|
||||||
|
BucketID: i.SectionID,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only try to parse the task done at date if the task is actually done
|
// Only try to parse the task done at date if the task is actually done
|
||||||
|
@ -367,7 +369,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
||||||
tasks[i.ParentID].RelatedTasks = make(models.RelatedTaskMap)
|
tasks[i.ParentID].RelatedTasks = make(models.RelatedTaskMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask] = append(tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask], tasks[i.ID])
|
tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask] = append(tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask], &tasks[i.ID].Task)
|
||||||
|
|
||||||
// Remove the task from the top level structure, otherwise it is added twice
|
// Remove the task from the top level structure, otherwise it is added twice
|
||||||
outer:
|
outer:
|
||||||
|
|
|
@ -271,7 +271,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi
|
||||||
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
|
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Tasks = append(list.Tasks, task)
|
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Buckets = append(list.Buckets, bucket)
|
list.Buckets = append(list.Buckets, bucket)
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Vikunja is a to-do list application to facilitate your life.
|
||||||
|
// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public Licensee
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vikunja_file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/events"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/config"
|
||||||
|
"code.vikunja.io/api/pkg/files"
|
||||||
|
"code.vikunja.io/api/pkg/models"
|
||||||
|
"code.vikunja.io/api/pkg/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain is the main test function used to bootstrap the test env
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// Set default config
|
||||||
|
config.InitDefaultConfig()
|
||||||
|
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
||||||
|
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||||
|
|
||||||
|
// Some tests use the file engine, so we'll need to initialize that
|
||||||
|
files.InitTests()
|
||||||
|
user.InitTests()
|
||||||
|
models.SetupTests()
|
||||||
|
events.Fake()
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
|
@ -83,12 +83,12 @@ func (v *VikunjaFileMigrator) Migrate(user *user.User, file io.ReaderAt, size in
|
||||||
for _, n := range namespaces {
|
for _, n := range namespaces {
|
||||||
for _, l := range n.Lists {
|
for _, l := range n.Lists {
|
||||||
for _, t := range l.Tasks {
|
for _, t := range l.Tasks {
|
||||||
if len(t.Labels) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, label := range t.Labels {
|
for _, label := range t.Labels {
|
||||||
label.ID = 0
|
label.ID = 0
|
||||||
}
|
}
|
||||||
|
for _, comment := range t.Comments {
|
||||||
|
comment.ID = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Vikunja is a to-do list application to facilitate your life.
|
||||||
|
// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public Licensee
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vikunja_file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.vikunja.io/api/pkg/config"
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
"code.vikunja.io/api/pkg/user"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVikunjaFileMigrator_Migrate(t *testing.T) {
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
|
||||||
|
m := &VikunjaFileMigrator{}
|
||||||
|
u := &user.User{ID: 1}
|
||||||
|
|
||||||
|
f, err := os.Open(config.ServiceRootpath.GetString() + "/pkg/modules/migration/vikunja-file/export.zip")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not open file: %s", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
s, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not stat file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.Migrate(u, f, s.Size())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
db.AssertExists(t, "namespaces", map[string]interface{}{
|
||||||
|
"title": "test",
|
||||||
|
"owner_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
db.AssertExists(t, "lists", map[string]interface{}{
|
||||||
|
"title": "Test list",
|
||||||
|
"owner_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
db.AssertExists(t, "lists", map[string]interface{}{
|
||||||
|
"title": "A list with a background",
|
||||||
|
"owner_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
db.AssertExists(t, "tasks", map[string]interface{}{
|
||||||
|
"title": "Some other task",
|
||||||
|
"created_by_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
db.AssertExists(t, "task_comments", map[string]interface{}{
|
||||||
|
"comment": "This is a comment",
|
||||||
|
"author_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
db.AssertExists(t, "files", map[string]interface{}{
|
||||||
|
"name": "cristiano-mozzillo-v3d5uBB26yA-unsplash.jpg",
|
||||||
|
"created_by_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
db.AssertExists(t, "labels", map[string]interface{}{
|
||||||
|
"title": "test",
|
||||||
|
"created_by_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
db.AssertExists(t, "buckets", map[string]interface{}{
|
||||||
|
"title": "Test Bucket",
|
||||||
|
"created_by_id": u.ID,
|
||||||
|
}, false)
|
||||||
|
}
|
|
@ -235,7 +235,7 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Tasks = append(l.Tasks, newTask)
|
l.Tasks = append(l.Tasks, &models.TaskWithComments{Task: *newTask})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return l, nil
|
return l, nil
|
||||||
|
|
|
@ -174,10 +174,10 @@ func (vcls *VikunjaCaldavListStorage) GetResourcesByFilters(rpath string, filter
|
||||||
for _, t := range vcls.list.Tasks {
|
for _, t := range vcls.list.Tasks {
|
||||||
rr := VikunjaListResourceAdapter{
|
rr := VikunjaListResourceAdapter{
|
||||||
list: vcls.list,
|
list: vcls.list,
|
||||||
task: t,
|
task: &t.Task,
|
||||||
isCollection: false,
|
isCollection: false,
|
||||||
}
|
}
|
||||||
r := data.NewResource(getTaskURL(t), &rr)
|
r := data.NewResource(getTaskURL(&t.Task), &rr)
|
||||||
r.Name = t.Title
|
r.Name = t.Title
|
||||||
resources = append(resources, r)
|
resources = append(resources, r)
|
||||||
}
|
}
|
||||||
|
@ -371,7 +371,7 @@ func (vcls *VikunjaCaldavListStorage) DeleteResource(rpath string) error {
|
||||||
// VikunjaListResourceAdapter holds the actual resource
|
// VikunjaListResourceAdapter holds the actual resource
|
||||||
type VikunjaListResourceAdapter struct {
|
type VikunjaListResourceAdapter struct {
|
||||||
list *models.ListWithTasksAndBuckets
|
list *models.ListWithTasksAndBuckets
|
||||||
listTasks []*models.Task
|
listTasks []*models.TaskWithComments
|
||||||
task *models.Task
|
task *models.Task
|
||||||
|
|
||||||
isPrincipal bool
|
isPrincipal bool
|
||||||
|
@ -417,7 +417,7 @@ func (vlra *VikunjaListResourceAdapter) GetContent() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if vlra.task != nil {
|
if vlra.task != nil {
|
||||||
list := models.ListWithTasksAndBuckets{Tasks: []*models.Task{vlra.task}}
|
list := models.ListWithTasksAndBuckets{Tasks: []*models.TaskWithComments{{Task: *vlra.task}}}
|
||||||
return caldav.GetCaldavTodosForTasks(&list, list.Tasks)
|
return caldav.GetCaldavTodosForTasks(&list, list.Tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,8 +481,10 @@ func (vcls *VikunjaCaldavListStorage) getListRessource(isCollection bool) (rr Vi
|
||||||
panic("Tasks returned from TaskCollection.ReadAll are not []*models.Task!")
|
panic("Tasks returned from TaskCollection.ReadAll are not []*models.Task!")
|
||||||
}
|
}
|
||||||
|
|
||||||
listTasks = tasks
|
for _, t := range tasks {
|
||||||
vcls.list.Tasks = tasks
|
listTasks = append(listTasks, &models.TaskWithComments{Task: *t})
|
||||||
|
}
|
||||||
|
vcls.list.Tasks = listTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Commit(); err != nil {
|
if err := s.Commit(); err != nil {
|
||||||
|
|
Loading…
Reference in New Issue