mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.15`: - [[ResponseOps][Cases] Fix template's custom fields bugs (#187591)](https://github.com/elastic/kibana/pull/187591) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Janki Salvi","email":"117571355+js-jankisalvi@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-07-11T16:13:19Z","message":"[ResponseOps][Cases] Fix template's custom fields bugs (#187591)\n\n## Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/187333\r\n\r\n## Testing behaviour: \r\nIssue 1: verify similar behaviour from API as well.\r\n\r\n1. Create a template\r\n2. Add new toggle custom field with default value as true\r\n3. Go to create case, See that new toggle custom field has value: true\r\n4. Select recently created template\r\n5. Toggle custom field new custom field with it's default value\r\n\r\nIssue 2: verify similar behaviour from API as well.\r\n1. Create a text custom field with default value\r\n2. Create a template\r\n3. Set text custom field value to empty\r\n4. Save template\r\n5. Go to create case\r\n6. Select recently created template\r\n7. See that text custom field value is updated as per template's custom\r\nfield value","sha":"6b0d62805352c391fc7bfdb47ff848c0a46080ee","branchLabelMapping":{"^v8.16.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:ResponseOps","Feature:Cases","v8.15.0","v8.16.0"],"title":"[ResponseOps][Cases] Fix template's custom fields bugs","number":187591,"url":"https://github.com/elastic/kibana/pull/187591","mergeCommit":{"message":"[ResponseOps][Cases] Fix template's custom fields bugs (#187591)\n\n## Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/187333\r\n\r\n## Testing behaviour: \r\nIssue 1: verify similar behaviour from API as well.\r\n\r\n1. Create a template\r\n2. Add new toggle custom field with default value as true\r\n3. Go to create case, See that new toggle custom field has value: true\r\n4. Select recently created template\r\n5. Toggle custom field new custom field with it's default value\r\n\r\nIssue 2: verify similar behaviour from API as well.\r\n1. Create a text custom field with default value\r\n2. Create a template\r\n3. Set text custom field value to empty\r\n4. Save template\r\n5. Go to create case\r\n6. Select recently created template\r\n7. See that text custom field value is updated as per template's custom\r\nfield value","sha":"6b0d62805352c391fc7bfdb47ff848c0a46080ee"}},"sourceBranch":"main","suggestedTargetBranches":["8.15"],"targetPullRequestStates":[{"branch":"8.15","label":"v8.15.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/187591","number":187591,"mergeCommit":{"message":"[ResponseOps][Cases] Fix template's custom fields bugs (#187591)\n\n## Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/187333\r\n\r\n## Testing behaviour: \r\nIssue 1: verify similar behaviour from API as well.\r\n\r\n1. Create a template\r\n2. Add new toggle custom field with default value as true\r\n3. Go to create case, See that new toggle custom field has value: true\r\n4. Select recently created template\r\n5. Toggle custom field new custom field with it's default value\r\n\r\nIssue 2: verify similar behaviour from API as well.\r\n1. Create a text custom field with default value\r\n2. Create a template\r\n3. Set text custom field value to empty\r\n4. Save template\r\n5. Go to create case\r\n6. Select recently created template\r\n7. See that text custom field value is updated as per template's custom\r\nfield value","sha":"6b0d62805352c391fc7bfdb47ff848c0a46080ee"}}]}] BACKPORT--> Co-authored-by: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com>
This commit is contained in:
parent
4806820b03
commit
124d22811c
23 changed files with 1026 additions and 253 deletions
|
@ -600,6 +600,11 @@ describe('CommonFlyout ', () => {
|
|||
type: 'toggle',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: 'test_key_3',
|
||||
type: 'text',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: 'test_key_4',
|
||||
type: 'toggle',
|
||||
|
|
|
@ -724,6 +724,166 @@ describe('ConfigureCases', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('deletes a custom field from template while deleting custom field from configuration', async () => {
|
||||
useGetCaseConfigurationMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
data: {
|
||||
...useCaseConfigureResponse.data,
|
||||
customFields: customFieldsConfigurationMock,
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_4',
|
||||
name: 'Fourth test template',
|
||||
caseFields: {
|
||||
title: 'Case with sample template 4',
|
||||
description: 'case desc',
|
||||
customFields: [
|
||||
{
|
||||
key: customFieldsConfigurationMock[0].key,
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'this is a text field value',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
appMockRender.render(<ConfigureCases />);
|
||||
|
||||
const list = await screen.findByTestId('custom-fields-list');
|
||||
|
||||
userEvent.click(
|
||||
within(list).getByTestId(`${customFieldsConfigurationMock[0].key}-custom-field-delete`)
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument();
|
||||
|
||||
userEvent.click(screen.getByText('Delete'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(persistCaseConfigure).toHaveBeenCalledWith({
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
closureType: 'close-by-user',
|
||||
customFields: [
|
||||
{ ...customFieldsConfigurationMock[1] },
|
||||
{ ...customFieldsConfigurationMock[2] },
|
||||
{ ...customFieldsConfigurationMock[3] },
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_4',
|
||||
name: 'Fourth test template',
|
||||
caseFields: {
|
||||
title: 'Case with sample template 4',
|
||||
description: 'case desc',
|
||||
customFields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
id: '',
|
||||
version: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a custom field to template while adding a new custom field', async () => {
|
||||
useGetCaseConfigurationMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
data: {
|
||||
...useCaseConfigureResponse.data,
|
||||
customFields: customFieldsConfigurationMock,
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_4',
|
||||
name: 'Fourth test template',
|
||||
caseFields: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
appMockRender.render(<ConfigureCases />);
|
||||
|
||||
userEvent.click(await screen.findByTestId(`add-custom-field`));
|
||||
|
||||
expect(await screen.findByTestId('common-flyout')).toBeInTheDocument();
|
||||
|
||||
userEvent.paste(screen.getByTestId('custom-field-label-input'), 'New custom field');
|
||||
userEvent.click(screen.getByTestId('text-custom-field-required'));
|
||||
userEvent.paste(
|
||||
screen.getByTestId('text-custom-field-default-value'),
|
||||
'This is a default value'
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByTestId('common-flyout-save'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(persistCaseConfigure).toHaveBeenCalledWith({
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
closureType: 'close-by-user',
|
||||
customFields: [
|
||||
...customFieldsConfigurationMock,
|
||||
{
|
||||
key: expect.anything(),
|
||||
label: 'New custom field',
|
||||
type: CustomFieldTypes.TEXT as const,
|
||||
required: true,
|
||||
defaultValue: 'This is a default value',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_4',
|
||||
name: 'Fourth test template',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: customFieldsConfigurationMock[0].key,
|
||||
type: customFieldsConfigurationMock[0].type,
|
||||
value: customFieldsConfigurationMock[0].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFieldsConfigurationMock[1].key,
|
||||
type: customFieldsConfigurationMock[1].type,
|
||||
value: customFieldsConfigurationMock[1].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFieldsConfigurationMock[2].key,
|
||||
type: customFieldsConfigurationMock[2].type,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: customFieldsConfigurationMock[3].key,
|
||||
type: customFieldsConfigurationMock[3].type,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
key: expect.anything(),
|
||||
type: CustomFieldTypes.TEXT as const,
|
||||
value: 'This is a default value',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
id: '',
|
||||
version: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('updates a custom field correctly', async () => {
|
||||
useGetCaseConfigurationMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
|
@ -929,6 +1089,11 @@ describe('ConfigureCases', () => {
|
|||
type: customFieldsConfigurationMock[1].type,
|
||||
value: customFieldsConfigurationMock[1].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFieldsConfigurationMock[2].key,
|
||||
type: customFieldsConfigurationMock[2].type,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: customFieldsConfigurationMock[3].key,
|
||||
type: customFieldsConfigurationMock[3].type,
|
||||
|
|
|
@ -24,7 +24,11 @@ import {
|
|||
|
||||
import type { ActionConnectorTableItem } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import type { CustomFieldConfiguration, TemplateConfiguration } from '../../../common/types/domain';
|
||||
import type {
|
||||
CustomFieldConfiguration,
|
||||
TemplateConfiguration,
|
||||
CustomFieldTypes,
|
||||
} from '../../../common/types/domain';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { useGetActionTypes } from '../../containers/configure/use_action_types';
|
||||
import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration';
|
||||
|
@ -48,6 +52,8 @@ import { Templates } from '../templates';
|
|||
import type { TemplateFormProps } from '../templates/types';
|
||||
import { CustomFieldsForm } from '../custom_fields/form';
|
||||
import { TemplateForm } from '../templates/form';
|
||||
import type { CasesConfigurationUI, CaseUI } from '../../containers/types';
|
||||
import { builderMap as customFieldsBuilderMap } from '../custom_fields/builder';
|
||||
|
||||
const sectionWrapperCss = css`
|
||||
box-sizing: content-box;
|
||||
|
@ -68,6 +74,40 @@ interface Flyout {
|
|||
visible: boolean;
|
||||
}
|
||||
|
||||
const addNewCustomFieldToTemplates = ({
|
||||
templates,
|
||||
customFields,
|
||||
}: Pick<CasesConfigurationUI, 'templates' | 'customFields'>) => {
|
||||
return templates.map((template) => {
|
||||
const templateCustomFields = template.caseFields?.customFields ?? [];
|
||||
|
||||
customFields.forEach((field) => {
|
||||
if (
|
||||
!templateCustomFields.length ||
|
||||
!templateCustomFields.find((templateCustomField) => templateCustomField.key === field.key)
|
||||
) {
|
||||
const customFieldFactory = customFieldsBuilderMap[field.type];
|
||||
const { getDefaultValue } = customFieldFactory();
|
||||
const value = getDefaultValue?.() ?? null;
|
||||
|
||||
templateCustomFields.push({
|
||||
key: field.key,
|
||||
type: field.type as CustomFieldTypes,
|
||||
value: field.defaultValue ?? value,
|
||||
} as CaseUI['customFields'][number]);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...template,
|
||||
caseFields: {
|
||||
...template.caseFields,
|
||||
customFields: [...templateCustomFields],
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const ConfigureCases: React.FC = React.memo(() => {
|
||||
const { permissions } = useCasesContext();
|
||||
const { triggersActionsUi } = useKibana().services;
|
||||
|
@ -334,10 +374,16 @@ export const ConfigureCases: React.FC = React.memo(() => {
|
|||
(data: CustomFieldConfiguration) => {
|
||||
const updatedCustomFields = addOrReplaceField(customFields, data);
|
||||
|
||||
// add the new custom field to each template as well
|
||||
const updatedTemplates = addNewCustomFieldToTemplates({
|
||||
templates,
|
||||
customFields: updatedCustomFields,
|
||||
});
|
||||
|
||||
persistCaseConfigure({
|
||||
connector,
|
||||
customFields: updatedCustomFields,
|
||||
templates,
|
||||
templates: updatedTemplates,
|
||||
id: configurationId,
|
||||
version: configurationVersion,
|
||||
closureType,
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { css } from '@emotion/react';
|
||||
import { useFormContext } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
|
||||
import type { CasePostRequest } from '../../../common';
|
||||
import type { CasePostRequest, CaseUI } from '../../../common';
|
||||
import type { ActionConnector } from '../../../common/types/domain';
|
||||
import { Connector } from '../case_form_fields/connector';
|
||||
import * as i18n from './translations';
|
||||
|
@ -28,6 +28,7 @@ import { useCasesFeatures } from '../../common/use_cases_features';
|
|||
import { TemplateSelector } from './templates';
|
||||
import { getInitialCaseValue } from './utils';
|
||||
import { CaseFormFields } from '../case_form_fields';
|
||||
import { builderMap as customFieldsBuilderMap } from '../custom_fields/builder';
|
||||
|
||||
export interface CreateCaseFormFieldsProps {
|
||||
configuration: CasesConfigurationUI;
|
||||
|
@ -42,7 +43,22 @@ const transformTemplateCaseFieldsToCaseFormFields = (
|
|||
caseTemplateFields: CasesConfigurationUITemplate['caseFields']
|
||||
): CasePostRequest => {
|
||||
const caseFields = removeEmptyFields(caseTemplateFields ?? {});
|
||||
return getInitialCaseValue({ owner, ...caseFields });
|
||||
const transFormedCustomFields = caseFields?.customFields?.map((customField) => {
|
||||
const customFieldFactory = customFieldsBuilderMap[customField.type];
|
||||
const { convertNullToEmpty } = customFieldFactory();
|
||||
const value = convertNullToEmpty ? convertNullToEmpty(customField.value) : customField.value;
|
||||
|
||||
return {
|
||||
...customField,
|
||||
value,
|
||||
};
|
||||
});
|
||||
|
||||
return getInitialCaseValue({
|
||||
owner,
|
||||
...caseFields,
|
||||
customFields: transFormedCustomFields as CaseUI['customFields'],
|
||||
});
|
||||
};
|
||||
|
||||
export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.memo(
|
||||
|
|
|
@ -20,6 +20,7 @@ describe('configureTextCustomFieldFactory ', () => {
|
|||
label: 'Text',
|
||||
getEuiTableColumn: expect.any(Function),
|
||||
build: expect.any(Function),
|
||||
convertNullToEmpty: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,4 +25,5 @@ export const configureTextCustomFieldFactory: CustomFieldFactory<CaseCustomField
|
|||
View,
|
||||
Create,
|
||||
}),
|
||||
convertNullToEmpty: (value: string | boolean | null) => (value == null ? '' : String(value)),
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('configureToggleCustomFieldFactory ', () => {
|
|||
{ key: 'on', label: 'On', value: true },
|
||||
{ key: 'off', label: 'Off', value: false },
|
||||
],
|
||||
getDefaultValue: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,4 +30,5 @@ export const configureToggleCustomFieldFactory: CustomFieldFactory<CaseCustomFie
|
|||
{ key: 'on', label: i18n.TOGGLE_FIELD_ON_LABEL, value: true },
|
||||
{ key: 'off', label: i18n.TOGGLE_FIELD_OFF_LABEL, value: false },
|
||||
],
|
||||
getDefaultValue: () => false,
|
||||
});
|
||||
|
|
|
@ -54,6 +54,8 @@ export type CustomFieldFactory<T extends CaseUICustomField> = () => {
|
|||
getEuiTableColumn: (params: { label: string }) => CustomFieldEuiTableColumn;
|
||||
build: () => CustomFieldType<T>;
|
||||
filterOptions?: CustomFieldFactoryFilterOption[];
|
||||
getDefaultValue?: () => string | boolean | null;
|
||||
convertNullToEmpty?: (value: string | boolean | null) => string;
|
||||
};
|
||||
|
||||
export type CustomFieldBuilderMap = {
|
||||
|
|
|
@ -561,6 +561,11 @@ describe('TemplateForm', () => {
|
|||
type: 'toggle',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: 'test_key_3',
|
||||
type: 'text',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: 'test_key_4',
|
||||
type: 'toggle',
|
||||
|
@ -645,6 +650,11 @@ describe('TemplateForm', () => {
|
|||
type: 'toggle',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: 'test_key_3',
|
||||
type: 'text',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: 'test_key_4',
|
||||
type: 'toggle',
|
||||
|
|
|
@ -117,9 +117,9 @@ describe('utils', () => {
|
|||
name: 'template 1',
|
||||
templateDescription: '',
|
||||
customFields: {
|
||||
custom_field_1: 'foobar',
|
||||
custom_fields_2: '',
|
||||
custom_field_3: true,
|
||||
test_key_1: 'foobar',
|
||||
test_key_3: '',
|
||||
test_key_2: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -131,7 +131,11 @@ describe('utils', () => {
|
|||
name: 'none',
|
||||
type: '.none',
|
||||
},
|
||||
customFields: [],
|
||||
customFields: [
|
||||
{ key: 'test_key_1', type: 'text', value: 'foobar' },
|
||||
{ key: 'test_key_3', type: 'text', value: null },
|
||||
{ key: 'test_key_2', type: 'toggle', value: true },
|
||||
],
|
||||
settings: {
|
||||
syncAlerts: false,
|
||||
},
|
||||
|
|
|
@ -79,14 +79,19 @@ export const templateSerializer = (
|
|||
return data;
|
||||
}
|
||||
|
||||
const { fields: connectorFields = null, key, name, ...rest } = data;
|
||||
const {
|
||||
fields: connectorFields = null,
|
||||
key,
|
||||
name,
|
||||
customFields: templateCustomFields,
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
const serializedConnectorFields = getConnectorsFormSerializer({ fields: connectorFields });
|
||||
const nonEmptyFields = removeEmptyFields({ ...rest });
|
||||
|
||||
const {
|
||||
connectorId,
|
||||
customFields: templateCustomFields,
|
||||
syncAlerts = false,
|
||||
templateTags,
|
||||
templateDescription,
|
||||
|
|
|
@ -556,51 +556,6 @@ describe('client', () => {
|
|||
});
|
||||
|
||||
describe('customFields', () => {
|
||||
it('throws when there are no customFields in configure and template has customField in the request', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
attributes: {
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
update(
|
||||
'test-id',
|
||||
{
|
||||
version: 'test-version',
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
clientArgs,
|
||||
casesClientInternal
|
||||
)
|
||||
).rejects.toThrow(
|
||||
'Failed to get patch configure in route: Error: No custom fields configured.'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when template has duplicated custom field keys in the request', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
|
@ -637,6 +592,14 @@ describe('client', () => {
|
|||
'test-id',
|
||||
{
|
||||
version: 'test-version',
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
|
@ -667,67 +630,6 @@ describe('client', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('throws when there are invalid customField keys in the request', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
attributes: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
update(
|
||||
'test-id',
|
||||
{
|
||||
version: 'test-version',
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_2',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
clientArgs,
|
||||
casesClientInternal
|
||||
)
|
||||
).rejects.toThrow(
|
||||
'Failed to get patch configure in route: Error: Invalid custom field keys: custom_field_key_2'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when template has customField with invalid type in the request', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
|
@ -764,6 +666,14 @@ describe('client', () => {
|
|||
'test-id',
|
||||
{
|
||||
version: 'test-version',
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
|
@ -789,6 +699,334 @@ describe('client', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('adds new custom field to template when configuration custom fields have new custom field', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
attributes: {
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
customFields: [],
|
||||
templates: [],
|
||||
closure_type: 'close-by-user',
|
||||
owner: 'cases',
|
||||
},
|
||||
id: 'test-id',
|
||||
version: 'test-version',
|
||||
});
|
||||
|
||||
await update(
|
||||
'test-id',
|
||||
{
|
||||
version: 'test-version',
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
defaultValue: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
clientArgs,
|
||||
casesClientInternal
|
||||
);
|
||||
|
||||
expect(clientArgs.services.caseConfigureService.patch).toHaveBeenCalledWith({
|
||||
configurationId: 'test-id',
|
||||
originalConfiguration: {
|
||||
attributes: {
|
||||
closure_type: 'close-by-user',
|
||||
connector: {
|
||||
fields: null,
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: '.none',
|
||||
},
|
||||
customFields: [],
|
||||
owner: 'cases',
|
||||
templates: [],
|
||||
},
|
||||
id: 'test-id',
|
||||
version: 'test-version',
|
||||
},
|
||||
unsecuredSavedObjectsClient: expect.anything(),
|
||||
updatedAttributes: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
defaultValue: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
description: 'this is test description',
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
},
|
||||
],
|
||||
updated_at: expect.anything(),
|
||||
updated_by: expect.anything(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('updates default value of existing custom fields in the configuration correctly', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
attributes: {
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
defaultValue: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
closure_type: 'close-by-user',
|
||||
owner: 'cases',
|
||||
},
|
||||
id: 'test-id',
|
||||
version: 'test-version',
|
||||
});
|
||||
|
||||
await update(
|
||||
'test-id',
|
||||
{
|
||||
version: 'test-version',
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
defaultValue: 'updated default value!!',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
clientArgs,
|
||||
casesClientInternal
|
||||
);
|
||||
|
||||
expect(clientArgs.services.caseConfigureService.patch).toHaveBeenCalledWith({
|
||||
configurationId: 'test-id',
|
||||
originalConfiguration: {
|
||||
attributes: {
|
||||
closure_type: 'close-by-user',
|
||||
connector: {
|
||||
fields: null,
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: '.none',
|
||||
},
|
||||
owner: 'cases',
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
defaultValue: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
id: 'test-id',
|
||||
version: 'test-version',
|
||||
},
|
||||
unsecuredSavedObjectsClient: expect.anything(),
|
||||
updatedAttributes: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
label: 'text label',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
required: false,
|
||||
defaultValue: 'updated default value!!',
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field 1 default value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
description: 'this is test description',
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
},
|
||||
],
|
||||
updated_at: expect.anything(),
|
||||
updated_by: expect.anything(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('removes custom field from template when there are no customFields in the request', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
attributes: {
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
customFields: [],
|
||||
templates: [],
|
||||
closure_type: 'close-by-user',
|
||||
owner: 'cases',
|
||||
},
|
||||
id: 'test-id',
|
||||
version: 'test-version',
|
||||
});
|
||||
|
||||
await update(
|
||||
'test-id',
|
||||
{
|
||||
version: 'test-version',
|
||||
customFields: [],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
clientArgs,
|
||||
casesClientInternal
|
||||
);
|
||||
|
||||
expect(clientArgs.services.caseConfigureService.patch).toHaveBeenCalledWith({
|
||||
configurationId: 'test-id',
|
||||
originalConfiguration: {
|
||||
attributes: {
|
||||
closure_type: 'close-by-user',
|
||||
connector: {
|
||||
fields: null,
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: '.none',
|
||||
},
|
||||
customFields: [],
|
||||
owner: 'cases',
|
||||
templates: [],
|
||||
},
|
||||
id: 'test-id',
|
||||
version: 'test-version',
|
||||
},
|
||||
unsecuredSavedObjectsClient: expect.anything(),
|
||||
updatedAttributes: {
|
||||
customFields: [],
|
||||
templates: [
|
||||
{
|
||||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
updated_at: expect.anything(),
|
||||
updated_by: expect.anything(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('removes deleted custom field from template correctly', async () => {
|
||||
clientArgs.services.caseConfigureService.get.mockResolvedValue({
|
||||
// @ts-ignore: these are all the attributes needed for the test
|
||||
|
@ -1166,7 +1404,7 @@ describe('client', () => {
|
|||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('throws when there are no customFields in configure and template has customField in the request', async () => {
|
||||
it('throws error when there are no customFields but template has custom fields in the request', async () => {
|
||||
await expect(
|
||||
create(
|
||||
{
|
||||
|
@ -1241,7 +1479,7 @@ describe('client', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('throws when there are invalid customField keys in the request', async () => {
|
||||
it('throw error when there are new customFields in the request but template does not have custom fields', async () => {
|
||||
await expect(
|
||||
create(
|
||||
{
|
||||
|
@ -1259,15 +1497,7 @@ describe('client', () => {
|
|||
key: 'template_1',
|
||||
name: 'template 1',
|
||||
description: 'this is test description',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'custom_field_key_2',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'custom field value 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
caseFields: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -1275,7 +1505,7 @@ describe('client', () => {
|
|||
casesClientInternal
|
||||
)
|
||||
).rejects.toThrow(
|
||||
'Failed to create case configuration: Error: Invalid custom field keys: custom_field_key_2'
|
||||
'Failed to create case configuration: Error: No custom fields added to template.'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ import type { CasesClientArgs } from '../types';
|
|||
import { getMappings } from './get_mappings';
|
||||
|
||||
import { Operations } from '../../authorization';
|
||||
import { combineAuthorizedAndOwnerFilter, removeCustomFieldFromTemplates } from '../utils';
|
||||
import { combineAuthorizedAndOwnerFilter, transformTemplateCustomFields } from '../utils';
|
||||
import type { MappingsArgs, CreateMappingsArgs, UpdateMappingsArgs } from './types';
|
||||
import { createMappings } from './create_mappings';
|
||||
import { updateMappings } from './update_mappings';
|
||||
|
@ -320,14 +320,14 @@ export async function update(
|
|||
originalCustomFields: configuration.attributes.customFields,
|
||||
});
|
||||
|
||||
await validateTemplates({
|
||||
const updatedTemplates = transformTemplateCustomFields({
|
||||
templates,
|
||||
clientArgs,
|
||||
customFields: configuration.attributes.customFields,
|
||||
customFields: request.customFields,
|
||||
});
|
||||
|
||||
const updatedTemplates = removeCustomFieldFromTemplates({
|
||||
templates,
|
||||
await validateTemplates({
|
||||
templates: updatedTemplates,
|
||||
clientArgs,
|
||||
customFields: request.customFields,
|
||||
});
|
||||
|
||||
|
|
|
@ -198,6 +198,35 @@ describe('validators', () => {
|
|||
).toThrowErrorMatchingInlineSnapshot(`"No custom fields configured."`);
|
||||
});
|
||||
|
||||
it('throws if configuration has custom fields and template has no custom fields', () => {
|
||||
expect(() =>
|
||||
validateTemplatesCustomFieldsInRequest({
|
||||
templates: [
|
||||
{
|
||||
key: 'template_key_1',
|
||||
name: 'first template',
|
||||
description: 'this is a first template value',
|
||||
caseFields: null,
|
||||
},
|
||||
],
|
||||
customFieldsConfiguration: [
|
||||
{
|
||||
key: 'first_key',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
label: 'foo',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: 'second_key',
|
||||
type: CustomFieldTypes.TOGGLE,
|
||||
label: 'foo',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(`"No custom fields added to template."`);
|
||||
});
|
||||
|
||||
it('throws for a single invalid type', () => {
|
||||
expect(() =>
|
||||
validateTemplatesCustomFieldsInRequest({
|
||||
|
|
|
@ -60,20 +60,21 @@ export const validateTemplatesCustomFieldsInRequest = ({
|
|||
}
|
||||
|
||||
templates.forEach((template, index) => {
|
||||
if (
|
||||
!template.caseFields ||
|
||||
!template.caseFields.customFields ||
|
||||
!template.caseFields.customFields.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (customFieldsConfiguration === undefined) {
|
||||
if (customFieldsConfiguration === undefined && template.caseFields?.customFields?.length) {
|
||||
throw Boom.badRequest('No custom fields configured.');
|
||||
}
|
||||
|
||||
if (
|
||||
(!template.caseFields ||
|
||||
!template.caseFields.customFields ||
|
||||
!template.caseFields.customFields.length) &&
|
||||
customFieldsConfiguration?.length
|
||||
) {
|
||||
throw Boom.badRequest('No custom fields added to template.');
|
||||
}
|
||||
|
||||
const params = {
|
||||
requestCustomFields: template.caseFields.customFields,
|
||||
requestCustomFields: template?.caseFields?.customFields,
|
||||
customFieldsConfiguration,
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
constructQueryOptions,
|
||||
constructSearch,
|
||||
convertSortField,
|
||||
removeCustomFieldFromTemplates,
|
||||
transformTemplateCustomFields,
|
||||
} from './utils';
|
||||
import { CasePersistedSeverity, CasePersistedStatus } from '../common/types/case';
|
||||
import type { CustomFieldsConfiguration } from '../../common/types/domain';
|
||||
|
@ -1132,7 +1132,7 @@ describe('utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('removeCustomFieldFromTemplates', () => {
|
||||
describe('transformTemplateCustomFields', () => {
|
||||
const customFields = [
|
||||
{
|
||||
type: CustomFieldTypes.TEXT as const,
|
||||
|
@ -1204,7 +1204,7 @@ describe('utils', () => {
|
|||
];
|
||||
|
||||
it('removes custom field from template correctly', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
const res = transformTemplateCustomFields({
|
||||
templates,
|
||||
customFields: [customFields[0], customFields[1]],
|
||||
});
|
||||
|
@ -1253,7 +1253,7 @@ describe('utils', () => {
|
|||
});
|
||||
|
||||
it('removes multiple custom fields from template correctly', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
const res = transformTemplateCustomFields({
|
||||
templates,
|
||||
customFields: [customFields[0]],
|
||||
});
|
||||
|
@ -1292,7 +1292,7 @@ describe('utils', () => {
|
|||
});
|
||||
|
||||
it('removes all custom fields from templates when custom fields are empty', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
const res = transformTemplateCustomFields({
|
||||
templates,
|
||||
customFields: [],
|
||||
});
|
||||
|
@ -1319,7 +1319,7 @@ describe('utils', () => {
|
|||
});
|
||||
|
||||
it('removes all custom fields from templates when custom fields are undefined', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
const res = transformTemplateCustomFields({
|
||||
templates,
|
||||
customFields: undefined,
|
||||
});
|
||||
|
@ -1330,8 +1330,8 @@ describe('utils', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('does not remove custom field when templates do not have custom fields', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
it('adds custom fields to templates when templates do not have custom fields', () => {
|
||||
const res = transformTemplateCustomFields({
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
|
@ -1353,7 +1353,20 @@ describe('utils', () => {
|
|||
|
||||
expect(res).toEqual([
|
||||
{
|
||||
caseFields: null,
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: customFields[0].key,
|
||||
type: customFields[0].type,
|
||||
value: customFields[0].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFields[1].key,
|
||||
type: customFields[1].type,
|
||||
value: customFields[1].defaultValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
description: 'This is a first test template',
|
||||
key: 'test_template_1',
|
||||
name: 'First test template',
|
||||
|
@ -1364,13 +1377,25 @@ describe('utils', () => {
|
|||
caseFields: {
|
||||
description: 'this is test',
|
||||
title: 'Test title',
|
||||
customFields: [
|
||||
{
|
||||
key: customFields[0].key,
|
||||
type: customFields[0].type,
|
||||
value: customFields[0].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFields[1].key,
|
||||
type: customFields[1].type,
|
||||
value: customFields[1].defaultValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not remove custom field when templates have empty custom fields', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
it('adds custom fields to templates when template custom fields are empty', () => {
|
||||
const res = transformTemplateCustomFields({
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_2',
|
||||
|
@ -1382,7 +1407,7 @@ describe('utils', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
customFields: [customFields[0], customFields[1]],
|
||||
customFields: [customFields[0], customFields[1], customFields[2]],
|
||||
});
|
||||
|
||||
expect(res).toEqual([
|
||||
|
@ -1392,14 +1417,149 @@ describe('utils', () => {
|
|||
caseFields: {
|
||||
title: 'Test title',
|
||||
description: 'this is test',
|
||||
customFields: [],
|
||||
customFields: [
|
||||
{
|
||||
key: customFields[0].key,
|
||||
type: customFields[0].type,
|
||||
value: customFields[0].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFields[1].key,
|
||||
type: customFields[1].type,
|
||||
value: customFields[1].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFields[2].key,
|
||||
type: customFields[2].type,
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds custom fields to templates with correct values', () => {
|
||||
const res = transformTemplateCustomFields({
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_2',
|
||||
name: 'Second test template',
|
||||
caseFields: {
|
||||
title: 'Test title',
|
||||
description: 'this is test',
|
||||
customFields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
customFields: [
|
||||
...customFields,
|
||||
{
|
||||
type: CustomFieldTypes.TOGGLE as const,
|
||||
key: 'test_key_4',
|
||||
label: 'My test label 4',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(res).toEqual([
|
||||
{
|
||||
key: 'test_template_2',
|
||||
name: 'Second test template',
|
||||
caseFields: {
|
||||
title: 'Test title',
|
||||
description: 'this is test',
|
||||
customFields: [
|
||||
{
|
||||
key: customFields[0].key,
|
||||
type: customFields[0].type,
|
||||
value: customFields[0].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFields[1].key,
|
||||
type: customFields[1].type,
|
||||
value: customFields[1].defaultValue,
|
||||
},
|
||||
{
|
||||
key: customFields[2].key,
|
||||
type: customFields[2].type,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
type: CustomFieldTypes.TOGGLE as const,
|
||||
key: 'test_key_4',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not change the existing template custom field', () => {
|
||||
const res = transformTemplateCustomFields({
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_2',
|
||||
name: 'Second test template',
|
||||
caseFields: {
|
||||
title: 'Test title',
|
||||
description: 'this is test',
|
||||
customFields: [
|
||||
{
|
||||
key: customFields[0].key,
|
||||
type: CustomFieldTypes.TEXT as const,
|
||||
value: 'updated text value',
|
||||
},
|
||||
{
|
||||
key: customFields[1].key,
|
||||
type: CustomFieldTypes.TOGGLE as const,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
key: customFields[2].key,
|
||||
type: customFields[2].type,
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
customFields,
|
||||
});
|
||||
|
||||
expect(res).toEqual([
|
||||
{
|
||||
key: 'test_template_2',
|
||||
name: 'Second test template',
|
||||
caseFields: {
|
||||
title: 'Test title',
|
||||
description: 'this is test',
|
||||
customFields: [
|
||||
{
|
||||
key: customFields[0].key,
|
||||
type: customFields[0].type,
|
||||
value: 'updated text value',
|
||||
},
|
||||
{
|
||||
key: customFields[1].key,
|
||||
type: customFields[1].type,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
key: customFields[2].key,
|
||||
type: customFields[2].type,
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not remove custom field from empty templates', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
const res = transformTemplateCustomFields({
|
||||
templates: [],
|
||||
customFields: [customFields[0], customFields[1]],
|
||||
});
|
||||
|
@ -1408,7 +1568,7 @@ describe('utils', () => {
|
|||
});
|
||||
|
||||
it('returns empty array when templates are undefined', () => {
|
||||
const res = removeCustomFieldFromTemplates({
|
||||
const res = transformTemplateCustomFields({
|
||||
templates: undefined,
|
||||
customFields: [customFields[0], customFields[1]],
|
||||
});
|
||||
|
|
|
@ -17,11 +17,13 @@ import { nodeBuilder, fromKueryExpression, escapeKuery } from '@kbn/es-query';
|
|||
import { spaceIdToNamespace } from '@kbn/spaces-plugin/server/lib/utils/namespace';
|
||||
|
||||
import type {
|
||||
CaseCustomField,
|
||||
CaseSeverity,
|
||||
CaseStatuses,
|
||||
CustomFieldsConfiguration,
|
||||
ExternalReferenceAttachmentPayload,
|
||||
TemplatesConfiguration,
|
||||
CustomFieldTypes,
|
||||
} from '../../common/types/domain';
|
||||
import {
|
||||
ActionsAttachmentPayloadRt,
|
||||
|
@ -607,9 +609,9 @@ export const constructSearch = (
|
|||
};
|
||||
|
||||
/**
|
||||
* remove deleted custom field from template
|
||||
* remove deleted custom field from template or add newly added custom field to template
|
||||
*/
|
||||
export const removeCustomFieldFromTemplates = ({
|
||||
export const transformTemplateCustomFields = ({
|
||||
templates,
|
||||
customFields,
|
||||
}: {
|
||||
|
@ -621,21 +623,40 @@ export const removeCustomFieldFromTemplates = ({
|
|||
}
|
||||
|
||||
return templates.map((template) => {
|
||||
if (!template.caseFields?.customFields || !template.caseFields?.customFields.length) {
|
||||
return template;
|
||||
}
|
||||
const templateCustomFields = template.caseFields?.customFields ?? [];
|
||||
|
||||
if (!customFields || !customFields?.length) {
|
||||
if (!customFields || !customFields.length) {
|
||||
return { ...template, caseFields: { ...template.caseFields, customFields: [] } };
|
||||
}
|
||||
|
||||
const templateCustomFields = template.caseFields.customFields.filter((templateCustomField) =>
|
||||
// remove deleted custom field from template
|
||||
const transformedTemplateCustomFields = templateCustomFields.filter((templateCustomField) =>
|
||||
customFields?.find((customField) => customField.key === templateCustomField.key)
|
||||
);
|
||||
|
||||
// add new custom fields to template
|
||||
if (customFields.length >= transformedTemplateCustomFields.length) {
|
||||
customFields.forEach((field) => {
|
||||
if (
|
||||
!transformedTemplateCustomFields.find(
|
||||
(templateCustomField) => templateCustomField.key === field.key
|
||||
)
|
||||
) {
|
||||
const { getDefaultValue } = casesCustomFields.get(field.type) ?? {};
|
||||
const value = getDefaultValue?.() ?? null;
|
||||
|
||||
transformedTemplateCustomFields.push({
|
||||
key: field.key,
|
||||
type: field.type as CustomFieldTypes,
|
||||
value: field.defaultValue ?? value,
|
||||
} as CaseCustomField);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...template,
|
||||
caseFields: { ...template.caseFields, customFields: templateCustomFields },
|
||||
caseFields: { ...template.caseFields, customFields: transformedTemplateCustomFields },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -19,4 +19,5 @@ export const getCasesToggleCustomField = () => ({
|
|||
}
|
||||
});
|
||||
},
|
||||
getDefaultValue: () => false,
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface ICasesCustomField {
|
|||
isSortable: boolean;
|
||||
savedObjectMappingType: string;
|
||||
validateFilteringValues: (values: Array<string | number | boolean | null>) => void;
|
||||
getDefaultValue?: () => boolean | string | null;
|
||||
}
|
||||
|
||||
export interface CasesCustomFieldsMap {
|
||||
|
|
|
@ -85,46 +85,48 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('should return a configuration with templates', async () => {
|
||||
const mockTemplates = [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
name: 'First test template',
|
||||
description: 'This is a first test template',
|
||||
tags: [],
|
||||
caseFields: null,
|
||||
},
|
||||
{
|
||||
key: 'test_template_2',
|
||||
name: 'Second test template',
|
||||
description: 'This is a second test template',
|
||||
tags: ['foobar'],
|
||||
caseFields: {
|
||||
title: 'Case with sample template 2',
|
||||
description: 'case desc',
|
||||
severity: CaseSeverity.LOW,
|
||||
category: null,
|
||||
tags: ['sample-4'],
|
||||
assignees: [],
|
||||
customFields: [],
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'My Connector',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'test_template_3',
|
||||
name: 'Third test template',
|
||||
description: 'This is a third test template',
|
||||
caseFields: {
|
||||
title: 'Case with sample template 3',
|
||||
tags: ['sample-3'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const templates = {
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
name: 'First test template',
|
||||
description: 'This is a first test template',
|
||||
tags: [],
|
||||
caseFields: null,
|
||||
},
|
||||
{
|
||||
key: 'test_template_2',
|
||||
name: 'Second test template',
|
||||
description: 'This is a second test template',
|
||||
tags: ['foobar'],
|
||||
caseFields: {
|
||||
title: 'Case with sample template 2',
|
||||
description: 'case desc',
|
||||
severity: CaseSeverity.LOW,
|
||||
category: null,
|
||||
tags: ['sample-4'],
|
||||
assignees: [],
|
||||
customFields: [],
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'My Connector',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'test_template_3',
|
||||
name: 'Third test template',
|
||||
description: 'This is a third test template',
|
||||
caseFields: {
|
||||
title: 'Case with sample template 3',
|
||||
tags: ['sample-3'],
|
||||
},
|
||||
},
|
||||
],
|
||||
templates: mockTemplates,
|
||||
};
|
||||
|
||||
await createConfiguration(
|
||||
|
@ -136,7 +138,11 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const configuration = await getConfiguration({ supertest });
|
||||
|
||||
const data = removeServerGeneratedPropertiesFromSavedObject(configuration[0]);
|
||||
expect(data).to.eql(getConfigurationOutput(false, templates));
|
||||
expect(data).to.eql(
|
||||
getConfigurationOutput(false, {
|
||||
templates: mockTemplates,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get a single configuration', async () => {
|
||||
|
|
|
@ -147,7 +147,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
},
|
||||
];
|
||||
|
||||
const templates = [
|
||||
const mockTemplates = [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
name: 'First test template',
|
||||
|
@ -196,23 +196,61 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
tags: ['sample-3'],
|
||||
},
|
||||
},
|
||||
] as ConfigurationPatchRequest['templates'];
|
||||
];
|
||||
|
||||
const configuration = await createConfiguration(supertest, {
|
||||
...getConfigurationRequest(),
|
||||
customFields: customFieldsConfiguration as ConfigurationPatchRequest['customFields'],
|
||||
});
|
||||
|
||||
const newConfiguration = await updateConfiguration(supertest, configuration.id, {
|
||||
version: configuration.version,
|
||||
customFields: customFieldsConfiguration,
|
||||
templates,
|
||||
templates: mockTemplates as ConfigurationPatchRequest['templates'],
|
||||
});
|
||||
|
||||
const data = removeServerGeneratedPropertiesFromSavedObject(newConfiguration);
|
||||
expect(data).to.eql({
|
||||
...getConfigurationOutput(true),
|
||||
customFields: customFieldsConfiguration as ConfigurationPatchRequest['customFields'],
|
||||
templates,
|
||||
templates: [
|
||||
{
|
||||
...mockTemplates[0],
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'text_field_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: 'toggle_field_1',
|
||||
value: false,
|
||||
type: CustomFieldTypes.TOGGLE,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ ...mockTemplates[1] },
|
||||
{
|
||||
...mockTemplates[2],
|
||||
caseFields: {
|
||||
...mockTemplates[2].caseFields,
|
||||
customFields: [
|
||||
{
|
||||
key: 'text_field_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: 'toggle_field_1',
|
||||
value: false,
|
||||
type: CustomFieldTypes.TOGGLE,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
] as ConfigurationPatchRequest['templates'],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -432,35 +470,6 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
);
|
||||
});
|
||||
|
||||
it("should not update a configuration with templates with custom fields that don't exist in the configuration", async () => {
|
||||
const configuration = await createConfiguration(supertest);
|
||||
|
||||
await updateConfiguration(
|
||||
supertest,
|
||||
configuration.id,
|
||||
{
|
||||
version: configuration.version,
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
name: 'First test template',
|
||||
description: 'This is a first test template',
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'random_key',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: 'Test',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
400
|
||||
);
|
||||
});
|
||||
|
||||
it('should not patch a configuration with duplicated template keys', async () => {
|
||||
const configuration = await createConfiguration(supertest);
|
||||
await updateConfiguration(
|
||||
|
|
|
@ -123,7 +123,20 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
key: 'test_template_1',
|
||||
name: 'First test template',
|
||||
description: 'This is a first test template',
|
||||
caseFields: null,
|
||||
caseFields: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'text_field_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: 'toggle_field_1',
|
||||
value: false,
|
||||
type: CustomFieldTypes.TOGGLE,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'test_template_2',
|
||||
|
@ -165,6 +178,18 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
caseFields: {
|
||||
title: 'Case with sample template 3',
|
||||
tags: ['sample-3'],
|
||||
customFields: [
|
||||
{
|
||||
key: 'text_field_1',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
key: 'toggle_field_1',
|
||||
value: false,
|
||||
type: CustomFieldTypes.TOGGLE,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -177,7 +202,11 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
);
|
||||
|
||||
const data = removeServerGeneratedPropertiesFromSavedObject(configuration);
|
||||
expect(data).to.eql({ ...getConfigurationOutput(false), customFields, templates });
|
||||
expect(data).to.eql({
|
||||
...getConfigurationOutput(false),
|
||||
customFields,
|
||||
templates,
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep only the latest configuration', async () => {
|
||||
|
@ -493,11 +522,40 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
);
|
||||
});
|
||||
|
||||
it("should not create a configuration with templates with custom fields that don't exist in the configuration", async () => {
|
||||
it('should not create a configuration with duplicated template keys', async () => {
|
||||
await createConfiguration(
|
||||
supertest,
|
||||
getConfigurationRequest({
|
||||
overrides: {
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
name: 'First test template',
|
||||
description: 'This is a first test template',
|
||||
caseFields: null,
|
||||
},
|
||||
{
|
||||
key: 'test_template_1',
|
||||
name: 'Third test template',
|
||||
description: 'This is a third test template',
|
||||
caseFields: {
|
||||
title: 'Case with sample template 3',
|
||||
tags: ['sample-3'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
400
|
||||
);
|
||||
});
|
||||
|
||||
it("should not create a configuration when templates have custom fields and custom fields don't exist in the configuration", async () => {
|
||||
await createConfiguration(
|
||||
supertest,
|
||||
getConfigurationRequest({
|
||||
overrides: {
|
||||
customFields: [],
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
|
@ -520,11 +578,20 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should not create a configuration with duplicated template keys', async () => {
|
||||
it('should not create a configuration when templates do not have custom fields and custom fields exist in the configuration', async () => {
|
||||
await createConfiguration(
|
||||
supertest,
|
||||
getConfigurationRequest({
|
||||
overrides: {
|
||||
customFields: [
|
||||
{
|
||||
key: 'random_key',
|
||||
type: CustomFieldTypes.TEXT,
|
||||
label: 'New custom field',
|
||||
defaultValue: 'Test',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
key: 'test_template_1',
|
||||
|
@ -532,15 +599,6 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
description: 'This is a first test template',
|
||||
caseFields: null,
|
||||
},
|
||||
{
|
||||
key: 'test_template_1',
|
||||
name: 'Third test template',
|
||||
description: 'This is a third test template',
|
||||
caseFields: {
|
||||
title: 'Case with sample template 3',
|
||||
tags: ['sample-3'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue