mirror of
https://github.com/mattermost/focalboard.git
synced 2024-11-27 08:31:20 +02:00
Add Members History support (#2595)
* Adding Members History support * Adding unit tests for boards members history * Changing insert_at to a milliseconds from epoch field * Addressing PR review comment * Addressing PR review comment * Fix golangci-lint * Fix migrations in postgres Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
f7fe5df8e8
commit
8212e5401e
@ -311,3 +311,23 @@ func (b *Board) IsValid() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BoardMemberHistoryEntry stores the information of the membership of a user on a board
|
||||
// swagger:model
|
||||
type BoardMemberHistoryEntry struct {
|
||||
// The ID of the board
|
||||
// required: true
|
||||
BoardID string `json:"boardId"`
|
||||
|
||||
// The ID of the user
|
||||
// required: true
|
||||
UserID string `json:"userId"`
|
||||
|
||||
// The action that added this history entry (created or deleted)
|
||||
// required: false
|
||||
Action string `json:"action"`
|
||||
|
||||
// The insertion time
|
||||
// required: true
|
||||
InsertAt int64 `json:"insertAt"`
|
||||
}
|
||||
|
@ -505,6 +505,21 @@ func (mr *MockStoreMockRecorder) GetBoardAndCardByID(arg0 interface{}) *gomock.C
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardAndCardByID", reflect.TypeOf((*MockStore)(nil).GetBoardAndCardByID), arg0)
|
||||
}
|
||||
|
||||
// GetBoardMemberHistory mocks base method.
|
||||
func (m *MockStore) GetBoardMemberHistory(arg0, arg1 string, arg2 uint64) ([]*model.BoardMemberHistoryEntry, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBoardMemberHistory", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].([]*model.BoardMemberHistoryEntry)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBoardMemberHistory indicates an expected call of GetBoardMemberHistory.
|
||||
func (mr *MockStoreMockRecorder) GetBoardMemberHistory(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardMemberHistory", reflect.TypeOf((*MockStore)(nil).GetBoardMemberHistory), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// GetBoardsForUserAndTeam mocks base method.
|
||||
func (m *MockStore) GetBoardsForUserAndTeam(arg0, arg1 string) ([]*model.Board, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -151,6 +151,28 @@ func (s *SQLStore) boardMembersFromRows(rows *sql.Rows) ([]*model.BoardMember, e
|
||||
return boardMembers, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) boardMemberHistoryEntriesFromRows(rows *sql.Rows) ([]*model.BoardMemberHistoryEntry, error) {
|
||||
boardMemberHistoryEntries := []*model.BoardMemberHistoryEntry{}
|
||||
|
||||
for rows.Next() {
|
||||
var boardMemberHistoryEntry model.BoardMemberHistoryEntry
|
||||
|
||||
err := rows.Scan(
|
||||
&boardMemberHistoryEntry.BoardID,
|
||||
&boardMemberHistoryEntry.UserID,
|
||||
&boardMemberHistoryEntry.Action,
|
||||
&boardMemberHistoryEntry.InsertAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
boardMemberHistoryEntries = append(boardMemberHistoryEntries, &boardMemberHistoryEntry)
|
||||
}
|
||||
|
||||
return boardMemberHistoryEntries, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) getBoardByCondition(db sq.BaseRunner, conditions ...interface{}) (*model.Board, error) {
|
||||
boards, err := s.getBoardsByCondition(db, conditions...)
|
||||
if err != nil {
|
||||
@ -405,6 +427,11 @@ func (s *SQLStore) saveMember(db sq.BaseRunner, bm *model.BoardMember) (*model.B
|
||||
"scheme_viewer": bm.SchemeViewer,
|
||||
}
|
||||
|
||||
oldMember, err := s.getMemberForBoard(db, bm.BoardID, bm.UserID)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder(db).
|
||||
Insert(s.tablePrefix + "board_members").
|
||||
SetMap(queryValues)
|
||||
@ -425,6 +452,17 @@ func (s *SQLStore) saveMember(db sq.BaseRunner, bm *model.BoardMember) (*model.B
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
if _, err := addToMembersHistory.Exec(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return bm, nil
|
||||
}
|
||||
|
||||
@ -434,10 +472,27 @@ func (s *SQLStore) deleteMember(db sq.BaseRunner, boardID, userID string) error
|
||||
Where(sq.Eq{"board_id": boardID}).
|
||||
Where(sq.Eq{"user_id": userID})
|
||||
|
||||
if _, err := deleteQuery.Exec(); err != nil {
|
||||
result, err := deleteQuery.Exec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
if _, err := addToMembersHistory.Exec(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -549,3 +604,30 @@ func (s *SQLStore) searchBoardsForUserAndTeam(db sq.BaseRunner, term, userID, te
|
||||
|
||||
return s.boardsFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) getBoardMemberHistory(db sq.BaseRunner, boardID, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error) {
|
||||
query := s.getQueryBuilder(db).
|
||||
Select("board_id", "user_id", "action", "insert_at").
|
||||
From(s.tablePrefix + "board_members_history").
|
||||
Where(sq.Eq{"board_id": boardID}).
|
||||
Where(sq.Eq{"user_id": userID}).
|
||||
OrderBy("insert_at DESC")
|
||||
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
s.logger.Error(`getBoardMemberHistory ERROR`, mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
defer s.CloseRows(rows)
|
||||
|
||||
memberHistory, err := s.boardMemberHistoryEntriesFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberHistory, nil
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
DROP TABLE {{.prefix}}board_members_history;
|
@ -0,0 +1,18 @@
|
||||
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 .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);
|
||||
|
||||
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;
|
@ -309,6 +309,11 @@ func (s *SQLStore) GetBoardAndCardByID(blockID string) (*model.Board, *model.Blo
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBoardMemberHistory(boardID string, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error) {
|
||||
return s.getBoardMemberHistory(s.db, boardID, userID, limit)
|
||||
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBoardsForUserAndTeam(userID string, teamID string) ([]*model.Board, error) {
|
||||
return s.getBoardsForUserAndTeam(s.db, userID, teamID)
|
||||
|
||||
|
@ -90,6 +90,7 @@ type Store interface {
|
||||
SaveMember(bm *model.BoardMember) (*model.BoardMember, error)
|
||||
DeleteMember(boardID, userID string) error
|
||||
GetMemberForBoard(boardID, userID string) (*model.BoardMember, error)
|
||||
GetBoardMemberHistory(boardID, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error)
|
||||
GetMembersForBoard(boardID string) ([]*model.BoardMember, error)
|
||||
GetMembersForUser(userID string) ([]*model.BoardMember, error)
|
||||
SearchBoardsForUserAndTeam(term, userID, teamID string) ([]*model.Board, error)
|
||||
|
@ -517,12 +517,20 @@ func testSaveMember(t *testing.T, store store.Store) {
|
||||
SchemeAdmin: true,
|
||||
}
|
||||
|
||||
memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
initialMemberHistory := len(memberHistory)
|
||||
|
||||
nbm, err := store.SaveMember(bm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, userID, nbm.UserID)
|
||||
require.Equal(t, boardID, nbm.BoardID)
|
||||
|
||||
require.True(t, nbm.SchemeAdmin)
|
||||
|
||||
memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memberHistory, initialMemberHistory+1)
|
||||
})
|
||||
|
||||
t.Run("should correctly update a member", func(t *testing.T) {
|
||||
@ -533,6 +541,10 @@ func testSaveMember(t *testing.T, store store.Store) {
|
||||
SchemeViewer: true,
|
||||
}
|
||||
|
||||
memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
initialMemberHistory := len(memberHistory)
|
||||
|
||||
nbm, err := store.SaveMember(bm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, userID, nbm.UserID)
|
||||
@ -541,6 +553,10 @@ func testSaveMember(t *testing.T, store store.Store) {
|
||||
require.False(t, nbm.SchemeAdmin)
|
||||
require.True(t, nbm.SchemeEditor)
|
||||
require.True(t, nbm.SchemeViewer)
|
||||
|
||||
memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memberHistory, initialMemberHistory)
|
||||
})
|
||||
}
|
||||
|
||||
@ -626,7 +642,15 @@ func testDeleteMember(t *testing.T, store store.Store) {
|
||||
boardID := testBoardID
|
||||
|
||||
t.Run("should return nil if deleting a nonexistent member", func(t *testing.T) {
|
||||
memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
initialMemberHistory := len(memberHistory)
|
||||
|
||||
require.NoError(t, store.DeleteMember(boardID, userID))
|
||||
|
||||
memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memberHistory, initialMemberHistory)
|
||||
})
|
||||
|
||||
t.Run("should correctly delete a member", func(t *testing.T) {
|
||||
@ -640,11 +664,19 @@ func testDeleteMember(t *testing.T, store store.Store) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, nbm)
|
||||
|
||||
memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
initialMemberHistory := len(memberHistory)
|
||||
|
||||
require.NoError(t, store.DeleteMember(boardID, userID))
|
||||
|
||||
rbm, err := store.GetMemberForBoard(boardID, userID)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
require.Nil(t, rbm)
|
||||
|
||||
memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, memberHistory, initialMemberHistory+1)
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user