Skip to content

Commit

Permalink
[ResponseOps] [Cases] UX enhancements (#146608)
Browse files Browse the repository at this point in the history
Fixes #144614 

## Summary

Case Detail Page
- The ‘Add comment’ button in the Case detail page is now disabled until
a comment is filled in.
- The save button in the 'Edit Comment' section of the Case detail page
is now disabled if a comment is empty.
- The Tags in the activity stream now display a hollow badge equal to
the ones on the sidebar.
- Fixed the spacing between the headings in the sidebar. The gutter size
was increased.

Case Creation Page
- The Create and Cancel buttons were too close together. The distance
between them was increased.
- There is now a confirmation dialog if the user clicks the cancel
button.
- Introduced a restricted page width. The max is now 1200px.

All Cases list
- Brought back the distance between tags in the rows

### Screenshots

<details><summary>Disabled Add comment button</summary><img width="705"
alt="Screenshot 2022-11-29 at 19 04 25"
src="https://user-images.githubusercontent.com/1533137/204610436-69311744-0761-4ed9-9c38-d1763f230157.png"></details>

<details><summary>Enabled Add comment button</summary><img width="706"
alt="Screenshot 2022-11-29 at 19 04 34"
src="https://user-images.githubusercontent.com/1533137/204610667-a8befd8a-944c-4e64-bc97-21bd7685acf8.png"></details>

<details><summary>Hollow tags</summary><img width="368" alt="Screenshot
2022-11-29 at 19 03 33"
src="https://user-images.githubusercontent.com/1533137/204609898-33f1609e-6f42-4fdb-853d-5c049838f1f3.png"></details>

<details><summary>Fixed sidebar spacing</summary><img width="696"
alt="Screenshot 2022-11-28 at 12 11 52"
src="https://user-images.githubusercontent.com/1533137/204607633-954b8aa4-697f-4a48-9337-4a82448b853d.png"></details>

<details><summary>Create and Cancel button spacing</summary><img
width="289" alt="Screenshot 2022-11-29 at 19 05 54"
src="https://user-images.githubusercontent.com/1533137/204611166-3644f59f-c5f8-405f-9587-00e7840f37fc.png"></details>

<details><summary>Cancel create case confirmation dialog</summary><img
width="424" alt="Screenshot 2022-11-29 at 19 06 03"
src="https://user-images.githubusercontent.com/1533137/204611225-1ef49533-8e14-49f1-8ba7-6cf17bac4305.png">
</details>

<details><summary>Centered, restricted page width</summary><img
width="2247" alt="Screenshot 2022-11-28 at 14 58 34"
src="https://user-images.githubusercontent.com/1533137/204609106-fae1d3dc-3f8d-47b3-a4bb-fe9398bf75c0.png"></details>

<details><summary>Margin between tags</summary><img width="662"
alt="Screenshot 2022-12-02 at 10 59 26"
src="https://user-images.githubusercontent.com/1533137/205267224-f7bbb909-1bb4-42f8-b0e3-e1c9a2a7b8a6.png"></details>
  • Loading branch information
adcoelho authored Dec 5, 2022
1 parent f7b74d0 commit 83657cd
Show file tree
Hide file tree
Showing 21 changed files with 438 additions and 91 deletions.
9 changes: 6 additions & 3 deletions x-pack/plugins/cases/public/common/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,12 @@ export const ARIA_KEYPAD_LEGEND = i18n.translate(
}
);

export const COMMENT_REQUIRED = i18n.translate('xpack.cases.caseView.commentFieldRequiredError', {
defaultMessage: 'A comment is required.',
});
export const EMPTY_COMMENTS_NOT_ALLOWED = i18n.translate(
'xpack.cases.caseView.commentFieldRequiredError',
{
defaultMessage: 'Empty comments are not allowed.',
}
);

export const REQUIRED_FIELD = i18n.translate('xpack.cases.caseView.fieldRequiredError', {
defaultMessage: 'Required field',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export const AddComment = React.memo(
data-test-subj="submit-comment"
fill
iconType="plusInCircle"
isDisabled={isLoading}
isDisabled={!comment || isLoading}
isLoading={isLoading}
onClick={onSubmit}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form
import { FIELD_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
import type { CommentRequestUserType } from '../../../common/api';

import * as i18n from './translations';

const { emptyField } = fieldValidators;
Expand All @@ -22,7 +23,7 @@ export const schema: FormSchema<AddCommentFormSchema> = {
type: FIELD_TYPES.TEXTAREA,
validations: [
{
validator: emptyField(i18n.COMMENT_REQUIRED),
validator: emptyField(i18n.EMPTY_COMMENTS_NOT_ALLOWED),
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ const LineClampedEuiBadgeGroup = euiStyled(EuiBadgeGroup)`
word-break: normal;
`;

// margin-right is required here because -webkit-box-orient: vertical;
// in the EuiBadgeGroup prevents us from defining gutterSize.
const StyledEuiBadge = euiStyled(EuiBadge)`
max-width: 100px
max-width: 100px;
margin-right: 5px;
`; // to allow for ellipsis

const renderStringField = (field: string, dataTestSubj: string) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export const CaseViewActivity = ({
)}
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiFlexGroup direction="column" responsive={false} gutterSize="l">
<EuiFlexGroup direction="column" responsive={false} gutterSize="xl">
{caseAssignmentAuthorized ? (
<>
<AssignUsers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 userEvent from '@testing-library/user-event';
import React from 'react';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';
import { CancelCreationConfirmationModal } from './cancel_creation_confirmation_modal';

describe('CancelCreationConfirmationModal', () => {
let appMock: AppMockRenderer;
const props = {
title: 'My title',
confirmButtonText: 'My confirm button text',
cancelButtonText: 'My cancel button text',
onCancel: jest.fn(),
onConfirm: jest.fn(),
};

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

it('renders correctly', async () => {
const result = appMock.render(<CancelCreationConfirmationModal {...props} />);

expect(result.getByTestId('cancel-creation-confirmation-modal')).toBeInTheDocument();
expect(result.getByText(props.title)).toBeInTheDocument();
expect(result.getByText(props.confirmButtonText)).toBeInTheDocument();
expect(result.getByText(props.cancelButtonText)).toBeInTheDocument();
});

it('calls onConfirm', async () => {
const result = appMock.render(<CancelCreationConfirmationModal {...props} />);

expect(result.getByText(props.confirmButtonText)).toBeInTheDocument();
userEvent.click(result.getByText(props.confirmButtonText));

expect(props.onConfirm).toHaveBeenCalled();
});

it('calls onCancel', async () => {
const result = appMock.render(<CancelCreationConfirmationModal {...props} />);

expect(result.getByText(props.cancelButtonText)).toBeInTheDocument();
userEvent.click(result.getByText(props.cancelButtonText));

expect(props.onCancel).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 type { EuiConfirmModalProps } from '@elastic/eui';
import { EuiConfirmModal } from '@elastic/eui';
import * as i18n from './translations';

type Props = Pick<
EuiConfirmModalProps,
'title' | 'confirmButtonText' | 'cancelButtonText' | 'onConfirm' | 'onCancel'
>;

const CancelCreationConfirmationModalComponent: React.FC<Props> = ({
title,
confirmButtonText = i18n.CONFIRM_MODAL_BUTTON,
cancelButtonText = i18n.CANCEL_MODAL_BUTTON,
onConfirm,
onCancel,
}) => {
return (
<EuiConfirmModal
title={title}
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText={cancelButtonText}
confirmButtonText={confirmButtonText}
buttonColor="danger"
defaultFocusedButton="confirm"
data-test-subj="cancel-creation-confirmation-modal"
/>
);
};

CancelCreationConfirmationModalComponent.displayName = 'CancelCreationConfirmationModal';

export const CancelCreationConfirmationModal = React.memo(CancelCreationConfirmationModalComponent);
94 changes: 55 additions & 39 deletions x-pack/plugins/cases/public/components/create/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { useAvailableCasesOwners } from '../app/use_available_owners';
import type { CaseAttachmentsWithoutOwner } from '../../types';
import { Severity } from './severity';
import { Assignees } from './assignees';
import { useCancelCreationAction } from './use_cancel_creation_action';
import { CancelCreationConfirmationModal } from './cancel_creation_confirmation_modal';

interface ContainerProps {
big?: boolean;
Expand Down Expand Up @@ -184,45 +186,59 @@ export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo(
timelineIntegration,
attachments,
initialValue,
}) => (
<CasesTimelineIntegrationProvider timelineIntegration={timelineIntegration}>
<FormContext
afterCaseCreated={afterCaseCreated}
onSuccess={onSuccess}
attachments={attachments}
initialValue={initialValue}
>
<CreateCaseFormFields
connectors={empty}
isLoadingConnectors={false}
withSteps={withSteps}
/>
<Container>
<EuiFlexGroup
alignItems="center"
justifyContent="flexEnd"
gutterSize="xs"
responsive={false}
>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="create-case-cancel"
iconType="cross"
onClick={onCancel}
size="s"
>
{i18n.CANCEL}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SubmitCaseButton />
</EuiFlexItem>
</EuiFlexGroup>
</Container>
<InsertTimeline fieldName={descriptionFieldName} />
</FormContext>
</CasesTimelineIntegrationProvider>
)
}) => {
const { showConfirmationModal, onOpenModal, onConfirmModal, onCancelModal } =
useCancelCreationAction({
onConfirmationCallback: onCancel,
});

return (
<CasesTimelineIntegrationProvider timelineIntegration={timelineIntegration}>
<FormContext
afterCaseCreated={afterCaseCreated}
onSuccess={onSuccess}
attachments={attachments}
initialValue={initialValue}
>
<CreateCaseFormFields
connectors={empty}
isLoadingConnectors={false}
withSteps={withSteps}
/>
<Container>
<EuiFlexGroup
alignItems="center"
justifyContent="flexEnd"
gutterSize="l"
responsive={false}
>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="create-case-cancel"
iconType="cross"
onClick={onOpenModal}
size="s"
>
{i18n.CANCEL}
</EuiButtonEmpty>
{showConfirmationModal && (
<CancelCreationConfirmationModal
title={i18n.MODAL_TITLE}
onConfirm={onConfirmModal}
onCancel={onCancelModal}
/>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SubmitCaseButton />
</EuiFlexItem>
</EuiFlexGroup>
</Container>
<InsertTimeline fieldName={descriptionFieldName} />
</FormContext>
</CasesTimelineIntegrationProvider>
);
}
);

CreateCaseForm.displayName = 'CreateCaseForm';
62 changes: 57 additions & 5 deletions x-pack/plugins/cases/public/components/create/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import React from 'react';
import type { ReactWrapper } from 'enzyme';
import { mount } from 'enzyme';
import { act } from '@testing-library/react';
import { act, waitFor } from '@testing-library/react';

import type { EuiComboBoxOptionOption } from '@elastic/eui';
import { EuiComboBox } from '@elastic/eui';

Expand Down Expand Up @@ -105,16 +106,66 @@ describe('CreateCase case', () => {
});
});

it('should call cancel on cancel click', async () => {
it('should open modal on cancel click', async () => {
const wrapper = mount(
<TestProviders>
<CreateCase {...defaultProps} />
</TestProviders>
);
await act(async () => {
wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click');

wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click');

await waitFor(() => {
expect(
wrapper.find(`[data-test-subj="cancel-creation-confirmation-modal"]`).exists()
).toBeTruthy();
});
});

it('should confirm cancelation on modal confirm click', async () => {
const wrapper = mount(
<TestProviders>
<CreateCase {...defaultProps} />
</TestProviders>
);

wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click');

await waitFor(() => {
expect(
wrapper.find(`[data-test-subj="cancel-creation-confirmation-modal"]`).exists()
).toBeTruthy();
});

wrapper.find(`button[data-test-subj="confirmModalConfirmButton"]`).simulate('click');

await waitFor(() => {
expect(defaultProps.onCancel).toHaveBeenCalled();
});
});

it('should close modal on modal cancel click', async () => {
const wrapper = mount(
<TestProviders>
<CreateCase {...defaultProps} />
</TestProviders>
);

wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click');

await waitFor(() => {
expect(
wrapper.find(`[data-test-subj="cancel-creation-confirmation-modal"]`).exists()
).toBeTruthy();
});

wrapper.find(`button[data-test-subj="confirmModalCancelButton"]`).simulate('click');

await waitFor(() => {
expect(
wrapper.find(`[data-test-subj="cancel-creation-confirmation-modal"]`).exists()
).toBeFalsy();
});
expect(defaultProps.onCancel).toHaveBeenCalled();
});

it('should redirect to new case when posting the case', async () => {
Expand All @@ -128,6 +179,7 @@ describe('CreateCase case', () => {
fillForm(wrapper);
wrapper.find(`button[data-test-subj="create-case-submit"]`).first().simulate('click');
});

expect(defaultProps.onSuccess).toHaveBeenCalled();
});
});
5 changes: 3 additions & 2 deletions x-pack/plugins/cases/public/components/create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React from 'react';

import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components';
import { getUseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { EuiPageSection } from '@elastic/eui';
import * as i18n from './translations';
import type { CreateCaseFormProps } from './form';
import { CreateCaseForm } from './form';
Expand All @@ -23,7 +24,7 @@ export const CreateCase = React.memo<CreateCaseFormProps>(
useCasesBreadcrumbs(CasesDeepLinkId.casesCreate);

return (
<>
<EuiPageSection restrictWidth={true}>
<HeaderPage
showBackButton={true}
data-test-subj="case-create-title"
Expand All @@ -36,7 +37,7 @@ export const CreateCase = React.memo<CreateCaseFormProps>(
timelineIntegration={timelineIntegration}
withSteps={withSteps}
/>
</>
</EuiPageSection>
);
}
);
Expand Down
Loading

0 comments on commit 83657cd

Please sign in to comment.