Skip to content

Commit

Permalink
Disable selection of filter status 'All' on AddToCaseAction (#99757)
Browse files Browse the repository at this point in the history
* Fix: Disable selection of filter status 'All' on AddToCaseAction

* UI: Hide disabled statuses on AddToCaseAction

* Refactor: Rename disabledStatuses to hiddenStatuses

* Fix: Pick the first valid status for initialFilterOptions

Previously it was always picking 'open', but it wouldn't work when hiddenStatuses contains "open".

* Add missing test

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
machadoum and kibanamachine authored May 14, 2021
1 parent b6635b0 commit fab9605
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 44 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Arguments:
|---|---|
|alertData?|`Omit<CommentRequestAlertType, 'type'>;` alert data to post to case
|createCaseNavigation|`CasesNavigation` route configuration for create cases page
|disabledStatuses?|`CaseStatuses[];` array of disabled statuses
|hiddenStatuses?|`CaseStatuses[];` array of hidden statuses
|onRowClick|<code>(theCase?: Case &vert; SubCase) => void;</code> callback for row click, passing case in row
|updateCase?|<code>(theCase: Case &vert; SubCase) => void;</code> callback after case has been updated
|userCanCrud|`boolean;` user permissions to crud
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 { mount } from 'enzyme';
import { AllCasesGeneric } from './all_cases_generic';

import { TestProviders } from '../../common/mock';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
import { useGetActionLicense } from '../../containers/use_get_action_license';
import { StatusAll } from '../../containers/types';
import { CaseStatuses } from '../../../common';
import { act } from 'react-dom/test-utils';

jest.mock('../../containers/use_get_reporters');
jest.mock('../../containers/use_get_tags');
jest.mock('../../containers/use_get_action_license');
jest.mock('../../containers/api');

const createCaseNavigation = { href: '', onClick: jest.fn() };

const alertDataMock = {
type: 'alert',
rule: {
id: 'rule-id',
name: 'rule',
},
index: 'index-id',
alertId: 'alert-id',
};

describe('AllCasesGeneric ', () => {
beforeEach(() => {
jest.resetAllMocks();
(useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'], fetchTags: jest.fn() });
(useGetReporters as jest.Mock).mockReturnValue({
reporters: ['casetester'],
respReporters: [{ username: 'casetester' }],
isLoading: true,
isError: false,
fetchReporters: jest.fn(),
});
(useGetActionLicense as jest.Mock).mockReturnValue({
actionLicense: null,
isLoading: false,
});
});

it('renders the first available status when hiddenStatus is given', () =>
act(async () => {
const wrapper = mount(
<TestProviders>
<AllCasesGeneric
alertData={alertDataMock}
createCaseNavigation={createCaseNavigation}
hiddenStatuses={[StatusAll, CaseStatuses.open]}
isSelectorView={true}
userCanCrud={true}
/>
</TestProviders>
);

expect(wrapper.find(`[data-test-subj="status-badge-in-progress"]`).exists()).toBeTruthy();
}));
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { EuiProgress } from '@elastic/eui';
import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types';
import { isEmpty, memoize } from 'lodash/fp';
import { difference, head, isEmpty, memoize } from 'lodash/fp';
import styled, { css } from 'styled-components';
import classnames from 'classnames';

Expand All @@ -17,10 +17,12 @@ import {
CaseStatuses,
CaseType,
CommentRequestAlertType,
CaseStatusWithAllStatus,
CommentType,
FilterOptions,
SortFieldCase,
SubCase,
caseStatuses,
} from '../../../common';
import { SELECTABLE_MESSAGE_COLLECTIONS } from '../../common/translations';
import { useGetActionLicense } from '../../containers/use_get_action_license';
Expand Down Expand Up @@ -59,7 +61,7 @@ interface AllCasesGenericProps {
caseDetailsNavigation?: CasesNavigation<CaseDetailsHrefSchema, 'configurable'>; // if not passed, case name is not displayed as a link (Formerly dependant on isSelectorView)
configureCasesNavigation?: CasesNavigation; // if not passed, header with nav is not displayed (Formerly dependant on isSelectorView)
createCaseNavigation: CasesNavigation;
disabledStatuses?: CaseStatuses[];
hiddenStatuses?: CaseStatusWithAllStatus[];
isSelectorView?: boolean;
onRowClick?: (theCase?: Case | SubCase) => void;
updateCase?: (newCase: Case) => void;
Expand All @@ -72,13 +74,17 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(
caseDetailsNavigation,
configureCasesNavigation,
createCaseNavigation,
disabledStatuses,
hiddenStatuses = [],
isSelectorView,
onRowClick,
updateCase,
userCanCrud,
}) => {
const { actionLicense } = useGetActionLicense();
const firstAvailableStatus = head(difference(caseStatuses, hiddenStatuses));
const initialFilterOptions =
!isEmpty(hiddenStatuses) && firstAvailableStatus ? { status: firstAvailableStatus } : {};

const {
data,
dispatchUpdateCaseProperty,
Expand All @@ -90,7 +96,7 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(
setFilters,
setQueryParams,
setSelectedCases,
} = useGetCases();
} = useGetCases({}, initialFilterOptions);

// Post Comment to Case
const { postComment, isLoading: isCommentUpdating } = usePostComment();
Expand Down Expand Up @@ -288,7 +294,7 @@ export const AllCasesGeneric = React.memo<AllCasesGenericProps>(
status: filterOptions.status,
}}
setFilterRefetch={setFilterRefetch}
disabledStatuses={disabledStatuses}
hiddenStatuses={hiddenStatuses}
/>
<CasesTable
columns={columns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('AllCasesSelectorModal', () => {
index: 'index-id',
alertId: 'alert-id',
},
disabledStatuses: [],
hiddenStatuses: [],
updateCase,
};
mount(
Expand All @@ -73,7 +73,7 @@ describe('AllCasesSelectorModal', () => {
expect.objectContaining({
alertData: fullProps.alertData,
createCaseNavigation,
disabledStatuses: fullProps.disabledStatuses,
hiddenStatuses: fullProps.hiddenStatuses,
isSelectorView: true,
userCanCrud: fullProps.userCanCrud,
updateCase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@
import React, { useState, useCallback } from 'react';
import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui';
import styled from 'styled-components';
import { Case, CaseStatuses, CommentRequestAlertType, SubCase } from '../../../../common';
import {
Case,
CaseStatusWithAllStatus,
CommentRequestAlertType,
SubCase,
} from '../../../../common';
import { CasesNavigation } from '../../links';
import * as i18n from '../../../common/translations';
import { AllCasesGeneric } from '../all_cases_generic';

export interface AllCasesSelectorModalProps {
alertData?: Omit<CommentRequestAlertType, 'type'>;
createCaseNavigation: CasesNavigation;
disabledStatuses?: CaseStatuses[];
hiddenStatuses?: CaseStatusWithAllStatus[];
onRowClick: (theCase?: Case | SubCase) => void;
updateCase?: (newCase: Case) => void;
userCanCrud: boolean;
Expand All @@ -32,7 +37,7 @@ const Modal = styled(EuiModal)`
export const AllCasesSelectorModal: React.FC<AllCasesSelectorModalProps> = ({
alertData,
createCaseNavigation,
disabledStatuses,
hiddenStatuses,
onRowClick,
updateCase,
userCanCrud,
Expand All @@ -55,7 +60,7 @@ export const AllCasesSelectorModal: React.FC<AllCasesSelectorModalProps> = ({
<AllCasesGeneric
alertData={alertData}
createCaseNavigation={createCaseNavigation}
disabledStatuses={disabledStatuses}
hiddenStatuses={hiddenStatuses}
isSelectorView={true}
onRowClick={onClick}
userCanCrud={userCanCrud}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,20 @@ describe('StatusFilter', () => {
});
});

it('should disabled selected statuses', () => {
it('should not render hidden statuses', () => {
const wrapper = mount(
<StatusFilter {...defaultProps} disabledStatuses={[CaseStatuses.closed]} />
<StatusFilter {...defaultProps} hiddenStatuses={[StatusAll, CaseStatuses.closed]} />
);

wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click');

expect(
wrapper.find('button[data-test-subj="case-status-filter-open"]').prop('disabled')
).toBeFalsy();
expect(wrapper.find(`[data-test-subj="case-status-filter-all"]`).exists()).toBeFalsy();
expect(wrapper.find('button[data-test-subj="case-status-filter-closed"]').exists()).toBeFalsy();

expect(
wrapper.find('button[data-test-subj="case-status-filter-in-progress"]').prop('disabled')
).toBeFalsy();
expect(wrapper.find('button[data-test-subj="case-status-filter-open"]').exists()).toBeTruthy();

expect(
wrapper.find('button[data-test-subj="case-status-filter-closed"]').prop('disabled')
wrapper.find('button[data-test-subj="case-status-filter-in-progress"]').exists()
).toBeTruthy();
});
});
34 changes: 16 additions & 18 deletions x-pack/plugins/cases/public/components/all_cases/status_filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,30 @@ interface Props {
stats: Record<CaseStatusWithAllStatus, number | null>;
selectedStatus: CaseStatusWithAllStatus;
onStatusChanged: (status: CaseStatusWithAllStatus) => void;
disabledStatuses?: CaseStatusWithAllStatus[];
hiddenStatuses?: CaseStatusWithAllStatus[];
}

const StatusFilterComponent: React.FC<Props> = ({
stats,
selectedStatus,
onStatusChanged,
disabledStatuses = [],
hiddenStatuses = [],
}) => {
const caseStatuses = Object.keys(statuses) as CaseStatusWithAllStatus[];
const options: Array<EuiSuperSelectOption<CaseStatusWithAllStatus>> = [
StatusAll,
...caseStatuses,
].map((status) => ({
value: status,
inputDisplay: (
<EuiFlexGroup gutterSize="xs" alignItems={'center'}>
<EuiFlexItem grow={false}>
<Status type={status} />
</EuiFlexItem>
{status !== StatusAll && <EuiFlexItem grow={false}>{` (${stats[status]})`}</EuiFlexItem>}
</EuiFlexGroup>
),
disabled: disabledStatuses.includes(status),
'data-test-subj': `case-status-filter-${status}`,
}));
const options: Array<EuiSuperSelectOption<CaseStatusWithAllStatus>> = [StatusAll, ...caseStatuses]
.filter((status) => !hiddenStatuses.includes(status))
.map((status) => ({
value: status,
inputDisplay: (
<EuiFlexGroup gutterSize="xs" alignItems={'center'}>
<EuiFlexItem grow={false}>
<Status type={status} />
</EuiFlexItem>
{status !== StatusAll && <EuiFlexItem grow={false}>{` (${stats[status]})`}</EuiFlexItem>}
</EuiFlexGroup>
),
'data-test-subj': `case-status-filter-${status}`,
}));

return (
<EuiSuperSelect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface CasesTableFiltersProps {
onFilterChanged: (filterOptions: Partial<FilterOptions>) => void;
initial: FilterOptions;
setFilterRefetch: (val: () => void) => void;
disabledStatuses?: CaseStatuses[];
hiddenStatuses?: CaseStatusWithAllStatus[];
}

// Fix the width of the status dropdown to prevent hiding long text items
Expand Down Expand Up @@ -56,7 +56,7 @@ const CasesTableFiltersComponent = ({
onFilterChanged,
initial = defaultInitial,
setFilterRefetch,
disabledStatuses,
hiddenStatuses,
}: CasesTableFiltersProps) => {
const [selectedReporters, setSelectedReporters] = useState(
initial.reporters.map((r) => r.full_name ?? r.username ?? '')
Expand Down Expand Up @@ -161,7 +161,7 @@ const CasesTableFiltersComponent = ({
selectedStatus={initial.status}
onStatusChanged={onStatusChanged}
stats={stats}
disabledStatuses={disabledStatuses}
hiddenStatuses={hiddenStatuses}
/>
</StatusFilterWrapper>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
EuiToolTip,
} from '@elastic/eui';

import { Case, CaseStatuses } from '../../../../../cases/common';
import { Case, CaseStatuses, StatusAll } from '../../../../../cases/common';
import { APP_ID } from '../../../../common/constants';
import { Ecs } from '../../../../common/ecs';
import { SecurityPageName } from '../../../app/types';
Expand Down Expand Up @@ -240,7 +240,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
href: formatUrl(getCreateCaseUrl()),
onClick: goToCreateCase,
},
disabledStatuses: [CaseStatuses.closed],
hiddenStatuses: [CaseStatuses.closed, StatusAll],
onRowClick: onCaseClicked,
updateCase: onCaseSuccess,
userCanCrud: userPermissions?.crud ?? false,
Expand Down

0 comments on commit fab9605

Please sign in to comment.