You've already forked focalboard
mirror of
https://github.com/mattermost/focalboard.git
synced 2025-07-12 23:50:27 +02:00
Adding the permissions for commenter + viewer roles (#2882)
Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Paul Esch-Laurent <paul.esch-laurent@mattermost.com>
This commit is contained in:
@ -221,13 +221,6 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
|||||||
val := r.URL.Query().Get("disable_notify")
|
val := r.URL.Query().Get("disable_notify")
|
||||||
disableNotify := val == True
|
disableNotify := val == True
|
||||||
|
|
||||||
// in phase 1 we use "manage_board_cards", but we would have to
|
|
||||||
// check on specific actions for phase 2
|
|
||||||
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
|
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
requestBody, err := ioutil.ReadAll(r.Body)
|
requestBody, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
@ -242,6 +235,8 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasComments := false
|
||||||
|
hasContents := false
|
||||||
for _, block := range blocks {
|
for _, block := range blocks {
|
||||||
// Error checking
|
// Error checking
|
||||||
if len(block.Type) < 1 {
|
if len(block.Type) < 1 {
|
||||||
@ -250,6 +245,12 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if block.Type == model.TypeComment {
|
||||||
|
hasComments = true
|
||||||
|
} else {
|
||||||
|
hasContents = true
|
||||||
|
}
|
||||||
|
|
||||||
if block.CreateAt < 1 {
|
if block.CreateAt < 1 {
|
||||||
message := fmt.Sprintf("invalid createAt for block id %s", block.ID)
|
message := fmt.Sprintf("invalid createAt for block id %s", block.ID)
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
|
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, message, nil)
|
||||||
@ -269,6 +270,19 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasContents {
|
||||||
|
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasComments {
|
||||||
|
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionCommentBoardCards) {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to post card comments"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
blocks = model.GenerateBlockIDs(blocks, a.logger)
|
blocks = model.GenerateBlockIDs(blocks, a.logger)
|
||||||
|
|
||||||
auditRec := a.makeAuditRecord(r, "postBlocks", audit.Fail)
|
auditRec := a.makeAuditRecord(r, "postBlocks", audit.Fail)
|
||||||
@ -748,10 +762,17 @@ func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
|
if block.Type == model.TypeComment {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board members"})
|
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionCommentBoardCards) {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to comment on board cards"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to modify board cards"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auditRec := a.makeAuditRecord(r, "duplicateBlock", audit.Fail)
|
auditRec := a.makeAuditRecord(r, "duplicateBlock", audit.Fail)
|
||||||
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
defer a.audit.LogRecord(audit.LevelRead, auditRec)
|
||||||
|
@ -1075,6 +1075,74 @@ func TestPermissionsCreateBoardBlocks(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPermissionsCreateBoardComments(t *testing.T) {
|
||||||
|
ttCasesF := func(testData TestData) []TestCase {
|
||||||
|
counter := 0
|
||||||
|
newBlockJSON := func(boardID string) string {
|
||||||
|
counter++
|
||||||
|
return toJSON(t, []*model.Block{{
|
||||||
|
ID: fmt.Sprintf("%d", counter),
|
||||||
|
Title: "Comment to create",
|
||||||
|
BoardID: boardID,
|
||||||
|
Type: model.TypeComment,
|
||||||
|
CreateAt: model.GetMillis(),
|
||||||
|
UpdateAt: model.GetMillis(),
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
return []TestCase{
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks", methodPost, newBlockJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks", methodPost, newBlockJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("plugin", func(t *testing.T) {
|
||||||
|
th := SetupTestHelperPluginMode(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
clients := setupClients(th)
|
||||||
|
testData := setupData(t, th)
|
||||||
|
ttCases := ttCasesF(testData)
|
||||||
|
runTestCases(t, ttCases, testData, clients)
|
||||||
|
})
|
||||||
|
t.Run("local", func(t *testing.T) {
|
||||||
|
th := SetupTestHelperLocalMode(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
clients := setupLocalClients(th)
|
||||||
|
testData := setupData(t, th)
|
||||||
|
ttCases := ttCasesF(testData)
|
||||||
|
runTestCases(t, ttCases, testData, clients)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPermissionsPatchBoardBlocks(t *testing.T) {
|
func TestPermissionsPatchBoardBlocks(t *testing.T) {
|
||||||
newBlocksPatchJSON := func(blockID string) string {
|
newBlocksPatchJSON := func(blockID string) string {
|
||||||
newTitle := "New Patch Block Title"
|
newTitle := "New Patch Block Title"
|
||||||
@ -1420,37 +1488,104 @@ func TestPermissionsDuplicateBoardBlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ttCases := []TestCase{
|
ttCases := []TestCase{
|
||||||
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
||||||
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userCommenter, http.StatusForbidden, 0},
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
|
// Invalid boardID/blockID combination
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-3/duplicate", methodPost, "", userAdmin, http.StatusNotFound, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("plugin", func(t *testing.T) {
|
||||||
|
th := SetupTestHelperPluginMode(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
clients := setupClients(th)
|
||||||
|
testData := setupData(t, th)
|
||||||
|
extraSetup(t, th, testData)
|
||||||
|
runTestCases(t, ttCases, testData, clients)
|
||||||
|
})
|
||||||
|
t.Run("local", func(t *testing.T) {
|
||||||
|
th := SetupTestHelperLocalMode(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
clients := setupLocalClients(th)
|
||||||
|
testData := setupData(t, th)
|
||||||
|
extraSetup(t, th, testData)
|
||||||
|
runTestCases(t, ttCases, testData, clients)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionsDuplicateBoardComment(t *testing.T) {
|
||||||
|
extraSetup := func(t *testing.T, th *TestHelper, testData TestData) {
|
||||||
|
err := th.Server.App().InsertBlock(model.Block{ID: "block-5", Title: "Test", Type: model.TypeComment, BoardID: testData.publicTemplate.ID}, userAdmin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = th.Server.App().InsertBlock(model.Block{ID: "block-6", Title: "Test", Type: model.TypeComment, BoardID: testData.privateTemplate.ID}, userAdmin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = th.Server.App().InsertBlock(model.Block{ID: "block-7", Title: "Test", Type: model.TypeComment, BoardID: testData.publicBoard.ID}, userAdmin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = th.Server.App().InsertBlock(model.Block{ID: "block-8", Title: "Test", Type: model.TypeComment, BoardID: testData.privateBoard.ID}, userAdmin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ttCases := []TestCase{
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userAnon, http.StatusUnauthorized, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userTeamMember, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userViewer, http.StatusForbidden, 0},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userCommenter, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userEditor, http.StatusOK, 1},
|
||||||
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/duplicate", methodPost, "", userAdmin, http.StatusOK, 1},
|
||||||
|
|
||||||
// Invalid boardID/blockID combination
|
// Invalid boardID/blockID combination
|
||||||
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-3/duplicate", methodPost, "", userAdmin, http.StatusNotFound, 0},
|
{"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-3/duplicate", methodPost, "", userAdmin, http.StatusNotFound, 0},
|
||||||
|
@ -17,4 +17,6 @@ var (
|
|||||||
PermissionShareBoard = &mmModel.Permission{Id: "share_board", Name: "", Description: "", Scope: ""}
|
PermissionShareBoard = &mmModel.Permission{Id: "share_board", Name: "", Description: "", Scope: ""}
|
||||||
PermissionManageBoardCards = &mmModel.Permission{Id: "manage_board_cards", Name: "", Description: "", Scope: ""}
|
PermissionManageBoardCards = &mmModel.Permission{Id: "manage_board_cards", Name: "", Description: "", Scope: ""}
|
||||||
PermissionManageBoardProperties = &mmModel.Permission{Id: "manage_board_properties", Name: "", Description: "", Scope: ""}
|
PermissionManageBoardProperties = &mmModel.Permission{Id: "manage_board_properties", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionCommentBoardCards = &mmModel.Permission{Id: "comment_board_cards", Name: "", Description: "", Scope: ""}
|
||||||
|
PermissionDeleteOthersComments = &mmModel.Permission{Id: "delete_others_comments", Name: "", Description: "", Scope: ""}
|
||||||
)
|
)
|
||||||
|
@ -67,10 +67,12 @@ func (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch permission {
|
switch permission {
|
||||||
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard:
|
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard, model.PermissionDeleteOthersComments:
|
||||||
return member.SchemeAdmin
|
return member.SchemeAdmin
|
||||||
case model.PermissionManageBoardCards, model.PermissionManageBoardProperties:
|
case model.PermissionManageBoardCards, model.PermissionManageBoardProperties:
|
||||||
return member.SchemeAdmin || member.SchemeEditor
|
return member.SchemeAdmin || member.SchemeEditor
|
||||||
|
case model.PermissionCommentBoardCards:
|
||||||
|
return member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter
|
||||||
case model.PermissionViewBoard:
|
case model.PermissionViewBoard:
|
||||||
return member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter || member.SchemeViewer
|
return member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter || member.SchemeViewer
|
||||||
default:
|
default:
|
||||||
|
@ -99,10 +99,12 @@ func (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch permission {
|
switch permission {
|
||||||
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard:
|
case model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard, model.PermissionDeleteOthersComments:
|
||||||
return member.SchemeAdmin
|
return member.SchemeAdmin
|
||||||
case model.PermissionManageBoardCards, model.PermissionManageBoardProperties:
|
case model.PermissionManageBoardCards, model.PermissionManageBoardProperties:
|
||||||
return member.SchemeAdmin || member.SchemeEditor
|
return member.SchemeAdmin || member.SchemeEditor
|
||||||
|
case model.PermissionCommentBoardCards:
|
||||||
|
return member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter
|
||||||
case model.PermissionViewBoard:
|
case model.PermissionViewBoard:
|
||||||
return member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter || member.SchemeViewer
|
return member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter || member.SchemeViewer
|
||||||
default:
|
default:
|
||||||
|
@ -61,6 +61,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
|
|||||||
}
|
}
|
||||||
}, [card.title, title])
|
}, [card.title, title])
|
||||||
const canEditBoardCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])
|
const canEditBoardCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])
|
||||||
|
const canCommentBoardCards = useHasCurrentBoardPermissions([Permission.CommentBoardCards])
|
||||||
|
|
||||||
const saveTitleRef = useRef<() => void>(saveTitle)
|
const saveTitleRef = useRef<() => void>(saveTitle)
|
||||||
saveTitleRef.current = saveTitle
|
saveTitleRef.current = saveTitle
|
||||||
@ -207,7 +208,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
|
|||||||
comments={comments}
|
comments={comments}
|
||||||
boardId={card.boardId}
|
boardId={card.boardId}
|
||||||
cardId={card.id}
|
cardId={card.id}
|
||||||
readonly={props.readonly || !canEditBoardCards}
|
readonly={props.readonly || !canCommentBoardCards}
|
||||||
/>
|
/>
|
||||||
</Fragment>}
|
</Fragment>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,6 +57,9 @@ describe('components/cardDetail/CommentsList', () => {
|
|||||||
board_id_1: {title: 'Board'},
|
board_id_1: {title: 'Board'},
|
||||||
},
|
},
|
||||||
current: 'board_id_1',
|
current: 'board_id_1',
|
||||||
|
myBoardMemberships: {
|
||||||
|
['board_id_1']: {userId: 'user_id_1', schemeAdmin: true},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cards: {
|
cards: {
|
||||||
cards: {
|
cards: {
|
||||||
@ -69,6 +72,9 @@ describe('components/cardDetail/CommentsList', () => {
|
|||||||
featureFlags: {},
|
featureFlags: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
teams: {
|
||||||
|
current: {id: 'team_id_1'},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const component = (
|
const component = (
|
||||||
@ -109,6 +115,18 @@ describe('components/cardDetail/CommentsList', () => {
|
|||||||
{username: 'username_1'},
|
{username: 'username_1'},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
boards: {
|
||||||
|
boards: {
|
||||||
|
board_id_1: {title: 'Board'},
|
||||||
|
},
|
||||||
|
current: 'board_id_1',
|
||||||
|
myBoardMemberships: {
|
||||||
|
['board_id_1']: {userId: 'user_id_1', schemeAdmin: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
current: {id: 'team_id_1'}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const component = (
|
const component = (
|
||||||
|
@ -13,6 +13,8 @@ import {MarkdownEditor} from '../markdownEditor'
|
|||||||
|
|
||||||
import {IUser} from '../../user'
|
import {IUser} from '../../user'
|
||||||
import {getMe} from '../../store/users'
|
import {getMe} from '../../store/users'
|
||||||
|
import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
|
||||||
|
import {Permission} from '../../constants'
|
||||||
|
|
||||||
import AddCommentTourStep from '../onboardingTour/addComments/addComments'
|
import AddCommentTourStep from '../onboardingTour/addComments/addComments'
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ type Props = {
|
|||||||
const CommentsList = (props: Props) => {
|
const CommentsList = (props: Props) => {
|
||||||
const [newComment, setNewComment] = useState('')
|
const [newComment, setNewComment] = useState('')
|
||||||
const me = useAppSelector<IUser|null>(getMe)
|
const me = useAppSelector<IUser|null>(getMe)
|
||||||
|
const canDeleteOthersComments = useHasCurrentBoardPermissions([Permission.DeleteOthersComments])
|
||||||
|
|
||||||
const onSendClicked = () => {
|
const onSendClicked = () => {
|
||||||
const commentText = newComment
|
const commentText = newComment
|
||||||
@ -88,15 +91,20 @@ const CommentsList = (props: Props) => {
|
|||||||
{/* New comment */}
|
{/* New comment */}
|
||||||
{!props.readonly && newCommentComponent}
|
{!props.readonly && newCommentComponent}
|
||||||
|
|
||||||
{comments.slice(0).reverse().map((comment) => (
|
{comments.slice(0).reverse().map((comment) => {
|
||||||
|
|
||||||
|
// Only modify _own_ comments, EXCEPT for Admins, which can delete _any_ comment
|
||||||
|
// NOTE: editing comments will exist in the future (in addition to deleting)
|
||||||
|
const canDeleteComment: boolean = canDeleteOthersComments || me?.id === comment.modifiedBy
|
||||||
|
return (
|
||||||
<Comment
|
<Comment
|
||||||
key={comment.id}
|
key={comment.id}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
userImageUrl={Utils.getProfilePicture(comment.modifiedBy)}
|
userImageUrl={Utils.getProfilePicture(comment.modifiedBy)}
|
||||||
userId={comment.modifiedBy}
|
userId={comment.modifiedBy}
|
||||||
readonly={props.readonly}
|
readonly={props.readonly || !canDeleteComment}
|
||||||
/>
|
/>
|
||||||
))}
|
)})}
|
||||||
|
|
||||||
{/* horizontal divider below comments */}
|
{/* horizontal divider below comments */}
|
||||||
{!(comments.length === 0 && props.readonly) && <hr className='CommentsList__divider'/>}
|
{!(comments.length === 0 && props.readonly) && <hr className='CommentsList__divider'/>}
|
||||||
|
@ -0,0 +1,683 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`src/components/shareBoard/userPermissionsRow should match snapshot 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="user-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="user-item__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ml-3"
|
||||||
|
>
|
||||||
|
<strong />
|
||||||
|
<strong
|
||||||
|
class="ml-2 text-light"
|
||||||
|
>
|
||||||
|
@username_1
|
||||||
|
</strong>
|
||||||
|
<strong
|
||||||
|
class="ml-2 text-light"
|
||||||
|
>
|
||||||
|
(You)
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="user-item__button"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-chevron-down CompassIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="Menu noselect left "
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-contents"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-options"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Viewer"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Viewer
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Commenter"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Commenter
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Editor"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Editor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Admin"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="CheckIcon Icon"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<polyline
|
||||||
|
points="20,60 40,80 80,40"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="MenuOption MenuSeparator menu-separator"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Remove member"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Remove member
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-spacer hideOnWidescreen"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="menu-options hideOnWidescreen"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Cancel"
|
||||||
|
class="MenuOption TextOption menu-option menu-cancel"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`src/components/shareBoard/userPermissionsRow should match snapshot in plugin mode 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="user-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="user-item__content"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="user-item__img"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ml-3"
|
||||||
|
>
|
||||||
|
<strong />
|
||||||
|
<strong
|
||||||
|
class="ml-2 text-light"
|
||||||
|
>
|
||||||
|
@username_1
|
||||||
|
</strong>
|
||||||
|
<strong
|
||||||
|
class="ml-2 text-light"
|
||||||
|
>
|
||||||
|
(You)
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="user-item__button"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-chevron-down CompassIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="Menu noselect left "
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-contents"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-options"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Viewer"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Viewer
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Commenter"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Commenter
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Editor"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Editor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Admin"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="CheckIcon Icon"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<polyline
|
||||||
|
points="20,60 40,80 80,40"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="MenuOption MenuSeparator menu-separator"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Remove member"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Remove member
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-spacer hideOnWidescreen"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="menu-options hideOnWidescreen"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Cancel"
|
||||||
|
class="MenuOption TextOption menu-option menu-cancel"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`src/components/shareBoard/userPermissionsRow should match snapshot in template 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="user-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="user-item__content"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="user-item__img"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ml-3"
|
||||||
|
>
|
||||||
|
<strong />
|
||||||
|
<strong
|
||||||
|
class="ml-2 text-light"
|
||||||
|
>
|
||||||
|
@username_1
|
||||||
|
</strong>
|
||||||
|
<strong
|
||||||
|
class="ml-2 text-light"
|
||||||
|
>
|
||||||
|
(You)
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="user-item__button"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-chevron-down CompassIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="Menu noselect left "
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-contents"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-options"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Viewer"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Viewer
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div />
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Editor"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Editor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Admin"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex menu-option__check"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="CheckIcon Icon"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<polyline
|
||||||
|
points="20,60 40,80 80,40"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="MenuOption MenuSeparator menu-separator"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-label="Remove member"
|
||||||
|
class="MenuOption TextOption menu-option"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Remove member
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-spacer hideOnWidescreen"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="menu-options hideOnWidescreen"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Cancel"
|
||||||
|
class="MenuOption TextOption menu-option menu-cancel"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-option__content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-name"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="noicon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
172
webapp/src/components/shareBoard/userPermissionsRow.test.tsx
Normal file
172
webapp/src/components/shareBoard/userPermissionsRow.test.tsx
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
import {act, render} from '@testing-library/react'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
import thunk from 'redux-thunk'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import {MemoryRouter} from 'react-router'
|
||||||
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
|
import {BoardMember} from '../../blocks/board'
|
||||||
|
|
||||||
|
import {IUser} from '../../user'
|
||||||
|
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||||
|
import {mockStateStore, wrapDNDIntl} from '../../testUtils'
|
||||||
|
import {Utils} from '../../utils'
|
||||||
|
|
||||||
|
import UserPermissionsRow from './userPermissionsRow'
|
||||||
|
|
||||||
|
jest.useFakeTimers()
|
||||||
|
|
||||||
|
const boardId = '1'
|
||||||
|
|
||||||
|
jest.mock('../../utils')
|
||||||
|
|
||||||
|
const mockedUtils = mocked(Utils, true)
|
||||||
|
|
||||||
|
const board = TestBlockFactory.createBoard()
|
||||||
|
board.id = boardId
|
||||||
|
board.teamId = 'team-id'
|
||||||
|
board.channelId = 'channel_1'
|
||||||
|
|
||||||
|
describe('src/components/shareBoard/userPermissionsRow', () => {
|
||||||
|
const me: IUser = {
|
||||||
|
id: 'user-id-1',
|
||||||
|
username: 'username_1',
|
||||||
|
email: '',
|
||||||
|
nickname: '',
|
||||||
|
firstname: '',
|
||||||
|
lastname: '',
|
||||||
|
props: {},
|
||||||
|
create_at: 0,
|
||||||
|
update_at: 0,
|
||||||
|
is_bot: false,
|
||||||
|
roles: 'system_user',
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
teams: {
|
||||||
|
current: {id: 'team-id', title: 'Test Team'},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
me,
|
||||||
|
boardUsers: [me],
|
||||||
|
blockSubscriptions: [],
|
||||||
|
},
|
||||||
|
boards: {
|
||||||
|
current: board.id,
|
||||||
|
boards: {
|
||||||
|
[board.id]: board,
|
||||||
|
},
|
||||||
|
templates: [],
|
||||||
|
membersInBoards: {
|
||||||
|
[board.id]: {},
|
||||||
|
},
|
||||||
|
myBoardMemberships: {
|
||||||
|
[board.id]: {userId: me.id, schemeAdmin: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should match snapshot', async () => {
|
||||||
|
let container: Element | undefined
|
||||||
|
mockedUtils.isFocalboardPlugin.mockReturnValue(false)
|
||||||
|
const store = mockStateStore([thunk], state)
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(
|
||||||
|
wrapDNDIntl(
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<UserPermissionsRow
|
||||||
|
user={me}
|
||||||
|
isMe={true}
|
||||||
|
member={state.boards.myBoardMemberships[board.id] as BoardMember}
|
||||||
|
teammateNameDisplay={'test'}
|
||||||
|
onDeleteBoardMember={() => {}}
|
||||||
|
onUpdateBoardMember={() => {}}
|
||||||
|
/>
|
||||||
|
</ReduxProvider>),
|
||||||
|
{wrapper: MemoryRouter},
|
||||||
|
)
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
|
||||||
|
const buttonElement = container?.querySelector('.user-item__button')
|
||||||
|
expect(buttonElement).toBeDefined()
|
||||||
|
userEvent.click(buttonElement!)
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should match snapshot in plugin mode', async () => {
|
||||||
|
let container: Element | undefined
|
||||||
|
mockedUtils.isFocalboardPlugin.mockReturnValue(true)
|
||||||
|
const store = mockStateStore([thunk], state)
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(
|
||||||
|
wrapDNDIntl(
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<UserPermissionsRow
|
||||||
|
user={me}
|
||||||
|
isMe={true}
|
||||||
|
member={state.boards.myBoardMemberships[board.id] as BoardMember}
|
||||||
|
teammateNameDisplay={'test'}
|
||||||
|
onDeleteBoardMember={() => {}}
|
||||||
|
onUpdateBoardMember={() => {}}/>
|
||||||
|
</ReduxProvider>),
|
||||||
|
{wrapper: MemoryRouter},
|
||||||
|
)
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
|
||||||
|
const buttonElement = container?.querySelector('.user-item__button')
|
||||||
|
expect(buttonElement).toBeDefined()
|
||||||
|
userEvent.click(buttonElement!)
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should match snapshot in template', async () => {
|
||||||
|
let container: Element | undefined
|
||||||
|
mockedUtils.isFocalboardPlugin.mockReturnValue(true)
|
||||||
|
const testState = {
|
||||||
|
...state,
|
||||||
|
boards: {
|
||||||
|
...state.boards,
|
||||||
|
boards: {},
|
||||||
|
templates: {
|
||||||
|
[board.id]: {...board, isTemplate: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const store = mockStateStore([thunk], testState)
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(
|
||||||
|
wrapDNDIntl(
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<UserPermissionsRow
|
||||||
|
user={me}
|
||||||
|
isMe={true}
|
||||||
|
member={state.boards.myBoardMemberships[board.id] as BoardMember}
|
||||||
|
teammateNameDisplay={'test'}
|
||||||
|
onDeleteBoardMember={() => {}}
|
||||||
|
onUpdateBoardMember={() => {}}
|
||||||
|
/>
|
||||||
|
</ReduxProvider>),
|
||||||
|
{wrapper: MemoryRouter},
|
||||||
|
)
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
|
||||||
|
const buttonElement = container?.querySelector('.user-item__button')
|
||||||
|
expect(buttonElement).toBeDefined()
|
||||||
|
userEvent.click(buttonElement!)
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
@ -14,6 +14,8 @@ import {BoardMember} from '../../blocks/board'
|
|||||||
import {IUser} from '../../user'
|
import {IUser} from '../../user'
|
||||||
import {Utils} from '../../utils'
|
import {Utils} from '../../utils'
|
||||||
import {Permission} from '../../constants'
|
import {Permission} from '../../constants'
|
||||||
|
import {useAppSelector} from '../../store/hooks'
|
||||||
|
import {getCurrentBoard} from '../../store/boards'
|
||||||
|
|
||||||
import BoardPermissionGate from '../permissions/boardPermissionGate'
|
import BoardPermissionGate from '../permissions/boardPermissionGate'
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ type Props = {
|
|||||||
|
|
||||||
const UserPermissionsRow = (props: Props): JSX.Element => {
|
const UserPermissionsRow = (props: Props): JSX.Element => {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const board = useAppSelector(getCurrentBoard)
|
||||||
const {user, member, isMe, teammateNameDisplay} = props
|
const {user, member, isMe, teammateNameDisplay} = props
|
||||||
let currentRole = 'Viewer'
|
let currentRole = 'Viewer'
|
||||||
if (member.schemeAdmin) {
|
if (member.schemeAdmin) {
|
||||||
@ -71,6 +74,14 @@ const UserPermissionsRow = (props: Props): JSX.Element => {
|
|||||||
name={intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})}
|
name={intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})}
|
||||||
onClick={() => props.onUpdateBoardMember(member, 'Viewer')}
|
onClick={() => props.onUpdateBoardMember(member, 'Viewer')}
|
||||||
/>
|
/>
|
||||||
|
{!board.isTemplate &&
|
||||||
|
<Menu.Text
|
||||||
|
id='Commenter'
|
||||||
|
check={true}
|
||||||
|
icon={currentRole === 'Commenter' ? <CheckIcon/> : null}
|
||||||
|
name={intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})}
|
||||||
|
onClick={() => props.onUpdateBoardMember(member, 'Commenter')}
|
||||||
|
/>}
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='Editor'
|
id='Editor'
|
||||||
check={true}
|
check={true}
|
||||||
|
@ -10,7 +10,9 @@ enum Permission {
|
|||||||
ManageBoardRoles = 'manage_board_roles',
|
ManageBoardRoles = 'manage_board_roles',
|
||||||
ManageBoardCards = 'manage_board_cards',
|
ManageBoardCards = 'manage_board_cards',
|
||||||
ManageBoardProperties = 'manage_board_properties',
|
ManageBoardProperties = 'manage_board_properties',
|
||||||
|
CommentBoardCards = 'comment_board_cards',
|
||||||
ViewBoard = 'view_board',
|
ViewBoard = 'view_board',
|
||||||
|
DeleteOthersComments = 'delete_others_comments'
|
||||||
}
|
}
|
||||||
|
|
||||||
class Constants {
|
class Constants {
|
||||||
|
@ -22,8 +22,9 @@ export const useHasPermissions = (teamId: string, boardId: string, permissions:
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const adminPermissions = [Permission.ManageBoardType, Permission.DeleteBoard, Permission.ShareBoard, Permission.ManageBoardRoles]
|
const adminPermissions = [Permission.ManageBoardType, Permission.DeleteBoard, Permission.ShareBoard, Permission.ManageBoardRoles, Permission.DeleteOthersComments]
|
||||||
const editorPermissions = [Permission.ManageBoardCards, Permission.ManageBoardProperties]
|
const editorPermissions = [Permission.ManageBoardCards, Permission.ManageBoardProperties]
|
||||||
|
const commenterPermissions = [Permission.CommentBoardCards]
|
||||||
const viewerPermissions = [Permission.ViewBoard]
|
const viewerPermissions = [Permission.ViewBoard]
|
||||||
|
|
||||||
for (const permission of permissions) {
|
for (const permission of permissions) {
|
||||||
@ -33,6 +34,9 @@ export const useHasPermissions = (teamId: string, boardId: string, permissions:
|
|||||||
if (editorPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor)) {
|
if (editorPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if (commenterPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor || member.schemeCommenter)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (viewerPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor || member.schemeCommenter || member.schemeViewer)) {
|
if (viewerPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor || member.schemeCommenter || member.schemeViewer)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user