Skip to content

Commit 9785c55

Browse files
janmonschkeelasticmachinekibanamachine
authored
[Cases] IBM Resilient form improvements (#238869)
## Summary Fixes: #240446 This PR provides an improved experience of editing IBM resilient fields. Instead of editing IBM resilient fields with a JSON editor, this PR adds dynamic form elements for the IBM resilient fields. ### Demo https://github.com/user-attachments/assets/aedd61e4-9566-4196-b24e-52d4cd85e925 ### Other changes - Removed `useGetIncidentTypes` and `useGetSeverity` since they can both be obtained via `useGetFields`. This also removes the API calls from 3 to just a single one. - The connector preview is now a proper table and all connector previews have been changed. ### Testing - Add an IBM Resilient connector (hmu in Slack for credentials) - Create case, add additional fields to the connector, hit save - Observe that the fields and their values are shown in the case page - Edit the additional fields to your liking (adding, removing, editing) and make sure the changes are saved when pushing to IBM Resilient --------- Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: kibanamachine <[email protected]>
1 parent 35b0639 commit 9785c55

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1921
-994
lines changed

x-pack/platform/plugins/private/translations/translations/de-DE.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13536,8 +13536,6 @@
1353613536
"xpack.cases.connectors.resilient.incidentTypesLabel": "Incident-Arten",
1353713537
"xpack.cases.connectors.resilient.incidentTypesPlaceholder": "Typen auswählen",
1353813538
"xpack.cases.connectors.resilient.severityLabel": "Priorität",
13539-
"xpack.cases.connectors.resilient.unableToGetIncidentTypesMessage": "Die Ereignistypen können nicht abgerufen werden",
13540-
"xpack.cases.connectors.resilient.unableToGetSeverityMessage": "Der Schweregrad konnte nicht ermittelt werden.",
1354113539
"xpack.cases.connectors.serviceNow.additionalFieldsFormatErrorMessage": "Ungültiges JSON.",
1354213540
"xpack.cases.connectors.serviceNow.additionalFieldsLabel": "Zusätzliche Felder",
1354313541
"xpack.cases.connectors.serviceNow.additionalFieldsLengthError": "Es können maximal {length} zusätzliche Felder gleichzeitig definiert werden.",

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13702,8 +13702,6 @@
1370213702
"xpack.cases.connectors.resilient.incidentTypesLabel": "Types d'incidents",
1370313703
"xpack.cases.connectors.resilient.incidentTypesPlaceholder": "Choisir les types",
1370413704
"xpack.cases.connectors.resilient.severityLabel": "Sévérité",
13705-
"xpack.cases.connectors.resilient.unableToGetIncidentTypesMessage": "Impossible d'obtenir les types d'incidents",
13706-
"xpack.cases.connectors.resilient.unableToGetSeverityMessage": "Impossible d'obtenir la sévérité",
1370713705
"xpack.cases.connectors.serviceNow.additionalFieldsFormatErrorMessage": "Syntaxe JSON non valide.",
1370813706
"xpack.cases.connectors.serviceNow.additionalFieldsLabel": "Champs supplémentaires",
1370913707
"xpack.cases.connectors.serviceNow.additionalFieldsLengthError": "Un maximum de {length} champs supplémentaires peut être défini à la fois.",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13719,8 +13719,6 @@
1371913719
"xpack.cases.connectors.resilient.incidentTypesLabel": "インシデントタイプ",
1372013720
"xpack.cases.connectors.resilient.incidentTypesPlaceholder": "タイプを選択",
1372113721
"xpack.cases.connectors.resilient.severityLabel": "深刻度",
13722-
"xpack.cases.connectors.resilient.unableToGetIncidentTypesMessage": "インシデントタイプを取得できません",
13723-
"xpack.cases.connectors.resilient.unableToGetSeverityMessage": "深刻度を取得できません",
1372413722
"xpack.cases.connectors.serviceNow.additionalFieldsFormatErrorMessage": "無効なJSONです。",
1372513723
"xpack.cases.connectors.serviceNow.additionalFieldsLabel": "追加フィールド",
1372613724
"xpack.cases.connectors.serviceNow.additionalFieldsLengthError": "最大{length}の追加フィールドを同時に定義できます。",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13712,8 +13712,6 @@
1371213712
"xpack.cases.connectors.resilient.incidentTypesLabel": "事件类型",
1371313713
"xpack.cases.connectors.resilient.incidentTypesPlaceholder": "选择类型",
1371413714
"xpack.cases.connectors.resilient.severityLabel": "严重性",
13715-
"xpack.cases.connectors.resilient.unableToGetIncidentTypesMessage": "无法获取事件类型",
13716-
"xpack.cases.connectors.resilient.unableToGetSeverityMessage": "无法获取严重性",
1371713715
"xpack.cases.connectors.serviceNow.additionalFieldsFormatErrorMessage": "JSON 无效。",
1371813716
"xpack.cases.connectors.serviceNow.additionalFieldsLabel": "其他字段",
1371913717
"xpack.cases.connectors.serviceNow.additionalFieldsLengthError": "一次最多可以定义 {length} 个附加字段。",

x-pack/platform/plugins/shared/cases/public/common/test_utils.tsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77

88
import type { FC, PropsWithChildren } from 'react';
99
import React from 'react';
10-
import type { ReactWrapper } from 'enzyme';
1110
import { act } from 'react-dom/test-utils';
12-
import type { MatcherFunction } from '@testing-library/react';
11+
import { within } from '@testing-library/react';
1312
import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
1413
import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
1514
import { EuiButton } from '@elastic/eui';
@@ -20,31 +19,11 @@ import { EuiButton } from '@elastic/eui';
2019
export const removeExternalLinkText = (str: string | null) =>
2120
str?.replace(/\(external[^)]*\)/g, '');
2221

23-
export async function waitForComponentToPaint<P = {}>(wrapper: ReactWrapper<P>, amount = 0) {
24-
await act(async () => {
25-
await new Promise((resolve) => setTimeout(resolve, amount));
26-
wrapper.update();
27-
});
28-
}
29-
3022
export const waitForComponentToUpdate = async () =>
3123
act(async () => {
3224
return Promise.resolve();
3325
});
3426

35-
type Query = (f: MatcherFunction) => HTMLElement;
36-
37-
export const createQueryWithMarkup =
38-
(query: Query) =>
39-
(text: string): HTMLElement =>
40-
query((content: string, node: Parameters<MatcherFunction>[1]) => {
41-
const hasText = (el: Parameters<MatcherFunction>[1]) => el?.textContent === text;
42-
const childrenDontHaveText = Array.from(node?.children ?? []).every(
43-
(child) => !hasText(child as HTMLElement)
44-
);
45-
return hasText(node) && childrenDontHaveText;
46-
});
47-
4827
export interface FormTestComponentProps {
4928
formDefaultValue?: Record<string, unknown>;
5029
onSubmit?: jest.Mock;
@@ -70,3 +49,20 @@ export const FormTestComponent: FC<PropsWithChildren<FormTestComponentProps>> =
7049
</Form>
7150
);
7251
};
52+
53+
export function tableMatchesExpectedContent({
54+
expectedContent,
55+
tableRows,
56+
}: {
57+
expectedContent: string[][];
58+
tableRows: HTMLElement[];
59+
}) {
60+
expect(tableRows).toHaveLength(expectedContent.length);
61+
62+
tableRows.forEach((row, index) => {
63+
const expected = expectedContent[index];
64+
expected.forEach((content) => {
65+
expect(within(row).getByText(content));
66+
});
67+
});
68+
}

x-pack/platform/plugins/shared/cases/public/components/case_form_fields/connector.test.tsx

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,18 @@ import userEvent from '@testing-library/user-event';
1111

1212
import { connectorsMock } from '../../containers/mock';
1313
import { Connector } from './connector';
14-
import { useGetIncidentTypes } from '../connectors/resilient/use_get_incident_types';
15-
import { useGetSeverity } from '../connectors/resilient/use_get_severity';
1614
import { useGetChoices } from '../connectors/servicenow/use_get_choices';
17-
import { incidentTypes, severity, choices } from '../connectors/mock';
15+
import { choices } from '../connectors/mock';
1816
import { noConnectorsCasePermission, renderWithTestingProviders } from '../../common/mock';
1917

2018
import { FormTestComponent } from '../../common/test_utils';
2119
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
2220
import { coreMock } from '@kbn/core/public/mocks';
2321

24-
jest.mock('../connectors/resilient/use_get_incident_types');
25-
jest.mock('../connectors/resilient/use_get_severity');
2622
jest.mock('../connectors/servicenow/use_get_choices');
2723

28-
const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock;
29-
const useGetSeverityMock = useGetSeverity as jest.Mock;
3024
const useGetChoicesMock = useGetChoices as jest.Mock;
3125

32-
const useGetIncidentTypesResponse = {
33-
isLoading: false,
34-
incidentTypes,
35-
};
36-
37-
const useGetSeverityResponse = {
38-
isLoading: false,
39-
severity,
40-
};
41-
4226
const useGetChoicesResponse = {
4327
isLoading: false,
4428
choices,
@@ -53,9 +37,6 @@ const defaultProps = {
5337
describe('Connector', () => {
5438
beforeEach(() => {
5539
jest.clearAllMocks();
56-
57-
useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse);
58-
useGetSeverityMock.mockReturnValue(useGetSeverityResponse);
5940
useGetChoicesMock.mockReturnValue(useGetChoicesResponse);
6041
});
6142

x-pack/platform/plugins/shared/cases/public/components/case_form_fields/connector.tsx

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import React, { memo } from 'react';
9-
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiFormRow } from '@elastic/eui';
9+
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiFormRow, EuiSpacer } from '@elastic/eui';
1010

1111
import type { FieldConfig } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
1212
import { UseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
@@ -47,25 +47,31 @@ const ConnectorComponent: React.FC<Props> = ({ connectors, isLoading, isLoadingC
4747

4848
return (
4949
<EuiFormRow fullWidth>
50-
<EuiFlexGroup>
51-
<EuiFlexItem>
52-
<UseField
53-
path="connectorId"
54-
config={connectorIdConfig}
55-
component={ConnectorSelector}
56-
componentProps={{
57-
connectors,
58-
dataTestSubj: 'caseConnectors',
59-
disabled: isLoading || isLoadingConnectors,
60-
idAria: 'caseConnectors',
61-
isLoading: isLoading || isLoadingConnectors,
62-
}}
63-
/>
64-
</EuiFlexItem>
65-
<EuiFlexItem>
66-
<ConnectorFieldsForm connector={connector} />
67-
</EuiFlexItem>
68-
</EuiFlexGroup>
50+
<>
51+
<EuiFlexGroup>
52+
<EuiFlexItem grow={1}>
53+
<UseField
54+
path="connectorId"
55+
config={connectorIdConfig}
56+
component={ConnectorSelector}
57+
componentProps={{
58+
connectors,
59+
dataTestSubj: 'caseConnectors',
60+
disabled: isLoading || isLoadingConnectors,
61+
idAria: 'caseConnectors',
62+
isLoading: isLoading || isLoadingConnectors,
63+
}}
64+
/>
65+
</EuiFlexItem>
66+
<EuiFlexItem grow={1} />
67+
</EuiFlexGroup>
68+
<EuiSpacer size="m" />
69+
<EuiFlexGroup>
70+
<EuiFlexItem grow={1}>
71+
<ConnectorFieldsForm connector={connector} isInSidebarForm={false} />
72+
</EuiFlexItem>
73+
</EuiFlexGroup>
74+
</>
6975
</EuiFormRow>
7076
);
7177
};

x-pack/platform/plugins/shared/cases/public/components/connectors/card.test.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { render, screen } from '@testing-library/react';
1010

1111
import { ConnectorTypes } from '../../../common/types/domain';
1212
import { ConnectorCard } from './card';
13-
import { createQueryWithMarkup } from '../../common/test_utils';
13+
import { tableMatchesExpectedContent } from '../../common/test_utils';
1414

1515
describe('ConnectorCard ', () => {
1616
it('does not throw when accessing the icon if the connector type is not registered', () => {
@@ -68,38 +68,40 @@ describe('ConnectorCard ', () => {
6868
/>
6969
);
7070

71-
const getByTextWithMarkup = createQueryWithMarkup(screen.getByText);
71+
const rows = screen.getAllByTestId('card-list-item-row');
72+
const expectedContent = listItems.map((item) => [item.title, item.description]);
7273

73-
for (const item of listItems) {
74-
expect(getByTextWithMarkup(`${item.title}: ${item.description}`)).toBeInTheDocument();
75-
}
74+
tableMatchesExpectedContent({ expectedContent, tableRows: rows });
7675
});
7776

78-
it('shows a codeblock when applicable', async () => {
79-
render(
80-
<ConnectorCard
81-
connectorType={ConnectorTypes.none}
82-
title="My connector"
83-
listItems={[{ title: 'some title', description: 'some code', displayAsCodeBlock: true }]}
84-
isLoading={false}
85-
/>
86-
);
87-
88-
expect(await screen.findByTestId('card-list-item')).toBeInTheDocument();
89-
expect(await screen.findByTestId('card-list-code-block')).toBeInTheDocument();
90-
});
77+
it('does not throw for unexpected values', () => {
78+
const listItems = [
79+
{ title: 'item 1 title', description: { testing: true } },
80+
{ title: 'item 2 title', description: ['item 2 desc'] },
81+
{ title: 'item 3 title', description: true },
82+
{ title: 'item 4 title', description: null },
83+
];
9184

92-
it('does not show a codeblock when not necessary', async () => {
9385
render(
9486
<ConnectorCard
9587
connectorType={ConnectorTypes.none}
9688
title="My connector"
97-
listItems={[{ title: 'some title', description: 'some code' }]}
89+
// @ts-expect-error testing unexpected values
90+
// since the values come from third-party services
91+
// it's better to check for unexpected values as well
92+
listItems={listItems}
9893
isLoading={false}
9994
/>
10095
);
10196

102-
expect(await screen.findByTestId('card-list-item')).toBeInTheDocument();
103-
expect(screen.queryByTestId('card-list-code-block')).not.toBeInTheDocument();
97+
const rows = screen.getAllByTestId('card-list-item-row');
98+
const expectedContent = [
99+
['item 1 title', '{"testing":true}'],
100+
['item 2 title', 'item 2 desc'],
101+
['item 3 title', 'true'],
102+
['item 4 title', 'null'],
103+
];
104+
105+
tableMatchesExpectedContent({ expectedContent, tableRows: rows });
104106
});
105107
});

0 commit comments

Comments
 (0)