mirror of
https://github.com/mattermost/focalboard.git
synced 2024-11-24 08:22:29 +02:00
Hide board feature (#4409)
* WIP * Added migrations * Updating store method * WIP * WIP * Updated DND * WIP * WIP * WIP * WIP * WIP * wip * WIP * Adding new DB tool * Used migration functions in new migrations * Unique constraint migration * Unique constraint migration * Added SQLITE migrations * Added SQLITE support in few more migrations * Added SQLITE support in few more migrations * WIP * Used old-fashioned way to add unique constraint * Using oldsqlite method * Using oldsqlite method * Fixed all store and app layer tests * fixed integration tests * test and lint fix * Updated migration for MySQL and Postgres on personal server * Types fix * sqlite fix * fix typo * misc cleanup * added new tests * added new tests * de-duping input for postgres * integration tests, rmeoved uneeded migration * Added some migration tests * Added some migration tests * Fixed a test * completed migration tests * completed migration tests * Removed leftover debug statements Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
d6207dde6c
commit
03f4717e96
@ -20,6 +20,8 @@ func (a *API) registerCategoriesRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleGetUserCategoryBoards)).Methods(http.MethodGet)
|
||||
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/reorder", a.sessionRequired(a.handleReorderCategoryBoards)).Methods(http.MethodPut)
|
||||
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}", a.sessionRequired(a.handleUpdateCategoryBoard)).Methods(http.MethodPost)
|
||||
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide", a.sessionRequired(a.handleHideBoard)).Methods(http.MethodPut)
|
||||
r.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}/unhide", a.sessionRequired(a.handleUnhideBoard)).Methods(http.MethodPut)
|
||||
}
|
||||
|
||||
func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) {
|
||||
@ -355,7 +357,7 @@ func (a *API) handleUpdateCategoryBoard(w http.ResponseWriter, r *http.Request)
|
||||
userID := session.UserID
|
||||
|
||||
// TODO: Check the category and the team matches
|
||||
err := a.app.AddUpdateUserCategoryBoard(teamID, userID, map[string]string{boardID: categoryID})
|
||||
err := a.app.AddUpdateUserCategoryBoard(teamID, userID, categoryID, []string{boardID})
|
||||
if err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
@ -521,3 +523,125 @@ func (a *API) handleReorderCategoryBoards(w http.ResponseWriter, r *http.Request
|
||||
jsonBytesResponse(w, http.StatusOK, data)
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func (a *API) handleHideBoard(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide hideBoard
|
||||
//
|
||||
// Hide the specified board for the user
|
||||
//
|
||||
// ---
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: teamID
|
||||
// in: path
|
||||
// description: Team ID
|
||||
// required: true
|
||||
// type: string
|
||||
// - name: categoryID
|
||||
// in: path
|
||||
// description: Category ID to which the board to be hidden belongs to
|
||||
// required: true
|
||||
// type: string
|
||||
// - name: boardID
|
||||
// in: path
|
||||
// description: ID of board to be hidden
|
||||
// required: true
|
||||
// type: string
|
||||
// security:
|
||||
// - BearerAuth: []
|
||||
// responses:
|
||||
// '200':
|
||||
// description: success
|
||||
// schema:
|
||||
// "$ref": "#/definitions/Category"
|
||||
// default:
|
||||
// description: internal error
|
||||
// schema:
|
||||
// "$ref": "#/definitions/ErrorResponse"
|
||||
|
||||
userID := getUserID(r)
|
||||
vars := mux.Vars(r)
|
||||
teamID := vars["teamID"]
|
||||
boardID := vars["boardID"]
|
||||
categoryID := vars["categoryID"]
|
||||
|
||||
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
|
||||
a.errorResponse(w, r, model.NewErrPermission("access denied to category"))
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := a.makeAuditRecord(r, "hideBoard", audit.Fail)
|
||||
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
||||
auditRec.AddMeta("board_id", boardID)
|
||||
auditRec.AddMeta("team_id", teamID)
|
||||
auditRec.AddMeta("category_id", categoryID)
|
||||
|
||||
if err := a.app.SetBoardVisibility(teamID, userID, categoryID, boardID, false); err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonStringResponse(w, http.StatusOK, "{}")
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func (a *API) handleUnhideBoard(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide unhideBoard
|
||||
//
|
||||
// Unhides the specified board for the user
|
||||
//
|
||||
// ---
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: teamID
|
||||
// in: path
|
||||
// description: Team ID
|
||||
// required: true
|
||||
// type: string
|
||||
// - name: categoryID
|
||||
// in: path
|
||||
// description: Category ID to which the board to be unhidden belongs to
|
||||
// required: true
|
||||
// type: string
|
||||
// - name: boardID
|
||||
// in: path
|
||||
// description: ID of board to be unhidden
|
||||
// required: true
|
||||
// type: string
|
||||
// security:
|
||||
// - BearerAuth: []
|
||||
// responses:
|
||||
// '200':
|
||||
// description: success
|
||||
// schema:
|
||||
// "$ref": "#/definitions/Category"
|
||||
// default:
|
||||
// description: internal error
|
||||
// schema:
|
||||
// "$ref": "#/definitions/ErrorResponse"
|
||||
|
||||
userID := getUserID(r)
|
||||
vars := mux.Vars(r)
|
||||
teamID := vars["teamID"]
|
||||
boardID := vars["boardID"]
|
||||
categoryID := vars["categoryID"]
|
||||
|
||||
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
|
||||
a.errorResponse(w, r, model.NewErrPermission("access denied to category"))
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := a.makeAuditRecord(r, "unhideBoard", audit.Fail)
|
||||
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
||||
auditRec.AddMeta("boardID", boardID)
|
||||
|
||||
if err := a.app.SetBoardVisibility(teamID, userID, categoryID, boardID, true); err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonStringResponse(w, http.StatusOK, "{}")
|
||||
auditRec.Success()
|
||||
}
|
||||
|
@ -154,8 +154,8 @@ func (a *App) setBoardCategoryFromSource(sourceBoardID, destinationBoardID, user
|
||||
var destinationCategoryID string
|
||||
|
||||
for _, categoryBoard := range userCategoryBoards {
|
||||
for _, boardID := range categoryBoard.BoardIDs {
|
||||
if boardID == sourceBoardID {
|
||||
for _, metadata := range categoryBoard.BoardMetadata {
|
||||
if metadata.BoardID == sourceBoardID {
|
||||
// category found!
|
||||
destinationCategoryID = categoryBoard.ID
|
||||
break
|
||||
@ -175,7 +175,7 @@ func (a *App) setBoardCategoryFromSource(sourceBoardID, destinationBoardID, user
|
||||
|
||||
// now that we have source board's category,
|
||||
// we send destination board to the same category
|
||||
return a.AddUpdateUserCategoryBoard(teamID, userID, map[string]string{destinationBoardID: destinationCategoryID})
|
||||
return a.AddUpdateUserCategoryBoard(teamID, userID, destinationCategoryID, []string{destinationBoardID})
|
||||
}
|
||||
|
||||
func (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {
|
||||
@ -329,12 +329,12 @@ func (a *App) addBoardsToDefaultCategory(userID, teamID string, boards []*model.
|
||||
return fmt.Errorf("%w userID: %s", errNoDefaultCategoryFound, userID)
|
||||
}
|
||||
|
||||
boardCategoryMapping := map[string]string{}
|
||||
for _, board := range boards {
|
||||
boardCategoryMapping[board.ID] = defaultCategoryID
|
||||
boardIDs := make([]string, len(boards))
|
||||
for i := range boards {
|
||||
boardIDs[i] = boards[i].ID
|
||||
}
|
||||
|
||||
if err := a.AddUpdateUserCategoryBoard(teamID, userID, boardCategoryMapping); err != nil {
|
||||
if err := a.AddUpdateUserCategoryBoard(teamID, userID, defaultCategoryID, boardIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -51,8 +51,8 @@ func TestAddMemberToBoard(t *testing.T) {
|
||||
Type: "system",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_1": "default_category_id"}).Return(nil)
|
||||
}, nil).Times(2)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "default_category_id", []string{"board_id_1"}).Return(nil)
|
||||
|
||||
addedBoardMember, err := th.App.AddMemberToBoard(boardMember)
|
||||
require.NoError(t, err)
|
||||
@ -125,8 +125,8 @@ func TestAddMemberToBoard(t *testing.T) {
|
||||
Type: "system",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_1": "default_category_id"}).Return(nil)
|
||||
}, nil).Times(2)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "default_category_id", []string{"board_id_1"}).Return(nil)
|
||||
|
||||
addedBoardMember, err := th.App.AddMemberToBoard(boardMember)
|
||||
require.NoError(t, err)
|
||||
@ -411,45 +411,72 @@ func TestBoardCategory(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
t.Run("test addBoardsToDefaultCategory", func(t *testing.T) {
|
||||
t.Run("no boards default category exists", func(t *testing.T) {
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
|
||||
BoardIDs: []string{"board_id_1", "board_id_2"},
|
||||
t.Run("no boards default category exists", func(t *testing.T) {
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_1"},
|
||||
{BoardID: "board_id_2"},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_2", Name: "Category 2"},
|
||||
BoardIDs: []string{"board_id_3"},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_2", Name: "Category 2"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_3"},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
|
||||
BoardIDs: []string{},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{},
|
||||
},
|
||||
}, nil).Times(1)
|
||||
|
||||
// when this function is called the second time, the default category is created
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_1"},
|
||||
{BoardID: "board_id_2"},
|
||||
},
|
||||
}, nil)
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_2", Name: "Category 2"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_3"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "default_category_id", Type: model.CategoryTypeSystem, Name: "Boards"},
|
||||
},
|
||||
}, nil).Times(1)
|
||||
|
||||
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
|
||||
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
||||
ID: "default_category_id",
|
||||
Name: "Boards",
|
||||
}, nil)
|
||||
th.Store.EXPECT().GetMembersForUser("user_id").Return([]*model.BoardMember{}, nil)
|
||||
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id", "team_id", false).Return([]*model.Board{}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{
|
||||
"board_id_1": "default_category_id",
|
||||
"board_id_2": "default_category_id",
|
||||
"board_id_3": "default_category_id",
|
||||
}).Return(nil)
|
||||
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
|
||||
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
||||
ID: "default_category_id",
|
||||
Name: "Boards",
|
||||
}, nil)
|
||||
th.Store.EXPECT().GetMembersForUser("user_id").Return([]*model.BoardMember{}, nil)
|
||||
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id", "team_id", false).Return([]*model.Board{}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "default_category_id", []string{
|
||||
"board_id_1",
|
||||
"board_id_2",
|
||||
"board_id_3",
|
||||
}).Return(nil)
|
||||
|
||||
boards := []*model.Board{
|
||||
{ID: "board_id_1"},
|
||||
{ID: "board_id_2"},
|
||||
{ID: "board_id_3"},
|
||||
}
|
||||
boards := []*model.Board{
|
||||
{ID: "board_id_1"},
|
||||
{ID: "board_id_2"},
|
||||
{ID: "board_id_3"},
|
||||
}
|
||||
|
||||
err := th.App.addBoardsToDefaultCategory("user_id", "team_id", boards)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
err := th.App.addBoardsToDefaultCategory("user_id", "team_id", boards)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -491,9 +518,9 @@ func TestDuplicateBoard(t *testing.T) {
|
||||
Type: "system",
|
||||
},
|
||||
},
|
||||
}, nil).Times(2)
|
||||
}, nil).Times(3)
|
||||
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", utils.Anything).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "category_id_1", utils.Anything).Return(nil)
|
||||
|
||||
// for WS change broadcast
|
||||
th.Store.EXPECT().GetMembersForBoard(utils.Anything).Return([]*model.BoardMember{}, nil).Times(2)
|
||||
|
@ -178,13 +178,12 @@ func (a *App) moveBoardsToDefaultCategory(userID, teamID, sourceCategoryID strin
|
||||
return fmt.Errorf("moveBoardsToDefaultCategory: %w", errNoDefaultCategoryFound)
|
||||
}
|
||||
|
||||
boardCategoryMapping := map[string]string{}
|
||||
|
||||
for _, boardID := range sourceCategoryBoards.BoardIDs {
|
||||
boardCategoryMapping[boardID] = defaultCategoryID
|
||||
boardIDs := make([]string, len(sourceCategoryBoards.BoardMetadata))
|
||||
for i := range sourceCategoryBoards.BoardMetadata {
|
||||
boardIDs[i] = sourceCategoryBoards.BoardMetadata[i].BoardID
|
||||
}
|
||||
|
||||
if err := a.AddUpdateUserCategoryBoard(teamID, userID, boardCategoryMapping); err != nil {
|
||||
if err := a.AddUpdateUserCategoryBoard(teamID, userID, defaultCategoryID, boardIDs); err != nil {
|
||||
return fmt.Errorf("moveBoardsToDefaultCategory: %w", err)
|
||||
}
|
||||
|
||||
|
@ -79,8 +79,8 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
|
||||
}
|
||||
|
||||
createdCategoryBoards := &model.CategoryBoards{
|
||||
Category: *createdCategory,
|
||||
BoardIDs: []string{},
|
||||
Category: *createdCategory,
|
||||
BoardMetadata: []model.CategoryBoardMetadata{},
|
||||
}
|
||||
|
||||
// get user's current team's baords
|
||||
@ -89,6 +89,8 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
|
||||
return nil, fmt.Errorf("createBoardsCategory error fetching user's team's boards: %w", err)
|
||||
}
|
||||
|
||||
boardIDsToAdd := []string{}
|
||||
|
||||
for _, board := range userTeamBoards {
|
||||
boardMembership, ok := boardMemberByBoardID[board.ID]
|
||||
if !ok {
|
||||
@ -107,8 +109,8 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
|
||||
belongsToCategory := false
|
||||
|
||||
for _, categoryBoard := range existingCategoryBoards {
|
||||
for _, boardID := range categoryBoard.BoardIDs {
|
||||
if boardID == board.ID {
|
||||
for _, metadata := range categoryBoard.BoardMetadata {
|
||||
if metadata.BoardID == board.ID {
|
||||
belongsToCategory = true
|
||||
break
|
||||
}
|
||||
@ -122,29 +124,58 @@ func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards
|
||||
}
|
||||
|
||||
if !belongsToCategory {
|
||||
if err := a.AddUpdateUserCategoryBoard(teamID, userID, map[string]string{board.ID: createdCategory.ID}); err != nil {
|
||||
return nil, fmt.Errorf("createBoardsCategory failed to add category-less board to the default category, defaultCategoryID: %s, error: %w", createdCategory.ID, err)
|
||||
boardIDsToAdd = append(boardIDsToAdd, board.ID)
|
||||
newBoardMetadata := model.CategoryBoardMetadata{
|
||||
BoardID: board.ID,
|
||||
Hidden: false,
|
||||
}
|
||||
createdCategoryBoards.BoardMetadata = append(createdCategoryBoards.BoardMetadata, newBoardMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
createdCategoryBoards.BoardIDs = append(createdCategoryBoards.BoardIDs, board.ID)
|
||||
if len(boardIDsToAdd) > 0 {
|
||||
if err := a.AddUpdateUserCategoryBoard(teamID, userID, createdCategory.ID, boardIDsToAdd); err != nil {
|
||||
return nil, fmt.Errorf("createBoardsCategory failed to add category-less board to the default category, defaultCategoryID: %s, error: %w", createdCategory.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return createdCategoryBoards, nil
|
||||
}
|
||||
|
||||
func (a *App) AddUpdateUserCategoryBoard(teamID, userID string, boardCategoryMapping map[string]string) error {
|
||||
err := a.store.AddUpdateCategoryBoard(userID, boardCategoryMapping)
|
||||
func (a *App) AddUpdateUserCategoryBoard(teamID, userID, categoryID string, boardIDs []string) error {
|
||||
if len(boardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := a.store.AddUpdateCategoryBoard(userID, categoryID, boardIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wsPayload := make([]*model.BoardCategoryWebsocketData, len(boardCategoryMapping))
|
||||
userCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var updatedCategory *model.CategoryBoards
|
||||
for i := range userCategoryBoards {
|
||||
if userCategoryBoards[i].ID == categoryID {
|
||||
updatedCategory = &userCategoryBoards[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if updatedCategory == nil {
|
||||
return errCategoryNotFound
|
||||
}
|
||||
|
||||
wsPayload := make([]*model.BoardCategoryWebsocketData, len(updatedCategory.BoardMetadata))
|
||||
i := 0
|
||||
for boardID, categoryID := range boardCategoryMapping {
|
||||
for _, categoryBoardMetadata := range updatedCategory.BoardMetadata {
|
||||
wsPayload[i] = &model.BoardCategoryWebsocketData{
|
||||
BoardID: boardID,
|
||||
BoardID: categoryBoardMetadata.BoardID,
|
||||
CategoryID: categoryID,
|
||||
Hidden: categoryBoardMetadata.Hidden,
|
||||
}
|
||||
i++
|
||||
}
|
||||
@ -198,12 +229,12 @@ func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID st
|
||||
return fmt.Errorf("%w categoryID: %s", errCategoryNotFound, categoryID)
|
||||
}
|
||||
|
||||
if len(targetCategoryBoards.BoardIDs) != len(newBoardsOrder) {
|
||||
if len(targetCategoryBoards.BoardMetadata) != len(newBoardsOrder) {
|
||||
return fmt.Errorf(
|
||||
"%w length new category boards: %d, length existing category boards: %d, userID: %s, teamID: %s, categoryID: %s",
|
||||
errCategoryBoardsLengthMismatch,
|
||||
len(newBoardsOrder),
|
||||
len(targetCategoryBoards.BoardIDs),
|
||||
len(targetCategoryBoards.BoardMetadata),
|
||||
userID,
|
||||
teamID,
|
||||
categoryID,
|
||||
@ -211,8 +242,8 @@ func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID st
|
||||
}
|
||||
|
||||
existingBoardMap := map[string]bool{}
|
||||
for _, boardID := range targetCategoryBoards.BoardIDs {
|
||||
existingBoardMap[boardID] = true
|
||||
for _, metadata := range targetCategoryBoards.BoardMetadata {
|
||||
existingBoardMap[metadata.BoardID] = true
|
||||
}
|
||||
|
||||
for _, boardID := range newBoardsOrder {
|
||||
@ -230,3 +261,19 @@ func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID st
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SetBoardVisibility(teamID, userID, categoryID, boardID string, visible bool) error {
|
||||
if err := a.store.SetBoardVisibility(userID, categoryID, boardID, visible); err != nil {
|
||||
return fmt.Errorf("SetBoardVisibility: failed to update board visibility: %w", err)
|
||||
}
|
||||
|
||||
a.wsAdapter.BroadcastCategoryBoardChange(teamID, userID, []*model.BoardCategoryWebsocketData{
|
||||
{
|
||||
BoardID: boardID,
|
||||
CategoryID: categoryID,
|
||||
Hidden: !visible,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -14,7 +14,16 @@ func TestGetUserCategoryBoards(t *testing.T) {
|
||||
defer tearDown()
|
||||
|
||||
t.Run("user had no default category and had boards", func(t *testing.T) {
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{}, nil)
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{}, nil).Times(1)
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{
|
||||
ID: "boards_category_id",
|
||||
Type: model.CategoryTypeSystem,
|
||||
Name: "Boards",
|
||||
},
|
||||
},
|
||||
}, nil).Times(1)
|
||||
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
|
||||
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
||||
ID: "boards_category_id",
|
||||
@ -49,18 +58,16 @@ func TestGetUserCategoryBoards(t *testing.T) {
|
||||
Synthetic: false,
|
||||
},
|
||||
}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_2": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_3": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "boards_category_id", []string{"board_id_1", "board_id_2", "board_id_3"}).Return(nil)
|
||||
|
||||
categoryBoards, err := th.App.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "Boards", categoryBoards[0].Name)
|
||||
assert.Equal(t, 3, len(categoryBoards[0].BoardIDs))
|
||||
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_1")
|
||||
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_2")
|
||||
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_3")
|
||||
assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false})
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false})
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false})
|
||||
})
|
||||
|
||||
t.Run("user had no default category BUT had no boards", func(t *testing.T) {
|
||||
@ -78,14 +85,17 @@ func TestGetUserCategoryBoards(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "Boards", categoryBoards[0].Name)
|
||||
assert.Equal(t, 0, len(categoryBoards[0].BoardIDs))
|
||||
assert.Equal(t, 0, len(categoryBoards[0].BoardMetadata))
|
||||
})
|
||||
|
||||
t.Run("user already had a default Boards category with boards in it", func(t *testing.T) {
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{Name: "Boards"},
|
||||
BoardIDs: []string{"board_id_1", "board_id_2"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_1", Hidden: false},
|
||||
{BoardID: "board_id_2", Hidden: false},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
@ -93,7 +103,7 @@ func TestGetUserCategoryBoards(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "Boards", categoryBoards[0].Name)
|
||||
assert.Equal(t, 2, len(categoryBoards[0].BoardIDs))
|
||||
assert.Equal(t, 2, len(categoryBoards[0].BoardMetadata))
|
||||
})
|
||||
}
|
||||
|
||||
@ -116,7 +126,7 @@ func TestCreateBoardsCategory(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, boardsCategory)
|
||||
assert.Equal(t, "Boards", boardsCategory.Name)
|
||||
assert.Equal(t, 0, len(boardsCategory.BoardIDs))
|
||||
assert.Equal(t, 0, len(boardsCategory.BoardMetadata))
|
||||
})
|
||||
|
||||
t.Run("user has implicit access to some board", func(t *testing.T) {
|
||||
@ -150,7 +160,7 @@ func TestCreateBoardsCategory(t *testing.T) {
|
||||
|
||||
// there should still be no boards in the default category as
|
||||
// the user had only implicit access to boards
|
||||
assert.Equal(t, 0, len(boardsCategory.BoardIDs))
|
||||
assert.Equal(t, 0, len(boardsCategory.BoardMetadata))
|
||||
})
|
||||
|
||||
t.Run("user has explicit access to some board", func(t *testing.T) {
|
||||
@ -185,9 +195,17 @@ func TestCreateBoardsCategory(t *testing.T) {
|
||||
Synthetic: false,
|
||||
},
|
||||
}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_2": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_3": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "boards_category_id", []string{"board_id_1", "board_id_2", "board_id_3"}).Return(nil)
|
||||
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{
|
||||
Type: model.CategoryTypeSystem,
|
||||
ID: "boards_category_id",
|
||||
Name: "Boards",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
existingCategoryBoards := []model.CategoryBoards{}
|
||||
boardsCategory, err := th.App.createBoardsCategory("user_id", "team_id", existingCategoryBoards)
|
||||
@ -197,7 +215,7 @@ func TestCreateBoardsCategory(t *testing.T) {
|
||||
|
||||
// since user has explicit access to three boards,
|
||||
// they should all end up in the default category
|
||||
assert.Equal(t, 3, len(boardsCategory.BoardIDs))
|
||||
assert.Equal(t, 3, len(boardsCategory.BoardMetadata))
|
||||
})
|
||||
|
||||
t.Run("user has both implicit and explicit access to some board", func(t *testing.T) {
|
||||
@ -226,7 +244,17 @@ func TestCreateBoardsCategory(t *testing.T) {
|
||||
Synthetic: true,
|
||||
},
|
||||
}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", "boards_category_id", []string{"board_id_1"}).Return(nil)
|
||||
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{
|
||||
Type: model.CategoryTypeSystem,
|
||||
ID: "boards_category_id",
|
||||
Name: "Boards",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
existingCategoryBoards := []model.CategoryBoards{}
|
||||
boardsCategory, err := th.App.createBoardsCategory("user_id", "team_id", existingCategoryBoards)
|
||||
@ -237,7 +265,7 @@ func TestCreateBoardsCategory(t *testing.T) {
|
||||
// there was only one explicit board access,
|
||||
// and so only that one should end up in the
|
||||
// default category
|
||||
assert.Equal(t, 1, len(boardsCategory.BoardIDs))
|
||||
assert.Equal(t, 1, len(boardsCategory.BoardMetadata))
|
||||
})
|
||||
}
|
||||
|
||||
@ -249,15 +277,20 @@ func TestReorderCategoryBoards(t *testing.T) {
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
|
||||
BoardIDs: []string{"board_id_1", "board_id_2"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_1", Hidden: false},
|
||||
{BoardID: "board_id_2", Hidden: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_2", Name: "Boards", Type: "system"},
|
||||
BoardIDs: []string{"board_id_3"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_3", Hidden: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
|
||||
BoardIDs: []string{},
|
||||
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
@ -274,15 +307,21 @@ func TestReorderCategoryBoards(t *testing.T) {
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user_id", "team_id").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{ID: "category_id_1", Name: "Category 1"},
|
||||
BoardIDs: []string{"board_id_1", "board_id_2", "board_id_3"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_1", Hidden: false},
|
||||
{BoardID: "board_id_2", Hidden: false},
|
||||
{BoardID: "board_id_3", Hidden: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_2", Name: "Boards", Type: "system"},
|
||||
BoardIDs: []string{"board_id_3"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{
|
||||
{BoardID: "board_id_3", Hidden: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
|
||||
BoardIDs: []string{},
|
||||
Category: model.Category{ID: "category_id_3", Name: "Category 3"},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
|
@ -278,7 +278,7 @@ func TestDeleteCategory(t *testing.T) {
|
||||
Type: "default",
|
||||
Name: "Boards",
|
||||
},
|
||||
BoardIDs: []string{},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{},
|
||||
},
|
||||
{
|
||||
Category: model.Category{
|
||||
@ -289,12 +289,10 @@ func TestDeleteCategory(t *testing.T) {
|
||||
Type: "custom",
|
||||
Name: "Category 1",
|
||||
},
|
||||
BoardIDs: []string{},
|
||||
BoardMetadata: []model.CategoryBoardMetadata{},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", utils.Anything).Return(nil)
|
||||
|
||||
deletedCategory, err := th.App.DeleteCategory("category_id_1", "user_id_1", "team_id_1")
|
||||
assert.NotNil(t, deletedCategory)
|
||||
assert.NoError(t, err)
|
||||
@ -351,8 +349,6 @@ func TestMoveBoardsToDefaultCategory(t *testing.T) {
|
||||
},
|
||||
}, nil)
|
||||
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", utils.Anything).Return(nil)
|
||||
|
||||
err := th.App.moveBoardsToDefaultCategory("user_id", "team_id", "category_id_2")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
@ -376,7 +372,6 @@ func TestMoveBoardsToDefaultCategory(t *testing.T) {
|
||||
}, nil)
|
||||
th.Store.EXPECT().GetMembersForUser("user_id").Return([]*model.BoardMember{}, nil)
|
||||
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id", "team_id", false).Return([]*model.Board{}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id", utils.Anything).Return(nil)
|
||||
|
||||
err := th.App.moveBoardsToDefaultCategory("user_id", "team_id", "category_id_2")
|
||||
assert.NoError(t, err)
|
||||
|
@ -47,6 +47,18 @@ 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").Return([]model.CategoryBoards{}, nil)
|
||||
th.Store.EXPECT().GetUserCategoryBoards("user", "test-team").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{
|
||||
Type: "default",
|
||||
Name: "Boards",
|
||||
ID: "boards_category_id",
|
||||
},
|
||||
},
|
||||
}, 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{
|
||||
@ -55,7 +67,7 @@ func TestApp_ImportArchive(t *testing.T) {
|
||||
}, nil)
|
||||
th.Store.EXPECT().GetBoardsForUserAndTeam("user", "test-team", false).Return([]*model.Board{}, nil)
|
||||
th.Store.EXPECT().GetMembersForUser("user").Return([]*model.BoardMember{}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user", utils.Anything).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user", utils.Anything, utils.Anything).Return(nil)
|
||||
|
||||
err := th.App.ImportArchive(r, opts)
|
||||
require.NoError(t, err, "import archive should not fail")
|
||||
@ -97,7 +109,16 @@ func TestApp_ImportArchive(t *testing.T) {
|
||||
|
||||
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().GetUserCategoryBoards("f1tydgc697fcbp8ampr6881jea", "test-team").Return([]model.CategoryBoards{}, nil)
|
||||
th.Store.EXPECT().GetUserCategoryBoards("f1tydgc697fcbp8ampr6881jea", "test-team").Return([]model.CategoryBoards{
|
||||
{
|
||||
Category: model.Category{
|
||||
ID: "boards_category_id",
|
||||
Name: "Boards",
|
||||
Type: model.CategoryTypeSystem,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)
|
||||
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
||||
ID: "boards_category_id",
|
||||
@ -105,7 +126,7 @@ func TestApp_ImportArchive(t *testing.T) {
|
||||
}, 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().AddUpdateCategoryBoard("f1tydgc697fcbp8ampr6881jea", utils.Anything, 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)
|
||||
|
@ -70,7 +70,7 @@ func TestPrepareOnboardingTour(t *testing.T) {
|
||||
{
|
||||
Category: model.Category{ID: "boards_category_id", Name: "Boards"},
|
||||
},
|
||||
}, nil).Times(1)
|
||||
}, nil).Times(2)
|
||||
|
||||
th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil).Times(1)
|
||||
th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{
|
||||
@ -78,7 +78,7 @@ func TestPrepareOnboardingTour(t *testing.T) {
|
||||
Name: "Boards",
|
||||
}, nil)
|
||||
th.Store.EXPECT().GetBoardsForUserAndTeam("user_id_1", teamID, false).Return([]*model.Board{}, nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_2": "boards_category_id"}).Return(nil)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "boards_category_id", []string{"board_id_2"}).Return(nil)
|
||||
|
||||
teamID, boardID, err := th.App.PrepareOnboardingTour(userID, teamID)
|
||||
assert.NoError(t, err)
|
||||
@ -120,8 +120,8 @@ func TestCreateWelcomeBoard(t *testing.T) {
|
||||
{
|
||||
Category: model.Category{ID: "boards_category_id", Name: "Boards"},
|
||||
},
|
||||
}, nil).Times(2)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", map[string]string{"board_id_1": "boards_category_id"}).Return(nil)
|
||||
}, nil).Times(3)
|
||||
th.Store.EXPECT().AddUpdateCategoryBoard("user_id_1", "boards_category_id", []string{"board_id_1"}).Return(nil)
|
||||
|
||||
boardID, err := th.App.createWelcomeBoard(userID, teamID)
|
||||
assert.Nil(t, err)
|
||||
|
@ -986,3 +986,21 @@ func (c *Client) GetStatistics() (*model.BoardsStatistics, *Response) {
|
||||
|
||||
return stats, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {
|
||||
r, err := c.DoAPIPut(c.GetTeamRoute(teamID)+"/categories/"+categoryID+"/boards/"+boardID+"/hide", "")
|
||||
if err != nil {
|
||||
return BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
return BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {
|
||||
r, err := c.DoAPIPut(c.GetTeamRoute(teamID)+"/categories/"+categoryID+"/boards/"+boardID+"/unhide", "")
|
||||
if err != nil {
|
||||
return BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
return BuildResponse(r)
|
||||
}
|
||||
|
@ -2082,8 +2082,8 @@ func TestDuplicateBoard(t *testing.T) {
|
||||
|
||||
var duplicateBoardCategoryID string
|
||||
for _, categoryBoard := range userCategoryBoards {
|
||||
for _, boardID := range categoryBoard.BoardIDs {
|
||||
if boardID == duplicateBoard.ID {
|
||||
for _, boardMetadata := range categoryBoard.BoardMetadata {
|
||||
if boardMetadata.BoardID == duplicateBoard.ID {
|
||||
duplicateBoardCategoryID = categoryBoard.Category.ID
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ func TestSidebar(t *testing.T) {
|
||||
categoryBoards := th.GetUserCategoryBoards("team-id")
|
||||
require.Equal(t, 1, len(categoryBoards))
|
||||
require.Equal(t, "Boards", categoryBoards[0].Name)
|
||||
require.Equal(t, 1, len(categoryBoards[0].BoardIDs))
|
||||
require.Equal(t, board.ID, categoryBoards[0].BoardIDs[0])
|
||||
require.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
|
||||
require.Equal(t, board.ID, categoryBoards[0].BoardMetadata[0].BoardID)
|
||||
|
||||
// create a new category, a new board
|
||||
// and move that board into the new category
|
||||
@ -39,8 +39,8 @@ func TestSidebar(t *testing.T) {
|
||||
// the newly created category should be the first one array
|
||||
// as new categories end up on top in LHS
|
||||
require.Equal(t, "Category 2", categoryBoards[0].Name)
|
||||
require.Equal(t, 1, len(categoryBoards[0].BoardIDs))
|
||||
require.Equal(t, board2.ID, categoryBoards[0].BoardIDs[0])
|
||||
require.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
|
||||
require.Equal(t, board2.ID, categoryBoards[0].BoardMetadata[0].BoardID)
|
||||
|
||||
// now we'll delete the custom category we created, "Category 2"
|
||||
// and all it's boards should get moved to the Boards category
|
||||
@ -48,7 +48,51 @@ func TestSidebar(t *testing.T) {
|
||||
categoryBoards = th.GetUserCategoryBoards("team-id")
|
||||
require.Equal(t, 1, len(categoryBoards))
|
||||
require.Equal(t, "Boards", categoryBoards[0].Name)
|
||||
require.Equal(t, 2, len(categoryBoards[0].BoardIDs))
|
||||
require.Contains(t, categoryBoards[0].BoardIDs, board.ID)
|
||||
require.Contains(t, categoryBoards[0].BoardIDs, board2.ID)
|
||||
require.Equal(t, 2, len(categoryBoards[0].BoardMetadata))
|
||||
require.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: board.ID, Hidden: false})
|
||||
require.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: board2.ID, Hidden: false})
|
||||
}
|
||||
|
||||
func TestHideUnhideBoard(t *testing.T) {
|
||||
th := SetupTestHelperWithToken(t).Start()
|
||||
defer th.TearDown()
|
||||
|
||||
// we'll create a new board.
|
||||
// The board should end up in a default "Boards" category
|
||||
th.CreateBoard("team-id", "O")
|
||||
|
||||
// the created board should not be hidden
|
||||
categoryBoards := th.GetUserCategoryBoards("team-id")
|
||||
require.Equal(t, 1, len(categoryBoards))
|
||||
require.Equal(t, "Boards", categoryBoards[0].Name)
|
||||
require.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
|
||||
require.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
|
||||
|
||||
// now we'll hide the board
|
||||
response := th.Client.HideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
|
||||
th.CheckOK(response)
|
||||
|
||||
// verifying if the board has been marked as hidden
|
||||
categoryBoards = th.GetUserCategoryBoards("team-id")
|
||||
require.True(t, categoryBoards[0].BoardMetadata[0].Hidden)
|
||||
|
||||
// trying to hide the already hidden board.This should have no effect
|
||||
response = th.Client.HideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
|
||||
th.CheckOK(response)
|
||||
categoryBoards = th.GetUserCategoryBoards("team-id")
|
||||
require.True(t, categoryBoards[0].BoardMetadata[0].Hidden)
|
||||
|
||||
// now we'll unhide the board
|
||||
response = th.Client.UnhideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
|
||||
th.CheckOK(response)
|
||||
|
||||
// verifying
|
||||
categoryBoards = th.GetUserCategoryBoards("team-id")
|
||||
require.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
|
||||
|
||||
// trying to unhide the already visible board.This should have no effect
|
||||
response = th.Client.UnhideBoard("team-id", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)
|
||||
th.CheckOK(response)
|
||||
categoryBoards = th.GetUserCategoryBoards("team-id")
|
||||
require.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ type CategoryBoards struct {
|
||||
|
||||
// The IDs of boards in this category
|
||||
// required: true
|
||||
BoardIDs []string `json:"boardIDs"`
|
||||
BoardMetadata []CategoryBoardMetadata `json:"boardMetadata"`
|
||||
|
||||
// The relative sort order of this board in its category
|
||||
// required: true
|
||||
@ -19,4 +19,10 @@ type CategoryBoards struct {
|
||||
type BoardCategoryWebsocketData struct {
|
||||
BoardID string `json:"boardID"`
|
||||
CategoryID string `json:"categoryID"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
type CategoryBoardMetadata struct {
|
||||
BoardID string `json:"boardID"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
//go:generate mockgen -destination=mocks/propValueResolverMock.go -package mocks . PropValueResolver
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
|
@ -37,17 +37,17 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
}
|
||||
|
||||
// AddUpdateCategoryBoard mocks base method.
|
||||
func (m *MockStore) AddUpdateCategoryBoard(arg0 string, arg1 map[string]string) error {
|
||||
func (m *MockStore) AddUpdateCategoryBoard(arg0, arg1 string, arg2 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddUpdateCategoryBoard", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "AddUpdateCategoryBoard", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddUpdateCategoryBoard indicates an expected call of AddUpdateCategoryBoard.
|
||||
func (mr *MockStoreMockRecorder) AddUpdateCategoryBoard(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockStoreMockRecorder) AddUpdateCategoryBoard(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateCategoryBoard", reflect.TypeOf((*MockStore)(nil).AddUpdateCategoryBoard), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateCategoryBoard", reflect.TypeOf((*MockStore)(nil).AddUpdateCategoryBoard), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CanSeeUser mocks base method.
|
||||
@ -1545,6 +1545,20 @@ func (mr *MockStoreMockRecorder) SendMessage(arg0, arg1, arg2 interface{}) *gomo
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockStore)(nil).SendMessage), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// SetBoardVisibility mocks base method.
|
||||
func (m *MockStore) SetBoardVisibility(arg0, arg1, arg2 string, arg3 bool) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetBoardVisibility", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetBoardVisibility indicates an expected call of SetBoardVisibility.
|
||||
func (mr *MockStoreMockRecorder) SetBoardVisibility(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBoardVisibility", reflect.TypeOf((*MockStore)(nil).SetBoardVisibility), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// SetSystemSetting mocks base method.
|
||||
func (m *MockStore) SetSystemSetting(arg0, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -19,14 +19,14 @@ func (s *SQLStore) getUserCategoryBoards(db sq.BaseRunner, userID, teamID string
|
||||
|
||||
userCategoryBoards := []model.CategoryBoards{}
|
||||
for _, category := range categories {
|
||||
boardIDs, err := s.getCategoryBoardAttributes(db, category.ID)
|
||||
boardMetadata, err := s.getCategoryBoardAttributes(db, category.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userCategoryBoard := model.CategoryBoards{
|
||||
Category: category,
|
||||
BoardIDs: boardIDs,
|
||||
Category: category,
|
||||
BoardMetadata: boardMetadata,
|
||||
}
|
||||
|
||||
userCategoryBoards = append(userCategoryBoards, userCategoryBoard)
|
||||
@ -35,13 +35,12 @@ func (s *SQLStore) getUserCategoryBoards(db sq.BaseRunner, userID, teamID string
|
||||
return userCategoryBoards, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID string) ([]string, error) {
|
||||
func (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID string) ([]model.CategoryBoardMetadata, error) {
|
||||
query := s.getQueryBuilder(db).
|
||||
Select("board_id").
|
||||
Select("board_id, COALESCE(hidden, false)").
|
||||
From(s.tablePrefix + "category_boards").
|
||||
Where(sq.Eq{
|
||||
"category_id": categoryID,
|
||||
"delete_at": 0,
|
||||
}).
|
||||
OrderBy("sort_order")
|
||||
|
||||
@ -54,21 +53,17 @@ func (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID strin
|
||||
return s.categoryBoardsFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) addUpdateCategoryBoard(db sq.BaseRunner, userID string, boardCategoryMapping map[string]string) error {
|
||||
boardIDs := []string{}
|
||||
for boardID := range boardCategoryMapping {
|
||||
boardIDs = append(boardIDs, boardID)
|
||||
}
|
||||
func (s *SQLStore) addUpdateCategoryBoard(db sq.BaseRunner, userID, categoryID string, boardIDsParam []string) error {
|
||||
// we need to de-duplicate this array as Postgres failes to
|
||||
// handle upsert if there are multiple incoming rows
|
||||
// that conflict the same existing row.
|
||||
// For example, having the entry "1" in DB and trying to upsert "1" and "1" will fail
|
||||
// as there are multiple duplicates of the same "1".
|
||||
//
|
||||
// Source: https://stackoverflow.com/questions/42994373/postgresql-on-conflict-cannot-affect-row-a-second-time
|
||||
boardIDs := utils.DedupeStringArr(boardIDsParam)
|
||||
|
||||
if err := s.deleteUserCategoryBoards(db, userID, boardIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.addUserCategoryBoard(db, userID, boardCategoryMapping)
|
||||
}
|
||||
|
||||
func (s *SQLStore) addUserCategoryBoard(db sq.BaseRunner, userID string, boardCategoryMapping map[string]string) error {
|
||||
if len(boardCategoryMapping) == 0 {
|
||||
if len(boardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -81,73 +76,62 @@ func (s *SQLStore) addUserCategoryBoard(db sq.BaseRunner, userID string, boardCa
|
||||
"board_id",
|
||||
"create_at",
|
||||
"update_at",
|
||||
"delete_at",
|
||||
"sort_order",
|
||||
"hidden",
|
||||
)
|
||||
|
||||
now := utils.GetMillis()
|
||||
for boardID, categoryID := range boardCategoryMapping {
|
||||
query = query.
|
||||
Values(
|
||||
utils.NewID(utils.IDTypeNone),
|
||||
userID,
|
||||
categoryID,
|
||||
boardID,
|
||||
now,
|
||||
now,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
for _, boardID := range boardIDs {
|
||||
query = query.Values(
|
||||
utils.NewID(utils.IDTypeNone),
|
||||
userID,
|
||||
categoryID,
|
||||
boardID,
|
||||
now,
|
||||
now,
|
||||
0,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
if s.dbType == model.MysqlDBType {
|
||||
query = query.Suffix(
|
||||
"ON DUPLICATE KEY UPDATE category_id = ?",
|
||||
categoryID,
|
||||
)
|
||||
} else {
|
||||
query = query.Suffix(
|
||||
`ON CONFLICT (user_id, board_id)
|
||||
DO UPDATE SET category_id = EXCLUDED.category_id, update_at = EXCLUDED.update_at`,
|
||||
)
|
||||
}
|
||||
|
||||
if _, err := query.Exec(); err != nil {
|
||||
s.logger.Error("addUserCategoryBoard error", mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) deleteUserCategoryBoards(db sq.BaseRunner, userID string, boardIDs []string) error {
|
||||
if len(boardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := s.getQueryBuilder(db).
|
||||
Update(s.tablePrefix+"category_boards").
|
||||
Set("delete_at", utils.GetMillis()).
|
||||
Where(sq.Eq{
|
||||
"user_id": userID,
|
||||
"board_id": boardIDs,
|
||||
"delete_at": 0,
|
||||
}).Exec()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"deleteUserCategoryBoards delete error",
|
||||
mlog.String("userID", userID),
|
||||
mlog.Array("boardID", boardIDs),
|
||||
mlog.Err(err),
|
||||
return fmt.Errorf(
|
||||
"store addUpdateCategoryBoard: failed to upsert user-board-category userID: %s, categoryID: %s, board_count: %d, error: %w",
|
||||
userID, categoryID, len(boardIDs), err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) categoryBoardsFromRows(rows *sql.Rows) ([]string, error) {
|
||||
blocks := []string{}
|
||||
func (s *SQLStore) categoryBoardsFromRows(rows *sql.Rows) ([]model.CategoryBoardMetadata, error) {
|
||||
metadata := []model.CategoryBoardMetadata{}
|
||||
|
||||
for rows.Next() {
|
||||
boardID := ""
|
||||
if err := rows.Scan(&boardID); err != nil {
|
||||
datum := model.CategoryBoardMetadata{}
|
||||
err := rows.Scan(&datum.BoardID, &datum.Hidden)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("categoryBoardsFromRows row scan error", mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blocks = append(blocks, boardID)
|
||||
metadata = append(metadata, datum)
|
||||
}
|
||||
|
||||
return blocks, nil
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) reorderCategoryBoards(db sq.BaseRunner, categoryID string, newBoardsOrder []string) ([]string, error) {
|
||||
@ -166,7 +150,6 @@ func (s *SQLStore) reorderCategoryBoards(db sq.BaseRunner, categoryID string, ne
|
||||
Set("sort_order", updateCase).
|
||||
Where(sq.Eq{
|
||||
"category_id": categoryID,
|
||||
"delete_at": 0,
|
||||
})
|
||||
|
||||
if _, err := query.Exec(); err != nil {
|
||||
@ -181,3 +164,28 @@ func (s *SQLStore) reorderCategoryBoards(db sq.BaseRunner, categoryID string, ne
|
||||
|
||||
return newBoardsOrder, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) setBoardVisibility(db sq.BaseRunner, userID, categoryID, boardID string, visible bool) error {
|
||||
query := s.getQueryBuilder(db).
|
||||
Update(s.tablePrefix+"category_boards").
|
||||
Set("hidden", !visible).
|
||||
Where(sq.Eq{
|
||||
"user_id": userID,
|
||||
"category_id": categoryID,
|
||||
"board_id": boardID,
|
||||
})
|
||||
|
||||
if _, err := query.Exec(); err != nil {
|
||||
s.logger.Error(
|
||||
"SQLStore setBoardVisibility: failed to update board visibility",
|
||||
mlog.String("user_id", userID),
|
||||
mlog.String("board_id", boardID),
|
||||
mlog.Bool("visible", visible),
|
||||
mlog.Err(err),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -295,13 +295,14 @@ func (s *SQLStore) ensureMigrationsAppliedUpToVersion(engine *morph.Morph, drive
|
||||
|
||||
func (s *SQLStore) GetTemplateHelperFuncs() template.FuncMap {
|
||||
funcs := template.FuncMap{
|
||||
"addColumnIfNeeded": s.genAddColumnIfNeeded,
|
||||
"dropColumnIfNeeded": s.genDropColumnIfNeeded,
|
||||
"createIndexIfNeeded": s.genCreateIndexIfNeeded,
|
||||
"renameTableIfNeeded": s.genRenameTableIfNeeded,
|
||||
"renameColumnIfNeeded": s.genRenameColumnIfNeeded,
|
||||
"doesTableExist": s.doesTableExist,
|
||||
"doesColumnExist": s.doesColumnExist,
|
||||
"addColumnIfNeeded": s.genAddColumnIfNeeded,
|
||||
"dropColumnIfNeeded": s.genDropColumnIfNeeded,
|
||||
"createIndexIfNeeded": s.genCreateIndexIfNeeded,
|
||||
"renameTableIfNeeded": s.genRenameTableIfNeeded,
|
||||
"renameColumnIfNeeded": s.genRenameColumnIfNeeded,
|
||||
"doesTableExist": s.doesTableExist,
|
||||
"doesColumnExist": s.doesColumnExist,
|
||||
"addConstraintIfNeeded": s.genAddConstraintIfNeeded,
|
||||
}
|
||||
return funcs
|
||||
}
|
||||
@ -607,6 +608,67 @@ func (s *SQLStore) doesColumnExist(tableName, columnName string) (bool, error) {
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) genAddConstraintIfNeeded(tableName, constraintName, constraintType, constraintDefinition string) (string, error) {
|
||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
||||
|
||||
var query string
|
||||
|
||||
vars := map[string]string{
|
||||
"schema": s.schemaName,
|
||||
"constraint_name": constraintName,
|
||||
"constraint_type": constraintType,
|
||||
"table_name": tableName,
|
||||
"constraint_definition": constraintDefinition,
|
||||
"norm_table_name": normTableName,
|
||||
}
|
||||
|
||||
switch s.dbType {
|
||||
case model.SqliteDBType:
|
||||
// SQLite doesn't have a generic way to add constraint. For example, you can only create indexes on existing tables.
|
||||
// For other constraints, you need to re-build the table. So skipping here.
|
||||
// Include SQLite specific migration in original migration file.
|
||||
query = fmt.Sprintf("\n-- Sqlite3 cannot drop constraints; drop constraint '%s' in table '%s' skipped\n", constraintName, tableName)
|
||||
case model.MysqlDBType:
|
||||
query = replaceVars(`
|
||||
SET @stmt = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
|
||||
WHERE constraint_schema = '[[schema]]'
|
||||
AND constraint_name = '[[constraint_name]]'
|
||||
AND constraint_type = '[[constraint_type]]'
|
||||
AND table_name = '[[table_name]]'
|
||||
) > 0,
|
||||
'SELECT 1;',
|
||||
'ALTER TABLE [[norm_table_name]] ADD CONSTRAINT [[constraint_name]] [[constraint_definition]];'
|
||||
));
|
||||
PREPARE addConstraintIfNeeded FROM @stmt;
|
||||
EXECUTE addConstraintIfNeeded;
|
||||
DEALLOCATE PREPARE addConstraintIfNeeded;
|
||||
`, vars)
|
||||
case model.PostgresDBType:
|
||||
query = replaceVars(`
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
|
||||
WHERE constraint_schema = '[[schema]]'
|
||||
AND constraint_name = '[[constraint_name]]'
|
||||
AND constraint_type = '[[constraint_type]]'
|
||||
AND table_name = '[[table_name]]'
|
||||
) THEN
|
||||
ALTER TABLE [[norm_table_name]] ADD CONSTRAINT [[constraint_name]] [[constraint_definition]];
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
`, vars)
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func addPrefixIfNeeded(s, prefix string) string {
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
return prefix + s
|
||||
|
@ -0,0 +1 @@
|
||||
SELECT 1;
|
@ -0,0 +1 @@
|
||||
DELETE FROM {{.prefix}}category_boards WHERE delete_at > 0;
|
@ -0,0 +1 @@
|
||||
SELECT 1;
|
@ -0,0 +1,3 @@
|
||||
{{ if or .postgres .mysql }}
|
||||
{{ dropColumnIfNeeded "category_boards" "delete_at" }}
|
||||
{{end}}
|
@ -0,0 +1 @@
|
||||
SELECT 1;
|
@ -0,0 +1 @@
|
||||
{{ addColumnIfNeeded "category_boards" "hidden" "boolean" "" }}
|
@ -0,0 +1 @@
|
||||
SELECT 1;
|
@ -0,0 +1,26 @@
|
||||
{{if or .mysql .postgres}}
|
||||
{{ addConstraintIfNeeded "category_boards" "unique_user_category_board" "UNIQUE" "UNIQUE(user_id, board_id)"}}
|
||||
{{end}}
|
||||
|
||||
{{if .sqlite}}
|
||||
ALTER TABLE {{.prefix}}category_boards RENAME TO {{.prefix}}category_boards_old;
|
||||
|
||||
CREATE TABLE {{.prefix}}category_boards (
|
||||
id varchar(36) NOT NULL,
|
||||
user_id varchar(36) NOT NULL,
|
||||
category_id varchar(36) NOT NULL,
|
||||
board_id VARCHAR(36) NOT NULL,
|
||||
create_at BIGINT,
|
||||
update_at BIGINT,
|
||||
sort_order BIGINT,
|
||||
hidden boolean,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT unique_user_category_board UNIQUE (user_id, board_id)
|
||||
);
|
||||
|
||||
INSERT INTO {{.prefix}}category_boards
|
||||
(id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)
|
||||
SELECT id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden FROM {{.prefix}}category_boards_old;
|
||||
DROP TABLE {{.prefix}}category_boards_old;
|
||||
|
||||
{{end}}
|
@ -0,0 +1 @@
|
||||
SELECT 1;
|
@ -0,0 +1,57 @@
|
||||
{{if .plugin}}
|
||||
{{if .mysql}}
|
||||
UPDATE {{.prefix}}category_boards AS fcb
|
||||
JOIN Preferences p
|
||||
ON fcb.user_id = p.userid
|
||||
AND p.category = 'focalboard'
|
||||
AND p.name = 'hiddenBoardIDs'
|
||||
SET hidden = true
|
||||
WHERE p.value LIKE concat('%', fcb.board_id, '%');
|
||||
{{end}}
|
||||
|
||||
{{if .postgres}}
|
||||
UPDATE {{.prefix}}category_boards as fcb
|
||||
SET hidden = true
|
||||
FROM preferences p
|
||||
WHERE p.userid = fcb.user_id
|
||||
AND p.category = 'focalboard'
|
||||
AND p.name = 'hiddenBoardIDs'
|
||||
AND p.value like ('%' || fcb.board_id || '%');
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if .mysql}}
|
||||
UPDATE {{.prefix}}category_boards AS fcb
|
||||
JOIN {{.prefix}}preferences p
|
||||
ON fcb.user_id = p.userid
|
||||
AND p.category = 'focalboard'
|
||||
AND p.name = 'hiddenBoardIDs'
|
||||
SET hidden = true
|
||||
WHERE p.value LIKE concat('%', fcb.board_id, '%');
|
||||
{{end}}
|
||||
|
||||
{{if .postgres}}
|
||||
UPDATE {{.prefix}}category_boards as fcb
|
||||
SET hidden = true
|
||||
FROM {{.prefix}}preferences p
|
||||
WHERE p.userid = fcb.user_id
|
||||
AND p.category = 'focalboard'
|
||||
AND p.name = 'hiddenBoardIDs'
|
||||
AND p.value like ('%' || fcb.board_id || '%');
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .sqlite}}
|
||||
UPDATE {{.prefix}}category_boards
|
||||
SET hidden = true
|
||||
WHERE (user_id || '_' || board_id)
|
||||
IN
|
||||
(
|
||||
SELECT (fcb.user_id || '_' || fcb.board_id)
|
||||
FROM {{.prefix}}category_boards AS fcb
|
||||
JOIN {{.prefix}}preferences p
|
||||
ON p.userid = fcb.user_id
|
||||
AND p.category = 'focalboard'
|
||||
AND p.name = 'hiddenBoardIDs' WHERE
|
||||
p.value LIKE ('%' || fcb.board_id || '%')
|
||||
);
|
||||
{{end}}
|
@ -0,0 +1 @@
|
||||
SELECT 1;
|
@ -0,0 +1,5 @@
|
||||
{{if .plugin}}
|
||||
DELETE FROM Preferences WHERE category = 'focalboard' AND name = 'hiddenBoardIDs';
|
||||
{{else}}
|
||||
DELETE FROM {{.prefix}}preferences WHERE category = 'focalboard' AND name = 'hiddenBoardIDs';
|
||||
{{end}}
|
@ -0,0 +1,6 @@
|
||||
INSERT INTO focalboard_category_boards values
|
||||
('id-1', 'user_id-1', 'category-id-1', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
|
||||
('id-2', 'user_id-1', 'category-id-2', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
|
||||
('id-3', 'user_id-2', 'category-id-3', 'board-id-2', 1672988834402, 1672988834402, 1672988834402, 0),
|
||||
('id-4', 'user_id-2', 'category-id-3', 'board-id-4', 1672988834402, 1672988834402, 0, 0),
|
||||
('id-5', 'user_id-3', 'category-id-4', 'board-id-3', 1672988834402, 1672988834402, 1672988834402, 0);
|
@ -0,0 +1,6 @@
|
||||
INSERT INTO focalboard_category_boards values
|
||||
('id-1', 'user_id-1', 'category-id-1', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
|
||||
('id-2', 'user_id-1', 'category-id-2', 'board-id-1', 1672988834402, 1672988834402, 0, 0),
|
||||
('id-3', 'user_id-2', 'category-id-3', 'board-id-2', 1672988834402, 1672988834402, 0, 0),
|
||||
('id-4', 'user_id-2', 'category-id-3', 'board-id-4', 1672988834402, 1672988834402, 0, 0),
|
||||
('id-5', 'user_id-3', 'category-id-4', 'board-id-3', 1672988834402, 1672988834402, 0, 0);
|
@ -0,0 +1 @@
|
||||
ALTER TABLE focalboard_category_boards DROP COLUMN delete_at;
|
@ -0,0 +1 @@
|
||||
ALTER TABLE focalboard_category_boards ADD COLUMN hidden boolean;
|
@ -0,0 +1,10 @@
|
||||
INSERT INTO focalboard_category_boards VALUES
|
||||
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
|
||||
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
|
||||
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
|
||||
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
|
||||
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
|
||||
|
||||
INSERT INTO Preferences VALUES
|
||||
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
|
||||
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]');
|
@ -0,0 +1,6 @@
|
||||
INSERT INTO focalboard_category_boards VALUES
|
||||
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
|
||||
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
|
||||
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
|
||||
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
|
||||
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
|
@ -0,0 +1,10 @@
|
||||
INSERT INTO focalboard_category_boards VALUES
|
||||
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
|
||||
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
|
||||
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
|
||||
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
|
||||
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
|
||||
|
||||
INSERT INTO Preferences VALUES
|
||||
('user-id-1', 'focalboard', 'hiddenBoardIDs', ''),
|
||||
('user-id-2', 'focalboard', 'hiddenBoardIDs', '');
|
@ -0,0 +1,10 @@
|
||||
INSERT INTO focalboard_category_boards VALUES
|
||||
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
|
||||
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
|
||||
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
|
||||
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
|
||||
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
|
||||
|
||||
INSERT INTO focalboard_preferences VALUES
|
||||
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
|
||||
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]');
|
@ -0,0 +1,10 @@
|
||||
INSERT INTO focalboard_category_boards VALUES
|
||||
('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),
|
||||
('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),
|
||||
('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),
|
||||
('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),
|
||||
('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);
|
||||
|
||||
INSERT INTO focalboard_preferences VALUES
|
||||
('user-id-1', 'focalboard', 'hiddenBoardIDs', ''),
|
||||
('user-id-2', 'focalboard', 'hiddenBoardIDs', '');
|
@ -0,0 +1,5 @@
|
||||
INSERT INTO Preferences VALUES
|
||||
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
|
||||
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]'),
|
||||
('user-id-3', 'lorem', 'lorem', ''),
|
||||
('user-id-4', 'ipsum', 'ipsum', '');
|
@ -0,0 +1,5 @@
|
||||
INSERT INTO focalboard_preferences VALUES
|
||||
('user-id-1', 'focalboard', 'hiddenBoardIDs', '["board-id-1"]'),
|
||||
('user-id-2', 'focalboard', 'hiddenBoardIDs', '["board-id-3", "board-id-4"]'),
|
||||
('user-id-2', 'lorem', 'lorem', ''),
|
||||
('user-id-2', 'ipsum', 'ipsum', '');
|
@ -15,6 +15,18 @@ type TestHelper struct {
|
||||
isPlugin bool
|
||||
}
|
||||
|
||||
func (th *TestHelper) IsPostgres() bool {
|
||||
return th.f.DB().DriverName() == "postgres"
|
||||
}
|
||||
|
||||
func (th *TestHelper) IsMySQL() bool {
|
||||
return th.f.DB().DriverName() == "mysql"
|
||||
}
|
||||
|
||||
func (th *TestHelper) IsSQLite() bool {
|
||||
return th.f.DB().DriverName() == "sqlite3"
|
||||
}
|
||||
|
||||
func SetupPluginTestHelper(t *testing.T) (*TestHelper, func()) {
|
||||
dbType := strings.TrimSpace(os.Getenv("FOCALBOARD_STORE_TEST_DB_TYPE"))
|
||||
if dbType == "" || dbType == model.SqliteDBType {
|
||||
|
@ -0,0 +1,57 @@
|
||||
package migrationstests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test34DropDeleteAtColumnMySQLPostgres(t *testing.T) {
|
||||
t.Run("column exists", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
th.f.MigrateToStep(34)
|
||||
|
||||
// migration 34 only works for MySQL and PostgreSQL
|
||||
if th.IsMySQL() {
|
||||
var count int
|
||||
query := "SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'"
|
||||
th.f.DB().Get(&count, query)
|
||||
require.Equal(t, 0, count)
|
||||
} else if th.IsPostgres() {
|
||||
var count int
|
||||
query := "select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'"
|
||||
th.f.DB().Get(&count, query)
|
||||
require.Equal(t, 0, count)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("column already deleted", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
// For migration 34, we don't drop column
|
||||
// on SQLite, so no need to test for it.
|
||||
if th.IsSQLite() {
|
||||
return
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(33).
|
||||
ExecFile("./fixtures/test34_drop_delete_at_column.sql")
|
||||
|
||||
th.f.MigrateToStep(34)
|
||||
|
||||
if th.IsMySQL() {
|
||||
var count int
|
||||
query := "SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'"
|
||||
th.f.DB().Get(&count, query)
|
||||
require.Equal(t, 0, count)
|
||||
} else if th.IsPostgres() {
|
||||
var count int
|
||||
query := "select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'"
|
||||
th.f.DB().Get(&count, query)
|
||||
require.Equal(t, 0, count)
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package migrationstests
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test35AddHIddenColumnToCategoryBoards(t *testing.T) {
|
||||
t.Run("base case - column doesn't already exist", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
th.f.MigrateToStep(35)
|
||||
})
|
||||
|
||||
t.Run("column already exist", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
// We don't support adding column in idempotent manner
|
||||
// for SQLite, so no need to check for it.
|
||||
if th.IsSQLite() {
|
||||
return
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(34).
|
||||
ExecFile("./fixtures/test35_add_hidden_column.sql")
|
||||
|
||||
th.f.MigrateToStep(35)
|
||||
})
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package migrationstests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test36AddUniqueConstraintToCategoryBoards(t *testing.T) {
|
||||
t.Run("constraint doesn't alreadt exists", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
th.f.MigrateToStep(36)
|
||||
|
||||
// verifying if constraint has been added
|
||||
|
||||
//can't verify in sqlite, so skipping it
|
||||
if th.IsSQLite() {
|
||||
return
|
||||
}
|
||||
|
||||
var count int
|
||||
query := "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " +
|
||||
"WHERE constraint_name = 'unique_user_category_board' " +
|
||||
"AND constraint_type = 'UNIQUE' " +
|
||||
"AND table_name = 'focalboard_category_boards'"
|
||||
th.f.DB().Get(&count, query)
|
||||
|
||||
require.Equal(t, 1, count)
|
||||
})
|
||||
|
||||
t.Run("constraint already exists", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
// SQLIte doesn't support adding constraint to existing table
|
||||
// and neither do we, so skipping for sqlite
|
||||
if th.IsSQLite() {
|
||||
return
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(35)
|
||||
|
||||
if th.IsMySQL() {
|
||||
th.f.DB().Exec("alter table focalboard_category_boards add constraint unique_user_category_board UNIQUE(user_id, board_id);")
|
||||
} else if th.IsPostgres() {
|
||||
th.f.DB().Exec("ALTER TABLE focalboard_category_boards ADD CONSTRAINT unique_user_category_board UNIQUE(user_id, board_id);")
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(36)
|
||||
|
||||
var schema string
|
||||
if th.IsMySQL() {
|
||||
schema = "DATABASE()"
|
||||
} else if th.IsPostgres() {
|
||||
schema = "'public'"
|
||||
}
|
||||
|
||||
var count int
|
||||
query := "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " +
|
||||
"WHERE constraint_schema = " + schema + " " +
|
||||
"AND constraint_name = 'unique_user_category_board' " +
|
||||
"AND constraint_type = 'UNIQUE' " +
|
||||
"AND table_name = 'focalboard_category_boards'"
|
||||
th.f.DB().Get(&count, query)
|
||||
require.Equal(t, 1, count)
|
||||
})
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package migrationstests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test37MigrateHiddenBoardIDTest(t *testing.T) {
|
||||
t.Run("no existing hidden boards exist", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
th.f.MigrateToStep(37)
|
||||
})
|
||||
|
||||
t.Run("SQLite - existsing category boards with some hidden boards", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
if th.IsMySQL() || th.IsPostgres() {
|
||||
return
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(36).
|
||||
ExecFile("./fixtures/test37_valid_data_sqlite.sql")
|
||||
|
||||
th.f.MigrateToStep(37)
|
||||
|
||||
type categoryBoard struct {
|
||||
User_ID string
|
||||
Category_ID string
|
||||
Board_ID string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
var hiddenCategoryBoards []categoryBoard
|
||||
|
||||
query := "SELECT user_id, category_id, board_id, hidden FROM focalboard_category_boards WHERE hidden = true"
|
||||
err := th.f.DB().Select(&hiddenCategoryBoards, query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(hiddenCategoryBoards))
|
||||
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-1", Category_ID: "category-id-1", Board_ID: "board-id-1", Hidden: true})
|
||||
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-3", Hidden: true})
|
||||
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-4", Hidden: true})
|
||||
})
|
||||
|
||||
t.Run("MySQL and PostgreSQL - existsing category boards with some hidden boards", func(t *testing.T) {
|
||||
th, tearDown := SetupPluginTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
if th.IsSQLite() {
|
||||
return
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(36).
|
||||
ExecFile("./fixtures/test37_valid_data.sql")
|
||||
|
||||
th.f.MigrateToStep(37)
|
||||
|
||||
type categoryBoard struct {
|
||||
User_ID string
|
||||
Category_ID string
|
||||
Board_ID string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
var hiddenCategoryBoards []categoryBoard
|
||||
|
||||
query := "SELECT user_id, category_id, board_id, hidden FROM focalboard_category_boards WHERE hidden = true"
|
||||
err := th.f.DB().Select(&hiddenCategoryBoards, query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(hiddenCategoryBoards))
|
||||
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-1", Category_ID: "category-id-1", Board_ID: "board-id-1", Hidden: true})
|
||||
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-3", Hidden: true})
|
||||
require.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: "user-id-2", Category_ID: "category-id-3", Board_ID: "board-id-4", Hidden: true})
|
||||
})
|
||||
|
||||
t.Run("no hidden boards", func(t *testing.T) {
|
||||
th, tearDown := SetupPluginTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
th.f.MigrateToStep(36).
|
||||
ExecFile("./fixtures/test37_valid_data_no_hidden_boards.sql")
|
||||
|
||||
th.f.MigrateToStep(37)
|
||||
|
||||
var count int
|
||||
query := "SELECT count(*) FROM focalboard_category_boards WHERE hidden = true"
|
||||
err := th.f.DB().Get(&count, query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, count)
|
||||
})
|
||||
|
||||
t.Run("SQLite - preference but no hidden board", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
if th.IsMySQL() || th.IsPostgres() {
|
||||
return
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(36).
|
||||
ExecFile("./fixtures/test37_valid_data_sqlite_preference_but_no_hidden_board.sql")
|
||||
|
||||
th.f.MigrateToStep(37)
|
||||
|
||||
var count int
|
||||
query := "SELECT count(*) FROM focalboard_category_boards WHERE hidden = true"
|
||||
err := th.f.DB().Get(&count, query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, count)
|
||||
})
|
||||
|
||||
t.Run("MySQL and PostgreSQL - preference but no hidden board", func(t *testing.T) {
|
||||
th, tearDown := SetupPluginTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
if th.IsSQLite() {
|
||||
return
|
||||
}
|
||||
|
||||
th.f.MigrateToStep(36).
|
||||
ExecFile("./fixtures/test37_valid_data_preference_but_no_hidden_board.sql")
|
||||
|
||||
th.f.MigrateToStep(37)
|
||||
|
||||
var count int
|
||||
query := "SELECT count(*) FROM focalboard_category_boards WHERE hidden = true"
|
||||
err := th.f.DB().Get(&count, query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, count)
|
||||
})
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package migrationstests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test38RemoveHiddenBoardIDsFromPreferences(t *testing.T) {
|
||||
t.Run("standalone - no data exist", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
th.f.MigrateToStep(38)
|
||||
})
|
||||
|
||||
t.Run("plugin - no data exist", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
th.f.MigrateToStep(38)
|
||||
})
|
||||
|
||||
t.Run("standalone - some data exist", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
th.f.MigrateToStep(37).
|
||||
ExecFile("./fixtures/test38_add_standalone_preferences.sql")
|
||||
|
||||
// verify existing data count
|
||||
var count int
|
||||
countQuery := "SELECT COUNT(*) FROM focalboard_preferences"
|
||||
err := th.f.DB().Get(&count, countQuery)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 4, count)
|
||||
|
||||
th.f.MigrateToStep(38)
|
||||
|
||||
// now the count should be 0
|
||||
err = th.f.DB().Get(&count, countQuery)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, count)
|
||||
})
|
||||
|
||||
t.Run("plugin - some data exist", func(t *testing.T) {
|
||||
th, tearDown := SetupPluginTestHelper(t)
|
||||
defer tearDown()
|
||||
th.f.MigrateToStep(37).
|
||||
ExecFile("./fixtures/test38_add_plugin_preferences.sql")
|
||||
|
||||
// verify existing data count
|
||||
var count int
|
||||
countQuery := "SELECT COUNT(*) FROM Preferences"
|
||||
err := th.f.DB().Get(&count, countQuery)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 4, count)
|
||||
|
||||
th.f.MigrateToStep(38)
|
||||
|
||||
// now the count should be 0
|
||||
err = th.f.DB().Get(&count, countQuery)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, count)
|
||||
})
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package migrationstests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test33RemoveDeletedCategoryBoards(t *testing.T) {
|
||||
t.Run("base case - no data in table", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
th.f.MigrateToStep(33)
|
||||
})
|
||||
|
||||
t.Run("existing data - 2 soft deleted records", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
th.f.MigrateToStep(32).
|
||||
ExecFile("./fixtures/test33_with_deleted_data.sql")
|
||||
|
||||
// cound total records
|
||||
var count int
|
||||
err := th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 5, count)
|
||||
|
||||
// now we run the migration
|
||||
th.f.MigrateToStep(33)
|
||||
|
||||
// and verify record count again.
|
||||
// The soft deleted records should have been removed from the DB now
|
||||
err = th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, count)
|
||||
})
|
||||
|
||||
t.Run("existing data - no soft deleted records", func(t *testing.T) {
|
||||
th, tearDown := SetupTestHelper(t)
|
||||
defer tearDown()
|
||||
|
||||
th.f.MigrateToStep(32).
|
||||
ExecFile("./fixtures/test33_with_no_deleted_data.sql")
|
||||
|
||||
// cound total records
|
||||
var count int
|
||||
err := th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 5, count)
|
||||
|
||||
// now we run the migration
|
||||
th.f.MigrateToStep(33)
|
||||
|
||||
// and verify record count again.
|
||||
// Since there were no soft-deleted records, nothing should have been
|
||||
// deleted from the database.
|
||||
err = th.f.DB().Get(&count, "SELECT COUNT(*) FROM focalboard_category_boards")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 5, count)
|
||||
|
||||
})
|
||||
}
|
@ -22,15 +22,15 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
func (s *SQLStore) AddUpdateCategoryBoard(userID string, boardCategoryMapping map[string]string) error {
|
||||
func (s *SQLStore) AddUpdateCategoryBoard(userID string, categoryID string, boardIDs []string) error {
|
||||
if s.dbType == model.SqliteDBType {
|
||||
return s.addUpdateCategoryBoard(s.db, userID, boardCategoryMapping)
|
||||
return s.addUpdateCategoryBoard(s.db, userID, categoryID, boardIDs)
|
||||
}
|
||||
tx, txErr := s.db.BeginTx(context.Background(), nil)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
err := s.addUpdateCategoryBoard(tx, userID, boardCategoryMapping)
|
||||
err := s.addUpdateCategoryBoard(tx, userID, categoryID, boardIDs)
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
s.logger.Error("transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "AddUpdateCategoryBoard"))
|
||||
@ -850,6 +850,11 @@ func (s *SQLStore) SendMessage(message string, postType string, receipts []strin
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) SetBoardVisibility(userID string, categoryID string, boardID string, visible bool) error {
|
||||
return s.setBoardVisibility(s.db, userID, categoryID, boardID, visible)
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) SetSystemSetting(key string, value string) error {
|
||||
return s.setSystemSetting(s.db, key, value)
|
||||
|
||||
|
@ -131,8 +131,9 @@ type Store interface {
|
||||
SaveFileInfo(fileInfo *mmModel.FileInfo) error
|
||||
|
||||
// @withTransaction
|
||||
AddUpdateCategoryBoard(userID string, boardCategoryMapping map[string]string) error
|
||||
AddUpdateCategoryBoard(userID, categoryID string, boardIDs []string) error
|
||||
ReorderCategoryBoards(categoryID string, newBoardsOrder []string) ([]string, error)
|
||||
SetBoardVisibility(userID, categoryID, boardID string, visible bool) error
|
||||
|
||||
CreateSubscription(sub *model.Subscription) (*model.Subscription, error)
|
||||
DeleteSubscription(blockID string, subscriberID string) error
|
||||
|
@ -294,11 +294,11 @@ func testReorderCategoryBoards(t *testing.T, store store.Store) {
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = store.AddUpdateCategoryBoard("user_id", map[string]string{
|
||||
"board_id_1": "category_id_1",
|
||||
"board_id_2": "category_id_1",
|
||||
"board_id_3": "category_id_1",
|
||||
"board_id_4": "category_id_1",
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id_1", []string{
|
||||
"board_id_1",
|
||||
"board_id_2",
|
||||
"board_id_3",
|
||||
"board_id_4",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
@ -306,11 +306,11 @@ func testReorderCategoryBoards(t *testing.T, store store.Store) {
|
||||
categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, 4, len(categoryBoards[0].BoardIDs))
|
||||
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_1")
|
||||
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_2")
|
||||
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_3")
|
||||
assert.Contains(t, categoryBoards[0].BoardIDs, "board_id_4")
|
||||
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false})
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false})
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false})
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_4", Hidden: false})
|
||||
|
||||
// reordering
|
||||
newOrder, err := store.ReorderCategoryBoards("category_id_1", []string{
|
||||
@ -329,9 +329,9 @@ func testReorderCategoryBoards(t *testing.T, store store.Store) {
|
||||
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, 4, len(categoryBoards[0].BoardIDs))
|
||||
assert.Equal(t, "board_id_3", categoryBoards[0].BoardIDs[0])
|
||||
assert.Equal(t, "board_id_1", categoryBoards[0].BoardIDs[1])
|
||||
assert.Equal(t, "board_id_2", categoryBoards[0].BoardIDs[2])
|
||||
assert.Equal(t, "board_id_4", categoryBoards[0].BoardIDs[3])
|
||||
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
|
||||
assert.Equal(t, "board_id_3", categoryBoards[0].BoardMetadata[0].BoardID)
|
||||
assert.Equal(t, "board_id_1", categoryBoards[0].BoardMetadata[1].BoardID)
|
||||
assert.Equal(t, "board_id_2", categoryBoards[0].BoardMetadata[2].BoardID)
|
||||
assert.Equal(t, "board_id_4", categoryBoards[0].BoardMetadata[3].BoardID)
|
||||
}
|
||||
|
@ -15,6 +15,18 @@ func StoreTestCategoryBoardsStore(t *testing.T, setup func(t *testing.T) (store.
|
||||
defer tearDown()
|
||||
testGetUserCategoryBoards(t, store)
|
||||
})
|
||||
|
||||
t.Run("AddUpdateCategoryBoard", func(t *testing.T) {
|
||||
store, tearDown := setup(t)
|
||||
defer tearDown()
|
||||
testAddUpdateCategoryBoard(t, store)
|
||||
})
|
||||
|
||||
t.Run("SetBoardVisibility", func(t *testing.T) {
|
||||
store, tearDown := setup(t)
|
||||
defer tearDown()
|
||||
testSetBoardVisibility(t, store)
|
||||
})
|
||||
}
|
||||
|
||||
func testGetUserCategoryBoards(t *testing.T, store store.Store) {
|
||||
@ -60,14 +72,14 @@ func testGetUserCategoryBoards(t *testing.T, store store.Store) {
|
||||
|
||||
// Adding Board 1 and Board 2 to Category 1
|
||||
// The boards don't need to exists in DB for this test
|
||||
err = store.AddUpdateCategoryBoard("user_id_1", map[string]string{"board_1": "category_id_1"})
|
||||
err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", []string{"board_1"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = store.AddUpdateCategoryBoard("user_id_1", map[string]string{"board_2": "category_id_1"})
|
||||
err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", []string{"board_2"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Adding Board 3 to Category 2
|
||||
err = store.AddUpdateCategoryBoard("user_id_1", map[string]string{"board_3": "category_id_2"})
|
||||
err = store.AddUpdateCategoryBoard("user_id_1", "category_id_2", []string{"board_3"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// we'll leave category 3 empty
|
||||
@ -94,13 +106,13 @@ func testGetUserCategoryBoards(t *testing.T, store store.Store) {
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, category1BoardCategory)
|
||||
assert.Equal(t, 2, len(category1BoardCategory.BoardIDs))
|
||||
assert.Equal(t, 2, len(category1BoardCategory.BoardMetadata))
|
||||
|
||||
assert.NotEmpty(t, category1BoardCategory)
|
||||
assert.Equal(t, 1, len(category2BoardCategory.BoardIDs))
|
||||
assert.Equal(t, 1, len(category2BoardCategory.BoardMetadata))
|
||||
|
||||
assert.NotEmpty(t, category1BoardCategory)
|
||||
assert.Equal(t, 0, len(category3BoardCategory.BoardIDs))
|
||||
assert.Equal(t, 0, len(category3BoardCategory.BoardMetadata))
|
||||
|
||||
t.Run("get empty category boards", func(t *testing.T) {
|
||||
userCategoryBoards, err := store.GetUserCategoryBoards("nonexistent-user-id", "nonexistent-team-id")
|
||||
@ -108,3 +120,142 @@ func testGetUserCategoryBoards(t *testing.T, store store.Store) {
|
||||
assert.Empty(t, userCategoryBoards)
|
||||
})
|
||||
}
|
||||
|
||||
func testAddUpdateCategoryBoard(t *testing.T, store store.Store) {
|
||||
// creating few boards and categories to later associoate with the category
|
||||
_, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{
|
||||
Boards: []*model.Board{
|
||||
{
|
||||
ID: "board_id_1",
|
||||
TeamID: "team_id",
|
||||
},
|
||||
{
|
||||
ID: "board_id_2",
|
||||
TeamID: "team_id",
|
||||
},
|
||||
},
|
||||
}, "user_id")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = store.CreateCategory(model.Category{
|
||||
ID: "category_id",
|
||||
Name: "Category",
|
||||
UserID: "user_id",
|
||||
TeamID: "team_id",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// adding a few boards to the category
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_2"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify inserted data
|
||||
categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "category_id", categoryBoards[0].ID)
|
||||
assert.Equal(t, 2, len(categoryBoards[0].BoardMetadata))
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false})
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false})
|
||||
|
||||
// adding new boards to the same category
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_3"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify inserted data
|
||||
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "category_id", categoryBoards[0].ID)
|
||||
assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false})
|
||||
|
||||
// passing empty array
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify inserted data
|
||||
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "category_id", categoryBoards[0].ID)
|
||||
assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))
|
||||
|
||||
// passing duplicate data in input
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_4", "board_id_4"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify inserted data
|
||||
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "category_id", categoryBoards[0].ID)
|
||||
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_4", Hidden: false})
|
||||
|
||||
// adding already added board
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_2"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify inserted data
|
||||
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "category_id", categoryBoards[0].ID)
|
||||
assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))
|
||||
|
||||
// passing already added board along with a new board
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_5"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify inserted data
|
||||
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "category_id", categoryBoards[0].ID)
|
||||
assert.Equal(t, 5, len(categoryBoards[0].BoardMetadata))
|
||||
assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_5", Hidden: false})
|
||||
}
|
||||
|
||||
func testSetBoardVisibility(t *testing.T, store store.Store) {
|
||||
_, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{
|
||||
Boards: []*model.Board{
|
||||
{
|
||||
ID: "board_id_1",
|
||||
TeamID: "team_id",
|
||||
},
|
||||
},
|
||||
}, "user_id")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = store.CreateCategory(model.Category{
|
||||
ID: "category_id",
|
||||
Name: "Category",
|
||||
UserID: "user_id",
|
||||
TeamID: "team_id",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// adding a few boards to the category
|
||||
err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = store.SetBoardVisibility("user_id", "category_id", "board_id_1", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify set visibility
|
||||
categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(categoryBoards))
|
||||
assert.Equal(t, "category_id", categoryBoards[0].ID)
|
||||
assert.Equal(t, 1, len(categoryBoards[0].BoardMetadata))
|
||||
assert.False(t, categoryBoards[0].BoardMetadata[0].Hidden)
|
||||
|
||||
err = store.SetBoardVisibility("user_id", "category_id", "board_id_1", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify set visibility
|
||||
categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, categoryBoards[0].BoardMetadata[0].Hidden)
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ func LoadData(t *testing.T, store store.Store) {
|
||||
err = store.UpsertSharing(sharing)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.AddUpdateCategoryBoard(testUserID, map[string]string{boardID: categoryID})
|
||||
err = store.AddUpdateCategoryBoard(testUserID, categoryID, []string{boardID})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -102,3 +102,20 @@ func IsCloudLicense(license *mmModel.License) bool {
|
||||
license.Features.Cloud != nil &&
|
||||
*license.Features.Cloud
|
||||
}
|
||||
|
||||
func DedupeStringArr(arr []string) []string {
|
||||
hashMap := map[string]bool{}
|
||||
|
||||
for _, item := range arr {
|
||||
hashMap[item] = true
|
||||
}
|
||||
|
||||
dedupedArr := make([]string, len(hashMap))
|
||||
i := 0
|
||||
for key := range hashMap {
|
||||
dedupedArr[i] = key
|
||||
i++
|
||||
}
|
||||
|
||||
return dedupedArr
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ const me: IUser = {
|
||||
|
||||
const categoryAttribute1 = TestBlockFactory.createCategoryBoards()
|
||||
categoryAttribute1.name = 'Category 1'
|
||||
categoryAttribute1.boardIDs = [board.id]
|
||||
categoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]
|
||||
|
||||
describe('src/components/shareBoard/shareBoard', () => {
|
||||
const w = (window as any)
|
||||
|
@ -41,11 +41,12 @@ describe('components/sidebarSidebar', () => {
|
||||
const categoryAttribute1 = TestBlockFactory.createCategoryBoards()
|
||||
categoryAttribute1.id = 'category1'
|
||||
categoryAttribute1.name = 'Category 1'
|
||||
categoryAttribute1.boardIDs = [board.id]
|
||||
categoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]
|
||||
|
||||
const defaultCategory = TestBlockFactory.createCategoryBoards()
|
||||
defaultCategory.id = 'default_category'
|
||||
defaultCategory.name = 'Boards'
|
||||
defaultCategory.boardMetadata = []
|
||||
|
||||
test('sidebar hidden', () => {
|
||||
const store = mockStore({
|
||||
@ -80,6 +81,7 @@ describe('components/sidebarSidebar', () => {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
})
|
||||
|
||||
@ -111,6 +113,11 @@ describe('components/sidebarSidebar', () => {
|
||||
|
||||
customGlobal.innerWidth = 500
|
||||
|
||||
const localCategoryAttribute = TestBlockFactory.createCategoryBoards()
|
||||
localCategoryAttribute.id = 'category1'
|
||||
localCategoryAttribute.name = 'Category 1'
|
||||
categoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]
|
||||
|
||||
const store = mockStore({
|
||||
teams: {
|
||||
current: {id: 'team-id'},
|
||||
@ -143,6 +150,7 @@ describe('components/sidebarSidebar', () => {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
})
|
||||
|
||||
@ -169,6 +177,11 @@ describe('components/sidebarSidebar', () => {
|
||||
})
|
||||
|
||||
test('dont show hidden boards', () => {
|
||||
const localCategoryAttribute = TestBlockFactory.createCategoryBoards()
|
||||
localCategoryAttribute.id = 'category1'
|
||||
localCategoryAttribute.name = 'Category 1'
|
||||
localCategoryAttribute.boardMetadata = [{boardID: board.id, hidden: true}]
|
||||
|
||||
const store = mockStore({
|
||||
teams: {
|
||||
current: {id: 'team-id'},
|
||||
@ -203,8 +216,9 @@ describe('components/sidebarSidebar', () => {
|
||||
},
|
||||
sidebar: {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
localCategoryAttribute,
|
||||
],
|
||||
hiddenBoardIDs: [board.id],
|
||||
},
|
||||
})
|
||||
|
||||
@ -236,6 +250,7 @@ describe('components/sidebarSidebar', () => {
|
||||
collapsedCategory.id = 'categoryCollapsed'
|
||||
collapsedCategory.name = 'Category 2'
|
||||
collapsedCategory.collapsed = true
|
||||
collapsedCategory.boardMetadata = []
|
||||
|
||||
const store = mockStore({
|
||||
teams: {
|
||||
@ -270,6 +285,7 @@ describe('components/sidebarSidebar', () => {
|
||||
categoryAttribute1,
|
||||
collapsedCategory,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
})
|
||||
|
||||
@ -327,6 +343,7 @@ describe('components/sidebarSidebar', () => {
|
||||
categoryAttribute1,
|
||||
defaultCategory,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
})
|
||||
|
||||
@ -355,7 +372,7 @@ describe('components/sidebarSidebar', () => {
|
||||
const categoryAttribute2 = TestBlockFactory.createCategoryBoards()
|
||||
categoryAttribute2.id = 'category2'
|
||||
categoryAttribute2.name = 'Category 2'
|
||||
categoryAttribute2.boardIDs = [board2.id]
|
||||
categoryAttribute2.boardMetadata = [{boardID: board2.id, hidden: false}]
|
||||
|
||||
const store = mockStore({
|
||||
teams: {
|
||||
|
@ -124,8 +124,8 @@ const Sidebar = (props: Props) => {
|
||||
// and thats the first time that user is opening that board.
|
||||
// Here we check if that board has a associated category for the user. If not,
|
||||
// we assign it to the default "Boards" category.
|
||||
// We do this on the client side rather than the server side live for all other cases
|
||||
// is because there is no good, explicit API call to add this logic to when opening
|
||||
// We do this on the client side rather than the server side like for all other cases
|
||||
// because there is no good, explicit API call to add this logic to when opening
|
||||
// a board that you have implicit access to.
|
||||
useEffect(() => {
|
||||
if (!sidebarCategories || sidebarCategories.length === 0 || !currentBoard || !team || currentBoard.isTemplate) {
|
||||
@ -133,7 +133,8 @@ const Sidebar = (props: Props) => {
|
||||
}
|
||||
|
||||
// find the category the current board belongs to
|
||||
const category = sidebarCategories.find((c) => c.boardIDs.indexOf(currentBoard.id) >= 0)
|
||||
// const category = sidebarCategories.find((c) => c.boardIDs.indexOf(currentBoard.id) >= 0)
|
||||
const category = sidebarCategories.find((c) => c.boardMetadata.find((boardMetadata) => boardMetadata.boardID === currentBoard.id))
|
||||
if (category) {
|
||||
// Boards does belong to a category.
|
||||
// All good here. Nothing to do
|
||||
@ -219,29 +220,41 @@ const Sidebar = (props: Props) => {
|
||||
return
|
||||
}
|
||||
|
||||
const boardIDs = [...toSidebarCategory.boardIDs]
|
||||
boardIDs.splice(source.index, 1)
|
||||
boardIDs.splice(destination.index, 0, toSidebarCategory.boardIDs[source.index])
|
||||
const categoryBoardMetadata = [...toSidebarCategory.boardMetadata]
|
||||
categoryBoardMetadata.splice(source.index, 1)
|
||||
categoryBoardMetadata.splice(destination.index, 0, toSidebarCategory.boardMetadata[source.index])
|
||||
|
||||
dispatch(updateCategoryBoardsOrder({categoryID: toCategoryID, boardIDs}))
|
||||
await octoClient.reorderSidebarCategoryBoards(team.id, toCategoryID, boardIDs)
|
||||
dispatch(updateCategoryBoardsOrder({categoryID: toCategoryID, boardsMetadata: categoryBoardMetadata}))
|
||||
|
||||
const reorderedBoardIDs = categoryBoardMetadata.map((m) => m.boardID)
|
||||
await octoClient.reorderSidebarCategoryBoards(team.id, toCategoryID, reorderedBoardIDs)
|
||||
} else {
|
||||
// board moved to a different category
|
||||
const toSidebarCategory = sidebarCategories.find((category) => category.id === toCategoryID)
|
||||
const fromSidebarCategory = sidebarCategories.find((category) => category.id === fromCategoryID)
|
||||
|
||||
if (!toSidebarCategory) {
|
||||
Utils.logError(`toCategoryID not found in list of sidebar categories. toCategoryID: ${toCategoryID}`)
|
||||
return
|
||||
}
|
||||
|
||||
const boardIDs = [...toSidebarCategory.boardIDs]
|
||||
boardIDs.splice(destination.index, 0, boardID)
|
||||
if (!fromSidebarCategory) {
|
||||
Utils.logError(`fromCategoryID not found in list of sidebar categories. fromCategoryID: ${fromCategoryID}`)
|
||||
return
|
||||
}
|
||||
|
||||
const categoryBoardMetadata = [...toSidebarCategory.boardMetadata]
|
||||
const fromCategoryBoardMetadata = fromSidebarCategory.boardMetadata[source.index]
|
||||
categoryBoardMetadata.splice(destination.index, 0, fromCategoryBoardMetadata)
|
||||
|
||||
// optimistically updating the store to create a lag-free UI.
|
||||
await dispatch(updateCategoryBoardsOrder({categoryID: toCategoryID, boardIDs}))
|
||||
dispatch(updateBoardCategories([{boardID, categoryID: toCategoryID}]))
|
||||
await dispatch(updateCategoryBoardsOrder({categoryID: toCategoryID, boardsMetadata: categoryBoardMetadata}))
|
||||
dispatch(updateBoardCategories([{...fromCategoryBoardMetadata, categoryID: toCategoryID}]))
|
||||
|
||||
await mutator.moveBoardToCategory(team.id, boardID, toCategoryID, fromCategoryID)
|
||||
await octoClient.reorderSidebarCategoryBoards(team.id, toCategoryID, boardIDs)
|
||||
|
||||
const reorderedBoardIDs = categoryBoardMetadata.map((m) => m.boardID)
|
||||
await octoClient.reorderSidebarCategoryBoards(team.id, toCategoryID, reorderedBoardIDs)
|
||||
}
|
||||
}, [team, sidebarCategories])
|
||||
|
||||
@ -313,7 +326,7 @@ const Sidebar = (props: Props) => {
|
||||
const getSortedCategoryBoards = (category: CategoryBoards): Board[] => {
|
||||
const categoryBoardsByID = new Map<string, Board>()
|
||||
boards.forEach((board) => {
|
||||
if (!category.boardIDs.includes(board.id)) {
|
||||
if (!category.boardMetadata.find((m) => m.boardID === board.id)) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -321,8 +334,8 @@ const Sidebar = (props: Props) => {
|
||||
})
|
||||
|
||||
const sortedBoards: Board[] = []
|
||||
category.boardIDs.forEach((boardID) => {
|
||||
const b = categoryBoardsByID.get(boardID)
|
||||
category.boardMetadata.forEach((boardMetadata) => {
|
||||
const b = categoryBoardsByID.get(boardMetadata.boardID)
|
||||
if (b) {
|
||||
sortedBoards.push(b)
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ describe('components/sidebarBoardItem', () => {
|
||||
|
||||
const categoryBoards1 = TestBlockFactory.createCategoryBoards()
|
||||
categoryBoards1.name = 'Category 1'
|
||||
categoryBoards1.boardIDs = [board.id]
|
||||
categoryBoards1.boardMetadata = [{boardID: board.id, hidden: false}]
|
||||
|
||||
const categoryBoards2 = TestBlockFactory.createCategoryBoards()
|
||||
categoryBoards2.name = 'Category 2'
|
||||
|
@ -35,10 +35,9 @@ import {Utils} from '../../utils'
|
||||
|
||||
import AddIcon from '../../widgets/icons/add'
|
||||
import CloseIcon from '../../widgets/icons/close'
|
||||
import {UserConfigPatch} from '../../user'
|
||||
import {getMe, getMyConfig, patchProps} from '../../store/users'
|
||||
import {getMe} from '../../store/users'
|
||||
import octoClient from '../../octoClient'
|
||||
import {getCurrentBoardId, getMySortedBoards} from '../../store/boards'
|
||||
import {getCurrentBoardId} from '../../store/boards'
|
||||
import {UserSettings} from '../../userSettings'
|
||||
import {Archiver} from '../../archiver'
|
||||
|
||||
@ -75,12 +74,10 @@ const SidebarBoardItem = (props: Props) => {
|
||||
const currentViewId = useAppSelector(getCurrentViewId)
|
||||
const teamID = team?.id || ''
|
||||
const me = useAppSelector(getMe)
|
||||
const myConfig = useAppSelector(getMyConfig)
|
||||
|
||||
const match = useRouteMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>()
|
||||
const history = useHistory()
|
||||
const dispatch = useAppDispatch()
|
||||
const myAllBoards = useAppSelector(getMySortedBoards)
|
||||
const currentBoardID = useAppSelector(getCurrentBoardId)
|
||||
|
||||
const generateMoveToCategoryOptions = (boardID: string) => {
|
||||
@ -136,6 +133,7 @@ const SidebarBoardItem = (props: Props) => {
|
||||
await dispatch(updateBoardCategories([{
|
||||
boardID: boardId,
|
||||
categoryID: props.categoryBoards.id,
|
||||
hidden: false,
|
||||
}]))
|
||||
}
|
||||
|
||||
@ -155,23 +153,14 @@ const SidebarBoardItem = (props: Props) => {
|
||||
return
|
||||
}
|
||||
|
||||
// creating new array as myConfig.hiddenBoardIDs.value
|
||||
// belongs to Redux state and so is immutable.
|
||||
const hiddenBoards = {...(myConfig.hiddenBoardIDs ? myConfig.hiddenBoardIDs.value : {})}
|
||||
|
||||
hiddenBoards[board.id] = true
|
||||
const hiddenBoardsArray = Object.keys(hiddenBoards)
|
||||
const patch: UserConfigPatch = {
|
||||
updatedFields: {
|
||||
hiddenBoardIDs: JSON.stringify(hiddenBoardsArray),
|
||||
await octoClient.hideBoard(props.categoryBoards.id, board.id)
|
||||
dispatch(updateBoardCategories([
|
||||
{
|
||||
boardID: board.id,
|
||||
categoryID: props.categoryBoards.id,
|
||||
hidden: true,
|
||||
},
|
||||
}
|
||||
const patchedProps = await octoClient.patchUserConfig(me.id, patch)
|
||||
if (!patchedProps) {
|
||||
return
|
||||
}
|
||||
|
||||
await dispatch(patchProps(patchedProps))
|
||||
]))
|
||||
|
||||
// If we're hiding the board we're currently on,
|
||||
// we need to switch to a different board once its hidden.
|
||||
@ -181,17 +170,22 @@ const SidebarBoardItem = (props: Props) => {
|
||||
|
||||
// Empty board ID navigates to template picker, which is
|
||||
// fine if there are no more visible boards to switch to.
|
||||
const visibleBoards = myAllBoards.filter((b) => !hiddenBoards[b.id])
|
||||
|
||||
if (visibleBoards.length === 0) {
|
||||
// find the first visible board
|
||||
let visibleBoardID: string | null = null
|
||||
for (const iterCategory of props.allCategories) {
|
||||
const visibleBoardMetadata = iterCategory.boardMetadata.find((categoryBoardMetadata) => !categoryBoardMetadata.hidden && categoryBoardMetadata.boardID !== props.board.id)
|
||||
if (visibleBoardMetadata) {
|
||||
visibleBoardID = visibleBoardMetadata.boardID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (visibleBoardID === null) {
|
||||
UserSettings.setLastBoardID(match.params.teamId!, null)
|
||||
showTemplatePicker()
|
||||
} else {
|
||||
let nextBoardID = ''
|
||||
if (visibleBoards.length > 0) {
|
||||
nextBoardID = visibleBoards[0].id
|
||||
}
|
||||
props.showBoard(nextBoardID)
|
||||
props.showBoard(visibleBoardID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ describe('components/sidebarCategory', () => {
|
||||
const categoryBoards1 = TestBlockFactory.createCategoryBoards()
|
||||
categoryBoards1.id = 'category_1_id'
|
||||
categoryBoards1.name = 'Category 1'
|
||||
categoryBoards1.boardIDs = [board1.id, board2.id]
|
||||
categoryBoards1.boardMetadata = [{boardID: board1.id, hidden: false}, {boardID: board2.id, hidden: false}]
|
||||
|
||||
const categoryBoards2 = TestBlockFactory.createCategoryBoards()
|
||||
categoryBoards2.id = 'category_2_id'
|
||||
|
@ -20,14 +20,13 @@ import Menu from '../../widgets/menu'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
|
||||
import './sidebarCategory.scss'
|
||||
import {Category, CategoryBoards} from '../../store/sidebar'
|
||||
import {Category, CategoryBoardMetadata, CategoryBoards} from '../../store/sidebar'
|
||||
import ChevronDown from '../../widgets/icons/chevronDown'
|
||||
import ChevronRight from '../../widgets/icons/chevronRight'
|
||||
import CreateNewFolder from '../../widgets/icons/newFolder'
|
||||
import CreateCategory from '../createCategory/createCategory'
|
||||
import {useAppSelector} from '../../store/hooks'
|
||||
import {
|
||||
getMyConfig,
|
||||
getOnboardingTourCategory,
|
||||
getOnboardingTourStep,
|
||||
} from '../../store/users'
|
||||
@ -76,7 +75,6 @@ const SidebarCategory = (props: Props) => {
|
||||
const match = useRouteMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>()
|
||||
const [showCreateCategoryModal, setShowCreateCategoryModal] = useState(false)
|
||||
const [showUpdateCategoryModal, setShowUpdateCategoryModal] = useState(false)
|
||||
const myConfig = useAppSelector(getMyConfig)
|
||||
|
||||
const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)
|
||||
const onboardingTourStep = useAppSelector(getOnboardingTourStep)
|
||||
@ -129,19 +127,20 @@ const SidebarCategory = (props: Props) => {
|
||||
props.hideSidebar()
|
||||
}, [match, history])
|
||||
|
||||
const isBoardVisible = (boardID: string): boolean => {
|
||||
const isBoardVisible = (boardID: string, existingBoardMetadata?: CategoryBoardMetadata): boolean => {
|
||||
const categoryBoardMetadata = existingBoardMetadata || sidebarBoardMetadata.find((metadata) => metadata.boardID === boardID)
|
||||
|
||||
// hide if board doesn't belong to current category
|
||||
if (!blocks.includes(boardID)) {
|
||||
if (!categoryBoardMetadata) {
|
||||
return false
|
||||
}
|
||||
|
||||
// hide if board was hidden by the user
|
||||
const hiddenBoardIDs = myConfig.hiddenBoardIDs?.value || {}
|
||||
return !hiddenBoardIDs[boardID]
|
||||
return !categoryBoardMetadata.hidden
|
||||
}
|
||||
|
||||
const blocks = props.categoryBoards.boardIDs || []
|
||||
const visibleBlocks = props.categoryBoards.boardIDs.filter((boardID) => isBoardVisible(boardID))
|
||||
const sidebarBoardMetadata = props.categoryBoards.boardMetadata || []
|
||||
const visibleBlocks = props.categoryBoards.boardMetadata.filter((boardMetadata) => isBoardVisible(boardMetadata.boardID, boardMetadata))
|
||||
|
||||
const handleCreateNewCategory = () => {
|
||||
setShowCreateCategoryModal(true)
|
||||
|
@ -92,7 +92,7 @@ const me: IUser = {
|
||||
|
||||
const categoryAttribute1 = TestBlockFactory.createCategoryBoards()
|
||||
categoryAttribute1.name = 'Category 1'
|
||||
categoryAttribute1.boardIDs = [board.id]
|
||||
categoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const originalModule = jest.requireActual('react-router-dom')
|
||||
@ -171,6 +171,7 @@ describe('src/components/workspace', () => {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
}
|
||||
mockedOctoClient.searchTeamUsers.mockResolvedValue(Object.values(state.users.boardUsers))
|
||||
@ -285,6 +286,12 @@ describe('src/components/workspace', () => {
|
||||
featureFlags: {},
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
})
|
||||
let container: Element | undefined
|
||||
await act(async () => {
|
||||
@ -388,6 +395,7 @@ describe('src/components/workspace', () => {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
}
|
||||
const localStore = mockStateStore([thunk], localState)
|
||||
@ -492,6 +500,7 @@ describe('src/components/workspace', () => {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
}
|
||||
const localStore = mockStateStore([thunk], localState)
|
||||
@ -601,6 +610,7 @@ describe('src/components/workspace', () => {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
hiddenBoardIDs: [],
|
||||
},
|
||||
}
|
||||
const localStore = mockStateStore([thunk], localState)
|
||||
|
@ -25,7 +25,9 @@ import {Utils} from '../utils'
|
||||
import {IUser} from '../user'
|
||||
import propsRegistry from '../properties'
|
||||
|
||||
import {getMe, getMyConfig} from '../store/users'
|
||||
import {getMe} from '../store/users'
|
||||
|
||||
import {getHiddenBoardIDs} from '../store/sidebar'
|
||||
|
||||
import CenterPanel from './centerPanel'
|
||||
import BoardTemplateSelector from './boardTemplateSelector/boardTemplateSelector'
|
||||
@ -54,12 +56,11 @@ function CenterContent(props: Props) {
|
||||
const cardLimitTimestamp = useAppSelector(getCardLimitTimestamp)
|
||||
const history = useHistory()
|
||||
const dispatch = useAppDispatch()
|
||||
const myConfig = useAppSelector(getMyConfig)
|
||||
const me = useAppSelector<IUser|null>(getMe)
|
||||
const hiddenBoardIDs = useAppSelector(getHiddenBoardIDs)
|
||||
|
||||
const isBoardHidden = () => {
|
||||
const hiddenBoardIDs = myConfig.hiddenBoardIDs?.value || {}
|
||||
return hiddenBoardIDs[board.id]
|
||||
return hiddenBoardIDs.includes(board.id)
|
||||
}
|
||||
|
||||
const showCard = useCallback((cardId?: string) => {
|
||||
|
@ -1033,6 +1033,22 @@ class OctoClient {
|
||||
body: '{}',
|
||||
})
|
||||
}
|
||||
|
||||
async hideBoard(categoryID: string, boardID: string): Promise<Response> {
|
||||
const path = `${this.teamPath()}/categories/${categoryID}/boards/${boardID}/hide`
|
||||
return fetch(this.getBaseURL() + path, {
|
||||
method: 'PUT',
|
||||
headers: this.headers(),
|
||||
})
|
||||
}
|
||||
|
||||
async unhideBoard(categoryID: string, boardID: string): Promise<Response> {
|
||||
const path = `${this.teamPath()}/categories/${categoryID}/boards/${boardID}/unhide`
|
||||
return fetch(this.getBaseURL() + path, {
|
||||
method: 'PUT',
|
||||
headers: this.headers(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const octoClient = new OctoClient()
|
||||
|
@ -12,7 +12,7 @@ import octoClient from '../../octoClient'
|
||||
import {Subscription, WSClient} from '../../wsclient'
|
||||
import {Utils} from '../../utils'
|
||||
import {useWebsockets} from '../../hooks/websockets'
|
||||
import {IUser, UserConfigPatch} from '../../user'
|
||||
import {IUser} from '../../user'
|
||||
import {Block} from '../../blocks/block'
|
||||
import {ContentBlock} from '../../blocks/contentBlock'
|
||||
import {CommentBlock} from '../../blocks/commentBlock'
|
||||
@ -40,7 +40,7 @@ import {
|
||||
fetchUserBlockSubscriptions,
|
||||
getMe,
|
||||
followBlock,
|
||||
unfollowBlock, patchProps, getMyConfig,
|
||||
unfollowBlock,
|
||||
} from '../../store/users'
|
||||
import {setGlobalError} from '../../store/globalError'
|
||||
import {UserSettings} from '../../userSettings'
|
||||
@ -52,6 +52,8 @@ import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../teleme
|
||||
|
||||
import {Constants} from '../../constants'
|
||||
|
||||
import {getCategoryOfBoard, getHiddenBoardIDs} from '../../store/sidebar'
|
||||
|
||||
import SetWindowTitleAndIcon from './setWindowTitleAndIcon'
|
||||
import TeamToBoardAndViewRedirect from './teamToBoardAndViewRedirect'
|
||||
import UndoRedoHotKeys from './undoRedoHotKeys'
|
||||
@ -75,7 +77,8 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||
const teamId = match.params.teamId || UserSettings.lastTeamId || Constants.globalTeamId
|
||||
const viewId = match.params.viewId
|
||||
const me = useAppSelector<IUser|null>(getMe)
|
||||
const myConfig = useAppSelector(getMyConfig)
|
||||
const hiddenBoardIDs = useAppSelector(getHiddenBoardIDs)
|
||||
const category = useAppSelector(getCategoryOfBoard(activeBoardId))
|
||||
|
||||
// if we're in a legacy route and not showing a shared board,
|
||||
// redirect to the new URL schema equivalent
|
||||
@ -220,28 +223,11 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||
}, [teamId, match.params.boardId, viewId, me?.id])
|
||||
|
||||
const handleUnhideBoard = async (boardID: string) => {
|
||||
Utils.log('handleUnhideBoard called')
|
||||
if (!me) {
|
||||
if (!me || !category) {
|
||||
return
|
||||
}
|
||||
|
||||
const hiddenBoards = {...(myConfig.hiddenBoardIDs ? myConfig.hiddenBoardIDs.value : {})}
|
||||
|
||||
// const index = hiddenBoards.indexOf(boardID)
|
||||
// hiddenBoards.splice(index, 1)
|
||||
delete hiddenBoards[boardID]
|
||||
const hiddenBoardsArray = Object.keys(hiddenBoards)
|
||||
const patch: UserConfigPatch = {
|
||||
updatedFields: {
|
||||
hiddenBoardIDs: JSON.stringify(hiddenBoardsArray),
|
||||
},
|
||||
}
|
||||
const patchedProps = await octoClient.patchUserConfig(me.id, patch)
|
||||
if (!patchedProps) {
|
||||
return
|
||||
}
|
||||
|
||||
await dispatch(patchProps(patchedProps))
|
||||
await octoClient.unhideBoard(category.id, boardID)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -249,8 +235,7 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||
return
|
||||
}
|
||||
|
||||
const hiddenBoardIDs = myConfig.hiddenBoardIDs?.value || {}
|
||||
if (hiddenBoardIDs[match.params.boardId]) {
|
||||
if (hiddenBoardIDs.indexOf(match.params.boardId) >= 0) {
|
||||
handleUnhideBoard(match.params.boardId)
|
||||
}
|
||||
}, [me?.id, teamId, match.params.boardId])
|
||||
|
@ -32,10 +32,10 @@ const TeamToBoardAndViewRedirect = (): null => {
|
||||
let goToBoardID: string | null = null
|
||||
|
||||
for (const category of categories) {
|
||||
for (const categoryBoardID of category.boardIDs) {
|
||||
if (boards[categoryBoardID]) {
|
||||
// pick the first category board that exists
|
||||
goToBoardID = categoryBoardID
|
||||
for (const boardMetadata of category.boardMetadata) {
|
||||
// pick the first category board that exists and is not hidden
|
||||
if (!boardMetadata.hidden && boards[boardMetadata.boardID]) {
|
||||
goToBoardID = boardMetadata.boardID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -25,18 +25,24 @@ interface Category {
|
||||
isNew: boolean
|
||||
}
|
||||
|
||||
interface CategoryBoardMetadata {
|
||||
boardID: string
|
||||
hidden: boolean
|
||||
}
|
||||
|
||||
interface CategoryBoards extends Category {
|
||||
boardIDs: string[]
|
||||
boardMetadata: CategoryBoardMetadata[]
|
||||
}
|
||||
|
||||
interface BoardCategoryWebsocketData {
|
||||
boardID: string
|
||||
categoryID: string
|
||||
hidden: boolean
|
||||
}
|
||||
|
||||
interface CategoryBoardsReorderData {
|
||||
categoryID: string
|
||||
boardIDs: string[]
|
||||
boardsMetadata: CategoryBoardMetadata[]
|
||||
}
|
||||
|
||||
export const DefaultCategory: CategoryBoards = {
|
||||
@ -53,11 +59,12 @@ export const fetchSidebarCategories = createAsyncThunk(
|
||||
|
||||
type Sidebar = {
|
||||
categoryAttributes: CategoryBoards[]
|
||||
hiddenBoardIDs: string[]
|
||||
}
|
||||
|
||||
const sidebarSlice = createSlice({
|
||||
name: 'sidebar',
|
||||
initialState: {categoryAttributes: []} as Sidebar,
|
||||
initialState: {categoryAttributes: [], hiddenBoardIDs: []} as Sidebar,
|
||||
reducers: {
|
||||
updateCategories: (state, action: PayloadAction<Category[]>) => {
|
||||
action.payload.forEach((updatedCategory) => {
|
||||
@ -68,7 +75,7 @@ const sidebarSlice = createSlice({
|
||||
// new categories should always show up on the top
|
||||
state.categoryAttributes.unshift({
|
||||
...updatedCategory,
|
||||
boardIDs: [],
|
||||
boardMetadata: [],
|
||||
isNew: true,
|
||||
})
|
||||
} else if (updatedCategory.deleteAt) {
|
||||
@ -87,31 +94,44 @@ const sidebarSlice = createSlice({
|
||||
},
|
||||
updateBoardCategories: (state, action: PayloadAction<BoardCategoryWebsocketData[]>) => {
|
||||
const updatedCategoryAttributes: CategoryBoards[] = []
|
||||
let updatedHiddenBoardIDs = state.hiddenBoardIDs
|
||||
|
||||
action.payload.forEach((boardCategory) => {
|
||||
for (let i = 0; i < state.categoryAttributes.length; i++) {
|
||||
const categoryAttribute = state.categoryAttributes[i]
|
||||
|
||||
if (categoryAttribute.id === boardCategory.categoryID) {
|
||||
// if board is already in the right category, don't do anything
|
||||
// and let the board stay in its right order.
|
||||
// Only if its not in the right category, do add it.
|
||||
if (categoryAttribute.boardIDs.indexOf(boardCategory.boardID) < 0) {
|
||||
categoryAttribute.boardIDs.unshift(boardCategory.boardID)
|
||||
const categoryBoardMetadataIndex = categoryAttribute.boardMetadata.findIndex((boardMetadata) => boardMetadata.boardID === boardCategory.boardID)
|
||||
if (categoryBoardMetadataIndex >= 0) {
|
||||
categoryAttribute.boardMetadata[categoryBoardMetadataIndex] = {
|
||||
...categoryAttribute.boardMetadata[categoryBoardMetadataIndex],
|
||||
hidden: boardCategory.hidden,
|
||||
}
|
||||
} else {
|
||||
categoryAttribute.boardMetadata.unshift({boardID: boardCategory.boardID, hidden: boardCategory.hidden})
|
||||
categoryAttribute.isNew = false
|
||||
}
|
||||
} else {
|
||||
// remove the board from other categories
|
||||
categoryAttribute.boardIDs = categoryAttribute.boardIDs.filter((boardID) => boardID !== boardCategory.boardID)
|
||||
categoryAttribute.boardMetadata = categoryAttribute.boardMetadata.filter((metadata) => metadata.boardID !== boardCategory.boardID)
|
||||
}
|
||||
|
||||
updatedCategoryAttributes[i] = categoryAttribute
|
||||
|
||||
if (boardCategory.hidden) {
|
||||
if (updatedHiddenBoardIDs.indexOf(boardCategory.boardID) < 0) {
|
||||
updatedHiddenBoardIDs.push(boardCategory.boardID)
|
||||
}
|
||||
} else {
|
||||
updatedHiddenBoardIDs = updatedHiddenBoardIDs.filter((hiddenBoardID) => hiddenBoardID !== boardCategory.boardID)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (updatedCategoryAttributes.length > 0) {
|
||||
state.categoryAttributes = updatedCategoryAttributes
|
||||
}
|
||||
state.hiddenBoardIDs = updatedHiddenBoardIDs
|
||||
},
|
||||
updateCategoryOrder: (state, action: PayloadAction<string[]>) => {
|
||||
if (action.payload.length === 0) {
|
||||
@ -134,7 +154,7 @@ const sidebarSlice = createSlice({
|
||||
state.categoryAttributes = newOrderedCategories
|
||||
},
|
||||
updateCategoryBoardsOrder: (state, action: PayloadAction<CategoryBoardsReorderData>) => {
|
||||
if (action.payload.boardIDs.length === 0) {
|
||||
if (action.payload.boardsMetadata.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -145,9 +165,9 @@ const sidebarSlice = createSlice({
|
||||
}
|
||||
|
||||
const category = state.categoryAttributes[categoryIndex]
|
||||
const updatedCategory = {
|
||||
const updatedCategory: CategoryBoards = {
|
||||
...category,
|
||||
boardIDs: action.payload.boardIDs,
|
||||
boardMetadata: action.payload.boardsMetadata,
|
||||
isNew: false,
|
||||
}
|
||||
|
||||
@ -158,6 +178,17 @@ const sidebarSlice = createSlice({
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(fetchSidebarCategories.fulfilled, (state, action) => {
|
||||
state.categoryAttributes = action.payload || []
|
||||
state.hiddenBoardIDs = state.categoryAttributes.flatMap(
|
||||
(ca) => {
|
||||
return ca.boardMetadata.reduce((collector, m) => {
|
||||
if (m.hidden) {
|
||||
collector.push(m.boardID)
|
||||
}
|
||||
|
||||
return collector
|
||||
}, [] as string[])
|
||||
},
|
||||
)
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -167,9 +198,18 @@ export const getSidebarCategories = createSelector(
|
||||
(sidebarCategories) => sidebarCategories,
|
||||
)
|
||||
|
||||
export const getHiddenBoardIDs = (state: RootState): string[] => state.sidebar.hiddenBoardIDs
|
||||
|
||||
export function getCategoryOfBoard(boardID: string): (state: RootState) => CategoryBoards | undefined {
|
||||
return createSelector(
|
||||
(state: RootState): CategoryBoards[] => state.sidebar.categoryAttributes,
|
||||
(sidebarCategories) => sidebarCategories.find((category) => category.boardMetadata.findIndex((m) => m.boardID === boardID) >= 0),
|
||||
)
|
||||
}
|
||||
|
||||
export const {reducer} = sidebarSlice
|
||||
|
||||
export const {updateCategories, updateBoardCategories, updateCategoryOrder, updateCategoryBoardsOrder} = sidebarSlice.actions
|
||||
|
||||
export {Category, CategoryBoards, BoardCategoryWebsocketData, CategoryBoardsReorderData}
|
||||
export {Category, CategoryBoards, BoardCategoryWebsocketData, CategoryBoardsReorderData, CategoryBoardMetadata}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class TestBlockFactory {
|
||||
static createCategoryBoards(): CategoryBoards {
|
||||
return {
|
||||
...TestBlockFactory.createCategory(),
|
||||
boardIDs: [],
|
||||
boardMetadata: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user