fix(views): duplicate all views and related entities when duplicating a project
This commit is contained in:
parent
9cc273d9bd
commit
d4bdd2d4e8
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user