From 3c8f6324e45d678807cbe313f50c11fdbeb862a3 Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Mon, 3 Oct 2022 10:11:19 -0600 Subject: [PATCH] Gh 3884 Fix Multiperson property (#3888) * retrieve all team members for specific lists * retrieve all team members for specific lists * final fixes * update to use enum * unit test fixes * lint fixes * fix integration test * fixup integration test * fix issues with making board private * update to make webapp and server enums match * fix to add member in correct role * remove unnecessary property, check Open/Private board * cleanup confirm for non admin * some final cleanup * move default to webapp * only allow greater roles in drop down, change property name to minimumRole * update multiperson property to confirm and select same as person property * implement hashing of keys to cause Select to reprocess initial load Co-authored-by: Mattermod --- .../properties/multiperson/multiperson.tsx | 135 ++++++++++++++---- webapp/src/properties/person/person.tsx | 5 +- webapp/src/utils.ts | 9 ++ 3 files changed, 119 insertions(+), 30 deletions(-) diff --git a/webapp/src/properties/multiperson/multiperson.tsx b/webapp/src/properties/multiperson/multiperson.tsx index 9f14339cc..4534007d7 100644 --- a/webapp/src/properties/multiperson/multiperson.tsx +++ b/webapp/src/properties/multiperson/multiperson.tsx @@ -1,8 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback} from 'react' -import Select from 'react-select' +import React, {useCallback, useState} from 'react' +import {useIntl} from 'react-intl' +import Select from 'react-select/async' import {CSSObject} from '@emotion/serialize' import {getSelectBaseStyle} from '../../theme' @@ -11,10 +12,16 @@ import {Utils} from '../../utils' import mutator from '../../mutator' import {useAppSelector} from '../../store/hooks' import {getBoardUsers, getBoardUsersList} from '../../store/users' +import {BoardMember, BoardTypeOpen, MemberRole} from '../../blocks/board' import {PropertyProps} from '../types' import {ClientConfig} from '../../config/clientConfig' import {getClientConfig} from '../../store/clientConfig' +import {useHasPermissions} from '../../hooks/permissions' +import {Permission} from '../../constants' +import client from '../../octoClient' +import ConfirmAddUserForNotifications from '../../components/confirmAddUserForNotifications' +import GuestBadge from '../../widgets/guestBadge' import './multiperson.scss' @@ -52,12 +59,21 @@ const selectStyles = { }), } -const MultiPerson = (props: PropertyProps) => { +const MultiPerson = (props: PropertyProps): JSX.Element => { const {card, board, propertyTemplate, propertyValue, readOnly} = props + const [confirmAddUser, setConfirmAddUser] = useState(null) const clientConfig = useAppSelector(getClientConfig) + const intl = useIntl() + const boardUsersById = useAppSelector<{[key: string]: IUser}>(getBoardUsers) const boardUsers = useAppSelector(getBoardUsersList) + const boardUsersKey = Object.keys(boardUsersById) ? Utils.hashCode(JSON.stringify(Object.keys(boardUsersById))) : 0 + + const allowManageBoardRoles = useHasPermissions(board.teamId, board.id, [Permission.ManageBoardRoles]) + const allowAddUsers = allowManageBoardRoles || board.type === BoardTypeOpen + + const onChange = useCallback((newValue) => mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValue), [board.id, card, propertyTemplate.id]) const formatOptionLabel = (user: any): JSX.Element => { if (!user) { @@ -80,12 +96,11 @@ const MultiPerson = (props: PropertyProps) => { /> )} {Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)} + ) } - const onChange = useCallback((newValue) => mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValue), [board.id, card, propertyTemplate.id]) - let users: IUser[] = [] if (typeof propertyValue === 'string' && propertyValue !== '') { @@ -94,6 +109,49 @@ const MultiPerson = (props: PropertyProps) => { users = propertyValue.map((id) => boardUsersById[id]) } + const addUser = useCallback(async (userId: string, role: string) => { + const minimumRole = role || MemberRole.Viewer + const newMember = { + boardId: board.id, + userId, + roles: role, + schemeEditor: minimumRole === MemberRole.Editor, + schemeCommenter: minimumRole === MemberRole.Editor || minimumRole === MemberRole.Commenter, + schemeViewer: minimumRole === MemberRole.Editor || minimumRole === MemberRole.Commenter || minimumRole === MemberRole.Viewer, + } as BoardMember + + setConfirmAddUser(null) + await mutator.createBoardMember(newMember) + + if (users) { + const userIds = users.map((a) => a.id) + await mutator.changePropertyValue(board.id, card, propertyTemplate.id, [...userIds, newMember.userId]) + } else { + await mutator.changePropertyValue(board.id, card, propertyTemplate.id, newMember.userId) + } + }, [board, card, propertyTemplate, users]) + + const loadOptions = useCallback(async (value: string) => { + if (!allowAddUsers) { + return boardUsers.filter((u) => u.username.toLowerCase().includes(value.toLowerCase())) + } + const excludeBots = true + const allUsers = await client.searchTeamUsers(value, excludeBots) + const usersInsideBoard: IUser[] = [] + const usersOutsideBoard: IUser[] = [] + for (const u of allUsers) { + if (boardUsersById[u.id]) { + usersInsideBoard.push(u) + } else { + usersOutsideBoard.push(u) + } + } + return [ + {label: intl.formatMessage({id: 'PersonProperty.board-members', defaultMessage: 'Board members'}), options: usersInsideBoard}, + {label: intl.formatMessage({id: 'PersonProperty.non-board-members', defaultMessage: 'Not board members'}), options: usersOutsideBoard}, + ] + }, [boardUsers, allowAddUsers, boardUsersById]) + if (readOnly) { return (
@@ -103,29 +161,50 @@ const MultiPerson = (props: PropertyProps) => { } return ( - o.username} + getOptionValue={(a: IUser) => a.id} + value={users} + onChange={(items, action) => { + if (action.action === 'select-option') { + const confirmedIds: string[] = [] + items.forEach((item) => { + if (boardUsersById[item.id]) { + confirmedIds.push(item.id) + } else { + setConfirmAddUser(item) + } + }) + onChange(confirmedIds) + } else if (action.action === 'clear') { + onChange([]) + } else if (action.action === 'remove-value') { + onChange(items.filter((a) => a.id !== action.removedValue.id).map((b) => b.id) || []) + } + }} + /> + ) } diff --git a/webapp/src/properties/person/person.tsx b/webapp/src/properties/person/person.tsx index 5dc6e00c2..508a9546f 100644 --- a/webapp/src/properties/person/person.tsx +++ b/webapp/src/properties/person/person.tsx @@ -63,7 +63,9 @@ const Person = (props: PropertyProps): JSX.Element => { const {card, board, propertyTemplate, propertyValue, readOnly} = props const [confirmAddUser, setConfirmAddUser] = useState(null) + const boardUsers = useAppSelector(getBoardUsersList) const boardUsersById = useAppSelector<{[key: string]: IUser}>(getBoardUsers) + const boardUsersKey = Object.keys(boardUsersById) ? Utils.hashCode(JSON.stringify(Object.keys(boardUsersById))) : 0 const onChange = useCallback((newValue) => mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValue), [board.id, card, propertyTemplate.id]) const me: IUser = boardUsersById[propertyValue as string] @@ -108,8 +110,6 @@ const Person = (props: PropertyProps): JSX.Element => { mutator.updateBoardMember(newMember, {...newMember, schemeAdmin: false, schemeEditor: true, schemeCommenter: true, schemeViewer: true}) }, [board, card, propertyTemplate]) - const boardUsers = useAppSelector(getBoardUsersList) - const allowManageBoardRoles = useHasPermissions(board.teamId, board.id, [Permission.ManageBoardRoles]) const allowAddUsers = allowManageBoardRoles || board.type === BoardTypeOpen @@ -153,6 +153,7 @@ const Person = (props: PropertyProps): JSX.Element => { onClose={() => setConfirmAddUser(null)} />}