Skip to content

Commit

Permalink
[ResponseOps][Cases] Allow users to edit or delete template (#185877)
Browse files Browse the repository at this point in the history
## Summary

Merging into feature branch.

Implements edit and delete functionality
#181864

Edit or delete template from case settings page.


https://github.com/elastic/kibana/assets/117571355/f6fd45fa-c1eb-45ab-8936-02c764dbadc4


**How to test**
- Go to Cases > Settings
- Add a template
- Click on edit template icon
- Update the fields
- Click save

- Click on delete template icon
- Click on confirm modal
- Click save

**Scenarios:** 
- Edit template with different custom fields
- Edit template  with connector
- Delete template

**Flaky Test**
[here](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6331)

### Checklist

- [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] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
js-jankisalvi and kibanamachine authored Jun 19, 2024
1 parent f975c33 commit c3b0759
Show file tree
Hide file tree
Showing 48 changed files with 2,026 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('CustomFields', () => {
configurationCustomFields: customFieldsConfigurationMock,
isLoading: false,
setCustomFieldsOptional: false,
isEditMode: false,
};

beforeEach(() => {
Expand Down Expand Up @@ -77,6 +78,23 @@ describe('CustomFields', () => {
expect(screen.getAllByTestId('form-optional-field-label')).toHaveLength(2);
});

it('should not set default value when in edit mode', async () => {
appMockRender.render(
<FormTestComponent onSubmit={onSubmit}>
<CustomFields
isLoading={false}
configurationCustomFields={[customFieldsConfigurationMock[0]]}
setCustomFieldsOptional={false}
isEditMode={true}
/>
</FormTestComponent>
);

expect(
screen.queryByText(`${customFieldsConfigurationMock[0].defaultValue}`)
).not.toBeInTheDocument();
});

it('should sort the custom fields correctly', async () => {
const reversedCustomFieldsConfiguration = [...customFieldsConfigurationMock].reverse();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ interface Props {
isLoading: boolean;
setCustomFieldsOptional: boolean;
configurationCustomFields: CasesConfigurationUI['customFields'];
isEditMode?: boolean;
}

const CustomFieldsComponent: React.FC<Props> = ({
isLoading,
setCustomFieldsOptional,
configurationCustomFields,
isEditMode,
}) => {
const sortedCustomFields = useMemo(
() => sortCustomFieldsByLabel(configurationCustomFields),
Expand All @@ -42,6 +44,7 @@ const CustomFieldsComponent: React.FC<Props> = ({
customFieldConfiguration={customField}
key={customField.key}
setAsOptional={setCustomFieldsOptional}
setDefaultValue={!isEditMode}
/>
);
}
Expand Down
120 changes: 111 additions & 9 deletions x-pack/plugins/cases/public/components/case_form_fields/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jest.mock('../../containers/user_profiles/api');
describe('CaseFormFields', () => {
let appMock: AppMockRenderer;
const onSubmit = jest.fn();
const formDefaultValue = { tags: [] };
const defaultProps = {
isLoading: false,
configurationCustomFields: [],
Expand All @@ -36,7 +37,7 @@ describe('CaseFormFields', () => {

it('renders correctly', async () => {
appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);
Expand All @@ -46,7 +47,7 @@ describe('CaseFormFields', () => {

it('renders case fields correctly', async () => {
appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);
Expand All @@ -60,7 +61,7 @@ describe('CaseFormFields', () => {

it('does not render customFields when empty', () => {
appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);
Expand All @@ -70,7 +71,7 @@ describe('CaseFormFields', () => {

it('renders customFields when not empty', async () => {
appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields
isLoading={false}
configurationCustomFields={customFieldsConfigurationMock}
Expand All @@ -83,7 +84,7 @@ describe('CaseFormFields', () => {

it('does not render assignees when no platinum license', () => {
appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);
Expand All @@ -99,7 +100,7 @@ describe('CaseFormFields', () => {
appMock = createAppMockRenderer({ license });

appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);
Expand All @@ -109,7 +110,7 @@ describe('CaseFormFields', () => {

it('calls onSubmit with case fields', async () => {
appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);
Expand Down Expand Up @@ -145,14 +146,44 @@ describe('CaseFormFields', () => {
});
});

it('calls onSubmit with existing case fields', async () => {
appMock.render(
<FormTestComponent
formDefaultValue={{
title: 'Case with Template 1',
description: 'This is a case description',
tags: ['case-tag-1', 'case-tag-2'],
category: null,
}}
onSubmit={onSubmit}
>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);

userEvent.click(await screen.findByText('Submit'));

await waitFor(() => {
expect(onSubmit).toBeCalledWith(
{
category: null,
tags: ['case-tag-1', 'case-tag-2'],
description: 'This is a case description',
title: 'Case with Template 1',
},
true
);
});
});

it('calls onSubmit with custom fields', async () => {
const newProps = {
...defaultProps,
configurationCustomFields: customFieldsConfigurationMock,
};

appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...newProps} />
</FormTestComponent>
);
Expand Down Expand Up @@ -191,6 +222,44 @@ describe('CaseFormFields', () => {
});
});

it('calls onSubmit with existing custom fields', async () => {
const newProps = {
...defaultProps,
configurationCustomFields: customFieldsConfigurationMock,
};

appMock.render(
<FormTestComponent
formDefaultValue={{
customFields: { [customFieldsConfigurationMock[0].key]: 'Test custom filed value' },
tags: [],
}}
onSubmit={onSubmit}
>
<CaseFormFields {...newProps} />
</FormTestComponent>
);

expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument();

userEvent.click(await screen.findByText('Submit'));

await waitFor(() => {
expect(onSubmit).toBeCalledWith(
{
category: null,
tags: [],
customFields: {
test_key_1: 'Test custom filed value',
test_key_2: true,
test_key_4: false,
},
},
true
);
});
});

it('calls onSubmit with assignees', async () => {
const license = licensingMock.createLicense({
license: { type: 'platinum' },
Expand All @@ -199,7 +268,7 @@ describe('CaseFormFields', () => {
appMock = createAppMockRenderer({ license });

appMock.render(
<FormTestComponent onSubmit={onSubmit}>
<FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);
Expand All @@ -225,4 +294,37 @@ describe('CaseFormFields', () => {
);
});
});

it('calls onSubmit with existing assignees', async () => {
const license = licensingMock.createLicense({
license: { type: 'platinum' },
});

appMock = createAppMockRenderer({ license });

appMock.render(
<FormTestComponent
formDefaultValue={{
assignees: [{ uid: userProfiles[1].uid }],
tags: [],
}}
onSubmit={onSubmit}
>
<CaseFormFields {...defaultProps} />
</FormTestComponent>
);

userEvent.click(await screen.findByText('Submit'));

await waitFor(() => {
expect(onSubmit).toBeCalledWith(
{
category: null,
tags: [],
assignees: [{ uid: userProfiles[1].uid }],
},
true
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ interface Props {
isLoading: boolean;
configurationCustomFields: CasesConfigurationUI['customFields'];
setCustomFieldsOptional?: boolean;
isEditMode?: boolean;
}

const CaseFormFieldsComponent: React.FC<Props> = ({
isLoading,
configurationCustomFields,
setCustomFieldsOptional = false,
isEditMode,
}) => {
const { caseAssignmentAuthorized } = useCasesFeatures();

Expand All @@ -48,6 +50,7 @@ const CaseFormFieldsComponent: React.FC<Props> = ({
isLoading={isLoading}
setCustomFieldsOptional={setCustomFieldsOptional}
configurationCustomFields={configurationCustomFields}
isEditMode={isEditMode}
/>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 { DeleteConfirmationModal } from './delete_confirmation_modal';

describe('DeleteConfirmationModal', () => {
let appMock: AppMockRenderer;
const props = {
title: 'My custom field',
message: 'This is a sample message',
onCancel: jest.fn(),
onConfirm: jest.fn(),
};

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

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

expect(result.getByTestId('confirm-delete-modal')).toBeInTheDocument();
expect(result.getByText('Delete')).toBeInTheDocument();
expect(result.getByText('Cancel')).toBeInTheDocument();
});

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

expect(result.getByText('Delete')).toBeInTheDocument();
userEvent.click(result.getByText('Delete'));

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

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

expect(result.getByText('Cancel')).toBeInTheDocument();
userEvent.click(result.getByText('Cancel'));

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

interface ConfirmDeleteCaseModalProps {
title: string;
message: string;
onCancel: () => void;
onConfirm: () => void;
}

const DeleteConfirmationModalComponent: React.FC<ConfirmDeleteCaseModalProps> = ({
title,
message,
onCancel,
onConfirm,
}) => {
return (
<EuiConfirmModal
buttonColor="danger"
cancelButtonText={i18n.CANCEL}
data-test-subj="confirm-delete-modal"
defaultFocusedButton="confirm"
onCancel={onCancel}
onConfirm={onConfirm}
title={title}
confirmButtonText={i18n.DELETE}
>
{message}
</EuiConfirmModal>
);
};
DeleteConfirmationModalComponent.displayName = 'DeleteConfirmationModal';

export const DeleteConfirmationModal = React.memo(DeleteConfirmationModalComponent);
Loading

0 comments on commit c3b0759

Please sign in to comment.