From 2c7509768026b26945c249c5173adcb9fdc32a08 Mon Sep 17 00:00:00 2001 From: wiggin77 Date: Tue, 12 Apr 2022 23:41:32 -0400 Subject: [PATCH 1/3] unit test for export board --- server/client/client.go | 39 ++++++++++++++ server/integrationtests/clienttestlib.go | 2 +- server/integrationtests/export_test.go | 66 ++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 server/integrationtests/export_test.go diff --git a/server/client/client.go b/server/client/client.go index bb53d999b..0879002fd 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -683,3 +683,42 @@ func (c *Client) GetTemplatesForTeam(teamID string) ([]*model.Board, *Response) return model.BoardsFromJSON(r.Body), BuildResponse(r) } + +func (c *Client) ExportBoardArchive(boardID string) ([]byte, *Response) { + r, err := c.DoAPIGet(c.GetBoardRoute(boardID)+"/archive/export", "") + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + + buf, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, BuildErrorResponse(r, err) + } + return buf, BuildResponse(r) +} + +func (c *Client) ImportArchive(teamID string, data io.Reader) *Response { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile(api.UploadFormFileKey, "file") + if err != nil { + return &Response{Error: err} + } + if _, err = io.Copy(part, data); err != nil { + return &Response{Error: err} + } + writer.Close() + + opt := func(r *http.Request) { + r.Header.Add("Content-Type", writer.FormDataContentType()) + } + + r, err := c.doAPIRequestReader(http.MethodPost, c.APIURL+c.GetTeamRoute(teamID)+"/archive/import", body, "", opt) + if err != nil { + return BuildErrorResponse(r, err) + } + defer closeBody(r) + + return BuildResponse(r) +} diff --git a/server/integrationtests/clienttestlib.go b/server/integrationtests/clienttestlib.go index 4579ffde8..70306077e 100644 --- a/server/integrationtests/clienttestlib.go +++ b/server/integrationtests/clienttestlib.go @@ -268,7 +268,7 @@ func (th *TestHelper) InitBasic() *TestHelper { th.RegisterAndLogin(th.Client, user1Username, "user1@sample.com", password, "") // get token - team, resp := th.Client.GetTeam("0") + team, resp := th.Client.GetTeam(model.GlobalTeamID) th.CheckOK(resp) require.NotNil(th.T, team) require.NotNil(th.T, team.SignupToken) diff --git a/server/integrationtests/export_test.go b/server/integrationtests/export_test.go new file mode 100644 index 000000000..49b10d404 --- /dev/null +++ b/server/integrationtests/export_test.go @@ -0,0 +1,66 @@ +package integrationtests + +import ( + "bytes" + "testing" + + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/focalboard/server/utils" + "github.com/stretchr/testify/require" +) + +func TestExportBoard(t *testing.T) { + t.Run("export single board", func(t *testing.T) { + th := SetupTestHelper(t).InitBasic() + defer th.TearDown() + + board := &model.Board{ + ID: utils.NewID(utils.IDTypeBoard), + TeamID: "test-team", + Title: "Export Test Board", + CreatedBy: th.GetUser1().ID, + Type: model.BoardTypeOpen, + CreateAt: utils.GetMillis(), + UpdateAt: utils.GetMillis(), + } + + block := model.Block{ + ID: utils.NewID(utils.IDTypeCard), + ParentID: board.ID, + Type: model.TypeCard, + BoardID: board.ID, + Title: "Test card # for export", + CreatedBy: th.GetUser1().ID, + CreateAt: utils.GetMillis(), + UpdateAt: utils.GetMillis(), + } + + babs := &model.BoardsAndBlocks{ + Boards: []*model.Board{board}, + Blocks: []model.Block{block}, + } + + babs, resp := th.Client.CreateBoardsAndBlocks(babs) + th.CheckOK(resp) + + // export the board to an in-memory archive file + buf, resp := th.Client.ExportBoardArchive(babs.Boards[0].ID) + th.CheckOK(resp) + require.NotNil(t, buf) + + // import the archive file to team 0 + resp = th.Client.ImportArchive(model.GlobalTeamID, bytes.NewReader(buf)) + th.CheckOK(resp) + require.NoError(t, resp.Error) + + // check for test card + boardsImported, err := th.Server.App().GetBoardsForUserAndTeam(th.GetUser1().ID, model.GlobalTeamID) + require.NoError(t, err) + require.Len(t, boardsImported, 1) + boardImported := boardsImported[0] + blocksImported, err := th.Server.App().GetBlocksForBoard(boardImported.ID) + require.NoError(t, err) + require.Len(t, blocksImported, 1) + require.Equal(t, block.Title, blocksImported[0].Title) + }) +} From 471096d3ae5d673ad7218eebf50f96f8a15bb9da Mon Sep 17 00:00:00 2001 From: wiggin77 Date: Wed, 13 Apr 2022 10:10:29 -0400 Subject: [PATCH 2/3] fix hash sign in block title for CSV export --- webapp/src/csvExporter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webapp/src/csvExporter.ts b/webapp/src/csvExporter.ts index 2737dc4a6..b7f4492e9 100644 --- a/webapp/src/csvExporter.ts +++ b/webapp/src/csvExporter.ts @@ -10,6 +10,7 @@ import {Utils} from './utils' import {IAppWindow} from './types' declare let window: IAppWindow +const hashSignToken = '___hash_sign___' class CsvExporter { static exportTableCsv(board: Board, activeView: BoardView, cards: Card[], intl: IntlShape, view?: BoardView): void { @@ -28,8 +29,9 @@ class CsvExporter { csvContent += encodedRow + '\r\n' }) + const encodedUri = encodeURI(csvContent).replace(hashSignToken, '%23') + const filename = `${Utils.sanitizeFilename(viewToExport.title || 'Untitled')}.csv` - const encodedUri = encodeURI(csvContent) const link = document.createElement('a') link.style.display = 'none' link.setAttribute('href', encodedUri) @@ -47,6 +49,7 @@ class CsvExporter { } private static encodeText(text: string): string { + text = text.replace(/#/g, hashSignToken) return text.replace(/"/g, '""') } From 92c0a889b6d9743c9d3631067997360532817914 Mon Sep 17 00:00:00 2001 From: wiggin77 Date: Wed, 13 Apr 2022 10:22:22 -0400 Subject: [PATCH 3/3] minor edit --- webapp/src/csvExporter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webapp/src/csvExporter.ts b/webapp/src/csvExporter.ts index b7f4492e9..5c5585a15 100644 --- a/webapp/src/csvExporter.ts +++ b/webapp/src/csvExporter.ts @@ -49,8 +49,7 @@ class CsvExporter { } private static encodeText(text: string): string { - text = text.replace(/#/g, hashSignToken) - return text.replace(/"/g, '""') + return text.replace(/"/g, '""').replace(/#/g, hashSignToken) } private static generateTableArray(board: Board, cards: Card[], viewToExport: BoardView, intl: IntlShape): string[][] {