Skip to content

Commit

Permalink
[8.12] [Cases] Filter Overflow When To Much Filters Active (#172860) (#…
Browse files Browse the repository at this point in the history
…173045)

# Backport

This will backport the following commits from `main` to `8.12`:
- [[Cases] Filter Overflow When To Much Filters Active
(#172860)](#172860)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Julian
Gernun","email":"[email protected]"},"sourceCommit":{"committedDate":"2023-12-11T12:33:34Z","message":"[Cases]
Filter Overflow When To Much Filters Active (#172860)\n\nCloses
https://github.com/elastic/kibana/issues/172855\r\n\r\n\r\n![Screenshot](https://github.com/elastic/kibana/assets/17549662/19537708-5fda-4773-a85c-f21344cab4cc)","sha":"f7b0e49e2f81cf4e995f6877731281db01566909","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:ResponseOps","Feature:Cases","v8.12.0","v8.13.0"],"number":172860,"url":"https://github.com/elastic/kibana/pull/172860","mergeCommit":{"message":"[Cases]
Filter Overflow When To Much Filters Active (#172860)\n\nCloses
https://github.com/elastic/kibana/issues/172855\r\n\r\n\r\n![Screenshot](https://github.com/elastic/kibana/assets/17549662/19537708-5fda-4773-a85c-f21344cab4cc)","sha":"f7b0e49e2f81cf4e995f6877731281db01566909"}},"sourceBranch":"main","suggestedTargetBranches":["8.12"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/172860","number":172860,"mergeCommit":{"message":"[Cases]
Filter Overflow When To Much Filters Active (#172860)\n\nCloses
https://github.com/elastic/kibana/issues/172855\r\n\r\n\r\n![Screenshot](https://github.com/elastic/kibana/assets/17549662/19537708-5fda-4773-a85c-f21344cab4cc)","sha":"f7b0e49e2f81cf4e995f6877731281db01566909"}}]}]
BACKPORT-->

Co-authored-by: Julian Gernun <[email protected]>
  • Loading branch information
kibanamachine and jcger authored Dec 11, 2023
1 parent cd879cb commit 6333a72
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { EuiFilterButton } from '@elastic/eui';
import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui';
import { UserProfilesPopover } from '@kbn/user-profile-components';
import { isEmpty } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
Expand Down Expand Up @@ -91,44 +91,46 @@ const AssigneesFilterPopoverComponent: React.FC<AssigneesFilterPopoverProps> = (
const isLoadingData = isLoading || isLoadingSuggest;

return (
<UserProfilesPopover
isOpen={isPopoverOpen}
closePopover={togglePopover}
panelStyle={{
minWidth: 520,
}}
button={
<EuiFilterButton
data-test-subj="options-filter-popover-button-assignees"
iconType="arrowDown"
onClick={togglePopover}
isLoading={isLoadingData}
isSelected={isPopoverOpen}
hasActiveFilters={selectedAssignees.length > 0}
numActiveFilters={selectedAssignees.length}
aria-label={i18n.FILTER_ASSIGNEES_ARIA_LABEL}
>
{i18n.ASSIGNEES}
</EuiFilterButton>
}
selectableProps={{
onChange,
onSearchChange,
selectedStatusMessage,
options: searchResultProfiles,
selectedOptions: selectedAssignees,
isLoading: isLoadingData || isUserTyping,
height: 'full',
searchPlaceholder: i18n.SEARCH_USERS,
clearButtonLabel: i18n.CLEAR_FILTERS,
emptyMessage: <EmptyMessage />,
noMatchesMessage: !isUserTyping && !isLoadingData ? <NoMatches /> : <EmptyMessage />,
limit: MAX_ASSIGNEES_FILTER_LENGTH,
limitReachedMessage,
singleSelection: false,
nullOptionLabel: i18n.NO_ASSIGNEES,
}}
/>
<EuiFilterGroup>
<UserProfilesPopover
isOpen={isPopoverOpen}
closePopover={togglePopover}
panelStyle={{
minWidth: 520,
}}
button={
<EuiFilterButton
data-test-subj="options-filter-popover-button-assignees"
iconType="arrowDown"
onClick={togglePopover}
isLoading={isLoadingData}
isSelected={isPopoverOpen}
hasActiveFilters={selectedAssignees.length > 0}
numActiveFilters={selectedAssignees.length}
aria-label={i18n.FILTER_ASSIGNEES_ARIA_LABEL}
>
{i18n.ASSIGNEES}
</EuiFilterButton>
}
selectableProps={{
onChange,
onSearchChange,
selectedStatusMessage,
options: searchResultProfiles,
selectedOptions: selectedAssignees,
isLoading: isLoadingData || isUserTyping,
height: 'full',
searchPlaceholder: i18n.SEARCH_USERS,
clearButtonLabel: i18n.CLEAR_FILTERS,
emptyMessage: <EmptyMessage />,
noMatchesMessage: !isUserTyping && !isLoadingData ? <NoMatches /> : <EmptyMessage />,
limit: MAX_ASSIGNEES_FILTER_LENGTH,
limitReachedMessage,
singleSelection: false,
nullOptionLabel: i18n.NO_ASSIGNEES,
}}
/>
</EuiFilterGroup>
);
};
AssigneesFilterPopoverComponent.displayName = 'AssigneesFilterPopover';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
EuiTextColor,
EuiSpacer,
useEuiTheme,
EuiFilterGroup,
EuiText,
} from '@elastic/eui';
import { isEqual } from 'lodash/fp';
import * as i18n from './translations';
Expand Down Expand Up @@ -63,16 +65,17 @@ const getEuiSelectableCheckedOptions = <T extends string, K extends string>(
) => options.filter((option) => option.checked === 'on') as Array<FilterOption<T, K>>;

interface UseFilterParams<T extends string, K extends string = string> {
buttonLabel?: string;
buttonIconType?: string;
buttonLabel?: string;
hideActiveOptionsNumber?: boolean;
id: string;
limit?: number;
limitReachedMessage?: string;
onChange: (params: { filterId: string; selectedOptionKeys: string[] }) => void;
options: Array<FilterOption<T, K>>;
selectedOptionKeys?: string[];
renderOption?: (option: FilterOption<T, K>) => React.ReactNode;
selectedOptionKeys?: string[];
transparentBackground?: boolean;
}
export const MultiSelectFilter = <T extends string, K extends string = string>({
buttonLabel,
Expand All @@ -85,6 +88,7 @@ export const MultiSelectFilter = <T extends string, K extends string = string>({
options: rawOptions,
selectedOptionKeys = [],
renderOption,
transparentBackground,
}: UseFilterParams<T, K>) => {
const { euiTheme } = useEuiTheme();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
Expand Down Expand Up @@ -118,74 +122,85 @@ export const MultiSelectFilter = <T extends string, K extends string = string>({
};

return (
<EuiPopover
ownFocus
button={
<EuiFilterButton
data-test-subj={`options-filter-popover-button-${id}`}
iconType={buttonIconType || 'arrowDown'}
onClick={toggleIsPopoverOpen}
isSelected={isPopoverOpen}
numFilters={showActiveOptionsNumber ? options.length : undefined}
hasActiveFilters={showActiveOptionsNumber ? selectedOptionKeys.length > 0 : undefined}
numActiveFilters={showActiveOptionsNumber ? selectedOptionKeys.length : undefined}
aria-label={buttonLabel}
>
{buttonLabel}
</EuiFilterButton>
}
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
panelPaddingSize="none"
repositionOnScroll
<EuiFilterGroup
css={css`
${transparentBackground && 'background-color: transparent;'};
`}
>
{isInvalid && (
<>
<EuiHorizontalRule margin="none" />
<EuiCallOut
title={limitReachedMessage}
color="warning"
size="s"
data-test-subj="maximum-length-warning"
/>
<EuiHorizontalRule margin="none" />
</>
)}
<EuiSelectable<FilterOption<T, K>>
options={options}
searchable
searchProps={{
placeholder: buttonLabel,
compressed: false,
'data-test-subj': `${id}-search-input`,
}}
emptyMessage={i18n.EMPTY_FILTER_MESSAGE}
onChange={_onChange}
singleSelection={false}
renderOption={renderOption}
>
{(list, search) => (
<div
<EuiPopover
ownFocus
button={
<EuiFilterButton
css={css`
width: 400px;
max-width: 186px;
`}
data-test-subj={`options-filter-popover-button-${id}`}
iconType={buttonIconType || 'arrowDown'}
onClick={toggleIsPopoverOpen}
isSelected={isPopoverOpen}
numFilters={showActiveOptionsNumber ? options.length : undefined}
hasActiveFilters={showActiveOptionsNumber ? selectedOptionKeys.length > 0 : undefined}
numActiveFilters={showActiveOptionsNumber ? selectedOptionKeys.length : undefined}
aria-label={buttonLabel}
>
<EuiPopoverTitle paddingSize="s">{search}</EuiPopoverTitle>
<EuiText size="s" className="eui-textTruncate">
{buttonLabel}
</EuiText>
</EuiFilterButton>
}
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
panelPaddingSize="none"
repositionOnScroll
>
{isInvalid && (
<>
<EuiHorizontalRule margin="none" />
<EuiCallOut
title={limitReachedMessage}
color="warning"
size="s"
data-test-subj="maximum-length-warning"
/>
<EuiHorizontalRule margin="none" />
</>
)}
<EuiSelectable<FilterOption<T, K>>
options={options}
searchable
searchProps={{
placeholder: buttonLabel,
compressed: false,
'data-test-subj': `${id}-search-input`,
}}
emptyMessage={i18n.EMPTY_FILTER_MESSAGE}
onChange={_onChange}
singleSelection={false}
renderOption={renderOption}
>
{(list, search) => (
<div
css={css`
line-height: ${euiTheme.size.xl};
padding-left: ${euiTheme.size.m};
border-bottom: ${euiTheme.border.thin};
width: 400px;
`}
>
<EuiTextColor color="subdued">{i18n.OPTIONS(options.length)}</EuiTextColor>
<EuiPopoverTitle paddingSize="s">{search}</EuiPopoverTitle>
<div
css={css`
line-height: ${euiTheme.size.xl};
padding-left: ${euiTheme.size.m};
border-bottom: ${euiTheme.border.thin};
`}
>
<EuiTextColor color="subdued">{i18n.OPTIONS(options.length)}</EuiTextColor>
</div>
<EuiSpacer size="xs" />
{list}
</div>
<EuiSpacer size="xs" />
{list}
</div>
)}
</EuiSelectable>
</EuiPopover>
)}
</EuiSelectable>
</EuiPopover>
</EuiFilterGroup>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const MoreFiltersSelectable = ({
onChange={onChange}
options={options}
selectedOptionKeys={activeFilters}
transparentBackground={true}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ describe('CasesTableFilters ', () => {
userEvent.click(screen.getByRole('option', { name: 'Toggle' }));
expect(screen.getByRole('button', { name: 'Toggle' })).toBeInTheDocument();

const filterBar = screen.getByTestId('cases-table-filters-group');
const filterBar = screen.getByTestId('cases-table-filters');
const allFilters = within(filterBar).getAllByRole('button');
const orderedFilterLabels = ['Severity', 'Status', 'Tags', 'Categories', 'Toggle', 'More'];
orderedFilterLabels.forEach((label, index) => {
Expand Down Expand Up @@ -587,7 +587,7 @@ describe('CasesTableFilters ', () => {
userEvent.click(screen.getByRole('option', { name: 'Status' }));
expect(screen.queryByRole('button', { name: 'Status' })).not.toBeInTheDocument();

const filterBar = screen.getByTestId('cases-table-filters-group');
const filterBar = screen.getByTestId('cases-table-filters');
const allFilters = within(filterBar).getAllByRole('button');
const orderedFilterLabels = ['Severity', 'Tags', 'Categories', 'More'];
orderedFilterLabels.forEach((label, index) => {
Expand Down Expand Up @@ -673,7 +673,7 @@ describe('CasesTableFilters ', () => {

appMockRender.render(<CasesTableFilters {...props} />);

const filterBar = screen.getByTestId('cases-table-filters-group');
const filterBar = screen.getByTestId('cases-table-filters');
let allFilters: HTMLElement[];
await waitFor(() => {
allFilters = within(filterBar).getAllByRole('button');
Expand Down Expand Up @@ -703,7 +703,7 @@ describe('CasesTableFilters ', () => {

appMockRender.render(<CasesTableFilters {...props} />);

const filterBar = screen.getByTestId('cases-table-filters-group');
const filterBar = screen.getByTestId('cases-table-filters');
let allFilters: HTMLElement[];
await waitFor(() => {
allFilters = within(filterBar).getAllByRole('button');
Expand Down Expand Up @@ -756,7 +756,7 @@ describe('CasesTableFilters ', () => {
it('when a filter is active and isnt last in the list, it should move the filter to last position after deactivating and activating', async () => {
appMockRender.render(<CasesTableFilters {...props} />);

const filterBar = screen.getByTestId('cases-table-filters-group');
const filterBar = screen.getByTestId('cases-table-filters');
let allFilters = within(filterBar).getAllByRole('button');
let orderedFilterLabels = ['Severity', 'Status', 'Tags', 'Categories', 'More'];
orderedFilterLabels.forEach((label, index) => {
Expand Down Expand Up @@ -788,7 +788,7 @@ describe('CasesTableFilters ', () => {
});
appMockRender.render(<CasesTableFilters {...props} />);

const filterBar = screen.getByTestId('cases-table-filters-group');
const filterBar = screen.getByTestId('cases-table-filters');
let allFilters: HTMLElement[];
await waitFor(() => {
allFilters = within(filterBar).getAllByRole('button');
Expand Down
38 changes: 22 additions & 16 deletions x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import React, { useCallback, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup, EuiButton } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiButton } from '@elastic/eui';
import { mergeWith, isEqual } from 'lodash';
import { MoreFiltersSelectable } from './table_filter_config/more_filters_selectable';
import type { CaseStatuses } from '../../../common/types/domain';
Expand Down Expand Up @@ -126,7 +126,12 @@ const CasesTableFiltersComponent = ({
}, [onCreateCasePressed]);

return (
<EuiFlexGroup gutterSize="s" justifyContent="flexStart">
<EuiFlexGroup
gutterSize="s"
justifyContent="flexStart"
wrap={true}
data-test-subj="cases-table-filters"
>
{isSelectorView && onCreateCasePressed ? (
<EuiFlexItem grow={false}>
<EuiButton
Expand All @@ -149,20 +154,21 @@ const CasesTableFiltersComponent = ({
onSearch={handleOnSearch}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFilterGroup data-test-subj="cases-table-filters-group">
{activeFilters.map((filter) => (
<React.Fragment key={filter.key}>{filter.render({ filterOptions })}</React.Fragment>
))}
{isSelectorView || (
<MoreFiltersSelectable
options={selectableOptions}
activeFilters={activeSelectableOptionKeys}
onChange={onFilterConfigChange}
/>
)}
</EuiFilterGroup>
</EuiFlexItem>
{activeFilters.map((filter) => (
<EuiFlexItem grow={false} key={filter.key}>
{filter.render({ filterOptions })}
</EuiFlexItem>
))}

{isSelectorView || (
<EuiFlexItem grow={false}>
<MoreFiltersSelectable
options={selectableOptions}
activeFilters={activeSelectableOptionKeys}
onChange={onFilterConfigChange}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
};
Expand Down

0 comments on commit 6333a72

Please sign in to comment.