1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-24 08:22:29 +02:00

Delete children when deleting boards and cards (#3943)

* delete and undelete handle children
This commit is contained in:
Doug Lauder 2022-11-08 11:42:01 -05:00 committed by GitHub
parent cf97e300c1
commit 8949c6b13f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 425 additions and 29 deletions

View File

@ -3,7 +3,7 @@ module github.com/mattermost/focalboard/server
go 1.18
require (
github.com/Masterminds/squirrel v1.5.2
github.com/Masterminds/squirrel v1.5.3
github.com/golang/mock v1.6.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0

View File

@ -87,6 +87,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE=
github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc=
github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=

View File

@ -196,6 +196,20 @@ func (mr *MockStoreMockRecorder) DeleteBlock(arg0, arg1 interface{}) *gomock.Cal
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlock", reflect.TypeOf((*MockStore)(nil).DeleteBlock), arg0, arg1)
}
// DeleteBlockRecord mocks base method.
func (m *MockStore) DeleteBlockRecord(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteBlockRecord", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteBlockRecord indicates an expected call of DeleteBlockRecord.
func (mr *MockStoreMockRecorder) DeleteBlockRecord(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlockRecord", reflect.TypeOf((*MockStore)(nil).DeleteBlockRecord), arg0, arg1)
}
// DeleteBoard mocks base method.
func (m *MockStore) DeleteBoard(arg0, arg1 string) error {
m.ctrl.T.Helper()
@ -210,6 +224,20 @@ func (mr *MockStoreMockRecorder) DeleteBoard(arg0, arg1 interface{}) *gomock.Cal
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBoard", reflect.TypeOf((*MockStore)(nil).DeleteBoard), arg0, arg1)
}
// DeleteBoardRecord mocks base method.
func (m *MockStore) DeleteBoardRecord(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteBoardRecord", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteBoardRecord indicates an expected call of DeleteBoardRecord.
func (mr *MockStoreMockRecorder) DeleteBoardRecord(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBoardRecord", reflect.TypeOf((*MockStore)(nil).DeleteBoardRecord), arg0, arg1)
}
// DeleteBoardsAndBlocks mocks base method.
func (m *MockStore) DeleteBoardsAndBlocks(arg0 *model.DeleteBoardsAndBlocks, arg1 string) error {
m.ctrl.T.Helper()

View File

@ -20,10 +20,16 @@ const (
descClause = " DESC "
)
type BoardIDNilError struct{}
type ErrEmptyBoardID struct{}
func (re BoardIDNilError) Error() string {
return "boardID is nil"
func (re ErrEmptyBoardID) Error() string {
return "boardID is empty"
}
type ErrLimitExceeded struct{ max int }
func (le ErrLimitExceeded) Error() string {
return fmt.Sprintf("limit exceeded (max=%d)", le.max)
}
func (s *SQLStore) timestampToCharField(name string, as string) string {
@ -231,7 +237,7 @@ func (s *SQLStore) blocksFromRows(rows *sql.Rows) ([]*model.Block, error) {
func (s *SQLStore) insertBlock(db sq.BaseRunner, block *model.Block, userID string) error {
if block.BoardID == "" {
return BoardIDNilError{}
return ErrEmptyBoardID{}
}
fieldsJSON, err := json.Marshal(block.Fields)
@ -339,7 +345,7 @@ func (s *SQLStore) patchBlocks(db sq.BaseRunner, blockPatches *model.BlockPatchB
func (s *SQLStore) insertBlocks(db sq.BaseRunner, blocks []*model.Block, userID string) error {
for _, block := range blocks {
if block.BoardID == "" {
return BoardIDNilError{}
return ErrEmptyBoardID{}
}
}
for i := range blocks {
@ -352,6 +358,10 @@ func (s *SQLStore) insertBlocks(db sq.BaseRunner, blocks []*model.Block, userID
}
func (s *SQLStore) deleteBlock(db sq.BaseRunner, blockID string, modifiedBy string) error {
return s.deleteBlockAndChildren(db, blockID, modifiedBy, false)
}
func (s *SQLStore) deleteBlockAndChildren(db sq.BaseRunner, blockID string, modifiedBy string, keepChildren bool) error {
block, err := s.getBlock(db, blockID)
if model.IsErrNotFound(err) {
s.logger.Warn("deleteBlock block not found", mlog.String("block_id", blockID))
@ -409,7 +419,11 @@ func (s *SQLStore) deleteBlock(db sq.BaseRunner, blockID string, modifiedBy stri
return err
}
return nil
if keepChildren {
return nil
}
return s.deleteBlockChildren(db, block.BoardID, block.ID, modifiedBy)
}
func (s *SQLStore) undeleteBlock(db sq.BaseRunner, blockID string, modifiedBy string) error {
@ -481,7 +495,7 @@ func (s *SQLStore) undeleteBlock(db sq.BaseRunner, blockID string, modifiedBy st
return err
}
return nil
return s.undeleteBlockChildren(db, block.BoardID, block.ID, modifiedBy)
}
func (s *SQLStore) getBlockCountsByType(db sq.BaseRunner) (map[string]int64, error) {
@ -620,7 +634,7 @@ func (s *SQLStore) getBlockHistoryDescendants(db sq.BaseRunner, boardID string,
rows, err := query.Query()
if err != nil {
s.logger.Error(`GetBlockHistory ERROR`, mlog.Err(err))
s.logger.Error(`GetBlockHistoryDescendants ERROR`, mlog.Err(err))
return nil, err
}
defer s.CloseRows(rows)
@ -789,3 +803,155 @@ func (s *SQLStore) duplicateBlock(db sq.BaseRunner, boardID string, blockID stri
}
return allBlocks, nil
}
func (s *SQLStore) deleteBlockChildren(db sq.BaseRunner, boardID string, parentID string, modifiedBy string) error {
now := utils.GetMillis()
selectQuery := s.getQueryBuilder(db).
Select(
"board_id",
"id",
"parent_id",
s.escapeField("schema"),
"type",
"title",
"fields",
"'"+modifiedBy+"'",
"create_at",
s.castInt(now, "update_at"),
s.castInt(now, "delete_at"),
"created_by",
).
From(s.tablePrefix + "blocks").
Where(sq.Eq{"board_id": boardID})
if parentID != "" {
selectQuery = selectQuery.Where(sq.Eq{"parent_id": parentID})
}
insertQuery := s.getQueryBuilder(db).
Insert(s.tablePrefix+"blocks_history").
Columns(
"board_id",
"id",
"parent_id",
s.escapeField("schema"),
"type",
"title",
"fields",
"modified_by",
"create_at",
"update_at",
"delete_at",
"created_by",
).Select(selectQuery)
if _, err := insertQuery.Exec(); err != nil {
return err
}
deleteQuery := s.getQueryBuilder(db).
Delete(s.tablePrefix + "blocks").
Where(sq.Eq{"board_id": boardID})
if parentID != "" {
deleteQuery = deleteQuery.Where(sq.Eq{"parent_id": parentID})
}
if _, err := deleteQuery.Exec(); err != nil {
return err
}
return nil
}
func (s *SQLStore) undeleteBlockChildren(db sq.BaseRunner, boardID string, parentID string, modifiedBy string) error {
if boardID == "" {
return ErrEmptyBoardID{}
}
where := fmt.Sprintf("board_id='%s'", boardID)
if parentID != "" {
where += fmt.Sprintf(" AND parent_id='%s'", parentID)
}
selectQuery := s.getQueryBuilder(db).
Select(
"bh.board_id",
"'' AS channel_id",
"bh.id",
"bh.parent_id",
"bh.schema",
"bh.type",
"bh.title",
"bh.fields",
"'"+modifiedBy+"' AS modified_by",
"bh.create_at",
s.castInt(utils.GetMillis(), "update_at"),
s.castInt(0, "delete_at"),
"bh.created_by",
).
From(fmt.Sprintf(`
%sblocks_history AS bh,
(SELECT id, max(insert_at) AS max_insert_at FROM %sblocks_history WHERE %s GROUP BY id) AS sub`,
s.tablePrefix, s.tablePrefix, where)).
Where("bh.id=sub.id").
Where("bh.insert_at=sub.max_insert_at").
Where(sq.NotEq{"bh.delete_at": 0})
columns := []string{
"board_id",
"channel_id",
"id",
"parent_id",
s.escapeField("schema"),
"type",
"title",
"fields",
"modified_by",
"create_at",
"update_at",
"delete_at",
"created_by",
}
insertQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + "blocks").
Columns(columns...).
Select(selectQuery)
insertHistoryQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + "blocks_history").
Columns(columns...).
Select(selectQuery)
sql, args, err := insertQuery.ToSql()
s.logger.Trace("undeleteBlockChildren - insertQuery",
mlog.String("sql", sql),
mlog.Array("args", args),
mlog.Err(err),
)
sql, args, err = insertHistoryQuery.ToSql()
s.logger.Trace("undeleteBlockChildren - insertHistoryQuery",
mlog.String("sql", sql),
mlog.Array("args", args),
mlog.Err(err),
)
// insert into blocks table must happen before history table, otherwise the history
// table will be changed and the second query will fail to find the same records.
result, err := insertQuery.Exec()
if err != nil {
return err
}
rowsAffected, _ := result.RowsAffected()
s.logger.Debug("undeleteBlockChildren - insertQuery", mlog.Int64("rows_affected", rowsAffected))
result, err = insertHistoryQuery.Exec()
if err != nil {
return err
}
rowsAffected, _ = result.RowsAffected()
s.logger.Debug("undeleteBlockChildren - insertHistoryQuery", mlog.Int64("rows_affected", rowsAffected))
return nil
}

View File

@ -422,6 +422,10 @@ func (s *SQLStore) patchBoard(db sq.BaseRunner, boardID string, boardPatch *mode
}
func (s *SQLStore) deleteBoard(db sq.BaseRunner, boardID, userID string) error {
return s.deleteBoardAndChildren(db, boardID, userID, false)
}
func (s *SQLStore) deleteBoardAndChildren(db sq.BaseRunner, boardID, userID string, keepChildren bool) error {
now := utils.GetMillis()
board, err := s.getBoard(db, boardID)
@ -477,7 +481,11 @@ func (s *SQLStore) deleteBoard(db sq.BaseRunner, boardID, userID string) error {
return err
}
return nil
if keepChildren {
return nil
}
return s.deleteBlockChildren(db, boardID, "", userID)
}
func (s *SQLStore) insertBoardWithAdmin(db sq.BaseRunner, board *model.Board, userID string) (*model.Board, *model.BoardMember, error) {
@ -861,7 +869,7 @@ func (s *SQLStore) undeleteBoard(db sq.BaseRunner, boardID string, modifiedBy st
return err
}
return nil
return s.undeleteBlockChildren(db, board.ID, "", modifiedBy)
}
func (s *SQLStore) getBoardMemberHistory(db sq.BaseRunner, boardID, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error) {

View File

@ -102,13 +102,11 @@ func (s *SQLStore) patchBoardsAndBlocks(db sq.BaseRunner, pbab *model.PatchBoard
func (s *SQLStore) deleteBoardsAndBlocks(db sq.BaseRunner, dbab *model.DeleteBoardsAndBlocks, userID string) error {
boardIDMap := map[string]bool{}
for _, boardID := range dbab.Boards {
if err := s.deleteBoard(db, boardID, userID); err != nil {
return err
}
boardIDMap[boardID] = true
}
// delete the blocks first, since deleting the board will clean up any children and we'll get
// not found errors when deleting the blocks after.
for _, blockID := range dbab.Blocks {
block, err := s.getBlock(db, blockID)
if err != nil {
@ -124,6 +122,12 @@ func (s *SQLStore) deleteBoardsAndBlocks(db sq.BaseRunner, dbab *model.DeleteBoa
}
}
for _, boardID := range dbab.Boards {
if err := s.deleteBoard(db, boardID, userID); err != nil {
return err
}
}
return nil
}

View File

@ -59,6 +59,7 @@ func legacyBoardFields(prefix string) []string {
// legacyBlocksFromRows is the old getBlock version that still uses
// the old block model. This method is kept to enable the unique IDs
// data migration.
//
//nolint:unused
func (s *SQLStore) legacyBlocksFromRows(rows *sql.Rows) ([]*model.Block, error) {
results := []*model.Block{}
@ -112,6 +113,7 @@ func (s *SQLStore) legacyBlocksFromRows(rows *sql.Rows) ([]*model.Block, error)
// getLegacyBlock is the old getBlock version that still uses the old
// block model. This method is kept to enable the unique IDs data
// migration.
//
//nolint:unused
func (s *SQLStore) getLegacyBlock(db sq.BaseRunner, workspaceID string, blockID string) (*model.Block, error) {
query := s.getQueryBuilder(db).
@ -156,10 +158,11 @@ func (s *SQLStore) getLegacyBlock(db sq.BaseRunner, workspaceID string, blockID
// insertLegacyBlock is the old insertBlock version that still uses
// the old block model. This method is kept to enable the unique IDs
// data migration.
//
//nolint:unused
func (s *SQLStore) insertLegacyBlock(db sq.BaseRunner, workspaceID string, block *model.Block, userID string) error {
if block.BoardID == "" {
return BoardIDNilError{}
return ErrEmptyBoardID{}
}
fieldsJSON, err := json.Marshal(block.Fields)

View File

@ -148,6 +148,11 @@ func (s *SQLStore) DeleteBlock(blockID string, modifiedBy string) error {
}
func (s *SQLStore) DeleteBlockRecord(blockID string, modifiedBy string) error {
return s.deleteBlockRecord(s.db, blockID, modifiedBy)
}
func (s *SQLStore) DeleteBoard(boardID string, userID string) error {
if s.dbType == model.SqliteDBType {
return s.deleteBoard(s.db, boardID, userID)
@ -172,6 +177,11 @@ func (s *SQLStore) DeleteBoard(boardID string, userID string) error {
}
func (s *SQLStore) DeleteBoardRecord(boardID string, modifiedBy string) error {
return s.deleteBoardRecord(s.db, boardID, modifiedBy)
}
func (s *SQLStore) DeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks, userID string) error {
if s.dbType == model.SqliteDBType {
return s.deleteBoardsAndBlocks(s.db, dbab, userID)

View File

@ -7,6 +7,7 @@ import (
"os"
"strings"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/utils"
@ -118,3 +119,22 @@ func newErrInvalidDBType(dbType string) error {
func (e ErrInvalidDBType) Error() string {
return "unsupported database type: " + e.dbType
}
// deleteBoardRecord deletes a boards record without deleting any child records in the blocks table.
// FOR UNIT TESTING ONLY.
func (s *SQLStore) deleteBoardRecord(db sq.BaseRunner, boardID string, modifiedBy string) error {
return s.deleteBoardAndChildren(db, boardID, modifiedBy, true)
}
// deleteBlockRecord deletes a blocks record without deleting any child records in the blocks table.
// FOR UNIT TESTING ONLY.
func (s *SQLStore) deleteBlockRecord(db sq.BaseRunner, blockID, modifiedBy string) error {
return s.deleteBlockAndChildren(db, blockID, modifiedBy, true)
}
func (s *SQLStore) castInt(val int64, as string) string {
if s.dbType == model.MysqlDBType {
return fmt.Sprintf("cast(%d as unsigned) AS %s", val, as)
}
return fmt.Sprintf("cast(%d as bigint) AS %s", val, as)
}

View File

@ -165,6 +165,10 @@ type Store interface {
GetTeamBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error)
GetUserBoardsInsights(teamID string, userID string, since int64, offset int, limit int, boardIDs []string) (*model.BoardInsightsList, error)
GetUserTimezone(userID string) (string, error)
// For unit testing only
DeleteBoardRecord(boardID, modifiedBy string) error
DeleteBlockRecord(blockID, modifiedBy string) error
}
type NotSupportedError struct {

View File

@ -74,6 +74,11 @@ func StoreTestBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, f
defer tearDown()
testGetBlockMetadata(t, store)
})
t.Run("UndeleteBlockChildren", func(t *testing.T) {
store, tearDown := setup(t)
defer tearDown()
testUndeleteBlockChildren(t, store)
})
}
func testInsertBlock(t *testing.T, store store.Store) {
@ -1021,6 +1026,7 @@ func testGetBlockMetadata(t *testing.T, store store.Store) {
t.Run("get full block history after delete", func(t *testing.T) {
time.Sleep(20 * time.Millisecond)
// this will delete `block1` and any other blocks with `block1` as parent.
err = store.DeleteBlock(blocksToInsert[0].ID, testUserID)
require.NoError(t, err)
@ -1029,15 +1035,14 @@ func testGetBlockMetadata(t *testing.T, store store.Store) {
}
blocks, err = store.GetBlockHistoryDescendants(boardID, opts)
require.NoError(t, err)
require.Len(t, blocks, 6)
expectedBlock := blocksToInsert[0]
block := blocks[0]
require.Equal(t, expectedBlock.ID, block.ID)
// all 5 blocks get a history record for insert, then `block1` gets a record for delete,
// and all 3 `block1` children get a record for delete. Thus total is 9.
require.Len(t, blocks, 9)
})
t.Run("get full block history after undelete", func(t *testing.T) {
time.Sleep(20 * time.Millisecond)
// this will undelete `block1` and its children
err = store.UndeleteBlock(blocksToInsert[0].ID, testUserID)
require.NoError(t, err)
@ -1046,11 +1051,9 @@ func testGetBlockMetadata(t *testing.T, store store.Store) {
}
blocks, err = store.GetBlockHistoryDescendants(boardID, opts)
require.NoError(t, err)
require.Len(t, blocks, 7)
expectedBlock := blocksToInsert[0]
block := blocks[0]
require.Equal(t, expectedBlock.ID, block.ID)
// previous test put 9 records in history table. In this test 1 record was added for undeleting
// `block1` and another 3 for undeleting the children for a total of 13.
require.Len(t, blocks, 13)
})
t.Run("get block history of a board with no history", func(t *testing.T) {
@ -1061,3 +1064,92 @@ func testGetBlockMetadata(t *testing.T, store store.Store) {
require.Empty(t, blocks)
})
}
func testUndeleteBlockChildren(t *testing.T, store store.Store) {
boards := createTestBoards(t, store, testUserID, 2)
boardDelete := boards[0]
boardKeep := boards[1]
// create some blocks to be deleted
cardsDelete := createTestCards(t, store, testUserID, boardDelete.ID, 3)
blocksDelete := createTestBlocksForCard(t, store, cardsDelete[0].ID, 5)
require.Len(t, blocksDelete, 5)
// create some blocks to keep
cardsKeep := createTestCards(t, store, testUserID, boardKeep.ID, 3)
blocksKeep := createTestBlocksForCard(t, store, cardsKeep[0].ID, 4)
require.Len(t, blocksKeep, 4)
t.Run("undelete block children for card", func(t *testing.T) {
cardDelete := cardsDelete[0]
cardKeep := cardsKeep[0]
// delete a card
err := store.DeleteBlock(cardDelete.ID, testUserID)
require.NoError(t, err)
// ensure the card was deleted
block, err := store.GetBlock(cardDelete.ID)
require.Error(t, err)
require.Nil(t, block)
// ensure the card children were deleted
blocks, err := store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText)
require.NoError(t, err)
assert.Empty(t, blocks)
// ensure the other card children remain.
blocks, err = store.GetBlocksWithParentAndType(cardKeep.BoardID, cardKeep.ID, model.TypeText)
require.NoError(t, err)
assert.Len(t, blocks, len(blocksKeep))
// undelete the card
err = store.UndeleteBlock(cardDelete.ID, testUserID)
require.NoError(t, err)
// ensure the card was restored
block, err = store.GetBlock(cardDelete.ID)
require.NoError(t, err)
require.NotNil(t, block)
// ensure the card children were restored
blocks, err = store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText)
require.NoError(t, err)
assert.Len(t, blocks, len(blocksDelete))
})
t.Run("undelete block children for board", func(t *testing.T) {
// delete the board
err := store.DeleteBoard(boardDelete.ID, testUserID)
require.NoError(t, err)
// ensure the board was deleted
board, err := store.GetBoard(boardDelete.ID)
require.Error(t, err)
require.Nil(t, board)
// ensure all cards and blocks for the board were deleted
blocks, err := store.GetBlocksForBoard(boardDelete.ID)
require.NoError(t, err)
assert.Empty(t, blocks)
// ensure the other board's cards and blocks remain.
blocks, err = store.GetBlocksForBoard(boardKeep.ID)
require.NoError(t, err)
assert.Len(t, blocks, len(blocksKeep)+len(cardsKeep))
// undelete the board
err = store.UndeleteBoard(boardDelete.ID, testUserID)
require.NoError(t, err)
// ensure the board was restored
board, err = store.GetBoard(boardDelete.ID)
require.NoError(t, err)
require.NotNil(t, board)
// ensure the board's cards and blocks were restored.
blocks, err = store.GetBlocksForBoard(boardDelete.ID)
require.NoError(t, err)
assert.Len(t, blocks, len(blocksDelete)+len(cardsDelete))
})
}

View File

@ -10,6 +10,7 @@ import (
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/store"
"github.com/mattermost/focalboard/server/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -36,7 +37,7 @@ func createTestBlocks(t *testing.T, store store.Store, userID string, num int) [
block := &model.Block{
ID: utils.NewID(utils.IDTypeBlock),
BoardID: utils.NewID(utils.IDTypeBoard),
Type: "card",
Type: model.TypeCard,
CreatedBy: userID,
}
err := store.InsertBlock(block, userID)
@ -46,3 +47,63 @@ func createTestBlocks(t *testing.T, store store.Store, userID string, num int) [
}
return blocks
}
func createTestBlocksForCard(t *testing.T, store store.Store, cardID string, num int) []*model.Block {
card, err := store.GetBlock(cardID)
require.NoError(t, err)
assert.EqualValues(t, model.TypeCard, card.Type)
var blocks []*model.Block
for i := 0; i < num; i++ {
block := &model.Block{
ID: utils.NewID(utils.IDTypeBlock),
BoardID: card.BoardID,
Type: model.TypeText,
CreatedBy: card.CreatedBy,
ParentID: card.ID,
Title: fmt.Sprintf("text %d", i),
}
err := store.InsertBlock(block, card.CreatedBy)
require.NoError(t, err)
blocks = append(blocks, block)
}
return blocks
}
func createTestCards(t *testing.T, store store.Store, userID string, boardID string, num int) []*model.Block {
var blocks []*model.Block
for i := 0; i < num; i++ {
block := &model.Block{
ID: utils.NewID(utils.IDTypeCard),
BoardID: boardID,
ParentID: boardID,
Type: model.TypeCard,
CreatedBy: userID,
Title: fmt.Sprintf("card %d", i),
}
err := store.InsertBlock(block, userID)
require.NoError(t, err)
blocks = append(blocks, block)
}
return blocks
}
func createTestBoards(t *testing.T, store store.Store, userID string, num int) []*model.Board {
var boards []*model.Board
for i := 0; i < num; i++ {
board := &model.Board{
ID: utils.NewID(utils.IDTypeBoard),
TeamID: testTeamID,
Type: "O",
CreatedBy: userID,
Title: fmt.Sprintf("board %d", i),
}
boardNew, err := store.InsertBoard(board, userID)
require.NoError(t, err)
boards = append(boards, boardNew)
}
return boards
}

View File

@ -2,7 +2,6 @@ package utils
import (
"context"
"runtime/debug"
"sync/atomic"
"time"
@ -123,7 +122,6 @@ func (cn *CallbackQueue) exec(f CallbackFunc) {
cn.logger.Error("CallbackQueue callback panic",
mlog.String("name", cn.name),
mlog.Any("panic", r),
mlog.String("stack", string(debug.Stack())),
)
}
}()