Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jcger committed Nov 8, 2023
1 parent 375d638 commit d5ce8b2
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 }) =>
Expand All @@ -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;
Expand Down Expand Up @@ -157,29 +144,9 @@ export const AllCasesList = React.memo<AllCasesListProps>(
const onFilterChangedCallback = useCallback(
(newFilterOptions: Partial<FilterOptions>) => {
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({
Expand All @@ -188,7 +155,8 @@ export const AllCasesList = React.memo<AllCasesListProps>(
isSelectorView,
connectors,
onRowClick,
showSolutionColumn: !hasOwner && availableSolutions.length > 1,
// FIXME: was removed in Antonio's PR, merge with his
showSolutionColumn: true,
disableActions: selectedCases.length > 0,
});

Expand Down Expand Up @@ -219,10 +187,6 @@ export const AllCasesList = React.memo<AllCasesListProps>(
[]
);

const availableSolutionsLabels = availableSolutions.map((solution) =>
mapToReadableSolutionName(solution)
);

const onCreateCasePressed = useCallback(() => {
onRowClick?.(undefined, true);
}, [onRowClick]);
Expand All @@ -241,7 +205,7 @@ export const AllCasesList = React.memo<AllCasesListProps>(
countOpenCases={data.countOpenCases}
countInProgressCases={data.countInProgressCases}
onFilterChanged={onFilterChangedCallback}
availableSolutions={hasOwner ? [] : availableSolutionsLabels}
availableSolutions={hasOwner ? [] : availableSolutions}
hiddenStatuses={hiddenStatuses}
onCreateCasePressed={onCreateCasePressed}
isSelectorView={isSelectorView}
Expand Down
160 changes: 67 additions & 93 deletions x-pack/plugins/cases/public/components/all_cases/solution_filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<EuiFlexGroup alignItems="center" justifyContent="flexStart" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon size="m" type={solution.iconType} title={solution.label} />
</EuiFlexItem>
<EuiFlexItem grow={false}>{solution.label}</EuiFlexItem>
</EuiFlexGroup>
);
};

return (
<EuiPopover
ownFocus
button={
<EuiFilterButton
data-test-subj={'solution-filter-popover-button'}
iconType="arrowDown"
onClick={setIsPopoverOpenCb}
isSelected={isPopoverOpen}
numFilters={options.length}
hasActiveFilters={selectedOptions.length > 0}
numActiveFilters={selectedOptions.length}
aria-label={i18n.SOLUTION}
>
{i18n.SOLUTION}
</EuiFilterButton>
}
isOpen={isPopoverOpen}
closePopover={setIsPopoverOpenCb}
panelPaddingSize="none"
repositionOnScroll
>
<ScrollableDiv>
{options.map((option, index) => (
<EuiFilterSelectItem
checked={selectedOptions.includes(option.id) ? 'on' : undefined}
data-test-subj={`solution-filter-popover-item-${option.id}`}
key={`${index}-${option.id}`}
onClick={toggleSelectedGroupCb.bind(null, option.id)}
>
<EuiFlexGroup alignItems="center" justifyContent="flexStart" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon size="m" type={option.iconType} title={option.label} />
</EuiFlexItem>
<EuiFlexItem grow={false}>{option.label}</EuiFlexItem>
</EuiFlexGroup>
</EuiFilterSelectItem>
))}
</ScrollableDiv>
{options.length === 0 && optionsEmptyLabel != null && (
<EuiFlexGroup gutterSize="m" justifyContent="spaceAround">
<EuiFlexItem grow={true}>
<EuiPanel>
<EuiText>{optionsEmptyLabel}</EuiText>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiPopover>
<MultiSelectFilter
buttonLabel={i18n.SOLUTION}
id={'owner'}
onChange={_onChange}
options={options}
renderOption={renderOption}
selectedOptions={selectedOptionsInFilter}
/>
);
};

Expand Down
20 changes: 4 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 @@ -23,15 +23,14 @@ 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;
countInProgressCases: number | null;
countOpenCases: number | null;
onFilterChanged: (filterOptions: Partial<FilterOptions>) => void;
hiddenStatuses?: CaseStatuses[];
availableSolutions: Solution[];
availableSolutions: string[];
isSelectorView?: boolean;
onCreateCasePressed?: () => void;
isLoading: boolean;
Expand All @@ -53,7 +52,6 @@ const CasesTableFiltersComponent = ({
filterOptions,
}: CasesTableFiltersProps) => {
const [search, setSearch] = useState(filterOptions.search);
const [selectedOwner, setSelectedOwner] = useState([]);
const [selectedAssignees, setSelectedAssignees] = useState<AssigneesFilteringSelection[]>([]);
const { data: tags = [] } = useGetTags();
const { data: categories = [] } = useGetCategories();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -178,9 +166,9 @@ const CasesTableFiltersComponent = ({
/>
{availableSolutions.length > 1 && (
<SolutionFilter
onSelectedOptionsChanged={handleSelectedSolution}
selectedOptions={selectedOwner}
options={availableSolutions}
onChange={onChange}
selectedOptions={filterOptions?.owner}
availableSolutions={availableSolutions}
/>
)}
</EuiFilterGroup>
Expand Down

0 comments on commit d5ce8b2

Please sign in to comment.