Skip to content

Commit

Permalink
Merge branch 'main' into compliance-history-export
Browse files Browse the repository at this point in the history
  • Loading branch information
mattermost-build authored Jan 21, 2023
2 parents ca704a2 + 50e15f3 commit a422f21
Show file tree
Hide file tree
Showing 22 changed files with 253 additions and 60 deletions.
5 changes: 5 additions & 0 deletions mattermost-plugin/product/api_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ func (a *serviceAPIAdapter) GetDirectChannel(userID1, userID2 string) (*mm_model
return channel, normalizeAppErr(appErr)
}

func (a *serviceAPIAdapter) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) {
channel, appErr := a.api.channelService.GetDirectChannelOrCreate(userID1, userID2)
return channel, normalizeAppErr(appErr)
}

func (a *serviceAPIAdapter) GetChannelByID(channelID string) (*mm_model.Channel, error) {
channel, appErr := a.api.channelService.GetChannelByID(channelID)
return channel, normalizeAppErr(appErr)
Expand Down
6 changes: 6 additions & 0 deletions mattermost-plugin/server/api_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ func (a *pluginAPIAdapter) GetDirectChannel(userID1, userID2 string) (*mm_model.
return channel, normalizeAppErr(appErr)
}

func (a *pluginAPIAdapter) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) {
// plugin API's GetDirectChannel will create channel if it does not exist.
channel, appErr := a.api.GetDirectChannel(userID1, userID2)
return channel, normalizeAppErr(appErr)
}

func (a *pluginAPIAdapter) GetChannelByID(channelID string) (*mm_model.Channel, error) {
channel, appErr := a.api.GetChannel(channelID)
return channel, normalizeAppErr(appErr)
Expand Down
3 changes: 3 additions & 0 deletions mattermost-plugin/server/boards/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ func (b *BoardsApp) OnConfigurationChange() error {
if mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true {
enableShareBoards = true
}
if mmconfig.ProductSettings.EnablePublicSharedBoards != nil {
enableShareBoards = *mmconfig.ProductSettings.EnablePublicSharedBoards
}
configuration := &configuration{
EnablePublicSharedBoards: enableShareBoards,
}
Expand Down
3 changes: 1 addition & 2 deletions mattermost-plugin/server/manifest.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 38 additions & 2 deletions server/app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,25 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
return err
}
if block.Type == model.TypeImage {
filename, err := extractImageFilename(block)
if err != nil {
filename, err2 := extractImageFilename(block)
if err2 != nil {
return err
}
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
for _, filename := range files {
if err := a.writeArchiveFile(zw, filename, board.ID, opt); err != nil {
Expand All @@ -109,6 +120,31 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp
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.
func (a *App) writeArchiveBlockLine(w io.Writer, block *model.Block) error {
b, err := json.Marshal(&block)
Expand Down
38 changes: 29 additions & 9 deletions server/app/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
}
now := utils.GetMillis()
var boardID string
var boardMembers []*model.BoardMember

lineNum := 1
firstLine := true
Expand Down Expand Up @@ -196,6 +197,12 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
block.UpdateAt = now
block.BoardID = boardID
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:
return "", model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type)
}
Expand All @@ -212,6 +219,13 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
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)

var err error
Expand All @@ -225,16 +239,22 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str
return "", fmt.Errorf("error inserting archive blocks: %w", err)
}

// add user to all the new boards (if not the fake system user).
if opt.ModifiedBy != model.SystemUserID {
for _, board := range boardsAndBlocks.Boards {
boardMember := &model.BoardMember{
BoardID: board.ID,
UserID: opt.ModifiedBy,
SchemeAdmin: true,
// add users to all the new boards (if not the fake system user).
for _, board := range boardsAndBlocks.Boards {
for _, boardMember := range boardMembers {
bm := &model.BoardMember{
BoardID: board.ID,
UserID: boardMember.UserID,
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 {
return "", fmt.Errorf("cannot add member to board: %w", err)
if _, err2 := a.AddMemberToBoard(bm); err2 != nil {
return "", fmt.Errorf("cannot add member to board: %w", err2)
}
}
}
Expand Down
69 changes: 67 additions & 2 deletions server/app/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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().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().CreateCategory(utils.Anything).Return(nil)
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
Expand All @@ -62,6 +60,64 @@ func TestApp_ImportArchive(t *testing.T) {
err := th.App.ImportArchive(r, opts)
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
Expand All @@ -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":"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}}
`
15 changes: 15 additions & 0 deletions server/model/mocks/mockservicesapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/model/services_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var FocalboardBot = &mm_model.Bot{
type ServicesAPI interface {
// Channels service
GetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error)
GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error)
GetChannelByID(channelID string) (*mm_model.Channel, error)
GetChannelMember(channelID string, userID string) (*mm_model.ChannelMember, error)
GetChannelsForTeamForUser(teamID string, userID string, includeDeleted bool) (mm_model.ChannelList, error)
Expand Down
6 changes: 3 additions & 3 deletions server/services/notify/plugindelivery/plugin_delivery.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
)

type servicesAPI interface {
// GetDirectChannel gets a direct message channel.
// If the channel does not exist it will create it.
GetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error)
// GetDirectChannelOrCreate gets a direct message channel,
// or creates one if it does not already exist
GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error)

// CreatePost creates a post.
CreatePost(post *mm_model.Post) (*mm_model.Post, error)
Expand Down
4 changes: 2 additions & 2 deletions server/services/notify/plugindelivery/subscription_deliver.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (pd *PluginDelivery) getDirectChannelID(teamID string, subscriberID string,
return "", fmt.Errorf("cannot find user: %w", err)
}
channel, err := pd.getDirectChannel(teamID, user.Id, botID)
if err != nil {
if err != nil || channel == nil {
return "", fmt.Errorf("cannot get direct channel: %w", err)
}
return channel.Id, nil
Expand All @@ -70,5 +70,5 @@ func (pd *PluginDelivery) getDirectChannel(teamID string, userID string, botID s
if err != nil {
return nil, fmt.Errorf("cannot add bot to team %s: %w", teamID, err)
}
return pd.api.GetDirectChannel(userID, botID)
return pd.api.GetDirectChannelOrCreate(userID, botID)
}
4 changes: 4 additions & 0 deletions server/services/notify/plugindelivery/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ func (m servicesAPIMock) GetDirectChannel(userID1, userID2 string) (*mm_model.Ch
return nil, nil
}

func (m servicesAPIMock) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) {
return nil, nil
}

func (m servicesAPIMock) CreatePost(post *mm_model.Post) (*mm_model.Post, error) {
return post, nil
}
Expand Down
2 changes: 1 addition & 1 deletion webapp/cypress/integration/createBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('Create and delete board / card', () => {
cy.contains('Project Tasks').should('exist')

// Create empty board
cy.contains('Create empty board').should('exist').click({force: true})
cy.contains('Create an empty board').should('exist').click({force: true})
cy.get('.BoardComponent').should('exist')
cy.get('.Editable.title').invoke('attr', 'placeholder').should('contain', 'Untitled board')

Expand Down
2 changes: 1 addition & 1 deletion webapp/cypress/support/ui_commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ Cypress.Commands.add('uiCreateEmptyBoard', () => {
cy.log('Create new empty board')

cy.contains('+ Add board').should('be.visible').click().wait(500)
return cy.contains('Create empty board').click({force: true}).wait(1000)
return cy.contains('Create an empty board').click({force: true}).wait(1000)
})
Loading

0 comments on commit a422f21

Please sign in to comment.