Export Import Memberships is retained (#4422)
Fixes https://github.com/mattermost/focalboard/issues/4275
This commit is contained in:
parent
333b448705
commit
8323b58ae0
3 changed files with 134 additions and 13 deletions
|
@ -92,14 +92,25 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if block.Type == model.TypeImage {
|
if block.Type == model.TypeImage {
|
||||||
filename, err := extractImageFilename(block)
|
filename, err2 := extractImageFilename(block)
|
||||||
if err != nil {
|
if err2 != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
files = append(files, filename)
|
files = append(files, filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boardMembers, err := a.GetMembersForBoard(board.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, boardMember := range boardMembers {
|
||||||
|
if err = a.writeArchiveBoardMemberLine(w, boardMember); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// write the files
|
// write the files
|
||||||
for _, filename := range files {
|
for _, filename := range files {
|
||||||
if err := a.writeArchiveFile(zw, filename, board.ID, opt); err != nil {
|
if err := a.writeArchiveFile(zw, filename, board.ID, opt); err != nil {
|
||||||
|
@ -109,6 +120,31 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeArchiveBoardMemberLine writes a single boardMember to the archive.
|
||||||
|
func (a *App) writeArchiveBoardMemberLine(w io.Writer, boardMember *model.BoardMember) error {
|
||||||
|
bm, err := json.Marshal(&boardMember)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line := model.ArchiveLine{
|
||||||
|
Type: "boardMember",
|
||||||
|
Data: bm,
|
||||||
|
}
|
||||||
|
|
||||||
|
bm, err = json.Marshal(&line)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(newline)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// writeArchiveBlockLine writes a single block to the archive.
|
// writeArchiveBlockLine writes a single block to the archive.
|
||||||
func (a *App) writeArchiveBlockLine(w io.Writer, block *model.Block) error {
|
func (a *App) writeArchiveBlockLine(w io.Writer, block *model.Block) error {
|
||||||
b, err := json.Marshal(&block)
|
b, err := json.Marshal(&block)
|
||||||
|
|
|
@ -137,6 +137,7 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
|
||||||
}
|
}
|
||||||
now := utils.GetMillis()
|
now := utils.GetMillis()
|
||||||
var boardID string
|
var boardID string
|
||||||
|
var boardMembers []*model.BoardMember
|
||||||
|
|
||||||
lineNum := 1
|
lineNum := 1
|
||||||
firstLine := true
|
firstLine := true
|
||||||
|
@ -196,6 +197,12 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
|
||||||
block.UpdateAt = now
|
block.UpdateAt = now
|
||||||
block.BoardID = boardID
|
block.BoardID = boardID
|
||||||
boardsAndBlocks.Blocks = append(boardsAndBlocks.Blocks, block)
|
boardsAndBlocks.Blocks = append(boardsAndBlocks.Blocks, block)
|
||||||
|
case "boardMember":
|
||||||
|
var boardMember *model.BoardMember
|
||||||
|
if err2 := json.Unmarshal(archiveLine.Data, &boardMember); err2 != nil {
|
||||||
|
return "", fmt.Errorf("invalid board Member in archive line %d: %w", lineNum, err2)
|
||||||
|
}
|
||||||
|
boardMembers = append(boardMembers, boardMember)
|
||||||
default:
|
default:
|
||||||
return "", model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type)
|
return "", model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type)
|
||||||
}
|
}
|
||||||
|
@ -212,6 +219,13 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
|
||||||
lineNum++
|
lineNum++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loop to remove the people how are not part of the team and system
|
||||||
|
for i := len(boardMembers) - 1; i >= 0; i-- {
|
||||||
|
if _, err := a.GetUser(boardMembers[i].UserID); err != nil {
|
||||||
|
boardMembers = append(boardMembers[:i], boardMembers[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.fixBoardsandBlocks(boardsAndBlocks, opt)
|
a.fixBoardsandBlocks(boardsAndBlocks, opt)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -225,16 +239,22 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
|
||||||
return "", fmt.Errorf("error inserting archive blocks: %w", err)
|
return "", fmt.Errorf("error inserting archive blocks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add user to all the new boards (if not the fake system user).
|
// add users to all the new boards (if not the fake system user).
|
||||||
if opt.ModifiedBy != model.SystemUserID {
|
for _, board := range boardsAndBlocks.Boards {
|
||||||
for _, board := range boardsAndBlocks.Boards {
|
for _, boardMember := range boardMembers {
|
||||||
boardMember := &model.BoardMember{
|
bm := &model.BoardMember{
|
||||||
BoardID: board.ID,
|
BoardID: board.ID,
|
||||||
UserID: opt.ModifiedBy,
|
UserID: boardMember.UserID,
|
||||||
SchemeAdmin: true,
|
Roles: boardMember.Roles,
|
||||||
|
MinimumRole: boardMember.MinimumRole,
|
||||||
|
SchemeAdmin: boardMember.SchemeAdmin,
|
||||||
|
SchemeEditor: boardMember.SchemeEditor,
|
||||||
|
SchemeCommenter: boardMember.SchemeCommenter,
|
||||||
|
SchemeViewer: boardMember.SchemeViewer,
|
||||||
|
Synthetic: boardMember.Synthetic,
|
||||||
}
|
}
|
||||||
if _, err := a.AddMemberToBoard(boardMember); err != nil {
|
if _, err2 := a.AddMemberToBoard(bm); err2 != nil {
|
||||||
return "", fmt.Errorf("cannot add member to board: %w", err)
|
return "", fmt.Errorf("cannot add member to board: %w", err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,6 @@ func TestApp_ImportArchive(t *testing.T) {
|
||||||
|
|
||||||
th.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), "user").Return(babs, nil)
|
th.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), "user").Return(babs, nil)
|
||||||
th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{boardMember}, nil)
|
th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{boardMember}, nil)
|
||||||
th.Store.EXPECT().GetBoard(board.ID).Return(board, nil)
|
|
||||||
th.Store.EXPECT().GetMemberForBoard(board.ID, "user").Return(boardMember, nil)
|
|
||||||
th.Store.EXPECT().GetUserCategoryBoards("user", "test-team")
|
th.Store.EXPECT().GetUserCategoryBoards("user", "test-team")
|
||||||
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
|
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
|
||||||
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
||||||
|
@ -62,6 +60,64 @@ func TestApp_ImportArchive(t *testing.T) {
|
||||||
err := th.App.ImportArchive(r, opts)
|
err := th.App.ImportArchive(r, opts)
|
||||||
require.NoError(t, err, "import archive should not fail")
|
require.NoError(t, err, "import archive should not fail")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("import board archive", func(t *testing.T) {
|
||||||
|
r := bytes.NewReader([]byte(boardArchive))
|
||||||
|
opts := model.ImportArchiveOptions{
|
||||||
|
TeamID: "test-team",
|
||||||
|
ModifiedBy: "f1tydgc697fcbp8ampr6881jea",
|
||||||
|
}
|
||||||
|
|
||||||
|
bm1 := &model.BoardMember{
|
||||||
|
BoardID: board.ID,
|
||||||
|
UserID: "f1tydgc697fcbp8ampr6881jea",
|
||||||
|
}
|
||||||
|
|
||||||
|
bm2 := &model.BoardMember{
|
||||||
|
BoardID: board.ID,
|
||||||
|
UserID: "hxxzooc3ff8cubsgtcmpn8733e",
|
||||||
|
}
|
||||||
|
|
||||||
|
bm3 := &model.BoardMember{
|
||||||
|
BoardID: board.ID,
|
||||||
|
UserID: "nto73edn5ir6ifimo5a53y1dwa",
|
||||||
|
}
|
||||||
|
|
||||||
|
user1 := &model.User{
|
||||||
|
ID: "f1tydgc697fcbp8ampr6881jea",
|
||||||
|
}
|
||||||
|
|
||||||
|
user2 := &model.User{
|
||||||
|
ID: "hxxzooc3ff8cubsgtcmpn8733e",
|
||||||
|
}
|
||||||
|
|
||||||
|
user3 := &model.User{
|
||||||
|
ID: "nto73edn5ir6ifimo5a53y1dwa",
|
||||||
|
}
|
||||||
|
|
||||||
|
th.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), "f1tydgc697fcbp8ampr6881jea").Return(babs, nil)
|
||||||
|
th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{bm1, bm2, bm3}, nil)
|
||||||
|
th.Store.EXPECT().GetUserCategoryBoards("f1tydgc697fcbp8ampr6881jea", "test-team")
|
||||||
|
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
|
||||||
|
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
||||||
|
ID: "boards_category_id",
|
||||||
|
Name: "Boards",
|
||||||
|
}, nil)
|
||||||
|
th.Store.EXPECT().GetMembersForUser("f1tydgc697fcbp8ampr6881jea").Return([]*model.BoardMember{}, nil)
|
||||||
|
th.Store.EXPECT().GetBoardsForUserAndTeam("f1tydgc697fcbp8ampr6881jea", "test-team", false).Return([]*model.Board{}, nil)
|
||||||
|
th.Store.EXPECT().AddUpdateCategoryBoard("f1tydgc697fcbp8ampr6881jea", utils.Anything).Return(nil)
|
||||||
|
th.Store.EXPECT().GetBoard(board.ID).AnyTimes().Return(board, nil)
|
||||||
|
th.Store.EXPECT().GetMemberForBoard(board.ID, "f1tydgc697fcbp8ampr6881jea").AnyTimes().Return(bm1, nil)
|
||||||
|
th.Store.EXPECT().GetMemberForBoard(board.ID, "hxxzooc3ff8cubsgtcmpn8733e").AnyTimes().Return(bm2, nil)
|
||||||
|
th.Store.EXPECT().GetMemberForBoard(board.ID, "nto73edn5ir6ifimo5a53y1dwa").AnyTimes().Return(bm3, nil)
|
||||||
|
th.Store.EXPECT().GetUserByID("f1tydgc697fcbp8ampr6881jea").AnyTimes().Return(user1, nil)
|
||||||
|
th.Store.EXPECT().GetUserByID("hxxzooc3ff8cubsgtcmpn8733e").AnyTimes().Return(user2, nil)
|
||||||
|
th.Store.EXPECT().GetUserByID("nto73edn5ir6ifimo5a53y1dwa").AnyTimes().Return(user3, nil)
|
||||||
|
|
||||||
|
boardID, err := th.App.ImportBoardJSONL(r, opts)
|
||||||
|
require.Equal(t, board.ID, boardID, "Board ID should be same")
|
||||||
|
require.NoError(t, err, "import archive should not fail")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
|
@ -78,3 +134,12 @@ const asana = `{"version":1,"date":1614714686842}
|
||||||
{"type":"block","data":{"id":"db1dd596-0999-4741-8b05-72ca8e438e31","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"deaab476-c690-48df-828f-725b064dc476"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Approve campaign copy"}}
|
{"type":"block","data":{"id":"db1dd596-0999-4741-8b05-72ca8e438e31","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"deaab476-c690-48df-828f-725b064dc476"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Approve campaign copy"}}
|
||||||
{"type":"block","data":{"id":"16861c05-f31f-46af-8429-80a87b5aa93a","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"2138305a-3157-461c-8bbe-f19ebb55846d"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Send out updated attendee list"}}
|
{"type":"block","data":{"id":"16861c05-f31f-46af-8429-80a87b5aa93a","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"2138305a-3157-461c-8bbe-f19ebb55846d"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Send out updated attendee list"}}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const boardArchive = `{"type":"board","data":{"id":"bfoi6yy6pa3yzika53spj7pq9ee","teamId":"wsmqbtwb5jb35jb3mtp85c8a9h","channelId":"","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","type":"P","minimumRole":"","title":"Custom","description":"","icon":"","showDescription":false,"isTemplate":false,"templateVersion":0,"properties":{},"cardProperties":[{"id":"aonihehbifijmx56aqzu3cc7w1r","name":"Status","options":[],"type":"select"},{"id":"aohjkzt769rxhtcz1o9xcoce5to","name":"Person","options":[],"type":"person"}],"createAt":1672750481591,"updateAt":1672750481591,"deleteAt":0}}
|
||||||
|
{"type":"block","data":{"id":"ckpc3b1dp3pbw7bqntfryy9jbzo","parentId":"bjaqxtbyqz3bu7pgyddpgpms74a","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","schema":1,"type":"card","title":"Test","fields":{"contentOrder":[],"icon":"","isTemplate":false,"properties":{"aohjkzt769rxhtcz1o9xcoce5to":"hxxzooc3ff8cubsgtcmpn8733e"}},"createAt":1672750481612,"updateAt":1672845003530,"deleteAt":0,"boardId":"bfoi6yy6pa3yzika53spj7pq9ee"}}
|
||||||
|
{"type":"block","data":{"id":"v7tdajwpm47r3u8duedk89bhxar","parentId":"bpypang3a3errqstj1agx9kuqay","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","schema":1,"type":"view","title":"Board view","fields":{"cardOrder":["crsyw7tbr3pnjznok6ppngmmyya","c5titiemp4pgaxbs4jksgybbj4y"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"board","visibleOptionIds":[],"visiblePropertyIds":["aohjkzt769rxhtcz1o9xcoce5to"]},"createAt":1672750481626,"updateAt":1672750481626,"deleteAt":0,"boardId":"bfoi6yy6pa3yzika53spj7pq9ee"}}
|
||||||
|
{"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"f1tydgc697fcbp8ampr6881jea","roles":"","minimumRole":"","schemeAdmin":false,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":true,"synthetic":false}}
|
||||||
|
{"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"hxxzooc3ff8cubsgtcmpn8733e","roles":"","minimumRole":"","schemeAdmin":false,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":true,"synthetic":false}}
|
||||||
|
{"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"nto73edn5ir6ifimo5a53y1dwa","roles":"","minimumRole":"","schemeAdmin":true,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":false,"synthetic":false}}
|
||||||
|
`
|
||||||
|
|
Loading…
Reference in a new issue