diff --git a/x-pack/plugins/cases/README.md b/x-pack/plugins/cases/README.md index 14afe89829a68..5cb9d82436137 100644 --- a/x-pack/plugins/cases/README.md +++ b/x-pack/plugins/cases/README.md @@ -73,7 +73,7 @@ Arguments: |---|---| |alertData?|`Omit;` 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|(theCase?: Case | SubCase) => void; callback for row click, passing case in row |updateCase?|(theCase: Case | SubCase) => void; callback after case has been updated |userCanCrud|`boolean;` user permissions to crud diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.test.tsx new file mode 100644 index 0000000000000..0e8d1da74b606 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.test.tsx @@ -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( + + + + ); + + expect(wrapper.find(`[data-test-subj="status-badge-in-progress"]`).exists()).toBeTruthy(); + })); +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx index 83f38aab21aa4..36527bd96700b 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx @@ -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'; @@ -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'; @@ -59,7 +61,7 @@ interface AllCasesGenericProps { caseDetailsNavigation?: CasesNavigation; // 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; @@ -72,13 +74,17 @@ export const AllCasesGeneric = React.memo( 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, @@ -90,7 +96,7 @@ export const AllCasesGeneric = React.memo( setFilters, setQueryParams, setSelectedCases, - } = useGetCases(); + } = useGetCases({}, initialFilterOptions); // Post Comment to Case const { postComment, isLoading: isCommentUpdating } = usePostComment(); @@ -288,7 +294,7 @@ export const AllCasesGeneric = React.memo( status: filterOptions.status, }} setFilterRefetch={setFilterRefetch} - disabledStatuses={disabledStatuses} + hiddenStatuses={hiddenStatuses} /> { index: 'index-id', alertId: 'alert-id', }, - disabledStatuses: [], + hiddenStatuses: [], updateCase, }; mount( @@ -73,7 +73,7 @@ describe('AllCasesSelectorModal', () => { expect.objectContaining({ alertData: fullProps.alertData, createCaseNavigation, - disabledStatuses: fullProps.disabledStatuses, + hiddenStatuses: fullProps.hiddenStatuses, isSelectorView: true, userCanCrud: fullProps.userCanCrud, updateCase, diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx index 0a83ef13e8ee6..d476d71d847a0 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx @@ -8,7 +8,12 @@ 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'; @@ -16,7 +21,7 @@ import { AllCasesGeneric } from '../all_cases_generic'; export interface AllCasesSelectorModalProps { alertData?: Omit; createCaseNavigation: CasesNavigation; - disabledStatuses?: CaseStatuses[]; + hiddenStatuses?: CaseStatusWithAllStatus[]; onRowClick: (theCase?: Case | SubCase) => void; updateCase?: (newCase: Case) => void; userCanCrud: boolean; @@ -32,7 +37,7 @@ const Modal = styled(EuiModal)` export const AllCasesSelectorModal: React.FC = ({ alertData, createCaseNavigation, - disabledStatuses, + hiddenStatuses, onRowClick, updateCase, userCanCrud, @@ -55,7 +60,7 @@ export const AllCasesSelectorModal: React.FC = ({ { }); }); - it('should disabled selected statuses', () => { + it('should not render hidden statuses', () => { const wrapper = mount( - + ); 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(); }); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx index 9fb00933f0307..7d02bf2c441d3 100644 --- a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx @@ -14,32 +14,30 @@ interface Props { stats: Record; selectedStatus: CaseStatusWithAllStatus; onStatusChanged: (status: CaseStatusWithAllStatus) => void; - disabledStatuses?: CaseStatusWithAllStatus[]; + hiddenStatuses?: CaseStatusWithAllStatus[]; } const StatusFilterComponent: React.FC = ({ stats, selectedStatus, onStatusChanged, - disabledStatuses = [], + hiddenStatuses = [], }) => { const caseStatuses = Object.keys(statuses) as CaseStatusWithAllStatus[]; - const options: Array> = [ - StatusAll, - ...caseStatuses, - ].map((status) => ({ - value: status, - inputDisplay: ( - - - - - {status !== StatusAll && {` (${stats[status]})`}} - - ), - disabled: disabledStatuses.includes(status), - 'data-test-subj': `case-status-filter-${status}`, - })); + const options: Array> = [StatusAll, ...caseStatuses] + .filter((status) => !hiddenStatuses.includes(status)) + .map((status) => ({ + value: status, + inputDisplay: ( + + + + + {status !== StatusAll && {` (${stats[status]})`}} + + ), + 'data-test-subj': `case-status-filter-${status}`, + })); return ( ) => void; initial: FilterOptions; setFilterRefetch: (val: () => void) => void; - disabledStatuses?: CaseStatuses[]; + hiddenStatuses?: CaseStatusWithAllStatus[]; } // Fix the width of the status dropdown to prevent hiding long text items @@ -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 ?? '') @@ -161,7 +161,7 @@ const CasesTableFiltersComponent = ({ selectedStatus={initial.status} onStatusChanged={onStatusChanged} stats={stats} - disabledStatuses={disabledStatuses} + hiddenStatuses={hiddenStatuses} /> diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index 1682b4b7e7dee..7379f5d6fd5dc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -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'; @@ -240,7 +240,7 @@ const AddToCaseActionComponent: React.FC = ({ href: formatUrl(getCreateCaseUrl()), onClick: goToCreateCase, }, - disabledStatuses: [CaseStatuses.closed], + hiddenStatuses: [CaseStatuses.closed, StatusAll], onRowClick: onCaseClicked, updateCase: onCaseSuccess, userCanCrud: userPermissions?.crud ?? false,