Skip to content

Commit

Permalink
[Cases] added UI to add custom field on configuration page (text and …
Browse files Browse the repository at this point in the history
…toggle field) (#166483)

## Summary

Connected to #160236


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [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] 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))

### 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]>
Co-authored-by: Christos Nasikas <[email protected]>
  • Loading branch information
3 people authored Sep 20, 2023
1 parent e933759 commit d1debc5
Show file tree
Hide file tree
Showing 26 changed files with 1,402 additions and 11 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/public/common/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate('xpack.cases.configureC
});

export const CONFIGURE_CASES_BUTTON = i18n.translate('xpack.cases.configureCasesButton', {
defaultMessage: 'Edit external connection',
defaultMessage: 'Settings',
});

export const ADD_COMMENT = i18n.translate('xpack.cases.caseView.comment.addComment', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,20 @@ export const useCaseConfigureResponse: ReturnUseCaseConfigure = {
fields: null,
},
closureType: 'close-by-user',
customFields: [],
},
firstLoad: false,
loading: false,
mappings: [],
customFields: [],
persistCaseConfigure: jest.fn(),
persistLoading: false,
refetchCaseConfigure: jest.fn(),
setClosureType: jest.fn(),
setConnector: jest.fn(),
setCurrentConfiguration: jest.fn(),
setMappings: jest.fn(),
setCustomFields: jest.fn(),
version: '',
id: '',
};
Expand Down
136 changes: 133 additions & 3 deletions x-pack/plugins/cases/public/components/configure_cases/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import React from 'react';
import type { ReactWrapper } from 'enzyme';
import { mount } from 'enzyme';
import { waitFor } from '@testing-library/react';
import { waitFor, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { ConfigureCases } from '.';
import { noUpdateCasesPermissions, TestProviders } from '../../common/mock';
import { noUpdateCasesPermissions, TestProviders, createAppMockRenderer } from '../../common/mock';
import type { AppMockRenderer } from '../../common/mock';
import { Connectors } from './connectors';
import { ClosureOptions } from './closure_options';

Expand All @@ -25,7 +27,8 @@ import {
useConnectorsResponse,
useActionTypesResponse,
} from './__mock__';
import { ConnectorTypes } from '../../../common/types/domain';
import type { CustomFieldsConfiguration } from '../../../common/types/domain';
import { ConnectorTypes, CustomFieldTypes } from '../../../common/types/domain';
import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock';
import { useGetActionTypes } from '../../containers/configure/use_action_types';
import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors';
Expand Down Expand Up @@ -422,6 +425,7 @@ describe('ConfigureCases', () => {
fields: null,
},
closureType: 'close-by-user',
customFields: [],
});
});

Expand Down Expand Up @@ -511,6 +515,7 @@ describe('ConfigureCases', () => {
fields: null,
},
closureType: 'close-by-pushing',
customFields: [],
});
});
});
Expand Down Expand Up @@ -597,4 +602,129 @@ describe('ConfigureCases', () => {
).toBeFalsy();
});
});

describe('custom fields', () => {
let appMockRender: AppMockRenderer;

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

it('renders custom field group when no custom fields available', () => {
appMockRender.render(<ConfigureCases />);

expect(screen.getByTestId('custom-fields-form-group')).toBeInTheDocument();
});

it('renders custom field when available', () => {
const customFieldsMock: CustomFieldsConfiguration = [
{
key: 'random_custom_key',
label: 'summary',
type: CustomFieldTypes.TEXT,
required: true,
},
];
useCaseConfigureMock.mockImplementation(() => ({
...useCaseConfigureResponse,
customFields: customFieldsMock,
currentConfiguration: {
connector: {
id: 'resilient-2',
name: 'unchanged',
type: ConnectorTypes.serviceNowITSM,
fields: null,
},
closureType: 'close-by-user',
customFields: customFieldsMock,
},
}));
appMockRender.render(<ConfigureCases />);

const draggable = screen.getByTestId('draggable');

expect(
within(draggable).getByTestId(
`custom-field-${customFieldsMock[0].label}-${customFieldsMock[0].type}`
)
).toBeInTheDocument();
});

it('renders multiple custom field when available', () => {
const customFieldsMock: CustomFieldsConfiguration = [
{
key: 'random_custom_key',
label: 'Summary',
type: CustomFieldTypes.TEXT,
required: true,
},
{
key: 'random_custom_key_2',
label: 'Maintenance',
type: CustomFieldTypes.TOGGLE,
required: false,
},
];
useCaseConfigureMock.mockImplementation(() => ({
...useCaseConfigureResponse,
customFields: customFieldsMock,
currentConfiguration: {
connector: {
id: 'resilient-2',
name: 'unchanged',
type: ConnectorTypes.serviceNowITSM,
fields: null,
},
closureType: 'close-by-user',
customFields: customFieldsMock,
},
}));
appMockRender.render(<ConfigureCases />);

const droppable = screen.getByTestId('droppable');

for (const field of customFieldsMock) {
expect(
within(droppable).getByTestId(`custom-field-${field.label}-${field.type}`)
).toBeInTheDocument();
}
});

it('opens fly out for when click on add field', async () => {
appMockRender.render(<ConfigureCases />);

userEvent.click(screen.getByTestId('add-custom-field'));

expect(await screen.findByTestId('add-custom-field-flyout')).toBeInTheDocument();
});

it('closes fly out for when click on cancel', async () => {
appMockRender.render(<ConfigureCases />);

userEvent.click(screen.getByTestId('add-custom-field'));

expect(await screen.findByTestId('add-custom-field-flyout')).toBeInTheDocument();

userEvent.click(screen.getByTestId('add-custom-field-flyout-cancel'));

expect(await screen.findByTestId('custom-fields-form-group')).toBeInTheDocument();
expect(screen.queryByTestId('add-custom-field-flyout')).not.toBeInTheDocument();
});

it('closes fly out for when click on save field', async () => {
appMockRender.render(<ConfigureCases />);

userEvent.click(screen.getByTestId('add-custom-field'));

expect(await screen.findByTestId('add-custom-field-flyout')).toBeInTheDocument();

userEvent.paste(screen.getByTestId('custom-field-label-input'), 'Summary');

userEvent.click(screen.getByTestId('add-custom-field-flyout-save'));

expect(await screen.findByTestId('custom-fields-form-group')).toBeInTheDocument();
expect(screen.queryByTestId('add-custom-field-flyout')).not.toBeInTheDocument();
});
});
});
67 changes: 63 additions & 4 deletions x-pack/plugins/cases/public/components/configure_cases/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled, { css } from 'styled-components';

import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiLink, EuiPageBody } from '@elastic/eui';
import { EuiCallOut, EuiFlexItem, EuiLink, EuiPageBody } from '@elastic/eui';

import type { ActionConnectorTableItem } from '@kbn/triggers-actions-ui-plugin/public/types';
import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common';
Expand All @@ -29,7 +29,10 @@ import { HeaderPage } from '../header_page';
import { useCasesContext } from '../cases_context/use_cases_context';
import { useCasesBreadcrumbs } from '../use_breadcrumbs';
import { CasesDeepLinkId } from '../../common/navigation';
import { CustomFields } from '../custom_fields';
import { AddFieldFlyout } from '../custom_fields/add_field_flyout';
import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors';
import type { CustomFieldsConfiguration } from '../../../common/types/domain';

const FormWrapper = styled.div`
${({ theme }) => css`
Expand Down Expand Up @@ -60,9 +63,11 @@ export const ConfigureCases: React.FC = React.memo(() => {
const [editedConnectorItem, setEditedConnectorItem] = useState<ActionConnectorTableItem | null>(
null
);
const [addFieldFlyoutVisible, setAddFieldFlyoutVisibility] = useState<boolean>(false);

const {
connector,
customFields,
closureType,
loading: loadingCaseConfigure,
mappings,
Expand All @@ -71,6 +76,7 @@ export const ConfigureCases: React.FC = React.memo(() => {
refetchCaseConfigure,
setConnector,
setClosureType,
setCustomFields,
} = useCaseConfigure();

const {
Expand Down Expand Up @@ -101,11 +107,12 @@ export const ConfigureCases: React.FC = React.memo(() => {
await persistCaseConfigure({
connector: caseConnector,
closureType,
customFields,
});
onConnectorUpdated(createdConnector);
setConnector(caseConnector);
},
[onConnectorUpdated, closureType, setConnector, persistCaseConfigure]
[onConnectorUpdated, closureType, setConnector, customFields, persistCaseConfigure]
);

const isLoadingAny =
Expand Down Expand Up @@ -137,9 +144,10 @@ export const ConfigureCases: React.FC = React.memo(() => {
persistCaseConfigure({
connector: caseConnector,
closureType,
customFields,
});
},
[connectors, closureType, persistCaseConfigure, setConnector]
[connectors, closureType, customFields, persistCaseConfigure, setConnector]
);

const onChangeClosureType = useCallback(
Expand All @@ -148,9 +156,10 @@ export const ConfigureCases: React.FC = React.memo(() => {
persistCaseConfigure({
connector,
closureType: type,
customFields,
});
},
[connector, persistCaseConfigure, setClosureType]
[connector, customFields, persistCaseConfigure, setClosureType]
);

useEffect(() => {
Expand Down Expand Up @@ -202,6 +211,45 @@ export const ConfigureCases: React.FC = React.memo(() => {
[connector.id, editedConnectorItem, editFlyoutVisible]
);

const onAddCustomFields = useCallback(() => {
setAddFieldFlyoutVisibility(true);
}, [setAddFieldFlyoutVisibility]);

const onCloseAddFieldFlyout = useCallback(() => {
setAddFieldFlyoutVisibility(false);
}, [setAddFieldFlyoutVisibility]);

const onCustomFieldCreated = useCallback(
(customFieldData: CustomFieldsConfiguration) => {
const data = customFields.length ? [...customFields, ...customFieldData] : customFieldData;
setCustomFields(data);
persistCaseConfigure({
connector,
closureType,
customFields: [...customFields, ...customFieldData],
});

setAddFieldFlyoutVisibility(false);
},
[
setAddFieldFlyoutVisibility,
customFields,
setCustomFields,
persistCaseConfigure,
connector,
closureType,
]
);

const CustomFieldAddFlyout = addFieldFlyoutVisible ? (
<AddFieldFlyout
isLoading={loadingCaseConfigure}
disabled={!permissions.create || !permissions.update}
onCloseFlyout={onCloseAddFieldFlyout}
onSaveField={onCustomFieldCreated}
/>
) : null;

return (
<>
<HeaderPage
Expand Down Expand Up @@ -254,8 +302,19 @@ export const ConfigureCases: React.FC = React.memo(() => {
updateConnectorDisabled={updateConnectorDisabled || !permissions.update}
/>
</SectionWrapper>
<SectionWrapper>
<EuiFlexItem grow={false}>
<CustomFields
customFields={customFields}
isLoading={loadingCaseConfigure}
disabled={loadingCaseConfigure}
handleAddCustomField={onAddCustomFields}
/>
</EuiFlexItem>
</SectionWrapper>
{ConnectorAddFlyout}
{ConnectorEditFlyout}
{CustomFieldAddFlyout}
</FormWrapper>
</EuiPageBody>
</>
Expand Down
Loading

0 comments on commit d1debc5

Please sign in to comment.