diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index 485b5ebb5134ff..6bbc2a35d8d44d 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -12,14 +12,11 @@ import { difference, head, isEmpty } from 'lodash/fp'; import styled, { css } from 'styled-components'; import type { CaseUI, FilterOptions, CasesUI } from '../../../common/ui/types'; -import type { CasesOwners } from '../../client/helpers/can_use_cases'; -import type { EuiBasicTableOnChange, Solution } from './types'; +import type { EuiBasicTableOnChange } from './types'; import { SortFieldCase } from '../../../common/ui/types'; import type { CaseStatuses } from '../../../common/types/domain'; import { caseStatuses } from '../../../common/types/domain'; -import { OWNER_INFO } from '../../../common/constants'; -import { useAvailableCasesOwners } from '../app/use_available_owners'; import { useCasesColumns } from './use_cases_columns'; import { CasesTableFilters } from './table_filters'; import { CASES_TABLE_PERPAGE_VALUES } from './types'; @@ -33,6 +30,7 @@ import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get import { getAllPermissionsExceptFrom, isReadOnlyPermissions } from '../../utils/permissions'; import { useIsLoadingCases } from './use_is_loading_cases'; import { useAllCasesState } from './use_all_cases_state'; +import { useAvailableCasesOwners } from '../app/use_available_owners'; const ProgressLoader = styled(EuiProgress)` ${({ $isShow }: { $isShow: boolean }) => @@ -51,17 +49,6 @@ const getSortField = (field: string): SortFieldCase => // @ts-ignore SortFieldCase[field] ?? SortFieldCase.title; -const isValidSolution = (solution: string): solution is CasesOwners => - Object.keys(OWNER_INFO).includes(solution); - -const mapToReadableSolutionName = (solution: string): Solution => { - if (isValidSolution(solution)) { - return OWNER_INFO[solution]; - } - - return { id: solution, label: solution, iconType: '' }; -}; - export interface AllCasesListProps { hiddenStatuses?: CaseStatuses[]; isSelectorView?: boolean; @@ -157,29 +144,9 @@ export const AllCasesList = React.memo( const onFilterChangedCallback = useCallback( (newFilterOptions: Partial) => { deselectCases(); - setFilterOptions({ - ...newFilterOptions, - /** - * If the user selects and deselects all solutions - * then the owner is set to an empty array. This results in fetching all cases the user has access to including - * the ones with read access. We want to show only the cases the user has full access to. - * For that reason we fallback to availableSolutions if the owner is empty. - * - * If the consumer of cases has passed an owner we fallback to the provided owner - */ - ...(newFilterOptions.owner != null && !hasOwner - ? { - owner: - newFilterOptions.owner.length === 0 ? availableSolutions : newFilterOptions.owner, - } - : newFilterOptions.owner != null && hasOwner - ? { - owner: newFilterOptions.owner.length === 0 ? owner : newFilterOptions.owner, - } - : {}), - }); + setFilterOptions(newFilterOptions); }, - [deselectCases, setFilterOptions, hasOwner, availableSolutions, owner] + [deselectCases, setFilterOptions] ); const { columns } = useCasesColumns({ @@ -188,7 +155,8 @@ export const AllCasesList = React.memo( isSelectorView, connectors, onRowClick, - showSolutionColumn: !hasOwner && availableSolutions.length > 1, + // FIXME: was removed in Antonio's PR, merge with his + showSolutionColumn: true, disableActions: selectedCases.length > 0, }); @@ -219,10 +187,6 @@ export const AllCasesList = React.memo( [] ); - const availableSolutionsLabels = availableSolutions.map((solution) => - mapToReadableSolutionName(solution) - ); - const onCreateCasePressed = useCallback(() => { onRowClick?.(undefined, true); }, [onRowClick]); @@ -241,7 +205,7 @@ export const AllCasesList = React.memo( countOpenCases={data.countOpenCases} countInProgressCases={data.countInProgressCases} onFilterChanged={onFilterChangedCallback} - availableSolutions={hasOwner ? [] : availableSolutionsLabels} + availableSolutions={hasOwner ? [] : availableSolutions} hiddenStatuses={hiddenStatuses} onCreateCasePressed={onCreateCasePressed} isSelectorView={isSelectorView} diff --git a/x-pack/plugins/cases/public/components/all_cases/solution_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/solution_filter.tsx index b776895e2fe9e0..00db4496a99d87 100644 --- a/x-pack/plugins/cases/public/components/all_cases/solution_filter.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/solution_filter.tsx @@ -5,117 +5,91 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; -import { - EuiFilterButton, - EuiFilterSelectItem, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiPopover, - EuiText, - EuiIcon, -} from '@elastic/eui'; -import styled from 'styled-components'; +import React from 'react'; +import type { EuiSelectableOption } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { OWNER_INFO } from '../../../common/constants'; +import type { FilterOptions } from '../../../common/ui'; import * as i18n from './translations'; import type { Solution } from './types'; +import { MultiSelectFilter } from './multi_select_filter'; +import type { CasesOwners } from '../../client/helpers/can_use_cases'; +import { useCasesContext } from '../cases_context/use_cases_context'; interface FilterPopoverProps { - onSelectedOptionsChanged: (value: string[]) => void; - options: Solution[]; - optionsEmptyLabel?: string; + onChange: ({ filterId, options }: { filterId: keyof FilterOptions; options: string[] }) => void; selectedOptions: string[]; + availableSolutions: string[]; } -const ScrollableDiv = styled.div` - max-height: 250px; - overflow: auto; -`; +const isValidSolution = (solution: string): solution is CasesOwners => + Object.keys(OWNER_INFO).includes(solution); -const toggleSelectedGroup = (group: string, selectedGroups: string[]): string[] => { - const selectedGroupIndex = selectedGroups.indexOf(group); - if (selectedGroupIndex >= 0) { - return [ - ...selectedGroups.slice(0, selectedGroupIndex), - ...selectedGroups.slice(selectedGroupIndex + 1), - ]; +const mapToReadableSolutionName = (solution: string): Solution => { + if (isValidSolution(solution)) { + return OWNER_INFO[solution]; } - return [...selectedGroups, group]; + + return { id: solution, label: solution, iconType: '' }; }; -/** - * Popover for selecting a field to filter on - * - * @param buttonLabel label on dropdwon button - * @param onSelectedOptionsChanged change listener to be notified when option selection changes - * @param options to display for filtering - * @param optionsEmptyLabel shows when options empty - * @param selectedOptions manage state of selectedOptions - */ export const SolutionFilterComponent = ({ - onSelectedOptionsChanged, - options, - optionsEmptyLabel, + onChange, selectedOptions, + availableSolutions, }: FilterPopoverProps) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const { owner } = useCasesContext(); + const hasOwner = Boolean(owner.length); + const options = hasOwner ? owner : availableSolutions; + const solutions = availableSolutions.map((solution) => mapToReadableSolutionName(solution)); - const setIsPopoverOpenCb = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); - const toggleSelectedGroupCb = useCallback( - (option) => onSelectedOptionsChanged(toggleSelectedGroup(option, selectedOptions)), - [selectedOptions, onSelectedOptionsChanged] - ); + /** + * If the user selects and deselects all solutions then the owner is set to an empty array. + * This results in fetching all cases the user has access to including + * the ones with read access. We want to show only the cases the user has full access to. + * For that reason we fallback to availableSolutions if the owner is empty. + * + * If the consumer of cases has passed an owner we fallback to the provided owner + */ + const _onChange = ({ + filterId, + options: newOptions, + }: { + filterId: keyof FilterOptions; + options: string[]; + }) => { + if (hasOwner) { + onChange({ filterId, options: newOptions.length === 0 ? owner : newOptions }); + } else { + onChange({ filterId, options: newOptions.length === 0 ? availableSolutions : newOptions }); + } + }; + + const selectedOptionsInFilter = + selectedOptions.length === availableSolutions.length ? [] : selectedOptions; + + const renderOption = (option: EuiSelectableOption) => { + const solution = solutions.find((solutionData) => solutionData.id === option.label) as Solution; + return ( + + + + + {solution.label} + + ); + }; return ( - 0} - numActiveFilters={selectedOptions.length} - aria-label={i18n.SOLUTION} - > - {i18n.SOLUTION} - - } - isOpen={isPopoverOpen} - closePopover={setIsPopoverOpenCb} - panelPaddingSize="none" - repositionOnScroll - > - - {options.map((option, index) => ( - - - - - - {option.label} - - - ))} - - {options.length === 0 && optionsEmptyLabel != null && ( - - - - {optionsEmptyLabel} - - - - )} - + ); }; diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx index 210398946d639b..bb96b0dfa4346b 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx @@ -23,7 +23,6 @@ import { AssigneesFilterPopover } from './assignees_filter'; import type { CurrentUserProfile } from '../types'; import { useCasesFeatures } from '../../common/use_cases_features'; import type { AssigneesFilteringSelection } from '../user_profiles/types'; -import type { Solution } from './types'; interface CasesTableFiltersProps { countClosedCases: number | null; @@ -31,7 +30,7 @@ interface CasesTableFiltersProps { countOpenCases: number | null; onFilterChanged: (filterOptions: Partial) => void; hiddenStatuses?: CaseStatuses[]; - availableSolutions: Solution[]; + availableSolutions: string[]; isSelectorView?: boolean; onCreateCasePressed?: () => void; isLoading: boolean; @@ -53,7 +52,6 @@ const CasesTableFiltersComponent = ({ filterOptions, }: CasesTableFiltersProps) => { const [search, setSearch] = useState(filterOptions.search); - const [selectedOwner, setSelectedOwner] = useState([]); const [selectedAssignees, setSelectedAssignees] = useState([]); const { data: tags = [] } = useGetTags(); const { data: categories = [] } = useGetCategories(); @@ -88,16 +86,6 @@ const CasesTableFiltersComponent = ({ [selectedAssignees, onFilterChanged] ); - const handleSelectedSolution = useCallback( - (newOwner) => { - if (!isEqual(newOwner, selectedOwner)) { - setSelectedOwner(newOwner); - onFilterChanged({ owner: newOwner }); - } - }, - [onFilterChanged, selectedOwner] - ); - const handleOnSearch = useCallback( (newSearch) => { const trimSearch = newSearch.trim(); @@ -178,9 +166,9 @@ const CasesTableFiltersComponent = ({ /> {availableSolutions.length > 1 && ( )}