Skip to content

Commit

Permalink
[Cases]: Redesign all cases list select modal (#149851)
Browse files Browse the repository at this point in the history
## Summary

This PR Fixes: #139194
I Removes below columns and filters from Cases select modal.

**Columns**
- Tags
- Reporters/Assignees
- Solution
- Comments
- Alerts
- Updated On
- Status
- External Incident

**Filters**
- Reporters/Assignees
- Create case button (Create case button will be visible when there are
no cases available)

**Before**

![image](https://user-images.githubusercontent.com/117571355/216052295-e1788c80-357b-4f38-a2ac-9ce980dbeac9.png)

**After:** **Updated**

![image](https://user-images.githubusercontent.com/117571355/216375245-382e58f2-46c6-4c39-b13a-81f439dd5a94.png)


![image](https://user-images.githubusercontent.com/117571355/216375410-f6cbbae7-40c9-4b94-83f4-c756379971a5.png)

Flaky test runner:
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/1867

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
js-jankisalvi and kibanamachine authored Feb 6, 2023
1 parent 5b366b9 commit daf1304
Show file tree
Hide file tree
Showing 16 changed files with 698 additions and 467 deletions.
3 changes: 3 additions & 0 deletions x-pack/plugins/cases/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,21 @@ export const GENERAL_CASES_OWNER = APP_ID;

export const OWNER_INFO = {
[SECURITY_SOLUTION_OWNER]: {
id: SECURITY_SOLUTION_OWNER,
appId: 'securitySolutionUI',
label: 'Security',
iconType: 'logoSecurity',
appRoute: '/app/security',
},
[OBSERVABILITY_OWNER]: {
id: OBSERVABILITY_OWNER,
appId: 'observability-overview',
label: 'Observability',
iconType: 'logoObservability',
appRoute: '/app/observability',
},
[GENERAL_CASES_OWNER]: {
id: GENERAL_CASES_OWNER,
appId: 'management',
label: 'Stack',
iconType: 'casesApp',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,20 @@ describe.skip('AllCasesListGeneric', () => {
});
});

it('should render only Name, CreatedOn and Severity columns when isSelectorView=true', async () => {
const wrapper = mount(
<TestProviders>
<AllCasesList isSelectorView={true} />
</TestProviders>
);
await waitFor(() => {
expect(wrapper.find('[data-test-subj="tableHeaderCell_title_0"]').exists()).toBe(true);
expect(wrapper.find('[data-test-subj="tableHeaderCell_createdAt_1"]').exists()).toBe(true);
expect(wrapper.find('[data-test-subj="tableHeaderCell_severity_2"]').exists()).toBe(true);
expect(wrapper.find('[data-test-subj="tableHeaderCell_assignees_1"]').exists()).toBe(false);
});
});

it('should sort by severity', async () => {
const result = appMockRenderer.render(<AllCasesList isSelectorView={false} />);

Expand Down Expand Up @@ -698,12 +712,12 @@ describe.skip('AllCasesListGeneric', () => {
queryParams: DEFAULT_QUERY_PARAMS,
});

userEvent.click(getByTestId('options-filter-popover-button-Solution'));
userEvent.click(getByTestId('solution-filter-popover-button'));

await waitForEuiPopoverOpen();

userEvent.click(
getByTestId(`options-filter-popover-item-${SECURITY_SOLUTION_OWNER}`),
getByTestId(`solution-filter-popover-item-${SECURITY_SOLUTION_OWNER}`),
undefined,
{
skipPointerEventsCheck: true,
Expand All @@ -725,7 +739,7 @@ describe.skip('AllCasesListGeneric', () => {
});

userEvent.click(
getByTestId(`options-filter-popover-item-${SECURITY_SOLUTION_OWNER}`),
getByTestId(`solution-filter-popover-item-${SECURITY_SOLUTION_OWNER}`),
undefined,
{
skipPointerEventsCheck: true,
Expand Down Expand Up @@ -754,7 +768,7 @@ describe.skip('AllCasesListGeneric', () => {
</TestProviders>
);

expect(queryByTestId('options-filter-popover-button-Solution')).toBeFalsy();
expect(queryByTestId('solution-filter-popover-button')).toBeFalsy();
});

it('should call useGetCases with the correct owner on initial render', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import styled, { css } from 'styled-components';
import type { Case, CaseStatusWithAllStatus, FilterOptions } from '../../../common/ui/types';
import { SortFieldCase, StatusAll } from '../../../common/ui/types';
import { CaseStatuses, caseStatuses } from '../../../common/api';
import { OWNER_INFO } from '../../../common/constants';
import type { CasesOwners } from '../../client/helpers/can_use_cases';

import { useAvailableCasesOwners } from '../app/use_available_owners';
import { useCasesColumns } from './use_cases_columns';
import { CasesTableFilters } from './table_filters';
import type { EuiBasicTableOnChange } from './types';
import type { EuiBasicTableOnChange, Solution } from './types';

import { CasesTable } from './table';
import { useCasesContext } from '../cases_context/use_cases_context';
Expand Down Expand Up @@ -48,6 +50,17 @@ 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?: CaseStatusWithAllStatus[];
isSelectorView?: boolean;
Expand Down Expand Up @@ -228,6 +241,10 @@ export const AllCasesList = React.memo<AllCasesListProps>(
[]
);

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

return (
<>
<ProgressLoader
Expand All @@ -242,7 +259,7 @@ export const AllCasesList = React.memo<AllCasesListProps>(
countOpenCases={data.countOpenCases}
countInProgressCases={data.countInProgressCases}
onFilterChanged={onFilterChangedCallback}
availableSolutions={hasOwner ? [] : availableSolutions}
availableSolutions={hasOwner ? [] : availableSolutionsLabels}
initial={{
search: filterOptions.search,
searchFields: filterOptions.searchFields,
Expand All @@ -254,8 +271,8 @@ export const AllCasesList = React.memo<AllCasesListProps>(
severity: filterOptions.severity,
}}
hiddenStatuses={hiddenStatuses}
displayCreateCaseButton={isSelectorView}
onCreateCasePressed={onRowClick}
isSelectorView={isSelectorView}
isLoading={isLoadingCurrentUserProfile}
currentUserProfile={currentUserProfile}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React, { useState, useCallback } from 'react';
import {
EuiButton,
EuiButtonEmpty,
EuiModal,
EuiModalBody,
EuiModalFooter,
Expand All @@ -29,7 +29,7 @@ export interface AllCasesSelectorModalProps {

const Modal = styled(EuiModal)`
${({ theme }) => `
min-width: ${theme.eui.euiBreakpoints.l};
min-width: ${theme.eui.euiBreakpoints.m};
max-width: ${theme.eui.euiBreakpoints.xl};
`}
`;
Expand Down Expand Up @@ -68,13 +68,13 @@ export const AllCasesSelectorModal = React.memo<AllCasesSelectorModalProps>(
/>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
color="text"
<EuiButtonEmpty
color="primary"
onClick={closeModal}
data-test-subj="all-cases-modal-cancel-button"
>
{i18n.CANCEL}
</EuiButton>
</EuiButtonEmpty>
</EuiModalFooter>
</Modal>
</QueryClientProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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 { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';

import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';
import type { Solution } from './types';
import {
OWNER_INFO,
SECURITY_SOLUTION_OWNER,
OBSERVABILITY_OWNER,
} from '../../../common/constants';

import { SolutionFilter } from './solution_filter';
import userEvent from '@testing-library/user-event';

describe('SolutionFilter ', () => {
let appMockRender: AppMockRenderer;
const onSelectedOptionsChanged = jest.fn();
const solutions: Solution[] = [
{
id: SECURITY_SOLUTION_OWNER,
label: OWNER_INFO[SECURITY_SOLUTION_OWNER].label,
iconType: OWNER_INFO[SECURITY_SOLUTION_OWNER].iconType,
},
{
id: OBSERVABILITY_OWNER,
label: OWNER_INFO[OBSERVABILITY_OWNER].label,
iconType: OWNER_INFO[OBSERVABILITY_OWNER].iconType,
},
];

beforeEach(() => {
appMockRender = createAppMockRenderer();
jest.clearAllMocks();
});

it('renders button correctly', () => {
const { getByTestId } = appMockRender.render(
<SolutionFilter
onSelectedOptionsChanged={onSelectedOptionsChanged}
selectedOptions={[]}
options={solutions}
/>
);

expect(getByTestId('solution-filter-popover-button')).toBeInTheDocument();
});

it('renders empty label correctly', async () => {
const { getByTestId, getByText } = appMockRender.render(
<SolutionFilter
onSelectedOptionsChanged={onSelectedOptionsChanged}
selectedOptions={[]}
options={[]}
optionsEmptyLabel="No options available"
/>
);

userEvent.click(getByTestId('solution-filter-popover-button'));

await waitForEuiPopoverOpen();

expect(getByText('No options available')).toBeInTheDocument();
});

it('renders options correctly', async () => {
const { getByTestId } = appMockRender.render(
<SolutionFilter
onSelectedOptionsChanged={onSelectedOptionsChanged}
selectedOptions={[]}
options={solutions}
/>
);

expect(getByTestId('solution-filter-popover-button')).toBeInTheDocument();

userEvent.click(getByTestId('solution-filter-popover-button'));

await waitForEuiPopoverOpen();

expect(getByTestId(`solution-filter-popover-item-${solutions[0].id}`)).toBeInTheDocument();
expect(getByTestId(`solution-filter-popover-item-${solutions[0].id}`)).toBeInTheDocument();
});

it('should call onSelectionChange with selected solution id', async () => {
const { getByTestId } = appMockRender.render(
<SolutionFilter
onSelectedOptionsChanged={onSelectedOptionsChanged}
selectedOptions={[]}
options={solutions}
/>
);

userEvent.click(getByTestId('solution-filter-popover-button'));

await waitForEuiPopoverOpen();

userEvent.click(getByTestId(`solution-filter-popover-item-${solutions[0].id}`));

expect(onSelectedOptionsChanged).toHaveBeenCalledWith([solutions[0].id]);
});

it('should call onSelectionChange with empty array when solution option is deselected', async () => {
const { getByTestId } = appMockRender.render(
<SolutionFilter
onSelectedOptionsChanged={onSelectedOptionsChanged}
selectedOptions={[solutions[1].id]}
options={solutions}
/>
);

userEvent.click(getByTestId('solution-filter-popover-button'));

await waitForEuiPopoverOpen();

userEvent.click(getByTestId(`solution-filter-popover-item-${solutions[1].id}`));

expect(onSelectedOptionsChanged).toHaveBeenCalledWith([]);
});
});
Loading

0 comments on commit daf1304

Please sign in to comment.