1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-03-26 20:53:55 +02:00

Allow to confirm add users on mention or assign to person field (#3724)

* Allow to confirm add users on mention or assign to person field

* Improving the confirm add user for notification modal style

* Add confirmation add user modal tests

* Fixing tests

* Fixing styles

* Adding missed snapshots file

* Fixing tests

* Fixing other tiny errors

* Fixing tests

* Fixing tests
This commit is contained in:
Jesús Espino 2022-09-07 08:20:10 +02:00 committed by GitHub
parent ad3b8fd454
commit 95c69cc46b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 523 additions and 29 deletions

View File

@ -0,0 +1,164 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/components/confirmAddUserForNotifications should match snapshot 1`] = `
<div>
<div
class="Dialog dialog-back confirmation-dialog-box"
>
<div
class="backdrop"
/>
<div
class="wrapper"
>
<div
class="dialog"
role="dialog"
>
<div
class="toolbar"
>
<button
aria-label="Close dialog"
class="IconButton dialog__close size--medium"
title="Close dialog"
type="button"
>
<i
class="CompassIcon icon-close CloseIcon"
/>
</button>
<div
class="toolbar--right"
/>
</div>
<div
class="box-area"
title="Confirmation Dialog Box"
>
<h3
class="text-heading5"
>
Add fake-username to board
</h3>
<div
class="sub-text"
>
<div
class="ConfirmAddUserForNotifications"
>
<p>
fake-username is not a member of the board, and will not received any notifications about it.
</p>
<p>
Do you want to add fake-username to the board?
</p>
<div
class="permissions-title"
>
<label>
Permissions
</label>
</div>
<div
class="select css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class=" css-1s2u09g-control"
>
<div
class=" css-319lph-ValueContainer"
>
<div
class=" css-qc6sy-singleValue"
>
Editor
</div>
<div
class=" css-6j8wv5-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class=""
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class=" css-1hb7zxy-IndicatorsContainer"
>
<span
class=" css-1okebmr-indicatorSeparator"
/>
<div
aria-hidden="true"
class=" css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="action-buttons"
>
<button
class="Button emphasis--tertiary size--medium"
title="Cancel"
type="button"
>
<span>
Cancel
</span>
</button>
<button
class="Button filled size--medium"
title="Add to board"
type="submit"
>
<span>
Add to board
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -10,18 +10,22 @@ import {Provider as ReduxProvider} from 'react-redux'
import {mocked} from 'jest-mock'
import mutator from '../mutator'
import {IUser} from '../user'
import {Utils} from '../utils'
import octoClient from '../octoClient'
import {TestBlockFactory} from '../test/testBlockFactory'
import {mockDOM, mockStateStore, wrapDNDIntl} from '../testUtils'
import CardDialog from './cardDialog'
jest.mock('../mutator')
jest.mock('../octoClient')
jest.mock('../utils')
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
const mockedUtils = mocked(Utils, true)
const mockedMutator = mocked(mutator, true)
const mockedOctoClient = mocked(octoClient, true)
mockedUtils.createGuid.mockReturnValue('test-id')
mockedUtils.isFocalboardPlugin.mockReturnValue(true)
@ -84,6 +88,8 @@ describe('components/cardDialog', () => {
blockSubscriptions: [],
},
}
mockedOctoClient.searchTeamUsers.mockResolvedValue(Object.values(state.users.boardUsers) as IUser[])
const store = mockStateStore([], state)
beforeEach(() => {
jest.clearAllMocks()

View File

@ -10,6 +10,8 @@ import {mockDOM, mockStateStore, wrapDNDIntl} from '../testUtils'
import {TestBlockFactory} from '../test/testBlockFactory'
import {IPropertyTemplate} from '../blocks/board'
import {Utils} from '../utils'
import {IUser} from '../user'
import octoClient from '../octoClient'
import Mutator from '../mutator'
import {Constants} from '../constants'
@ -26,11 +28,13 @@ jest.mock('react-router-dom', () => {
}
})
jest.mock('../utils')
jest.mock('../octoClient')
jest.mock('../mutator')
jest.mock('../telemetry/telemetryClient')
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
const mockedUtils = mocked(Utils, true)
const mockedMutator = mocked(Mutator, true)
const mockedOctoClient= mocked(octoClient, true)
mockedUtils.createGuid.mockReturnValue('test-id')
mockedUtils.generateClassName = jest.requireActual('../utils').Utils.generateClassName
describe('components/centerPanel', () => {
@ -139,6 +143,7 @@ describe('components/centerPanel', () => {
},
},
}
mockedOctoClient.searchTeamUsers.mockResolvedValue(Object.values(state.users.boardUsers) as IUser[])
const store = mockStateStore([], state)
beforeAll(() => {
mockDOM()

View File

@ -0,0 +1,32 @@
@import '../styles/z-index';
.ConfirmAddUserForNotifications {
display: flex;
flex-direction: column;
align-items: center;
.select {
text-align: left;
width: 250px;
margin-top: 10px;
}
.permissions-title {
width: 250px;
position: relative;
top: -3px;
left: 6px;
height: 0;
text-align: left;
font-size: 12px;
label {
@include z-index(modal-permission-label);
position: absolute;
padding: 0 5px;
margin: 0;
background-color: rgb(var(--center-channel-bg-rgb));
color: rgba(var(--center-channel-color-rgb), 0.8);
}
}
}

View File

@ -0,0 +1,58 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import '@testing-library/jest-dom'
import {render} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import {wrapDNDIntl} from '../testUtils'
import {IUser} from '../user'
import ConfirmAddUserForNotifications from './confirmAddUserForNotifications'
describe('/components/confirmAddUserForNotifications', () => {
it('should match snapshot', async () => {
const result = render(
wrapDNDIntl(
<ConfirmAddUserForNotifications
user={{id: 'fake-user-id', username: 'fake-username'} as IUser}
onConfirm={jest.fn()}
onClose={jest.fn()}
/>,
),
)
expect(result.container).toMatchSnapshot()
})
it('confirm button click, run onConfirm Function once', () => {
const onConfirm = jest.fn()
const result = render(
wrapDNDIntl(
<ConfirmAddUserForNotifications
user={{id: 'fake-user-id', username: 'fake-username'} as IUser}
onConfirm={onConfirm}
onClose={jest.fn()}
/>,
),
)
userEvent.click(result.getByTitle('Add to board'))
expect(onConfirm).toBeCalledTimes(1)
})
it('cancel button click runs onClose function', () => {
const onClose = jest.fn()
const result = render(
wrapDNDIntl(
<ConfirmAddUserForNotifications
user={{id: 'fake-user-id', username: 'fake-username'} as IUser}
onConfirm={jest.fn()}
onClose={onClose}
/>,
),
)
userEvent.click(result.getByTitle('Cancel'))
expect(onClose).toBeCalledTimes(1)
})
})

View File

@ -0,0 +1,87 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState, useRef} from 'react'
import Select from 'react-select'
import {useIntl, FormattedMessage} from 'react-intl'
import {IUser} from '../user'
import ConfirmationDialog from './confirmationDialogBox'
import './confirmAddUserForNotifications.scss'
type Props = {
user: IUser,
onConfirm: (userId: string, role: string) => void
onClose: () => void
}
const ConfirmAddUserForNotifications = (props: Props): JSX.Element => {
const {user} = props
const [newUserRole, setNewUserRole] = useState('Editor')
const userRole = useRef<string>('Editor')
const intl = useIntl()
const roleOptions = [
{id: 'Admin', label: intl.formatMessage({id:'PersonProperty.add-user-admin-role', defaultMessage:'Admin'})},
{id: 'Editor', label: intl.formatMessage({id:'PersonProperty.add-user-editor-role', defaultMessage:'Editor'})},
{id: 'Commenter', label: intl.formatMessage({id:'PersonProperty.add-user-commenter-role', defaultMessage:'Commenter'})},
{id: 'Viewer', label: intl.formatMessage({id:'PersonProperty.add-user-viewer-role', defaultMessage:'Viewer'})},
]
const subText = (
<div className='ConfirmAddUserForNotifications'>
<p>
<FormattedMessage
id='person.add-user-to-board-warning'
defaultMessage='{username} is not a member of the board, and will not received any notifications about it.'
values={{username: props.user.username}}
/>
</p>
<p>
<FormattedMessage
id='person.add-user-to-board-question'
defaultMessage='Do you want to add {username} to the board?'
values={{username: props.user.username}}
/>
</p>
<div className='permissions-title'>
<label>
<FormattedMessage
id='person.add-user-to-board-permissions'
defaultMessage='Permissions'
/>
</label>
</div>
<Select
className='select'
getOptionLabel={(o: {id: string, label: string}) => o.label}
getOptionValue={(o: {id: string, label: string}) => o.id}
styles={{ menuPortal: (base) => ({ ...base, zIndex: 9999 }) }}
menuPortalTarget={document.body}
options={roleOptions}
onChange={(option) => {
setNewUserRole(option?.id || 'Editor')
userRole.current = option?.id || 'Editor'
}}
value={roleOptions.find((o) => o.id === newUserRole)}
/>
</div>
)
return (
<ConfirmationDialog
dialogBox={{
heading: intl.formatMessage({id: 'person.add-user-to-board', defaultMessage: 'Add {username} to board'}, {username: props.user.username}),
subText,
confirmButtonText: intl.formatMessage({id: 'person.add-user-to-board-confirm-button', defaultMessage: 'Add to board'}),
onConfirm: () => props.onConfirm(user.id, userRole.current),
onClose: props.onClose,
}}
/>
)
}
export default ConfirmAddUserForNotifications

View File

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactElement} from 'react'
import {FormattedMessage} from 'react-intl'
import {EntryComponentProps} from '@draft-js-plugins/mention/lib/MentionSuggestions/Entry/Entry'
import GuestBadge from '../../../widgets/guestBadge'
@ -34,6 +35,13 @@ const Entry = (props: EntryComponentProps): ReactElement => {
<div className={theme?.mentionSuggestionsEntryText}>
{mention.displayName}
</div>
{!mention.isBoardMember &&
<div className={theme?.mentionSuggestionsEntryText}>
<FormattedMessage
id='MentionSuggestion.is-not-board-member'
defaultMessage='(not board member)'
/>
</div>}
</div>
</div>
)

View File

@ -18,6 +18,12 @@ import {useAppSelector} from '../../store/hooks'
import {IUser} from '../../user'
import {getBoardUsersList, getMe} from '../../store/users'
import createLiveMarkdownPlugin from '../live-markdown-plugin/liveMarkdownPlugin'
import {useHasPermissions} from '../../hooks/permissions'
import {Permission} from '../../constants'
import {BoardMember} from '../../blocks/board'
import mutator from '../../mutator'
import ConfirmAddUserForNotifications from '../confirmAddUserForNotifications'
import RootPortal from '../rootPortal'
import './markdownEditorInput.scss'
@ -34,11 +40,13 @@ import Entry from './entryComponent/entryComponent'
const imageURLForUser = (window as any).Components?.imageURLForUser
type MentionUser = {
user: IUser,
name: string
avatar: string
is_bot: boolean
is_guest: boolean
displayName: string
isBoardMember: boolean
}
type Props = {
@ -56,6 +64,8 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
const board = useAppSelector(getCurrentBoard)
const clientConfig = useAppSelector<ClientConfig>(getClientConfig)
const ref = useRef<Editor>(null)
const allowAddUsers = useHasPermissions(board.teamId, board.id, [Permission.ManageBoardRoles])
const [confirmAddUser, setConfirmAddUser] = useState<IUser|null>(null)
const me = useAppSelector<IUser|null>(getMe)
const [suggestions, setSuggestions] = useState<Array<MentionUser>>([])
@ -63,7 +73,7 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
const loadSuggestions = async (term: string) => {
let users: Array<IUser>
if (!me?.is_guest && (board && board.type === BoardTypeOpen)) {
if (!me?.is_guest && (allowAddUsers || (board && board.type === BoardTypeOpen))) {
users = await octoClient.searchTeamUsers(term)
} else {
users = boardUsers
@ -83,8 +93,10 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
avatar: `${imageURLForUser ? imageURLForUser(user.id) : ''}`,
is_bot: user.is_bot,
is_guest: user.is_guest,
displayName: Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)}
))
displayName: Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay),
isBoardMember: Boolean(boardUsers.find((u) => u.id === user.id)),
user: user,
}))
setSuggestions(mentions)
}
@ -96,7 +108,6 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
loadSuggestions('')
}, [])
const generateEditorState = (text?: string) => {
const state = EditorState.createWithContent(ContentState.createFromText(text || ''))
return EditorState.moveSelectionToEnd(state)
@ -104,6 +115,39 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
const [editorState, setEditorState] = useState(() => generateEditorState(initialText))
const addUser = useCallback(async (userId: string, role: string) => {
const newMember = {
boardId: board.id,
userId: userId,
roles: role,
schemeAdmin: role === 'Admin',
schemeEditor: role === 'Admin' || role === 'Editor',
schemeCommenter: role === 'Admin' || role === 'Editor' || role === 'Commenter',
schemeViewer: role === 'Admin' || role === 'Editor' || role === 'Commenter' || role === 'Viewer',
} as BoardMember
setConfirmAddUser(null)
setEditorState(EditorState.moveSelectionToEnd(editorState))
ref.current?.focus()
await mutator.createBoardMember(board.id, newMember.userId)
mutator.updateBoardMember(newMember, {...newMember, schemeAdmin: false, schemeEditor: true, schemeCommenter: true, schemeViewer: true})
}, [board, editorState])
const [initialTextCache, setInitialTextCache] = useState<string | undefined>(initialText)
// avoiding stale closure
useEffect(() => {
// only change editor state when initialText actually changes from one defined value to another.
// This is needed to make the mentions plugin work. For some reason, if we don't check
// for this if condition here, mentions don't work. I suspect it's because without
// the in condition, we're changing editor state twice during component initialization
// and for some reason it causes mentions to not show up.
if (initialText && initialText !== initialTextCache) {
setEditorState(generateEditorState(initialText || ''))
setInitialTextCache(initialText)
}
}, [initialText])
const [isMentionPopoverOpen, setIsMentionPopoverOpen] = useState(false)
const [isEmojiPopoverOpen, setIsEmojiPopoverOpen] = useState(false)
@ -177,6 +221,9 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
}, [])
const onEditorStateBlur = useCallback(() => {
if (confirmAddUser) {
return
}
const text = editorState.getCurrentContent().getPlainText()
onBlur && onBlur(text)
}, [editorState, onBlur])
@ -220,11 +267,29 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
suggestions={suggestions}
onSearchChange={onSearchChange}
entryComponent={Entry}
onAddMention={(mention) => {
if (mention.isBoardMember) {
return
}
setConfirmAddUser(mention.user)
}}
/>
<EmojiSuggestions
onOpen={onEmojiPopoverOpen}
onClose={onEmojiPopoverClose}
/>
{confirmAddUser &&
<RootPortal>
<ConfirmAddUserForNotifications
user={confirmAddUser}
onConfirm={addUser}
onClose={() => {
setConfirmAddUser(null)
setEditorState(EditorState.moveSelectionToEnd(editorState))
ref.current?.focus()
}}
/>
</RootPortal>}
</div>
)
}

View File

@ -11,6 +11,7 @@ import userEvent from '@testing-library/user-event'
import thunk from 'redux-thunk'
import {IUser} from '../user'
import octoClient from '../octoClient'
import {TestBlockFactory} from '../test/testBlockFactory'
import {mockDOM, mockMatchMedia, mockStateStore, wrapDNDIntl} from '../testUtils'
import {Constants} from '../constants'
@ -21,8 +22,10 @@ import Workspace from './workspace'
Object.defineProperty(Constants, 'versionString', {value: '1.0.0'})
jest.useFakeTimers()
jest.mock('../utils')
jest.mock('../octoClient')
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
const mockedUtils = mocked(Utils, true)
const mockedOctoClient= mocked(octoClient, true)
const board = TestBlockFactory.createBoard()
board.id = 'board1'
board.teamId = 'team-id'
@ -170,6 +173,7 @@ describe('src/components/workspace', () => {
],
},
}
mockedOctoClient.searchTeamUsers.mockResolvedValue(Object.values(state.users.boardUsers))
const store = mockStateStore([thunk], state)
beforeAll(() => {
mockDOM()

View File

@ -85,7 +85,7 @@ describe('properties/createdBy', () => {
</ReduxProvider>
)
const {container} = render(component)
const {container} = render(wrapIntl(component))
expect(container).toMatchSnapshot()
})
})

View File

@ -1,18 +1,24 @@
// 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 Select from 'react-select/async'
import {useIntl} from 'react-intl'
import {CSSObject} from '@emotion/serialize'
import {Utils} from '../../utils'
import {IUser} from '../../user'
import {getBoardUsersList, getBoardUsers} from '../../store/users'
import {BoardMember} from '../../blocks/board'
import {useAppSelector} from '../../store/hooks'
import mutator from '../../mutator'
import {getSelectBaseStyle} from '../../theme'
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 {PropertyProps} from '../types'
@ -55,6 +61,7 @@ const selectStyles = {
const Person = (props: PropertyProps): JSX.Element => {
const {card, board, propertyTemplate, propertyValue, readOnly} = props
const [confirmAddUser, setConfirmAddUser] = useState<IUser|null>(null)
const boardUsersById = useAppSelector<{[key:string]: IUser}>(getBoardUsers)
const onChange = useCallback((newValue) => mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValue), [board.id, card, propertyTemplate.id])
@ -62,6 +69,7 @@ const Person = (props: PropertyProps): JSX.Element => {
const me: IUser = boardUsersById[propertyValue as string]
const clientConfig = useAppSelector<ClientConfig>(getClientConfig)
const intl = useIntl()
const formatOptionLabel = (user: IUser) => {
let profileImg
@ -83,6 +91,23 @@ const Person = (props: PropertyProps): JSX.Element => {
)
}
const addUser = useCallback(async (userId: string, role: string) => {
const newMember = {
boardId: board.id,
userId: userId,
roles: role,
schemeAdmin: role === 'Admin',
schemeEditor: role === 'Admin' || role === 'Editor',
schemeCommenter: role === 'Admin' || role === 'Editor' || role === 'Commenter',
schemeViewer: role === 'Admin' || role === 'Editor' || role === 'Commenter' || role === 'Viewer',
} as BoardMember
setConfirmAddUser(null)
await mutator.createBoardMember(board.id, newMember.userId)
await mutator.changePropertyValue(board.id, card, propertyTemplate.id, newMember.userId)
mutator.updateBoardMember(newMember, {...newMember, schemeAdmin: false, schemeEditor: true, schemeCommenter: true, schemeViewer: true})
}, [board, card, propertyTemplate])
if (readOnly) {
return (
<div className={`Person ${props.property.valueClassName(true)}`}>
@ -93,28 +118,66 @@ const Person = (props: PropertyProps): JSX.Element => {
const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)
const allowAddUsers = useHasPermissions(board.teamId, board.id, [Permission.ManageBoardRoles])
const loadOptions = useCallback(async (value: string) => {
if (value === '') {
return boardUsers
}
if (!allowAddUsers) {
return boardUsers.filter((u) => u.username.toLowerCase().includes(value.toLowerCase()))
}
const allUsers = await client.searchTeamUsers(value)
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])
return (
<Select
options={boardUsers}
isSearchable={true}
isClearable={true}
backspaceRemovesValue={true}
className={`Person ${props.property.valueClassName(props.readOnly)}`}
classNamePrefix={'react-select'}
formatOptionLabel={formatOptionLabel}
styles={selectStyles}
placeholder={'Empty'}
getOptionLabel={(o: IUser) => o.username}
getOptionValue={(a: IUser) => a.id}
value={boardUsersById[propertyValue as string] || null}
onChange={(item, action) => {
if (action.action === 'select-option') {
onChange(item?.id || '')
} else if (action.action === 'clear') {
onChange('')
}
}}
/>
<>
{confirmAddUser &&
<ConfirmAddUserForNotifications
user={confirmAddUser}
onConfirm={addUser}
onClose={() => setConfirmAddUser(null)}
/>}
<Select
loadOptions={loadOptions}
defaultOptions={boardUsers}
isSearchable={true}
isClearable={true}
backspaceRemovesValue={true}
className={`Person ${props.property.valueClassName(props.readOnly)}`}
classNamePrefix={'react-select'}
formatOptionLabel={formatOptionLabel}
styles={selectStyles}
placeholder={'Empty'}
getOptionLabel={(o: IUser) => o.username}
getOptionValue={(a: IUser) => a.id}
value={boardUsersById[propertyValue as string] || null}
onChange={(item, action) => {
if (action.action === 'select-option') {
if (!boardUsersById[item?.id || '']) {
setConfirmAddUser(item)
} else {
onChange(item?.id || '')
}
} else if (action.action === 'clear') {
onChange('')
}
}}
/>
</>
)
}

View File

@ -9,6 +9,7 @@ import configureStore from 'redux-mock-store'
import {createCard} from '../../blocks/card'
import {IUser} from '../../user'
import {wrapIntl} from '../../testUtils'
import {createBoard, IPropertyTemplate} from '../../blocks/board'
@ -64,7 +65,7 @@ describe('properties/updatedBy', () => {
</ReduxProvider>
)
const {container} = render(component)
const {container} = render(wrapIntl(component))
expect(container).toMatchSnapshot()
})
})

View File

@ -9,6 +9,7 @@
$z-index: (
// key: value
modal-permissions-label: 1000,
board-template-selector: 1000,
notification-box: 1000,
calculation-dropdown: 999,