diff --git a/pkg/models/project.go b/pkg/models/project.go index 1311b6eaa..677ed34ff 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -738,7 +738,7 @@ func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) (err er return nil } -func CreateProject(s *xorm.Session, project *Project, auth web.Auth, createBacklogBucket bool) (err error) { +func CreateProject(s *xorm.Session, project *Project, auth web.Auth, createBacklogBucket bool, createDefaultViews bool) (err error) { err = project.CheckIsArchived(s) if err != nil { return err @@ -775,9 +775,11 @@ func CreateProject(s *xorm.Session, project *Project, auth web.Auth, createBackl } } - err = CreateDefaultViewsForProject(s, project, auth, createBacklogBucket) - if err != nil { - return + if createDefaultViews { + err = CreateDefaultViewsForProject(s, project, auth, createBacklogBucket) + if err != nil { + return + } } return events.Dispatch(&ProjectCreatedEvent{ @@ -987,7 +989,7 @@ func updateProjectByTaskID(s *xorm.Session, taskID int64) (err error) { // @Failure 500 {object} models.Message "Internal error" // @Router /projects [put] func (p *Project) Create(s *xorm.Session, a web.Auth) (err error) { - err = CreateProject(s, p, a, true) + err = CreateProject(s, p, a, true, true) if err != nil { return } diff --git a/pkg/models/project_duplicate.go b/pkg/models/project_duplicate.go index 03062f9f8..1cfeede51 100644 --- a/pkg/models/project_duplicate.go +++ b/pkg/models/project_duplicate.go @@ -81,7 +81,8 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) { pd.Project.ParentProjectID = pd.ParentProjectID // Set the owner to the current user pd.Project.OwnerID = doer.GetID() - if err := CreateProject(s, pd.Project, doer, false); err != nil { + err = CreateProject(s, pd.Project, doer, false, false) + if err != nil { // If there is no available unique project identifier, just reset it. if IsErrProjectIdentifierIsNotUnique(err) { pd.Project.Identifier = "" @@ -92,32 +93,20 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) { log.Debugf("Duplicated project %d into new project %d", pd.ProjectID, pd.Project.ID) - // Duplicate kanban buckets - // Old bucket ID as key, new id as value - // Used to map the newly created tasks to their new buckets - bucketMap := make(map[int64]int64) - buckets := []*Bucket{} - err = s.Where("project_id = ?", pd.ProjectID).Find(&buckets) + newTaskIDs, err := duplicateTasks(s, doer, pd) if err != nil { return } - for _, b := range buckets { - oldID := b.ID - b.ID = 0 - b.ProjectID = pd.Project.ID - if err := b.Create(s, doer); err != nil { - return err - } - bucketMap[oldID] = b.ID - } - log.Debugf("Duplicated all buckets from project %d into %d", pd.ProjectID, pd.Project.ID) + log.Debugf("Duplicated all tasks from project %d into %d", pd.ProjectID, pd.Project.ID) - err = duplicateTasks(s, doer, pd, bucketMap) + err = duplicateViews(s, pd, doer, newTaskIDs) if err != nil { return } + log.Debugf("Duplicated all views, buckets and positions from project %d into %d", pd.ProjectID, pd.Project.ID) + err = duplicateProjectBackground(s, pd, doer) if err != nil { return @@ -173,6 +162,93 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) { return } +func duplicateViews(s *xorm.Session, pd *ProjectDuplicate, doer web.Auth, taskMap map[int64]int64) (err error) { + // Duplicate Views + views := make(map[int64]*ProjectView) + err = s.Where("project_id = ?", pd.ProjectID).Find(&views) + if err != nil { + return + } + + oldViewIDs := []int64{} + viewMap := make(map[int64]int64) + for _, view := range views { + oldID := view.ID + oldViewIDs = append(oldViewIDs, oldID) + + view.ID = 0 + view.ProjectID = pd.Project.ID + err = view.Create(s, doer) + if err != nil { + return + } + + viewMap[oldID] = view.ID + } + + buckets := []*Bucket{} + err = s.In("project_view_id", oldViewIDs).Find(&buckets) + if err != nil { + return + } + + // Old bucket ID as key, new id as value + // Used to map the newly created tasks to their new buckets + bucketMap := make(map[int64]int64) + + oldBucketIDs := []int64{} + for _, b := range buckets { + oldID := b.ID + oldBucketIDs = append(oldBucketIDs, oldID) + + b.ID = 0 + b.ProjectID = pd.Project.ID + + err = b.Create(s, doer) + if err != nil { + return err + } + + bucketMap[oldID] = b.ID + } + + oldTaskBuckets := []*TaskBucket{} + err = s.In("bucket_id", oldBucketIDs).Find(&oldTaskBuckets) + if err != nil { + return err + } + + taskBuckets := []*TaskBucket{} + for _, tb := range oldTaskBuckets { + taskBuckets = append(taskBuckets, &TaskBucket{ + BucketID: bucketMap[tb.BucketID], + TaskID: taskMap[tb.TaskID], + }) + } + + _, err = s.Insert(&taskBuckets) + if err != nil { + return err + } + + oldTaskPositions := []*TaskPosition{} + err = s.In("project_view_id", oldViewIDs).Find(&oldTaskPositions) + if err != nil { + return + } + + taskPositions := []*TaskPosition{} + for _, tp := range oldTaskPositions { + taskPositions = append(taskPositions, &TaskPosition{ + ProjectViewID: viewMap[tp.ProjectViewID], + TaskID: taskMap[tp.TaskID], + Position: tp.Position, + }) + } + + return +} + func duplicateProjectBackground(s *xorm.Session, pd *ProjectDuplicate, doer web.Auth) (err error) { if pd.Project.BackgroundFileID == 0 { return @@ -221,33 +297,32 @@ func duplicateProjectBackground(s *xorm.Session, pd *ProjectDuplicate, doer web. return } -func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucketMap map[int64]int64) (err error) { +func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate) (newTaskIDs map[int64]int64, err error) { // Get all tasks + all task details tasks, _, _, err := getTasksForProjects(s, []*Project{{ID: ld.ProjectID}}, doer, &taskSearchOptions{}, nil) if err != nil { - return err + return nil, err } if len(tasks) == 0 { - return nil + return } // This map contains the old task id as key and the new duplicated task id as value. // It is used to map old task items to new ones. - taskMap := make(map[int64]int64) + newTaskIDs = make(map[int64]int64, len(tasks)) // Create + update all tasks (includes reminders) oldTaskIDs := make([]int64, 0, len(tasks)) for _, t := range tasks { oldID := t.ID t.ID = 0 t.ProjectID = ld.Project.ID - t.BucketID = bucketMap[t.BucketID] t.UID = "" - err := createTask(s, t, doer, false) + err = createTask(s, t, doer, false, false) if err != nil { - return err + return nil, err } - taskMap[oldID] = t.ID + newTaskIDs[oldID] = t.ID oldTaskIDs = append(oldTaskIDs, oldID) } @@ -258,14 +333,14 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucket // file changes in the other project which is not something we want. attachments, err := getTaskAttachmentsByTaskIDs(s, oldTaskIDs) if err != nil { - return err + return nil, err } for _, attachment := range attachments { oldAttachmentID := attachment.ID attachment.ID = 0 var exists bool - attachment.TaskID, exists = taskMap[attachment.TaskID] + attachment.TaskID, exists = newTaskIDs[attachment.TaskID] if !exists { log.Debugf("Error duplicating attachment %d from old task %d to new task: Old task <-> new task does not seem to exist.", oldAttachmentID, attachment.TaskID) continue @@ -276,15 +351,15 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucket log.Debugf("Not duplicating attachment %d (file %d) because it does not exist from project %d into %d", oldAttachmentID, attachment.FileID, ld.ProjectID, ld.Project.ID) continue } - return err + return nil, err } if err := attachment.File.LoadFileByID(); err != nil { - return err + return nil, err } err := attachment.NewAttachment(s, attachment.File.File, attachment.File.Name, attachment.File.Size, doer) if err != nil { - return err + return nil, err } if attachment.File.File != nil { @@ -305,9 +380,9 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucket for _, lt := range labelTasks { lt.ID = 0 - lt.TaskID = taskMap[lt.TaskID] + lt.TaskID = newTaskIDs[lt.TaskID] if _, err := s.Insert(lt); err != nil { - return err + return nil, err } } @@ -322,14 +397,14 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucket } for _, a := range assignees { t := &Task{ - ID: taskMap[a.TaskID], + ID: newTaskIDs[a.TaskID], ProjectID: ld.Project.ID, } if err := t.addNewAssigneeByID(s, a.UserID, ld.Project, doer); err != nil { if IsErrUserDoesNotHaveAccessToProject(err) { continue } - return err + return nil, err } } @@ -343,9 +418,9 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucket } for _, c := range comments { c.ID = 0 - c.TaskID = taskMap[c.TaskID] + c.TaskID = newTaskIDs[c.TaskID] if _, err := s.Insert(c); err != nil { - return err + return nil, err } } @@ -360,19 +435,19 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucket return } for _, r := range relations { - otherTaskID, exists := taskMap[r.OtherTaskID] + otherTaskID, exists := newTaskIDs[r.OtherTaskID] if !exists { continue } r.ID = 0 r.OtherTaskID = otherTaskID - r.TaskID = taskMap[r.TaskID] + r.TaskID = newTaskIDs[r.TaskID] if _, err := s.Insert(r); err != nil { - return err + return nil, err } } log.Debugf("Duplicated all task relations from project %d into %d", ld.ProjectID, ld.Project.ID) - return nil + return } diff --git a/pkg/models/project_duplicate_test.go b/pkg/models/project_duplicate_test.go index 0f3957791..aa16d960a 100644 --- a/pkg/models/project_duplicate_test.go +++ b/pkg/models/project_duplicate_test.go @@ -48,11 +48,11 @@ func TestProjectDuplicate(t *testing.T) { require.NoError(t, err) // assert the new project has the same number of buckets as the old one - numberOfOriginalBuckets, err := s.Where("project_id = ?", l.ProjectID).Count(&Bucket{}) + numberOfOriginalViews, err := s.Where("project_id = ?", l.ProjectID).Count(&ProjectView{}) require.NoError(t, err) - numberOfDuplicatedBuckets, err := s.Where("project_id = ?", l.Project.ID).Count(&Bucket{}) + numberOfDuplicatedViews, err := s.Where("project_id = ?", l.Project.ID).Count(&ProjectView{}) require.NoError(t, err) - assert.Equal(t, numberOfOriginalBuckets, numberOfDuplicatedBuckets, "duplicated project does not have the same amount of buckets as the original one") + assert.Equal(t, numberOfOriginalViews, numberOfDuplicatedViews, "duplicated project does not have the same amount of views as the original one") // To make this test 100% useful, it would need to assert a lot more stuff, but it is good enough for now. // Also, we're lacking utility functions to do all needed assertions. diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 514020b6e..1823d165f 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -776,10 +776,10 @@ func getNextTaskIndex(s *xorm.Session, projectID int64) (nextIndex int64, err er // @Failure 500 {object} models.Message "Internal error" // @Router /projects/{id}/tasks [put] func (t *Task) Create(s *xorm.Session, a web.Auth) (err error) { - return createTask(s, t, a, true) + return createTask(s, t, a, true, true) } -func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err error) { +func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool, updateBucket bool) (err error) { t.ID = 0 @@ -827,10 +827,12 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err for _, view := range views { - // Get the default bucket and move the task there - err = setTaskBucket(s, t, nil, view, 0) - if err != nil { - return + if updateBucket { + // Get the default bucket and move the task there + err = setTaskBucket(s, t, nil, view, t.BucketID) + if err != nil { + return + } } positions = append(positions, &TaskPosition{ @@ -840,9 +842,11 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err }) } - _, err = s.Insert(&positions) - if err != nil { - return + if updateBucket { + _, err = s.Insert(&positions) + if err != nil { + return + } } t.CreatedBy = createdBy