[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:
Antonio 2022-12-05 18:48:03 +01:00 committed by GitHub
parent f7b74d0c4e
commit 83657cd537
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 438 additions and 91 deletions

View file

@ -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',

View file

@ -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}
>

View file

@ -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),
},
],
},

View file

@ -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) =>

View file

@ -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

View file

@ -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();
});
});

View file

@ -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);

View file

@ -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';

View file

@ -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();
});
});

View file

@ -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>
);
}
);

View file

@ -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',
});

View file

@ -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();
});
});

View file

@ -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 };
};

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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>
);

View file

@ -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",

View file

@ -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": "ケースを作成",

View file

@ -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": "创建案例",

View file

@ -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);