Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] Disable selection of filter status 'All' on AddToCaseAction (#99757) #100123

Merged
merged 1 commit into from
May 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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