Applying migration changes (#2752)

* Applying migration changes

* Fixing linter erros

* Restoring userID

* Updated user id length

* Update server/app/category_boards.go

* Skiped creating categories for boards belonging to DMs

* Handled private group messages as well

* fix sql error for insert_at

* fix timestamp parsing

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
This commit is contained in:
Jesús Espino 2022-04-14 00:10:53 +02:00 committed by GitHub
parent 89cc947a21
commit bc37e97ae9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 300 additions and 281 deletions

View file

@ -39,7 +39,7 @@ import wsClient, {
ACTION_UPDATE_BLOCK,
ACTION_UPDATE_CLIENT_CONFIG,
ACTION_UPDATE_SUBSCRIPTION,
ACTION_UPDATE_CATEGORY, ACTION_UPDATE_BLOCK_CATEGORY, ACTION_UPDATE_BOARD,
ACTION_UPDATE_CATEGORY, ACTION_UPDATE_BOARD_CATEGORY, ACTION_UPDATE_BOARD,
} from './../../../webapp/src/wsclient'
import manifest from './manifest'
@ -273,7 +273,7 @@ export default class Plugin {
// register websocket handlers
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_BOARD}`, (e: any) => wsClient.updateHandler(e.data))
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_CATEGORY}`, (e: any) => wsClient.updateHandler(e.data))
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_BLOCK_CATEGORY}`, (e: any) => wsClient.updateHandler(e.data))
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_BOARD_CATEGORY}`, (e: any) => wsClient.updateHandler(e.data))
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_CLIENT_CONFIG}`, (e: any) => wsClient.updateClientConfigHandler(e.data))
this.registry?.registerWebSocketEventHandler(`custom_${manifest.id}_${ACTION_UPDATE_SUBSCRIPTION}`, (e: any) => wsClient.updateSubscriptionHandler(e.data))
this.registry?.registerWebSocketEventHandler('plugin_statuses_changed', (e: any) => wsClient.pluginStatusesChangedHandler(e.data))

View file

@ -135,8 +135,8 @@ func (a *API) RegisterRoutes(r *mux.Router) {
apiv2.HandleFunc("/teams/{teamID}/categories/{categoryID}", a.sessionRequired(a.handleDeleteCategory)).Methods(http.MethodDelete)
// Category Block APIs
apiv2.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleGetUserCategoryBlocks)).Methods(http.MethodGet)
apiv2.HandleFunc("/teams/{teamID}/categories/{categoryID}/blocks/{blockID}", a.sessionRequired(a.handleUpdateCategoryBlock)).Methods(http.MethodPost)
apiv2.HandleFunc("/teams/{teamID}/categories", a.sessionRequired(a.handleGetUserCategoryBoards)).Methods(http.MethodGet)
apiv2.HandleFunc("/teams/{teamID}/categories/{categoryID}/boards/{boardID}", a.sessionRequired(a.handleUpdateCategoryBoard)).Methods(http.MethodPost)
// Get Files API
apiv2.HandleFunc("/files/teams/{teamID}/{boardID}/{filename}", a.attachSession(a.handleServeFile, false)).Methods("GET")
@ -539,7 +539,7 @@ func (a *API) handleDeleteCategory(w http.ResponseWriter, r *http.Request) {
auditRec.Success()
}
func (a *API) handleGetUserCategoryBlocks(w http.ResponseWriter, r *http.Request) {
func (a *API) handleGetUserCategoryBoards(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session := ctx.Value(sessionContextKey).(*model.Session)
userID := session.UserID
@ -550,7 +550,7 @@ func (a *API) handleGetUserCategoryBlocks(w http.ResponseWriter, r *http.Request
auditRec := a.makeAuditRecord(r, "getUserCategoryBlocks", audit.Fail)
defer a.audit.LogRecord(audit.LevelModify, auditRec)
categoryBlocks, err := a.app.GetUserCategoryBlocks(userID, teamID)
categoryBlocks, err := a.app.GetUserCategoryBoards(userID, teamID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
@ -566,13 +566,13 @@ func (a *API) handleGetUserCategoryBlocks(w http.ResponseWriter, r *http.Request
auditRec.Success()
}
func (a *API) handleUpdateCategoryBlock(w http.ResponseWriter, r *http.Request) {
auditRec := a.makeAuditRecord(r, "updateCategoryBlock", audit.Fail)
func (a *API) handleUpdateCategoryBoard(w http.ResponseWriter, r *http.Request) {
auditRec := a.makeAuditRecord(r, "updateCategoryBoard", audit.Fail)
defer a.audit.LogRecord(audit.LevelModify, auditRec)
vars := mux.Vars(r)
categoryID := vars["categoryID"]
blockID := vars["blockID"]
boardID := vars["boardID"]
teamID := vars["teamID"]
ctx := r.Context()
@ -580,7 +580,7 @@ func (a *API) handleUpdateCategoryBlock(w http.ResponseWriter, r *http.Request)
userID := session.UserID
// TODO: Check the category and the team matches
err := a.app.AddUpdateUserCategoryBlock(teamID, userID, categoryID, blockID)
err := a.app.AddUpdateUserCategoryBoard(teamID, userID, categoryID, boardID)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return

View file

@ -1,26 +0,0 @@
package app
import "github.com/mattermost/focalboard/server/model"
func (a *App) GetUserCategoryBlocks(userID, teamID string) ([]model.CategoryBlocks, error) {
return a.store.GetUserCategoryBlocks(userID, teamID)
}
func (a *App) AddUpdateUserCategoryBlock(teamID, userID, categoryID, blockID string) error {
err := a.store.AddUpdateCategoryBlock(userID, categoryID, blockID)
if err != nil {
return err
}
go func() {
a.wsAdapter.BroadcastCategoryBlockChange(
teamID,
userID,
model.BlockCategoryWebsocketData{
BlockID: blockID,
CategoryID: categoryID,
})
}()
return nil
}

View file

@ -0,0 +1,27 @@
package app
import "github.com/mattermost/focalboard/server/model"
func (a *App) GetUserCategoryBoards(userID, teamID string) ([]model.CategoryBoards, error) {
return a.store.GetUserCategoryBoards(userID, teamID)
}
func (a *App) AddUpdateUserCategoryBoard(teamID, userID, categoryID, boardID string) error {
err := a.store.AddUpdateCategoryBoard(userID, categoryID, boardID)
if err != nil {
return err
}
a.blockChangeNotifier.Enqueue(func() error {
a.wsAdapter.BroadcastCategoryBoardChange(
teamID,
userID,
model.BoardCategoryWebsocketData{
BoardID: boardID,
CategoryID: categoryID,
})
return nil
})
return nil
}

View file

@ -1948,7 +1948,7 @@ func TestPermissionsDeleteCategory(t *testing.T) {
runTestCases(t, ttCases, testData, clients)
}
func TestPermissionsUpdateCategoryBlock(t *testing.T) {
func TestPermissionsUpdateCategoryBoard(t *testing.T) {
th := SetupTestHelperPluginMode(t)
defer th.TearDown()
testData := setupData(t, th)
@ -1980,13 +1980,13 @@ func TestPermissionsUpdateCategoryBlock(t *testing.T) {
require.NoError(t, err)
ttCases := []TestCase{
{"/teams/test-team/categories/any/blocks/any", methodPost, "", userAnon, http.StatusUnauthorized, 0},
{"/teams/test-team/categories/" + categoryNoTeamMember.ID + "/blocks/" + testData.publicBoard.ID, methodPost, "", userNoTeamMember, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryTeamMember.ID + "/blocks/" + testData.publicBoard.ID, methodPost, "", userTeamMember, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryViewer.ID + "/blocks/" + testData.publicBoard.ID, methodPost, "", userViewer, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryCommenter.ID + "/blocks/" + testData.publicBoard.ID, methodPost, "", userCommenter, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryEditor.ID + "/blocks/" + testData.publicBoard.ID, methodPost, "", userEditor, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryAdmin.ID + "/blocks/" + testData.publicBoard.ID, methodPost, "", userAdmin, http.StatusOK, 0},
{"/teams/test-team/categories/any/boards/any", methodPost, "", userAnon, http.StatusUnauthorized, 0},
{"/teams/test-team/categories/" + categoryNoTeamMember.ID + "/boards/" + testData.publicBoard.ID, methodPost, "", userNoTeamMember, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryTeamMember.ID + "/boards/" + testData.publicBoard.ID, methodPost, "", userTeamMember, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryViewer.ID + "/boards/" + testData.publicBoard.ID, methodPost, "", userViewer, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryCommenter.ID + "/boards/" + testData.publicBoard.ID, methodPost, "", userCommenter, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryEditor.ID + "/boards/" + testData.publicBoard.ID, methodPost, "", userEditor, http.StatusOK, 0},
{"/teams/test-team/categories/" + categoryAdmin.ID + "/boards/" + testData.publicBoard.ID, methodPost, "", userAdmin, http.StatusOK, 0},
}
runTestCases(t, ttCases, testData, clients)
}

View file

@ -3,6 +3,7 @@ package model
import (
"encoding/json"
"io"
"time"
)
type BoardType string
@ -359,5 +360,5 @@ type BoardMemberHistoryEntry struct {
// The insertion time
// required: true
InsertAt int64 `json:"insertAt"`
InsertAt time.Time `json:"insertAt"`
}

View file

@ -1,11 +0,0 @@
package model
type CategoryBlocks struct {
Category
BlockIDs []string `json:"blockIDs"`
}
type BlockCategoryWebsocketData struct {
BlockID string `json:"blockID"`
CategoryID string `json:"categoryID"`
}

View file

@ -0,0 +1,11 @@
package model
type CategoryBoards struct {
Category
BoardIDs []string `json:"boardIDs"`
}
type BoardCategoryWebsocketData struct {
BoardID string `json:"boardID"`
CategoryID string `json:"categoryID"`
}

View file

@ -36,18 +36,18 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
return m.recorder
}
// AddUpdateCategoryBlock mocks base method.
func (m *MockStore) AddUpdateCategoryBlock(arg0, arg1, arg2 string) error {
// AddUpdateCategoryBoard mocks base method.
func (m *MockStore) AddUpdateCategoryBoard(arg0, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddUpdateCategoryBlock", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "AddUpdateCategoryBoard", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// AddUpdateCategoryBlock indicates an expected call of AddUpdateCategoryBlock.
func (mr *MockStoreMockRecorder) AddUpdateCategoryBlock(arg0, arg1, arg2 interface{}) *gomock.Call {
// AddUpdateCategoryBoard indicates an expected call of AddUpdateCategoryBoard.
func (mr *MockStoreMockRecorder) AddUpdateCategoryBoard(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateCategoryBlock", reflect.TypeOf((*MockStore)(nil).AddUpdateCategoryBlock), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateCategoryBoard", reflect.TypeOf((*MockStore)(nil).AddUpdateCategoryBoard), arg0, arg1, arg2)
}
// CleanUpSessions mocks base method.
@ -925,19 +925,19 @@ func (mr *MockStoreMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Cal
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockStore)(nil).GetUserByUsername), arg0)
}
// GetUserCategoryBlocks mocks base method.
func (m *MockStore) GetUserCategoryBlocks(arg0, arg1 string) ([]model.CategoryBlocks, error) {
// GetUserCategoryBoards mocks base method.
func (m *MockStore) GetUserCategoryBoards(arg0, arg1 string) ([]model.CategoryBoards, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserCategoryBlocks", arg0, arg1)
ret0, _ := ret[0].([]model.CategoryBlocks)
ret := m.ctrl.Call(m, "GetUserCategoryBoards", arg0, arg1)
ret0, _ := ret[0].([]model.CategoryBoards)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserCategoryBlocks indicates an expected call of GetUserCategoryBlocks.
func (mr *MockStoreMockRecorder) GetUserCategoryBlocks(arg0, arg1 interface{}) *gomock.Call {
// GetUserCategoryBoards indicates an expected call of GetUserCategoryBoards.
func (mr *MockStoreMockRecorder) GetUserCategoryBoards(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserCategoryBlocks", reflect.TypeOf((*MockStore)(nil).GetUserCategoryBlocks), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserCategoryBoards", reflect.TypeOf((*MockStore)(nil).GetUserCategoryBoards), arg0, arg1)
}
// GetUsersByTeam mocks base method.

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/mattermost/focalboard/server/utils"
@ -181,17 +182,29 @@ func (s *SQLStore) boardMemberHistoryEntriesFromRows(rows *sql.Rows) ([]*model.B
for rows.Next() {
var boardMemberHistoryEntry model.BoardMemberHistoryEntry
var insertAt sql.NullString
err := rows.Scan(
&boardMemberHistoryEntry.BoardID,
&boardMemberHistoryEntry.UserID,
&boardMemberHistoryEntry.Action,
&boardMemberHistoryEntry.InsertAt,
&insertAt,
)
if err != nil {
return nil, err
}
// parse the insert_at timestamp which is different based on database type.
dateTemplate := "2006-01-02T15:04:05Z0700"
if s.dbType == model.MysqlDBType {
dateTemplate = "2006-01-02 15:04:05.000000"
}
ts, err := time.Parse(dateTemplate, insertAt.String)
if err != nil {
return nil, fmt.Errorf("cannot parse datetime '%s' for board_members_history scan: %w", insertAt.String, err)
}
boardMemberHistoryEntry.InsertAt = ts
boardMemberHistoryEntries = append(boardMemberHistoryEntries, &boardMemberHistoryEntry)
}
@ -300,7 +313,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri
existingBoard, err := s.getBoard(db, board.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
return nil, fmt.Errorf("insertBoard error occurred while fetching existing board %s: %w", board.ID, err)
}
insertQuery := s.getQueryBuilder(db).Insert("").
@ -348,7 +361,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri
if _, err := query.Exec(); err != nil {
s.logger.Error(`InsertBoard error occurred while updating existing board`, mlog.String("boardID", board.ID), mlog.Err(err))
return nil, err
return nil, fmt.Errorf("insertBoard error occurred while updating existing board %s: %w", board.ID, err)
}
} else {
insertQueryValues["created_by"] = userID
@ -357,7 +370,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri
query := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + "boards")
if _, err := query.Exec(); err != nil {
return nil, err
return nil, fmt.Errorf("insertBoard error occurred while inserting board %s: %w", board.ID, err)
}
}
@ -365,7 +378,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri
query := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + "boards_history")
if _, err := query.Exec(); err != nil {
s.logger.Error("failed to insert board history", mlog.String("board_id", board.ID), mlog.Err(err))
return nil, err
return nil, fmt.Errorf("failed to insert board %s history: %w", board.ID, err)
}
return s.getBoard(db, board.ID)
@ -462,7 +475,7 @@ func (s *SQLStore) insertBoardWithAdmin(db sq.BaseRunner, board *model.Board, us
nbm, err := s.saveMember(db, bm)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("cannot save member %s while inserting board %s: %w", bm.UserID, bm.BoardID, err)
}
return newBoard, nbm, nil
@ -507,8 +520,8 @@ func (s *SQLStore) saveMember(db sq.BaseRunner, bm *model.BoardMember) (*model.B
if oldMember == nil {
addToMembersHistory := s.getQueryBuilder(db).
Insert(s.tablePrefix+"board_members_history").
Columns("board_id", "user_id", "action", "insert_at").
Values(bm.BoardID, bm.UserID, "created", model.GetMillis())
Columns("board_id", "user_id", "action").
Values(bm.BoardID, bm.UserID, "created")
if _, err := addToMembersHistory.Exec(); err != nil {
return nil, err
@ -537,8 +550,8 @@ func (s *SQLStore) deleteMember(db sq.BaseRunner, boardID, userID string) error
if rowsAffected > 0 {
addToMembersHistory := s.getQueryBuilder(db).
Insert(s.tablePrefix+"board_members_history").
Columns("board_id", "user_id", "action", "insert_at").
Values(boardID, userID, "deleted", model.GetMillis())
Columns("board_id", "user_id", "action").
Values(boardID, userID, "deleted")
if _, err := addToMembersHistory.Exec(); err != nil {
return err

View file

@ -15,34 +15,34 @@ var (
errDuplicateCategoryEntries = errors.New("duplicate entries found for user-board-category mapping")
)
func (s *SQLStore) getUserCategoryBlocks(db sq.BaseRunner, userID, teamID string) ([]model.CategoryBlocks, error) {
func (s *SQLStore) getUserCategoryBoards(db sq.BaseRunner, userID, teamID string) ([]model.CategoryBoards, error) {
categories, err := s.getUserCategories(db, userID, teamID)
if err != nil {
return nil, err
}
userCategoryBlocks := []model.CategoryBlocks{}
userCategoryBoards := []model.CategoryBoards{}
for _, category := range categories {
blockIDs, err := s.getCategoryBlockAttributes(db, category.ID)
boardIDs, err := s.getCategoryBoardAttributes(db, category.ID)
if err != nil {
return nil, err
}
userCategoryBlock := model.CategoryBlocks{
userCategoryBoard := model.CategoryBoards{
Category: category,
BlockIDs: blockIDs,
BoardIDs: boardIDs,
}
userCategoryBlocks = append(userCategoryBlocks, userCategoryBlock)
userCategoryBoards = append(userCategoryBoards, userCategoryBoard)
}
return userCategoryBlocks, nil
return userCategoryBoards, nil
}
func (s *SQLStore) getCategoryBlockAttributes(db sq.BaseRunner, categoryID string) ([]string, error) {
func (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID string) ([]string, error) {
query := s.getQueryBuilder(db).
Select("block_id").
From(s.tablePrefix + "category_blocks").
Select("board_id").
From(s.tablePrefix + "category_boards").
Where(sq.Eq{
"category_id": categoryID,
"delete_at": 0,
@ -50,19 +50,19 @@ func (s *SQLStore) getCategoryBlockAttributes(db sq.BaseRunner, categoryID strin
rows, err := query.Query()
if err != nil {
s.logger.Error("getCategoryBlocks error fetching categoryblocks", mlog.String("categoryID", categoryID), mlog.Err(err))
s.logger.Error("getCategoryBoards error fetching categoryblocks", mlog.String("categoryID", categoryID), mlog.Err(err))
return nil, err
}
return s.categoryBlocksFromRows(rows)
return s.categoryBoardsFromRows(rows)
}
func (s *SQLStore) addUpdateCategoryBlock(db sq.BaseRunner, userID, categoryID, blockID string) error {
func (s *SQLStore) addUpdateCategoryBoard(db sq.BaseRunner, userID, categoryID, boardID string) error {
if categoryID == "0" {
return s.deleteUserCategoryBlock(db, userID, blockID)
return s.deleteUserCategoryBoard(db, userID, boardID)
}
rowsAffected, err := s.updateUserCategoryBlock(db, userID, blockID, categoryID)
rowsAffected, err := s.updateUserCategoryBoard(db, userID, boardID, categoryID)
if err != nil {
return err
}
@ -73,28 +73,28 @@ func (s *SQLStore) addUpdateCategoryBlock(db sq.BaseRunner, userID, categoryID,
if rowsAffected == 0 {
// user-block mapping didn't already exist. So we'll create a new entry
return s.addUserCategoryBlock(db, userID, categoryID, blockID)
return s.addUserCategoryBoard(db, userID, categoryID, boardID)
}
return nil
}
/*
func (s *SQLStore) userCategoryBlockExists(db sq.BaseRunner, userID, teamID, categoryID, blockID string) (bool, error) {
func (s *SQLStore) userCategoryBoardExists(db sq.BaseRunner, userID, teamID, categoryID, boardID string) (bool, error) {
query := s.getQueryBuilder(db).
Select("blocks.id").
From(s.tablePrefix + "categories AS categories").
Join(s.tablePrefix + "category_blocks AS blocks ON blocks.category_id = categories.id").
Join(s.tablePrefix + "category_boards AS blocks ON blocks.category_id = categories.id").
Where(sq.Eq{
"user_id": userID,
"team_id": teamID,
"categories.id": categoryID,
"block_id": blockID,
"board_id": boardID,
})
rows, err := query.Query()
if err != nil {
s.logger.Error("getCategoryBlock error", mlog.Err(err))
s.logger.Error("getCategoryBoard error", mlog.Err(err))
return false, err
}
@ -102,39 +102,39 @@ func (s *SQLStore) userCategoryBlockExists(db sq.BaseRunner, userID, teamID, cat
}
*/
func (s *SQLStore) updateUserCategoryBlock(db sq.BaseRunner, userID, blockID, categoryID string) (int64, error) {
func (s *SQLStore) updateUserCategoryBoard(db sq.BaseRunner, userID, boardID, categoryID string) (int64, error) {
result, err := s.getQueryBuilder(db).
Update(s.tablePrefix+"category_blocks").
Update(s.tablePrefix+"category_boards").
Set("category_id", categoryID).
Set("delete_at", 0).
Where(sq.Eq{
"block_id": blockID,
"board_id": boardID,
"user_id": userID,
}).
Exec()
if err != nil {
s.logger.Error("updateUserCategoryBlock error", mlog.Err(err))
s.logger.Error("updateUserCategoryBoard error", mlog.Err(err))
return 0, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
s.logger.Error("updateUserCategoryBlock affected row count error", mlog.Err(err))
s.logger.Error("updateUserCategoryBoard affected row count error", mlog.Err(err))
return 0, err
}
return rowsAffected, nil
}
func (s *SQLStore) addUserCategoryBlock(db sq.BaseRunner, userID, categoryID, blockID string) error {
func (s *SQLStore) addUserCategoryBoard(db sq.BaseRunner, userID, categoryID, boardID string) error {
_, err := s.getQueryBuilder(db).
Insert(s.tablePrefix+"category_blocks").
Insert(s.tablePrefix+"category_boards").
Columns(
"id",
"user_id",
"category_id",
"block_id",
"board_id",
"create_at",
"update_at",
"delete_at",
@ -143,34 +143,34 @@ func (s *SQLStore) addUserCategoryBlock(db sq.BaseRunner, userID, categoryID, bl
utils.NewID(utils.IDTypeNone),
userID,
categoryID,
blockID,
boardID,
utils.GetMillis(),
utils.GetMillis(),
0,
).Exec()
if err != nil {
s.logger.Error("addUserCategoryBlock error", mlog.Err(err))
s.logger.Error("addUserCategoryBoard error", mlog.Err(err))
return err
}
return nil
}
func (s *SQLStore) deleteUserCategoryBlock(db sq.BaseRunner, userID, blockID string) error {
func (s *SQLStore) deleteUserCategoryBoard(db sq.BaseRunner, userID, boardID string) error {
_, err := s.getQueryBuilder(db).
Update(s.tablePrefix+"category_blocks").
Update(s.tablePrefix+"category_boards").
Set("delete_at", utils.GetMillis()).
Where(sq.Eq{
"user_id": userID,
"block_id": blockID,
"board_id": boardID,
"delete_at": 0,
}).Exec()
if err != nil {
s.logger.Error(
"deleteUserCategoryBlock delete error",
"deleteUserCategoryBoard delete error",
mlog.String("userID", userID),
mlog.String("blockID", blockID),
mlog.String("boardID", boardID),
mlog.Err(err),
)
return err
@ -179,17 +179,17 @@ func (s *SQLStore) deleteUserCategoryBlock(db sq.BaseRunner, userID, blockID str
return nil
}
func (s *SQLStore) categoryBlocksFromRows(rows *sql.Rows) ([]string, error) {
func (s *SQLStore) categoryBoardsFromRows(rows *sql.Rows) ([]string, error) {
blocks := []string{}
for rows.Next() {
blockID := ""
if err := rows.Scan(&blockID); err != nil {
s.logger.Error("categoryBlocksFromRows row scan error", mlog.Err(err))
boardID := ""
if err := rows.Scan(&boardID); err != nil {
s.logger.Error("categoryBoardsFromRows row scan error", mlog.Err(err))
return nil, err
}
blocks = append(blocks, blockID)
blocks = append(blocks, boardID)
}
return blocks, nil

View file

@ -238,7 +238,7 @@ func (s *SQLStore) updateCategoryID(db sq.BaseRunner, oldID, newID string) error
// update category boards table
rows, err = s.getQueryBuilder(db).
Update(s.tablePrefix+"category_blocks").
Update(s.tablePrefix+"category_boards").
Set("category_id", newID).
Where(sq.Eq{"category_id": oldID}).
Query()
@ -258,7 +258,7 @@ func (s *SQLStore) updateCategoryID(db sq.BaseRunner, oldID, newID string) error
func (s *SQLStore) updateCategoryBlocksIDs(db sq.BaseRunner) error {
// fetch all category IDs
oldCategoryIDs, err := s.getIDs(db, "category_blocks")
oldCategoryIDs, err := s.getIDs(db, "category_boards")
if err != nil {
return err
}
@ -284,7 +284,7 @@ func (s *SQLStore) updateCategoryBlocksIDs(db sq.BaseRunner) error {
func (s *SQLStore) updateCategoryBlocksID(db sq.BaseRunner, oldID, newID string) error {
// update in category table
rows, err := s.getQueryBuilder(db).
Update(s.tablePrefix+"category_blocks").
Update(s.tablePrefix+"category_boards").
Set("id", newID).
Where(sq.Eq{"id": oldID}).
Query()

View file

@ -23,6 +23,8 @@ UPDATE {{.prefix}}blocks SET fields = fields::jsonb - 'columnCalculations' || '{
UPDATE {{.prefix}}blocks SET fields = replace(fields, '"columnCalculations":[]', '"columnCalculations":{}');
{{end}}
/* TODO: Migrate the columnCalculations at app level and remove it from the boards and boards_history tables */
{{- /* add boards tables */ -}}
CREATE TABLE {{.prefix}}boards (
id VARCHAR(36) NOT NULL PRIMARY KEY,
@ -62,6 +64,8 @@ CREATE TABLE {{.prefix}}boards (
delete_at BIGINT
) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};
CREATE INDEX idx_board_team_id ON {{.prefix}}boards(team_id, is_template);
CREATE TABLE {{.prefix}}boards_history (
id VARCHAR(36) NOT NULL,

View file

@ -1,15 +1,17 @@
CREATE TABLE {{.prefix}}categories (
id varchar(36) NOT NULL,
name varchar(100) NOT NULL,
user_id varchar(32) NOT NULL,
team_id varchar(32) NOT NULL,
channel_id varchar(32),
user_id varchar(36) NOT NULL,
team_id varchar(36) NOT NULL,
channel_id varchar(36),
create_at BIGINT,
update_at BIGINT,
delete_at BIGINT,
PRIMARY KEY (id)
) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};
CREATE INDEX idx_categories_user_id_team_id ON {{.prefix}}categories(user_id, team_id);
{{if .plugin}}
INSERT INTO {{.prefix}}categories(
id,
@ -28,9 +30,9 @@ CREATE TABLE {{.prefix}}categories (
{{ if .mysql }}
UUID(),
{{ end }}
COALESCE(nullif(c.DisplayName, ''), 'Direct Message') as category_name,
c.DisplayName,
cm.UserId,
COALESCE(nullif(c.TeamId, ''), 'direct_message') as team_id,
c.TeamId,
cm.ChannelId,
{{if .postgres}}(extract(epoch from now())*1000)::bigint,{{end}}
{{if .mysql}}UNIX_TIMESTAMP() * 1000,{{end}}
@ -39,6 +41,6 @@ CREATE TABLE {{.prefix}}categories (
FROM
{{.prefix}}boards boards
JOIN ChannelMembers cm on boards.channel_id = cm.ChannelId
JOIN Channels c on cm.ChannelId = c.id
JOIN Channels c on cm.ChannelId = c.id and (c.Type = 'O' or c.Type = 'P')
GROUP BY cm.UserId, c.TeamId, cm.ChannelId, c.DisplayName;
{{end}}

View file

@ -1 +1 @@
DELETE from {{.prefix}}category_blocks;
DELETE from {{.prefix}}category_boards;

View file

@ -1,16 +1,18 @@
CREATE TABLE {{.prefix}}category_blocks (
CREATE TABLE {{.prefix}}category_boards (
id varchar(36) NOT NULL,
user_id varchar(32) NOT NULL,
user_id varchar(36) NOT NULL,
category_id varchar(36) NOT NULL,
block_id VARCHAR(36) NOT NULL,
board_id VARCHAR(36) NOT NULL,
create_at BIGINT,
update_at BIGINT,
delete_at BIGINT,
PRIMARY KEY (id)
) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};
CREATE INDEX idx_categoryboards_category_id ON {{.prefix}}category_boards(category_id);
{{if .plugin}}
INSERT INTO {{.prefix}}category_blocks(id, user_id, category_id, block_id, create_at, update_at, delete_at)
INSERT INTO {{.prefix}}category_boards(id, user_id, category_id, board_id, create_at, update_at, delete_at)
SELECT
{{ if .postgres }}
REPLACE(uuid_in(md5(random()::text || clock_timestamp()::text)::cstring)::varchar, '-', ''),

View file

@ -1,18 +1,14 @@
CREATE TABLE {{.prefix}}board_members_history (
{{if .postgres}}id SERIAL PRIMARY KEY,{{end}}
{{if .sqlite}}id INTEGER PRIMARY KEY AUTOINCREMENT,{{end}}
{{if .mysql}}id INT PRIMARY KEY AUTO_INCREMENT,{{end}}
board_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
action VARCHAR(10),
insert_at BIGINT NOT NULL
{{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}
{{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}
{{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}
PRIMARY KEY (board_id, user_id, insert_at)
) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};
CREATE INDEX idx_boardmembershistory_user_id ON {{.prefix}}board_members_history(user_id);
CREATE INDEX idx_boardmembershistory_board_id_userid ON {{.prefix}}board_members_history(board_id, user_id);
CREATE INDEX idx_boardmembershistory_board_id_user_id ON {{.prefix}}board_members_history(board_id, user_id);
INSERT INTO {{.prefix}}board_members_history (board_id, user_id, action, insert_at) SELECT board_id, user_id, 'created',
{{if .postgres}}CAST(extract(epoch from now()) * 1000 AS BIGINT){{end}}
{{if .sqlite}}strftime('%s')*1000{{end}}
{{if .mysql}}UNIX_TIMESTAMP(now())*1000{{end}}
from {{.prefix}}board_members;
INSERT INTO {{.prefix}}board_members_history (board_id, user_id, action) SELECT board_id, user_id, 'created' from {{.prefix}}board_members;

View file

@ -22,8 +22,8 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
func (s *SQLStore) AddUpdateCategoryBlock(userID string, categoryID string, blockID string) error {
return s.addUpdateCategoryBlock(s.db, userID, categoryID, blockID)
func (s *SQLStore) AddUpdateCategoryBoard(userID string, categoryID string, blockID string) error {
return s.addUpdateCategoryBoard(s.db, userID, categoryID, blockID)
}
@ -450,8 +450,8 @@ func (s *SQLStore) GetUserByUsername(username string) (*model.User, error) {
}
func (s *SQLStore) GetUserCategoryBlocks(userID string, teamID string) ([]model.CategoryBlocks, error) {
return s.getUserCategoryBlocks(s.db, userID, teamID)
func (s *SQLStore) GetUserCategoryBoards(userID string, teamID string) ([]model.CategoryBoards, error) {
return s.getUserCategoryBoards(s.db, userID, teamID)
}

View file

@ -113,8 +113,8 @@ type Store interface {
UpdateCategory(category model.Category) error
DeleteCategory(categoryID, userID, teamID string) error
GetUserCategoryBlocks(userID, teamID string) ([]model.CategoryBlocks, error)
AddUpdateCategoryBlock(userID, categoryID, blockID string) error
GetUserCategoryBoards(userID, teamID string) ([]model.CategoryBoards, error)
AddUpdateCategoryBoard(userID, categoryID, blockID string) error
CreateSubscription(sub *model.Subscription) (*model.Subscription, error)
DeleteSubscription(blockID string, subscriberID string) error

View file

@ -17,7 +17,7 @@ const (
websocketActionUpdateBlock = "UPDATE_BLOCK"
websocketActionUpdateConfig = "UPDATE_CLIENT_CONFIG"
websocketActionUpdateCategory = "UPDATE_CATEGORY"
websocketActionUpdateCategoryBlock = "UPDATE_BLOCK_CATEGORY"
websocketActionUpdateCategoryBoard = "UPDATE_BOARD_CATEGORY"
websocketActionUpdateSubscription = "UPDATE_SUBSCRIPTION"
)
@ -35,6 +35,6 @@ type Adapter interface {
BroadcastMemberDelete(teamID, boardID, userID string)
BroadcastConfigChange(clientConfig model.ClientConfig)
BroadcastCategoryChange(category model.Category)
BroadcastCategoryBlockChange(teamID, userID string, blockCategory model.BlockCategoryWebsocketData)
BroadcastCategoryBoardChange(teamID, userID string, blockCategory model.BoardCategoryWebsocketData)
BroadcastSubscriptionChange(teamID string, subscription *model.Subscription)
}

View file

@ -9,7 +9,7 @@ type UpdateCategoryMessage struct {
Action string `json:"action"`
TeamID string `json:"teamId"`
Category *model.Category `json:"category,omitempty"`
BlockCategories *model.BlockCategoryWebsocketData `json:"blockCategories,omitempty"`
BoardCategories *model.BoardCategoryWebsocketData `json:"blockCategories,omitempty"`
}
// UpdateBlockMsg is sent on block updates.

View file

@ -494,22 +494,22 @@ func (pa *PluginAdapter) BroadcastCategoryChange(category model.Category) {
pa.sendUserMessageSkipCluster(websocketActionUpdateCategory, payload, category.UserID)
}
func (pa *PluginAdapter) BroadcastCategoryBlockChange(teamID, userID string, blockCategory model.BlockCategoryWebsocketData) {
func (pa *PluginAdapter) BroadcastCategoryBoardChange(teamID, userID string, boardCategory model.BoardCategoryWebsocketData) {
pa.logger.Debug(
"BroadcastCategoryBlockChange",
"BroadcastCategoryBoardChange",
mlog.String("userID", userID),
mlog.String("teamID", teamID),
mlog.String("categoryID", blockCategory.CategoryID),
mlog.String("blockID", blockCategory.BlockID),
mlog.String("categoryID", boardCategory.CategoryID),
mlog.String("blockID", boardCategory.BoardID),
)
message := UpdateCategoryMessage{
Action: websocketActionUpdateCategoryBlock,
Action: websocketActionUpdateCategoryBoard,
TeamID: teamID,
BlockCategories: &blockCategory,
BoardCategories: &boardCategory,
}
pa.sendTeamMessage(websocketActionUpdateCategoryBlock, teamID, utils.StructToMap(message))
pa.sendTeamMessage(websocketActionUpdateCategoryBoard, teamID, utils.StructToMap(message))
}
func (pa *PluginAdapter) BroadcastBlockDelete(teamID, blockID, boardID string) {

View file

@ -595,27 +595,27 @@ func (ws *Server) BroadcastCategoryChange(category model.Category) {
}
}
func (ws *Server) BroadcastCategoryBlockChange(teamID, userID string, blockCategory model.BlockCategoryWebsocketData) {
func (ws *Server) BroadcastCategoryBoardChange(teamID, userID string, boardCategory model.BoardCategoryWebsocketData) {
message := UpdateCategoryMessage{
Action: websocketActionUpdateCategoryBlock,
Action: websocketActionUpdateCategoryBoard,
TeamID: teamID,
BlockCategories: &blockCategory,
BoardCategories: &boardCategory,
}
listeners := ws.getListenersForTeam(teamID)
ws.logger.Debug("listener(s) for teamID",
mlog.Int("listener_count", len(listeners)),
mlog.String("teamID", teamID),
mlog.String("categoryID", blockCategory.CategoryID),
mlog.String("blockID", blockCategory.BlockID),
mlog.String("categoryID", boardCategory.CategoryID),
mlog.String("blockID", boardCategory.BoardID),
)
for _, listener := range listeners {
ws.logger.Debug("Broadcast block change",
mlog.Int("listener_count", len(listeners)),
mlog.String("teamID", teamID),
mlog.String("categoryID", blockCategory.CategoryID),
mlog.String("blockID", blockCategory.BlockID),
mlog.String("categoryID", boardCategory.CategoryID),
mlog.String("blockID", boardCategory.BoardID),
mlog.Stringer("remoteAddr", listener.conn.RemoteAddr()),
)

View file

@ -98,9 +98,9 @@ card3.boardId = fakeBoard.id
const me: IUser = {id: 'user-id-1', username: 'username_1', email: '', props: {}, create_at: 0, update_at: 0, is_bot: false}
const categoryAttribute1 = TestBlockFactory.createCategoryBlocks()
const categoryAttribute1 = TestBlockFactory.createCategoryBoards()
categoryAttribute1.name = 'Category 1'
categoryAttribute1.blockIDs = [board.id]
categoryAttribute1.boardIDs = [board.id]
describe('src/components/shareBoard/shareBoard', () => {
const w = (window as any)

View file

@ -28,9 +28,9 @@ describe('components/sidebarSidebar', () => {
const board = TestBlockFactory.createBoard()
board.id = 'board1'
const categoryAttribute1 = TestBlockFactory.createCategoryBlocks()
const categoryAttribute1 = TestBlockFactory.createCategoryBoards()
categoryAttribute1.name = 'Category 1'
categoryAttribute1.blockIDs = [board.id]
categoryAttribute1.boardIDs = [board.id]
test('sidebar hidden', () => {
const store = mockStore({

View file

@ -15,11 +15,11 @@ import {Utils} from '../../utils'
import './sidebar.scss'
import {
BlockCategoryWebsocketData,
BoardCategoryWebsocketData,
Category,
CategoryBlocks,
CategoryBoards,
fetchSidebarCategories,
getSidebarCategories, updateBlockCategories,
getSidebarCategories, updateBoardCategories,
updateCategories,
} from '../../store/sidebar'
@ -55,7 +55,7 @@ const Sidebar = (props: Props) => {
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
const boards = useAppSelector(getSortedBoards)
const dispatch = useAppDispatch()
const partialCategories = useAppSelector<Array<CategoryBlocks>>(getSidebarCategories)
const partialCategories = useAppSelector<Array<CategoryBoards>>(getSidebarCategories)
const sidebarCategories = addMissingItems(partialCategories, boards)
useEffect(() => {
@ -63,8 +63,8 @@ const Sidebar = (props: Props) => {
dispatch(updateCategories(categories))
}, 'category')
wsClient.addOnChange((_: WSClient, blockCategories: Array<BlockCategoryWebsocketData>) => {
dispatch(updateBlockCategories(blockCategories))
wsClient.addOnChange((_: WSClient, blockCategories: Array<BoardCategoryWebsocketData>) => {
dispatch(updateBoardCategories(blockCategories))
}, 'blockCategories')
}, [])
@ -182,7 +182,7 @@ const Sidebar = (props: Props) => {
hideSidebar={hideSidebar}
key={category.id}
activeBoardID={props.activeBoardId}
categoryBlocks={category}
categoryBoards={category}
boards={boards}
allCategories={sidebarCategories}
/>

View file

@ -24,20 +24,20 @@ describe('components/sidebarBoardItem', () => {
view.fields.sortOptions = []
const history = createMemoryHistory()
const categoryBlocks1 = TestBlockFactory.createCategoryBlocks()
categoryBlocks1.name = 'Category 1'
categoryBlocks1.blockIDs = [board.id]
const categoryBoards1 = TestBlockFactory.createCategoryBoards()
categoryBoards1.name = 'Category 1'
categoryBoards1.boardIDs = [board.id]
const categoryBlocks2 = TestBlockFactory.createCategoryBlocks()
categoryBlocks2.name = 'Category 2'
const categoryBoards2 = TestBlockFactory.createCategoryBoards()
categoryBoards2.name = 'Category 2'
const categoryBlocks3 = TestBlockFactory.createCategoryBlocks()
categoryBlocks3.name = 'Category 3'
const categoryBoards3 = TestBlockFactory.createCategoryBoards()
categoryBoards3.name = 'Category 3'
const allCategoryBlocks = [
categoryBlocks1,
categoryBlocks2,
categoryBlocks3,
const allCategoryBoards = [
categoryBoards1,
categoryBoards2,
categoryBoards3,
]
const state = {
@ -73,9 +73,9 @@ describe('components/sidebarBoardItem', () => {
<ReduxProvider store={store}>
<Router history={history}>
<SidebarBoardItem
categoryBlocks={categoryBlocks1}
categoryBoards={categoryBoards1}
board={board}
allCategories={allCategoryBlocks}
allCategories={allCategoryBoards}
isActive={true}
showBoard={jest.fn()}
showView={jest.fn()}

View file

@ -15,7 +15,7 @@ import MenuWrapper from '../../widgets/menuWrapper'
import BoardPermissionGate from '../permissions/boardPermissionGate'
import './sidebarBoardItem.scss'
import {CategoryBlocks} from '../../store/sidebar'
import {CategoryBoards} from '../../store/sidebar'
import CreateNewFolder from '../../widgets/icons/newFolder'
import {useAppSelector} from '../../store/hooks'
import {getCurrentBoardViews, getCurrentViewId} from '../../store/views'
@ -45,9 +45,9 @@ const iconForViewType = (viewType: IViewType): JSX.Element => {
type Props = {
isActive: boolean
categoryBlocks: CategoryBlocks
categoryBoards: CategoryBoards
board: Board
allCategories: Array<CategoryBlocks>
allCategories: Array<CategoryBoards>
onDeleteRequest: (board: Board) => void
showBoard: (boardId: string) => void
showView: (viewId: string, boardId: string) => void
@ -66,16 +66,16 @@ const SidebarBoardItem = (props: Props) => {
const match = useRouteMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>()
const history = useHistory()
const generateMoveToCategoryOptions = (blockID: string) => {
const generateMoveToCategoryOptions = (boardID: string) => {
return props.allCategories.map((category) => (
<Menu.Text
key={category.id}
id={category.id}
name={category.name}
icon={category.id === props.categoryBlocks.id ? <Check/> : <Folder/>}
icon={category.id === props.categoryBoards.id ? <Check/> : <Folder/>}
onClick={async (toCategoryID) => {
const fromCategoryID = props.categoryBlocks.id
await mutator.moveBlockToCategory(teamID, blockID, toCategoryID, fromCategoryID)
const fromCategoryID = props.categoryBoards.id
await mutator.moveBoardToCategory(teamID, boardID, toCategoryID, fromCategoryID)
}}
/>
))

View file

@ -28,20 +28,20 @@ describe('components/sidebarCategory', () => {
const board1 = TestBlockFactory.createBoard()
const board2 = TestBlockFactory.createBoard()
const boards = [board1, board2]
const categoryBlocks1 = TestBlockFactory.createCategoryBlocks()
categoryBlocks1.name = 'Category 1'
categoryBlocks1.blockIDs = [board1.id, board2.id]
const categoryBoards1 = TestBlockFactory.createCategoryBoards()
categoryBoards1.name = 'Category 1'
categoryBoards1.boardIDs = [board1.id, board2.id]
const categoryBlocks2 = TestBlockFactory.createCategoryBlocks()
categoryBlocks2.name = 'Category 2'
const categoryBoards2 = TestBlockFactory.createCategoryBoards()
categoryBoards2.name = 'Category 2'
const categoryBlocks3 = TestBlockFactory.createCategoryBlocks()
categoryBlocks3.name = 'Category 3'
const categoryBoards3 = TestBlockFactory.createCategoryBoards()
categoryBoards3.name = 'Category 3'
const allCategoryBlocks = [
categoryBlocks1,
categoryBlocks2,
categoryBlocks3,
const allCategoryBoards = [
categoryBoards1,
categoryBoards2,
categoryBoards3,
]
const state = {
@ -78,9 +78,9 @@ describe('components/sidebarCategory', () => {
<Router history={history}>
<SidebarCategory
hideSidebar={() => {}}
categoryBlocks={categoryBlocks1}
categoryBoards={categoryBoards1}
boards={boards}
allCategories={allCategoryBlocks}
allCategories={allCategoryBoards}
/>
</Router>
</ReduxProvider>,

View file

@ -13,7 +13,7 @@ import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper'
import './sidebarCategory.scss'
import {Category, CategoryBlocks} from '../../store/sidebar'
import {Category, CategoryBoards} from '../../store/sidebar'
import ChevronDown from '../../widgets/icons/chevronDown'
import ChevronRight from '../../widgets/icons/chevronRight'
import CreateNewFolder from '../../widgets/icons/newFolder'
@ -37,9 +37,9 @@ type Props = {
activeCategoryId?: string
activeBoardID?: string
hideSidebar: () => void
categoryBlocks: CategoryBlocks
categoryBoards: CategoryBoards
boards: Board[]
allCategories: Array<CategoryBlocks>
allCategories: Array<CategoryBoards>
}
const SidebarCategory = (props: Props) => {
@ -76,14 +76,14 @@ const SidebarCategory = (props: Props) => {
props.hideSidebar()
}, [match, history])
const blocks = props.categoryBlocks.blockIDs || []
const blocks = props.categoryBoards.boardIDs || []
const handleCreateNewCategory = () => {
setShowCreateCategoryModal(true)
}
const handleDeleteCategory = async () => {
await mutator.deleteCategory(teamID, props.categoryBlocks.id)
await mutator.deleteCategory(teamID, props.categoryBoards.id)
}
const handleUpdateCategory = async () => {
@ -101,7 +101,7 @@ const SidebarCategory = (props: Props) => {
defaultMessage: 'Boards in <b>{categoryName}</b> will move back to the Boards categories. You\'re not removed from any boards.',
},
{
categoryName: props.categoryBlocks.name,
categoryName: props.categoryBoards.name,
b: (...chunks) => <b>{chunks}</b>,
},
),
@ -140,7 +140,7 @@ const SidebarCategory = (props: Props) => {
return (
<div className='SidebarCategory'>
<div
className={`octo-sidebar-item category ' ${collapsed ? 'collapsed' : 'expanded'} ${props.categoryBlocks.id === props.activeCategoryId ? 'active' : ''}`}
className={`octo-sidebar-item category ' ${collapsed ? 'collapsed' : 'expanded'} ${props.categoryBoards.id === props.activeCategoryId ? 'active' : ''}`}
>
<IconButton
icon={collapsed ? <ChevronRight/> : <ChevronDown/>}
@ -148,9 +148,9 @@ const SidebarCategory = (props: Props) => {
/>
<div
className='octo-sidebar-title category-title'
title={props.categoryBlocks.name}
title={props.categoryBoards.name}
>
{props.categoryBlocks.name}
{props.categoryBoards.name}
</div>
<MenuWrapper
className={categoryMenuOpen ? 'menuOpen' : ''}
@ -166,7 +166,7 @@ const SidebarCategory = (props: Props) => {
onClick={handleCreateNewCategory}
/>
{
props.categoryBlocks.id !== '' &&
props.categoryBoards.id !== '' &&
<React.Fragment>
<Menu.Text
id='deleteCategory'
@ -201,7 +201,7 @@ const SidebarCategory = (props: Props) => {
<SidebarBoardItem
key={board.id}
board={board}
categoryBlocks={props.categoryBlocks}
categoryBoards={props.categoryBoards}
allCategories={props.allCategories}
isActive={board.id === props.activeBoardID}
showBoard={showBoard}
@ -243,7 +243,7 @@ const SidebarCategory = (props: Props) => {
{
showUpdateCategoryModal && (
<CreateCategory
initialValue={props.categoryBlocks.name}
initialValue={props.categoryBoards.name}
title={(
<FormattedMessage
id='SidebarCategories.CategoryMenu.Update'
@ -259,7 +259,7 @@ const SidebarCategory = (props: Props) => {
const category: Category = {
name,
id: props.categoryBlocks.id,
id: props.categoryBoards.id,
userID: me.id,
teamID,
} as Category

View file

@ -1,27 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {CategoryBlocks, DefaultCategory} from '../../store/sidebar'
import {CategoryBoards, DefaultCategory} from '../../store/sidebar'
import {Block} from '../../blocks/block'
import {Board} from '../../blocks/board'
export function addMissingItems(sidebarCategories: Array<CategoryBlocks>, allItems: Array<Block | Board>): Array<CategoryBlocks> {
export function addMissingItems(sidebarCategories: Array<CategoryBoards>, allItems: Array<Block | Board>): Array<CategoryBoards> {
const blocksInCategories = new Map<string, boolean>()
sidebarCategories.forEach(
(category) => category.blockIDs.forEach(
(blockID) => blocksInCategories.set(blockID, true),
(category) => category.boardIDs.forEach(
(boardID) => blocksInCategories.set(boardID, true),
),
)
const defaultCategory: CategoryBlocks = {
const defaultCategory: CategoryBoards = {
...DefaultCategory,
blockIDs: [],
boardIDs: [],
}
allItems.forEach((block) => {
if (!blocksInCategories.get(block.id)) {
defaultCategory.blockIDs.push(block.id)
defaultCategory.boardIDs.push(block.id)
}
})

View file

@ -74,9 +74,9 @@ card3.boardId = fakeBoard.id
const me: IUser = {id: 'user-id-1', username: 'username_1', email: '', props: {}, create_at: 0, update_at: 0, is_bot: false}
const categoryAttribute1 = TestBlockFactory.createCategoryBlocks()
const categoryAttribute1 = TestBlockFactory.createCategoryBoards()
categoryAttribute1.name = 'Category 1'
categoryAttribute1.blockIDs = [board.id]
categoryAttribute1.boardIDs = [board.id]
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom')

View file

@ -914,8 +914,8 @@ class Mutator {
await octoClient.updateSidebarCategory(category)
}
async moveBlockToCategory(teamID: string, blockID: string, toCategoryID: string, fromCategoryID: string): Promise<void> {
await octoClient.moveBlockToCategory(teamID, blockID, toCategoryID, fromCategoryID)
async moveBoardToCategory(teamID: string, blockID: string, toCategoryID: string, fromCategoryID: string): Promise<void> {
await octoClient.moveBoardToCategory(teamID, blockID, toCategoryID, fromCategoryID)
}
async followBlock(blockId: string, blockType: string, userId: string) {

View file

@ -8,7 +8,7 @@ import {IUser, UserConfigPatch} from './user'
import {Utils} from './utils'
import {ClientConfig} from './config/clientConfig'
import {UserSettings} from './userSettings'
import {Category, CategoryBlocks} from './store/sidebar'
import {Category, CategoryBoards} from './store/sidebar'
import {Team} from './store/teams'
import {Subscription} from './wsclient'
import {PrepareOnboardingResponse} from './onboardingTour'
@ -721,14 +721,14 @@ class OctoClient {
})
}
async getSidebarCategories(teamID: string): Promise<Array<CategoryBlocks>> {
async getSidebarCategories(teamID: string): Promise<Array<CategoryBoards>> {
const path = `/api/v2/teams/${teamID}/categories`
const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})
if (response.status !== 200) {
return []
}
return (await this.getJson(response, [])) as Array<CategoryBlocks>
return (await this.getJson(response, [])) as Array<CategoryBoards>
}
async createSidebarCategory(category: Category): Promise<Response> {
@ -759,8 +759,8 @@ class OctoClient {
})
}
async moveBlockToCategory(teamID: string, blockID: string, toCategoryID: string, fromCategoryID: string): Promise<Response> {
const url = `/api/v2/teams/${teamID}/categories/${toCategoryID || '0'}/blocks/${blockID}`
async moveBoardToCategory(teamID: string, boardID: string, toCategoryID: string, fromCategoryID: string): Promise<Response> {
const url = `/api/v2/teams/${teamID}/categories/${toCategoryID || '0'}/boards/${boardID}`
const payload = {
fromCategoryID,
}

View file

@ -29,11 +29,11 @@ const TeamToBoardAndViewRedirect = (): null => {
if (!boardID && categories.length > 0) {
// a category may exist without any boards.
// find the first category with a board and pick it's first board
const categoryWithBoards = categories.find((category) => category.blockIDs.length > 0)
const categoryWithBoards = categories.find((category) => category.boardIDs.length > 0)
// there may even be no boards at all
if (categoryWithBoards) {
boardID = categoryWithBoards.blockIDs[0]
boardID = categoryWithBoards.boardIDs[0]
}
}

View file

@ -17,19 +17,19 @@ interface Category {
deleteAt: number
}
interface CategoryBlocks extends Category {
blockIDs: Array<string>
interface CategoryBoards extends Category {
boardIDs: Array<string>
}
interface BlockCategoryWebsocketData {
blockID: string
interface BoardCategoryWebsocketData {
boardID: string
categoryID: string
}
export const DefaultCategory: CategoryBlocks = {
export const DefaultCategory: CategoryBoards = {
id: '',
name: 'Boards',
} as CategoryBlocks
} as CategoryBoards
export const fetchSidebarCategories = createAsyncThunk(
'sidebarCategories/fetch',
@ -40,7 +40,7 @@ export const fetchSidebarCategories = createAsyncThunk(
)
type Sidebar = {
categoryAttributes: Array<CategoryBlocks>
categoryAttributes: Array<CategoryBoards>
}
const sidebarSlice = createSlice({
@ -55,7 +55,7 @@ const sidebarSlice = createSlice({
if (index === -1) {
state.categoryAttributes.push({
...updatedCategory,
blockIDs: [],
boardIDs: [],
})
} else if (updatedCategory.deleteAt) {
// when category is deleted
@ -70,17 +70,17 @@ const sidebarSlice = createSlice({
}
})
},
updateBlockCategories: (state, action: PayloadAction<Array<BlockCategoryWebsocketData>>) => {
action.payload.forEach((blockCategory) => {
updateBoardCategories: (state, action: PayloadAction<Array<BoardCategoryWebsocketData>>) => {
action.payload.forEach((boardCategory) => {
for (let i = 0; i < state.categoryAttributes.length; i++) {
const categoryAttribute = state.categoryAttributes[i]
// first we remove the block from list of blocks
categoryAttribute.blockIDs = categoryAttribute.blockIDs.filter((blockID) => blockID !== blockCategory.blockID)
// first we remove the board from list of boards
categoryAttribute.boardIDs = categoryAttribute.boardIDs.filter((boardID) => boardID !== boardCategory.boardID)
// then we add it if this is the target category
if (categoryAttribute.id === blockCategory.categoryID) {
categoryAttribute.blockIDs.push(blockCategory.blockID)
if (categoryAttribute.id === boardCategory.categoryID) {
categoryAttribute.boardIDs.push(boardCategory.boardID)
}
}
})
@ -94,13 +94,13 @@ const sidebarSlice = createSlice({
})
export const getSidebarCategories = createSelector(
(state: RootState): Array<CategoryBlocks> => state.sidebar.categoryAttributes,
(state: RootState): Array<CategoryBoards> => state.sidebar.categoryAttributes,
(sidebarCategories) => sidebarCategories,
)
export const {reducer} = sidebarSlice
export const {updateCategories, updateBlockCategories} = sidebarSlice.actions
export const {updateCategories, updateBoardCategories} = sidebarSlice.actions
export {Category, CategoryBlocks, BlockCategoryWebsocketData}
export {Category, CategoryBoards, BoardCategoryWebsocketData}

View file

@ -10,7 +10,7 @@ import {createFilterClause} from '../blocks/filterClause'
import {createFilterGroup} from '../blocks/filterGroup'
import {ImageBlock, createImageBlock} from '../blocks/imageBlock'
import {TextBlock, createTextBlock} from '../blocks/textBlock'
import {Category, CategoryBlocks} from '../store/sidebar'
import {Category, CategoryBoards} from '../store/sidebar'
import {Utils} from '../utils'
import {CheckboxBlock, createCheckboxBlock} from '../blocks/checkboxBlock'
import {Block} from '../blocks/block'
@ -175,10 +175,10 @@ class TestBlockFactory {
}
}
static createCategoryBlocks(): CategoryBlocks {
static createCategoryBoards(): CategoryBoards {
return {
...TestBlockFactory.createCategory(),
blockIDs: [],
boardIDs: [],
}
}
}

View file

@ -15,7 +15,7 @@ import {createCard} from './blocks/card'
import {createCommentBlock} from './blocks/commentBlock'
import {IAppWindow} from './types'
import {ChangeHandlerType, WSMessage} from './wsclient'
import {BlockCategoryWebsocketData, Category} from './store/sidebar'
import {BoardCategoryWebsocketData, Category} from './store/sidebar'
declare let window: IAppWindow
@ -26,7 +26,7 @@ const SpacerClass = 'octo-spacer'
const HorizontalGripClass = 'HorizontalGrip'
const base32Alphabet = 'ybndrfg8ejkmcpqxot1uwisza345h769'
export type WSMessagePayloads = Block | Category | BlockCategoryWebsocketData | BoardType | BoardMember | null
export type WSMessagePayloads = Block | Category | BoardCategoryWebsocketData | BoardType | BoardMember | null
// eslint-disable-next-line no-shadow
enum IDType {

View file

@ -7,7 +7,7 @@ import {Utils, WSMessagePayloads} from './utils'
import {Block} from './blocks/block'
import {Board, BoardMember} from './blocks/board'
import {OctoUtils} from './octoUtils'
import {BlockCategoryWebsocketData, Category} from './store/sidebar'
import {BoardCategoryWebsocketData, Category} from './store/sidebar'
// These are outgoing commands to the server
type WSCommand = {
@ -23,7 +23,7 @@ export type WSMessage = {
block?: Block
board?: Board
category?: Category
blockCategories?: BlockCategoryWebsocketData
blockCategories?: BoardCategoryWebsocketData
error?: string
teamId?: string
member?: BoardMember
@ -40,7 +40,7 @@ export const ACTION_UNSUBSCRIBE_TEAM = 'UNSUBSCRIBE_TEAM'
export const ACTION_UNSUBSCRIBE_BLOCKS = 'UNSUBSCRIBE_BLOCKS'
export const ACTION_UPDATE_CLIENT_CONFIG = 'UPDATE_CLIENT_CONFIG'
export const ACTION_UPDATE_CATEGORY = 'UPDATE_CATEGORY'
export const ACTION_UPDATE_BLOCK_CATEGORY = 'UPDATE_BLOCK_CATEGORY'
export const ACTION_UPDATE_BOARD_CATEGORY = 'UPDATE_BOARD_CATEGORY'
export const ACTION_UPDATE_SUBSCRIPTION = 'UPDATE_SUBSCRIPTION'
type WSSubscriptionMsg = {
@ -81,7 +81,7 @@ export type ChangeHandlerType = 'block' | 'category' | 'blockCategories' | 'boar
type UpdatedData = {
Blocks: Block[]
Categories: Category[]
BlockCategories: Array<BlockCategoryWebsocketData>
BoardCategories: Array<BoardCategoryWebsocketData>
Boards: Board[]
BoardMembers: BoardMember[]
}
@ -89,7 +89,7 @@ type UpdatedData = {
type ChangeHandlers = {
Block: OnChangeHandler[]
Category: OnChangeHandler[]
BlockCategory: OnChangeHandler[]
BoardCategory: OnChangeHandler[]
Board: OnChangeHandler[]
BoardMember: OnChangeHandler[]
}
@ -107,14 +107,14 @@ class WSClient {
state: 'init'|'open'|'close' = 'init'
onStateChange: OnStateChangeHandler[] = []
onReconnect: OnReconnectHandler[] = []
onChange: ChangeHandlers = {Block: [], Category: [], BlockCategory: [], Board: [], BoardMember: []}
onChange: ChangeHandlers = {Block: [], Category: [], BoardCategory: [], Board: [], BoardMember: []}
onError: OnErrorHandler[] = []
onConfigChange: OnConfigChangeHandler[] = []
onFollowBlock: FollowChangeHandler = () => {}
onUnfollowBlock: FollowChangeHandler = () => {}
private notificationDelay = 100
private reopenDelay = 3000
private updatedData: UpdatedData = {Blocks: [], Categories: [], BlockCategories: [], Boards: [], BoardMembers: []}
private updatedData: UpdatedData = {Blocks: [], Categories: [], BoardCategories: [], Boards: [], BoardMembers: []}
private updateTimeout?: NodeJS.Timeout
private errorPollId?: NodeJS.Timeout
@ -169,7 +169,7 @@ class WSClient {
this.onChange.Category.push(handler)
break
case 'blockCategories':
this.onChange.BlockCategory.push(handler)
this.onChange.BoardCategory.push(handler)
break
case 'board':
this.onChange.Board.push(handler)
@ -187,7 +187,7 @@ class WSClient {
haystack = this.onChange.Block
break
case 'blockCategories':
haystack = this.onChange.BlockCategory
haystack = this.onChange.BoardCategory
break
case 'board':
haystack = this.onChange.Board
@ -385,7 +385,7 @@ class WSClient {
case ACTION_UPDATE_CATEGORY:
this.updateHandler(message)
break
case ACTION_UPDATE_BLOCK_CATEGORY:
case ACTION_UPDATE_BOARD_CATEGORY:
this.updateHandler(message)
break
case ACTION_UPDATE_SUBSCRIPTION:
@ -568,8 +568,8 @@ class WSClient {
this.updatedData.Categories = this.updatedData.Categories.filter((c) => c.id !== (data as Category).id)
this.updatedData.Categories.push(data as Category)
} else if (type === 'blockCategories') {
this.updatedData.BlockCategories = this.updatedData.BlockCategories.filter((b) => b.blockID === (data as BlockCategoryWebsocketData).blockID)
this.updatedData.BlockCategories.push(data as BlockCategoryWebsocketData)
this.updatedData.BoardCategories = this.updatedData.BoardCategories.filter((b) => b.boardID === (data as BoardCategoryWebsocketData).boardID)
this.updatedData.BoardCategories.push(data as BoardCategoryWebsocketData)
} else if (type === 'board') {
this.updatedData.Boards = this.updatedData.Boards.filter((b) => b.id !== (data as Board).id)
this.updatedData.Boards.push(data as Board)
@ -612,8 +612,8 @@ class WSClient {
Utils.log(`WSClient flush update category: ${category.id}`)
}
for (const blockCategories of this.updatedData.BlockCategories) {
Utils.log(`WSClient flush update blockCategory: ${blockCategories.blockID} ${blockCategories.categoryID}`)
for (const blockCategories of this.updatedData.BoardCategories) {
Utils.log(`WSClient flush update blockCategory: ${blockCategories.boardID} ${blockCategories.categoryID}`)
}
for (const board of this.updatedData.Boards) {
@ -636,8 +636,8 @@ class WSClient {
handler(this, this.updatedData.Categories)
}
for (const handler of this.onChange.BlockCategory) {
handler(this, this.updatedData.BlockCategories)
for (const handler of this.onChange.BoardCategory) {
handler(this, this.updatedData.BoardCategories)
}
for (const handler of this.onChange.Board) {
@ -651,7 +651,7 @@ class WSClient {
this.updatedData = {
Blocks: [],
Categories: [],
BlockCategories: [],
BoardCategories: [],
Boards: [],
BoardMembers: [],
}
@ -667,7 +667,7 @@ class WSClient {
// Use this sequence so the onclose method doesn't try to re-open
const ws = this.ws
this.ws = null
this.onChange = {Block: [], Category: [], BlockCategory: [], Board: [], BoardMember: []}
this.onChange = {Block: [], Category: [], BoardCategory: [], Board: [], BoardMember: []}
this.onReconnect = []
this.onStateChange = []
this.onError = []