focalboard/server/app/category_boards.go
Harshil Sharma 9918a0b3f8
DND support for category and boards in LHS (#3964)
* WIP

* WIP

* Removed unused webapp util

* Added server tests

* Lint fix

* Updating existing tests

* Updating existing tests

* Updating existing tests

* Fixing existing tests

* Fixing existing tests

* Fixing existing tests

* WIP

* Added category sort order migration

* Added logic to set new category on top

* Implemented api, WS listein logic remining

* finished webapp implementation

* Added category type and tests

* updated tests

* Fixed integration test

* type fix

* WIP

* implemented boards DND to other category and in same category

* removed seconds from boards name

* wip

* debugging cy test

* Enabled hiding views list while DNDing

* Removed some debug logs

* Fixed a bug preventing users from collapsing boards category

* WIP

* Debugging cypress test

* CI

* debugging cy test

* Testing a fix

* reverting test fix

* Handled personal server

* WIP

* WIP

* Adding support for building with esbuild

* Using different index.html templates for esbuild

* WIP

* WIP

* Fixed delete category and rename category

* WIP

* WIP

* Finally, its done.

* Adde suppor tot update board-category mapping in bulk

* Fixed a bug where create category option didn't show up on default category

* Fixed bug where new board was added as last board in Boards category instead of first board

* Minor cleanup

* WIP

* Added support to drab boards onto collapsed categories

* Fixed route order from specific to generic

* Fix linter

* Updated existin server tests

* fixed integration tests

* Fixed webapp test err

* Removed accidental dependencies

* Adding  new server tests

* Finished server tests

* added api to client.go

* Added API integration test

* Fixed existing webapp tests

* WIP

* WIP

* WIP

* WIP

* WIP

* Fixed missing paranthesis

* Some cleanup

* fixed server lint

* noopped down migration

* Fixed issue with DND not working great with newly added category

* Fixed a test

* Fixed a test

* Fixed a test

* Fixed console error while DNDing

* pakg lock restore

* Fixed missing react beautiful dnd in package.lock.json

* updated snapshots

* Fixed webapp test

* Review fixes

* Added API permission check

Co-authored-by: Jesús Espino <jespinog@gmail.com>
2022-11-24 15:31:32 +05:30

206 lines
5.7 KiB
Go

package app
import (
"errors"
"fmt"
"github.com/mattermost/focalboard/server/model"
)
const defaultCategoryBoards = "Boards"
var errCategoryBoardsLengthMismatch = errors.New("cannot update category boards order, passed list of categories boards different size than in database")
var errBoardNotFoundInCategory = errors.New("specified board ID not found in specified category ID")
func (a *App) GetUserCategoryBoards(userID, teamID string) ([]model.CategoryBoards, error) {
categoryBoards, err := a.store.GetUserCategoryBoards(userID, teamID)
if err != nil {
return nil, err
}
createdCategoryBoards, err := a.createDefaultCategoriesIfRequired(categoryBoards, userID, teamID)
if err != nil {
return nil, err
}
categoryBoards = append(categoryBoards, createdCategoryBoards...)
return categoryBoards, nil
}
func (a *App) createDefaultCategoriesIfRequired(existingCategoryBoards []model.CategoryBoards, userID, teamID string) ([]model.CategoryBoards, error) {
createdCategories := []model.CategoryBoards{}
boardsCategoryExist := false
for _, categoryBoard := range existingCategoryBoards {
if categoryBoard.Name == defaultCategoryBoards {
boardsCategoryExist = true
}
}
if !boardsCategoryExist {
createdCategoryBoards, err := a.createBoardsCategory(userID, teamID, existingCategoryBoards)
if err != nil {
return nil, err
}
createdCategories = append(createdCategories, *createdCategoryBoards)
}
return createdCategories, nil
}
func (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards []model.CategoryBoards) (*model.CategoryBoards, error) {
// create the category
category := model.Category{
Name: defaultCategoryBoards,
UserID: userID,
TeamID: teamID,
Collapsed: false,
Type: model.CategoryTypeSystem,
SortOrder: len(existingCategoryBoards) * model.CategoryBoardsSortOrderGap,
}
createdCategory, err := a.CreateCategory(&category)
if err != nil {
return nil, fmt.Errorf("createBoardsCategory default category creation failed: %w", err)
}
// once the category is created, we need to move all boards which do not
// belong to any category, into this category.
userBoards, err := a.GetBoardsForUserAndTeam(userID, teamID, false)
if err != nil {
return nil, fmt.Errorf("createBoardsCategory error fetching user's team's boards: %w", err)
}
createdCategoryBoards := &model.CategoryBoards{
Category: *createdCategory,
BoardIDs: []string{},
}
for _, board := range userBoards {
belongsToCategory := false
for _, categoryBoard := range existingCategoryBoards {
for _, boardID := range categoryBoard.BoardIDs {
if boardID == board.ID {
belongsToCategory = true
break
}
}
// stop looking into other categories if
// the board was found in a category
if belongsToCategory {
break
}
}
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)
}
createdCategoryBoards.BoardIDs = append(createdCategoryBoards.BoardIDs, board.ID)
}
}
return createdCategoryBoards, nil
}
func (a *App) AddUpdateUserCategoryBoard(teamID, userID string, boardCategoryMapping map[string]string) error {
err := a.store.AddUpdateCategoryBoard(userID, boardCategoryMapping)
if err != nil {
return err
}
wsPayload := make([]*model.BoardCategoryWebsocketData, len(boardCategoryMapping))
i := 0
for boardID, categoryID := range boardCategoryMapping {
wsPayload[i] = &model.BoardCategoryWebsocketData{
BoardID: boardID,
CategoryID: categoryID,
}
i++
}
a.blockChangeNotifier.Enqueue(func() error {
a.wsAdapter.BroadcastCategoryBoardChange(
teamID,
userID,
wsPayload,
)
return nil
})
return nil
}
func (a *App) ReorderCategoryBoards(userID, teamID, categoryID string, newBoardsOrder []string) ([]string, error) {
if err := a.verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID, newBoardsOrder); err != nil {
return nil, err
}
newOrder, err := a.store.ReorderCategoryBoards(categoryID, newBoardsOrder)
if err != nil {
return nil, err
}
go func() {
a.wsAdapter.BroadcastCategoryBoardsReorder(teamID, userID, categoryID, newOrder)
}()
return newOrder, nil
}
func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID string, newBoardsOrder []string) error {
// this function is to ensure that we don't miss specifying
// all boards of the category while reordering.
existingCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)
if err != nil {
return err
}
var targetCategoryBoards *model.CategoryBoards
for i := range existingCategoryBoards {
if existingCategoryBoards[i].Category.ID == categoryID {
targetCategoryBoards = &existingCategoryBoards[i]
break
}
}
if targetCategoryBoards == nil {
return fmt.Errorf("%w categoryID: %s", errCategoryNotFound, categoryID)
}
if len(targetCategoryBoards.BoardIDs) != 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),
userID,
teamID,
categoryID,
)
}
existingBoardMap := map[string]bool{}
for _, boardID := range targetCategoryBoards.BoardIDs {
existingBoardMap[boardID] = true
}
for _, boardID := range newBoardsOrder {
if _, found := existingBoardMap[boardID]; !found {
return fmt.Errorf(
"%w board ID: %s, category ID: %s, userID: %s, teamID: %s",
errBoardNotFoundInCategory,
boardID,
categoryID,
userID,
teamID,
)
}
}
return nil
}