-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Cases] Assignees enhancements (#144836)
WIP This PR implements some enhancements for the assignees feature that wasn't completed in 8.5. Issue: #141057 Fixes: #140889 ### List sorting The current user is not brought to the front of lists (only in the popovers). Unknown users are still placed at the end of the list. <details><summary>Current user is sorted like other users</summary> #### Case View Page ![image](https://user-images.githubusercontent.com/56361221/200646181-9744622f-fe11-41c5-97ac-ce7b777d47a1.png) #### Case List Page Avatars ![image](https://user-images.githubusercontent.com/56361221/200646269-b637743f-35f1-48d0-91bd-faee32784613.png) </details> ### Limit assignee selection Leverage the `limit` prop exposed by the `UserProfilesSelectable` here: #144618 <details><summary>Adding limit message</summary> ![image](https://user-images.githubusercontent.com/56361221/200653672-9c195031-3117-4ac9-b6e9-98ac11ee170e.png) </details> ### Show the selected count Show the selected count even when it is zero so the component doesn't jump around. <details><summary>Selected count</summary> #### View case page ![image](https://user-images.githubusercontent.com/56361221/200659972-a6eca466-0d4c-4736-9a2e-62b422f99944.png) #### All cases filter ![image](https://user-images.githubusercontent.com/56361221/200660181-da13092b-6f6a-4b2d-98cd-325ebf8d75b1.png) </details> ### Expandable assignees column Added a button to expand/collapse the assignee avatars column on the all cases list page <details><summary>Cases list page assignees column</summary> ![image](https://user-images.githubusercontent.com/56361221/200891826-08f15531-3a47-40c1-9cc6-12558b645083.png) ![image](https://user-images.githubusercontent.com/56361221/200892014-92cd3142-15d0-4250-b83e-b32b1c9dd03f.png) </details> Co-authored-by: Kibana Machine <[email protected]>
- Loading branch information
1 parent
ec849e5
commit 1e77d8d
Showing
15 changed files
with
373 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
x-pack/plugins/cases/public/components/all_cases/assignees_column.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { screen, waitFor } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import type { AppMockRenderer } from '../../common/mock'; | ||
import { createAppMockRenderer } from '../../common/mock'; | ||
import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock'; | ||
import type { AssigneesColumnProps } from './assignees_column'; | ||
import { AssigneesColumn } from './assignees_column'; | ||
|
||
describe('AssigneesColumn', () => { | ||
const defaultProps: AssigneesColumnProps = { | ||
assignees: userProfiles, | ||
userProfiles: userProfilesMap, | ||
compressedDisplayLimit: 2, | ||
}; | ||
|
||
let appMockRender: AppMockRenderer; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
|
||
appMockRender = createAppMockRenderer(); | ||
}); | ||
|
||
it('renders a long dash if the assignees is an empty array', async () => { | ||
const props = { | ||
...defaultProps, | ||
assignees: [], | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect( | ||
screen.queryByTestId('case-table-column-assignee-damaged_raccoon') | ||
).not.toBeInTheDocument(); | ||
expect(screen.queryByTestId('case-table-column-expand-button')).not.toBeInTheDocument(); | ||
// u2014 is the unicode for a long dash | ||
expect(screen.getByText('\u2014')).toBeInTheDocument(); | ||
}); | ||
|
||
it('only renders 2 avatars when the limit is 2', async () => { | ||
const props = { | ||
...defaultProps, | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect(screen.getByTestId('case-table-column-assignee-damaged_raccoon')).toBeInTheDocument(); | ||
expect(screen.getByTestId('case-table-column-assignee-physical_dinosaur')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders all 3 avatars when the limit is 5', async () => { | ||
const props = { | ||
...defaultProps, | ||
compressedDisplayLimit: 5, | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect(screen.getByTestId('case-table-column-assignee-damaged_raccoon')).toBeInTheDocument(); | ||
expect(screen.getByTestId('case-table-column-assignee-physical_dinosaur')).toBeInTheDocument(); | ||
expect(screen.getByTestId('case-table-column-assignee-wet_dingo')).toBeInTheDocument(); | ||
}); | ||
|
||
it('shows the show more avatars button when the limit is 2', async () => { | ||
const props = { | ||
...defaultProps, | ||
compressedDisplayLimit: 2, | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect(screen.getByTestId('case-table-column-expand-button')).toBeInTheDocument(); | ||
expect(screen.getByText('+1 more')).toBeInTheDocument(); | ||
}); | ||
|
||
it('does not show the show more button when the limit is 5', async () => { | ||
const props = { | ||
...defaultProps, | ||
compressedDisplayLimit: 5, | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect(screen.queryByTestId('case-table-column-expand-button')).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('does not show the show more button when the limit is the same number of the assignees', async () => { | ||
const props = { | ||
...defaultProps, | ||
compressedDisplayLimit: userProfiles.length, | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect(screen.queryByTestId('case-table-column-expand-button')).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('displays the show less avatars button when the show more is clicked', async () => { | ||
const props = { | ||
...defaultProps, | ||
compressedDisplayLimit: 2, | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect(screen.queryByTestId('case-table-column-assignee-wet_dingo')).not.toBeInTheDocument(); | ||
|
||
expect(screen.getByTestId('case-table-column-expand-button')).toBeInTheDocument(); | ||
expect(screen.getByText('+1 more')).toBeInTheDocument(); | ||
|
||
userEvent.click(screen.getByTestId('case-table-column-expand-button')); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText('show less')).toBeInTheDocument(); | ||
expect(screen.getByTestId('case-table-column-assignee-wet_dingo')).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('shows more avatars and then hides them when the expand row button is clicked multiple times', async () => { | ||
const props = { | ||
...defaultProps, | ||
compressedDisplayLimit: 2, | ||
}; | ||
|
||
appMockRender.render(<AssigneesColumn {...props} />); | ||
|
||
expect(screen.queryByTestId('case-table-column-assignee-wet_dingo')).not.toBeInTheDocument(); | ||
|
||
expect(screen.getByTestId('case-table-column-expand-button')).toBeInTheDocument(); | ||
expect(screen.getByText('+1 more')).toBeInTheDocument(); | ||
|
||
userEvent.click(screen.getByTestId('case-table-column-expand-button')); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText('show less')).toBeInTheDocument(); | ||
expect(screen.getByTestId('case-table-column-assignee-wet_dingo')).toBeInTheDocument(); | ||
}); | ||
|
||
userEvent.click(screen.getByTestId('case-table-column-expand-button')); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText('+1 more')).toBeInTheDocument(); | ||
expect(screen.queryByTestId('case-table-column-assignee-wet_dingo')).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
97 changes: 97 additions & 0 deletions
97
x-pack/plugins/cases/public/components/all_cases/assignees_column.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React, { useCallback, useMemo, useState } from 'react'; | ||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; | ||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; | ||
import type { Case } from '../../../common/ui/types'; | ||
import { getEmptyTagValue } from '../empty_value'; | ||
import { UserToolTip } from '../user_profiles/user_tooltip'; | ||
import { useAssignees } from '../../containers/user_profiles/use_assignees'; | ||
import { getUsernameDataTestSubj } from '../user_profiles/data_test_subject'; | ||
import { SmallUserAvatar } from '../user_profiles/small_user_avatar'; | ||
import * as i18n from './translations'; | ||
|
||
const COMPRESSED_AVATAR_LIMIT = 3; | ||
|
||
export interface AssigneesColumnProps { | ||
assignees: Case['assignees']; | ||
userProfiles: Map<string, UserProfileWithAvatar>; | ||
compressedDisplayLimit?: number; | ||
} | ||
|
||
const AssigneesColumnComponent: React.FC<AssigneesColumnProps> = ({ | ||
assignees, | ||
userProfiles, | ||
compressedDisplayLimit = COMPRESSED_AVATAR_LIMIT, | ||
}) => { | ||
const [isAvatarListExpanded, setIsAvatarListExpanded] = useState<boolean>(false); | ||
|
||
const { allAssignees } = useAssignees({ | ||
caseAssignees: assignees, | ||
userProfiles, | ||
}); | ||
|
||
const toggleExpandedAvatars = useCallback( | ||
() => setIsAvatarListExpanded((prevState) => !prevState), | ||
[] | ||
); | ||
|
||
const numHiddenAvatars = allAssignees.length - compressedDisplayLimit; | ||
const shouldShowExpandListButton = numHiddenAvatars > 0; | ||
|
||
const limitedAvatars = useMemo( | ||
() => allAssignees.slice(0, compressedDisplayLimit), | ||
[allAssignees, compressedDisplayLimit] | ||
); | ||
|
||
const avatarsToDisplay = useMemo(() => { | ||
if (isAvatarListExpanded || !shouldShowExpandListButton) { | ||
return allAssignees; | ||
} | ||
|
||
return limitedAvatars; | ||
}, [allAssignees, isAvatarListExpanded, limitedAvatars, shouldShowExpandListButton]); | ||
|
||
if (allAssignees.length <= 0) { | ||
return getEmptyTagValue(); | ||
} | ||
|
||
return ( | ||
<EuiFlexGroup gutterSize="xs" data-test-subj="case-table-column-assignee" wrap> | ||
{avatarsToDisplay.map((assignee) => { | ||
const dataTestSubjName = getUsernameDataTestSubj(assignee); | ||
return ( | ||
<EuiFlexItem | ||
grow={false} | ||
key={assignee.uid} | ||
data-test-subj={`case-table-column-assignee-${dataTestSubjName}`} | ||
> | ||
<UserToolTip userInfo={assignee.profile}> | ||
<SmallUserAvatar userInfo={assignee.profile} /> | ||
</UserToolTip> | ||
</EuiFlexItem> | ||
); | ||
})} | ||
|
||
{shouldShowExpandListButton ? ( | ||
<EuiButtonEmpty | ||
size="xs" | ||
data-test-subj="case-table-column-expand-button" | ||
onClick={toggleExpandedAvatars} | ||
style={{ alignSelf: 'center' }} | ||
> | ||
{isAvatarListExpanded ? i18n.SHOW_LESS : i18n.SHOW_MORE(numHiddenAvatars)} | ||
</EuiButtonEmpty> | ||
) : null} | ||
</EuiFlexGroup> | ||
); | ||
}; | ||
|
||
AssigneesColumnComponent.displayName = 'AssigneesColumn'; | ||
|
||
export const AssigneesColumn = React.memo(AssigneesColumnComponent); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.