mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ResponseOps] [Cases] UX enhancements (#146608)
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>
This commit is contained in:
parent
f7b74d0c4e
commit
83657cd537
21 changed files with 438 additions and 91 deletions
|
@ -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',
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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;
|
||||
|
@ -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),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
|
@ -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;
|
||||
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -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 () => {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
@ -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"
|
||||
|
@ -36,7 +37,7 @@ export const CreateCase = React.memo<CreateCaseFormProps>(
|
|||
timelineIntegration={timelineIntegration}
|
||||
withSteps={withSteps}
|
||||
/>
|
||||
</>
|
||||
</EuiPageSection>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -29,3 +29,15 @@ export const SYNC_ALERTS_LABEL = i18n.translate('xpack.cases.create.syncAlertsLa
|
|||
export const ASSIGN_YOURSELF = i18n.translate('xpack.cases.create.assignYourself', {
|
||||
defaultMessage: 'Assign yourself',
|
||||
});
|
||||
|
||||
export const MODAL_TITLE = i18n.translate('xpack.cases.create.modalTitle', {
|
||||
defaultMessage: 'Discard case?',
|
||||
});
|
||||
|
||||
export const CANCEL_MODAL_BUTTON = i18n.translate('xpack.cases.create.cancelModalButton', {
|
||||
defaultMessage: 'Cancel',
|
||||
});
|
||||
|
||||
export const CONFIRM_MODAL_BUTTON = i18n.translate('xpack.cases.create.confirmModalButton', {
|
||||
defaultMessage: 'Exit without saving',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { renderHook, act } from '@testing-library/react-hooks';
|
||||
import type { AppMockRenderer } from '../../common/mock';
|
||||
import { createAppMockRenderer } from '../../common/mock';
|
||||
import { useCancelCreationAction } from './use_cancel_creation_action';
|
||||
|
||||
describe('UseConfirmationModal', () => {
|
||||
let appMockRender: AppMockRenderer;
|
||||
const onConfirmationCallback = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
appMockRender = createAppMockRenderer();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('init', async () => {
|
||||
const { result } = renderHook(() => useCancelCreationAction({ onConfirmationCallback }), {
|
||||
wrapper: appMockRender.AppWrapper,
|
||||
});
|
||||
|
||||
expect(result.current.showConfirmationModal).toBe(false);
|
||||
});
|
||||
|
||||
it('opens the modal', async () => {
|
||||
const { result } = renderHook(() => useCancelCreationAction({ onConfirmationCallback }), {
|
||||
wrapper: appMockRender.AppWrapper,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.onOpenModal();
|
||||
});
|
||||
|
||||
expect(result.current.showConfirmationModal).toBe(true);
|
||||
});
|
||||
|
||||
it('closes the modal', async () => {
|
||||
const { result } = renderHook(() => useCancelCreationAction({ onConfirmationCallback }), {
|
||||
wrapper: appMockRender.AppWrapper,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.onOpenModal();
|
||||
});
|
||||
|
||||
expect(result.current.showConfirmationModal).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.onCancelModal();
|
||||
});
|
||||
|
||||
expect(result.current.showConfirmationModal).toBe(false);
|
||||
});
|
||||
|
||||
it('calls onConfirmationCallback on confirm', async () => {
|
||||
const { result } = renderHook(() => useCancelCreationAction({ onConfirmationCallback }), {
|
||||
wrapper: appMockRender.AppWrapper,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.onOpenModal();
|
||||
});
|
||||
|
||||
expect(result.current.showConfirmationModal).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.onConfirmModal();
|
||||
});
|
||||
|
||||
expect(result.current.showConfirmationModal).toBe(false);
|
||||
expect(onConfirmationCallback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { useCallback, useState } from 'react';
|
||||
|
||||
interface Props {
|
||||
onConfirmationCallback: () => void;
|
||||
}
|
||||
|
||||
export const useCancelCreationAction = ({ onConfirmationCallback }: Props) => {
|
||||
const [showConfirmationModal, setShowConfirmationModal] = useState(false);
|
||||
|
||||
const onOpenModal = useCallback(() => {
|
||||
setShowConfirmationModal(true);
|
||||
}, []);
|
||||
|
||||
const onConfirmModal = useCallback(() => {
|
||||
setShowConfirmationModal(false);
|
||||
onConfirmationCallback();
|
||||
}, [onConfirmationCallback]);
|
||||
|
||||
const onCancelModal = useCallback(() => {
|
||||
setShowConfirmationModal(false);
|
||||
}, []);
|
||||
|
||||
return { showConfirmationModal, onOpenModal, onConfirmModal, onCancelModal };
|
||||
};
|
|
@ -16,6 +16,7 @@ const onChangeEditable = jest.fn();
|
|||
const onSaveContent = jest.fn();
|
||||
|
||||
const newValue = 'Hello from Tehas';
|
||||
const emptyValue = '';
|
||||
const hyperlink = `[hyperlink](http://elastic.co)`;
|
||||
const defaultProps = {
|
||||
content: `A link to a timeline ${hyperlink}`,
|
||||
|
@ -61,6 +62,7 @@ describe('UserActionMarkdown ', () => {
|
|||
expect(onChangeEditable).toHaveBeenCalledWith(defaultProps.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('Does not call onSaveContent if no change from current text', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
|
@ -75,6 +77,28 @@ describe('UserActionMarkdown ', () => {
|
|||
});
|
||||
expect(onSaveContent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Save button disabled if current text is empty', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<UserActionMarkdown {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find(`.euiMarkdownEditorTextArea`)
|
||||
.first()
|
||||
.simulate('change', {
|
||||
target: { value: emptyValue },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.find(`button[data-test-subj="user-action-save-markdown"]`).first().prop('disabled')
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('Cancel button click calls only onChangeEditable', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
|
|
|
@ -5,15 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/eui';
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Form, useForm, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import * as i18n from '../case_view/translations';
|
||||
import type { Content } from './schema';
|
||||
import { schema } from './schema';
|
||||
import { MarkdownRenderer, MarkdownEditorForm } from '../markdown_editor';
|
||||
import { UserActionMarkdownFooter } from './markdown_form_footer';
|
||||
|
||||
export const ContentWrapper = styled.div`
|
||||
padding: ${({ theme }) => `${theme.eui.euiSizeM} ${theme.eui.euiSizeL}`};
|
||||
|
@ -66,36 +65,6 @@ const UserActionMarkdownComponent = forwardRef<
|
|||
[setFieldValue]
|
||||
);
|
||||
|
||||
const EditorButtons = useMemo(
|
||||
() => (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="user-action-cancel-markdown"
|
||||
size="s"
|
||||
onClick={handleCancelAction}
|
||||
iconType="cross"
|
||||
>
|
||||
{i18n.CANCEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="user-action-save-markdown"
|
||||
color="success"
|
||||
fill
|
||||
iconType="save"
|
||||
onClick={handleSaveAction}
|
||||
size="s"
|
||||
>
|
||||
{i18n.SAVE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[handleCancelAction, handleSaveAction]
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setComment,
|
||||
editor: editorRef.current,
|
||||
|
@ -111,7 +80,12 @@ const UserActionMarkdownComponent = forwardRef<
|
|||
'aria-label': 'Cases markdown editor',
|
||||
value: content,
|
||||
id,
|
||||
bottomRightContent: EditorButtons,
|
||||
bottomRightContent: (
|
||||
<UserActionMarkdownFooter
|
||||
handleSaveAction={handleSaveAction}
|
||||
handleCancelAction={handleCancelAction}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Form>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
|
||||
import * as i18n from '../case_view/translations';
|
||||
|
||||
interface UserActionMarkdownFooterProps {
|
||||
handleSaveAction: () => Promise<void>;
|
||||
handleCancelAction: () => void;
|
||||
}
|
||||
|
||||
const UserActionMarkdownFooterComponent: React.FC<UserActionMarkdownFooterProps> = ({
|
||||
handleSaveAction,
|
||||
handleCancelAction,
|
||||
}) => {
|
||||
const [{ content }] = useFormData<{ content: string }>({ watch: ['content'] });
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="user-action-cancel-markdown"
|
||||
size="s"
|
||||
onClick={handleCancelAction}
|
||||
iconType="cross"
|
||||
>
|
||||
{i18n.CANCEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="user-action-save-markdown"
|
||||
color="success"
|
||||
fill
|
||||
iconType="save"
|
||||
onClick={handleSaveAction}
|
||||
disabled={!content}
|
||||
size="s"
|
||||
>
|
||||
{i18n.SAVE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
UserActionMarkdownFooterComponent.displayName = 'UserActionMarkdownFooterComponent';
|
||||
|
||||
export const UserActionMarkdownFooter = React.memo(UserActionMarkdownFooterComponent);
|
|
@ -25,7 +25,7 @@ const getLabelTitle = (userAction: UserActionResponse<TagsUserAction>) => {
|
|||
{userAction.action === Actions.delete && i18n.REMOVED_FIELD} {i18n.TAGS.toLowerCase()}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Tags tags={tags} gutterSize="xs" />
|
||||
<Tags tags={tags} gutterSize="xs" color="hollow" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -9278,7 +9278,6 @@
|
|||
"xpack.cases.caseView.comment": "commentaire",
|
||||
"xpack.cases.caseView.comment.addComment": "Ajouter un commentaire",
|
||||
"xpack.cases.caseView.comment.addCommentHelpText": "Ajouter un nouveau commentaire...",
|
||||
"xpack.cases.caseView.commentFieldRequiredError": "Un commentaire est requis.",
|
||||
"xpack.cases.caseView.connectors": "Système de gestion des incidents externes",
|
||||
"xpack.cases.caseView.copyCommentLinkAria": "Copier le lien de référence",
|
||||
"xpack.cases.caseView.create": "Créer un cas",
|
||||
|
|
|
@ -9265,7 +9265,6 @@
|
|||
"xpack.cases.caseView.comment": "コメント",
|
||||
"xpack.cases.caseView.comment.addComment": "コメントを追加",
|
||||
"xpack.cases.caseView.comment.addCommentHelpText": "新しいコメントを追加...",
|
||||
"xpack.cases.caseView.commentFieldRequiredError": "コメントが必要です。",
|
||||
"xpack.cases.caseView.connectors": "外部インシデント管理システム",
|
||||
"xpack.cases.caseView.copyCommentLinkAria": "参照リンクをコピー",
|
||||
"xpack.cases.caseView.create": "ケースを作成",
|
||||
|
|
|
@ -9283,7 +9283,6 @@
|
|||
"xpack.cases.caseView.comment": "注释",
|
||||
"xpack.cases.caseView.comment.addComment": "添加注释",
|
||||
"xpack.cases.caseView.comment.addCommentHelpText": "添加新注释......",
|
||||
"xpack.cases.caseView.commentFieldRequiredError": "注释必填。",
|
||||
"xpack.cases.caseView.connectors": "外部事件管理系统",
|
||||
"xpack.cases.caseView.copyCommentLinkAria": "复制引用链接",
|
||||
"xpack.cases.caseView.create": "创建案例",
|
||||
|
|
|
@ -11,6 +11,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
|
|||
|
||||
export function ObservabilityPageProvider({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const textValue = 'Foobar';
|
||||
|
||||
return {
|
||||
async clickSolutionNavigationEntry(appId: string, navId: string) {
|
||||
|
@ -44,6 +45,7 @@ export function ObservabilityPageProvider({ getService, getPageObjects }: FtrPro
|
|||
},
|
||||
|
||||
async expectAddCommentButton() {
|
||||
await testSubjects.setValue('add-comment', textValue);
|
||||
const button = await testSubjects.find('submit-comment', 20000);
|
||||
const disabledAttr = await button.getAttribute('disabled');
|
||||
expect(disabledAttr).to.be(null);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue