Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] Add custom fields: List type #194236

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8a5af8c
WIP
Zacqary Sep 24, 2024
facfa12
Get creatie and view components working
Zacqary Sep 25, 2024
cbe96ac
Fix creating with default value
Zacqary Sep 25, 2024
79c47bc
Fix edit component
Zacqary Sep 25, 2024
6986bc9
Commit missing helpers
Zacqary Sep 26, 2024
6363db9
Move to pure key/val SO system and implement in frontend
Zacqary Sep 26, 2024
48bba35
Add filtering for list fields
Zacqary Sep 26, 2024
9609732
Add configure test
Zacqary Sep 26, 2024
35caf9d
Fix server default value for list
Zacqary Sep 26, 2024
d254d33
Fix user action display
Zacqary Sep 27, 2024
8c56aa2
Update custom field user action tests
Zacqary Sep 27, 2024
8750918
Add create test
Zacqary Sep 27, 2024
9f365ab
Add create test
Zacqary Sep 27, 2024
7ef1d32
Add edit test
Zacqary Sep 27, 2024
8f445ee
Add getEuiTableColumn test
Zacqary Sep 27, 2024
c0c5816
Add view test
Zacqary Sep 27, 2024
8595ac4
Add options field validation
Zacqary Sep 27, 2024
90b9e92
Add configure test
Zacqary Sep 27, 2024
66be662
Type fix in index test
Zacqary Sep 27, 2024
cd00f54
Merge remote-tracking branch 'upstream/main' into 176491-cases-list-c…
Zacqary Sep 27, 2024
4e60410
Fix cases index jest test
Zacqary Sep 30, 2024
df2bded
Fix api jest
Zacqary Sep 30, 2024
1d807aa
Fix flyout jest
Zacqary Sep 30, 2024
17287c5
Fix utils jest
Zacqary Sep 30, 2024
5366f26
Fix form jest
Zacqary Sep 30, 2024
4682981
Fix form fields jest
Zacqary Sep 30, 2024
c1d2552
Fix remaining jest tests
Zacqary Sep 30, 2024
1f3b3d1
Fix Jest 9
Zacqary Oct 2, 2024
41ba5ba
Fix typecheck
Zacqary Oct 2, 2024
b0cdbfb
Merge remote-tracking branch 'upstream/main' into 176491-cases-list-c…
Zacqary Oct 2, 2024
2d986f4
Fix typecheck
Zacqary Oct 3, 2024
910bce2
Fix test typechecks
Zacqary Oct 3, 2024
096fc7c
Merge remote-tracking branch 'upstream/main' into 176491-cases-list-c…
Zacqary Oct 3, 2024
1f4c6b1
Limit list options to 10
Zacqary Oct 14, 2024
f3a02dc
Make enter key add new list option to options field
Zacqary Oct 14, 2024
41406df
Fix editing fields with deleted list values
Zacqary Oct 14, 2024
c22af8a
Merge remote-tracking branch 'upstream/main' into 176491-cases-list-c…
Zacqary Oct 14, 2024
b77d0fc
Uncomment await.waitFors
Zacqary Oct 16, 2024
de13ab6
Fix bugs with options field and add test file
Zacqary Oct 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions x-pack/plugins/cases/common/types/api/case/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import {
NonEmptyString,
paginationSchema,
} from '../../../schema';
import { CaseCustomFieldToggleRt, CustomFieldTextTypeRt } from '../../domain';
import {
CaseCustomFieldToggleRt,
CustomFieldTextTypeRt,
CaseCustomFieldListRt,
} from '../../domain';
import {
CaseRt,
CaseSettingsRt,
Expand All @@ -49,7 +53,11 @@ const CaseCustomFieldTextWithValidationRt = rt.strict({
value: rt.union([CaseCustomFieldTextWithValidationValueRt('value'), rt.null]),
});

const CustomFieldRt = rt.union([CaseCustomFieldTextWithValidationRt, CaseCustomFieldToggleRt]);
const CustomFieldRt = rt.union([
CaseCustomFieldTextWithValidationRt,
CaseCustomFieldToggleRt,
CaseCustomFieldListRt,
]);

export const CaseRequestCustomFieldsRt = limitedArraySchema({
codec: CustomFieldRt,
Expand Down
35 changes: 33 additions & 2 deletions x-pack/plugins/cases/common/types/api/configure/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import {
MAX_TEMPLATE_TAG_LENGTH,
} from '../../../constants';
import { limitedArraySchema, limitedStringSchema, regexStringRt } from '../../../schema';
import { CustomFieldTextTypeRt, CustomFieldToggleTypeRt } from '../../domain';
import {
CustomFieldTextTypeRt,
CustomFieldToggleTypeRt,
CustomFieldListTypeRt,
} from '../../domain';
import type { Configurations, Configuration } from '../../domain/configure/v1';
import { ConfigurationBasicWithoutOwnerRt, ClosureTypeRt } from '../../domain/configure/v1';
import { CaseConnectorRt } from '../../domain/connector/v1';
Expand Down Expand Up @@ -64,8 +68,35 @@ export const ToggleCustomFieldConfigurationRt = rt.intersection([
),
]);

export const ListCustomFieldOptionRt = rt.strict({
label: rt.string,
key: rt.string,
});

export const ListCustomFieldConfigurationRt = rt.intersection([
rt.strict({ type: CustomFieldListTypeRt }),
CustomFieldConfigurationWithoutTypeRt,
rt.strict({
options: limitedArraySchema({
codec: ListCustomFieldOptionRt,
min: 1,
max: 10,
fieldName: 'options',
}),
}),
rt.exact(
rt.partial({
defaultValue: rt.union([rt.string, rt.null]),
})
),
]);

export const CustomFieldsConfigurationRt = limitedArraySchema({
codec: rt.union([TextCustomFieldConfigurationRt, ToggleCustomFieldConfigurationRt]),
codec: rt.union([
TextCustomFieldConfigurationRt,
ToggleCustomFieldConfigurationRt,
ListCustomFieldConfigurationRt,
]),
min: 0,
max: MAX_CUSTOM_FIELDS_PER_CASE,
fieldName: 'customFields',
Expand Down
30 changes: 29 additions & 1 deletion x-pack/plugins/cases/common/types/domain/configure/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import * as rt from 'io-ts';
import { CaseConnectorRt, ConnectorMappingsRt } from '../connector/v1';
import { UserRt } from '../user/v1';
import { CustomFieldTextTypeRt, CustomFieldToggleTypeRt } from '../custom_field/v1';
import {
CustomFieldTextTypeRt,
CustomFieldToggleTypeRt,
CustomFieldListTypeRt,
} from '../custom_field/v1';
import { CaseBaseOptionalFieldsRt } from '../case/v1';

export const ClosureTypeRt = rt.union([
Expand Down Expand Up @@ -51,9 +55,28 @@ export const ToggleCustomFieldConfigurationRt = rt.intersection([
),
]);

export const ListCustomFieldOptionRt = rt.strict({
label: rt.string,
key: rt.string,
});

export const ListCustomFieldConfigurationRt = rt.intersection([
rt.strict({ type: CustomFieldListTypeRt }),
CustomFieldConfigurationWithoutTypeRt,
rt.strict({
options: rt.array(ListCustomFieldOptionRt),
}),
rt.exact(
rt.partial({
defaultValue: rt.union([rt.string, rt.null]),
})
),
]);

export const CustomFieldConfigurationRt = rt.union([
TextCustomFieldConfigurationRt,
ToggleCustomFieldConfigurationRt,
ListCustomFieldConfigurationRt,
]);

export const CustomFieldsConfigurationRt = rt.array(CustomFieldConfigurationRt);
Expand Down Expand Up @@ -151,3 +174,8 @@ export type ClosureType = rt.TypeOf<typeof ClosureTypeRt>;
export type ConfigurationAttributes = rt.TypeOf<typeof ConfigurationAttributesRt>;
export type Configuration = rt.TypeOf<typeof ConfigurationRt>;
export type Configurations = rt.TypeOf<typeof ConfigurationsRt>;
export type ListCustomFieldOption = rt.TypeOf<typeof ListCustomFieldOptionRt>;

export type TextCustomFieldConfiguration = rt.TypeOf<typeof TextCustomFieldConfigurationRt>;
export type ListCustomFieldConfiguration = rt.TypeOf<typeof ListCustomFieldConfigurationRt>;
export type ToggleCustomFieldConfiguration = rt.TypeOf<typeof ToggleCustomFieldConfigurationRt>;
15 changes: 14 additions & 1 deletion x-pack/plugins/cases/common/types/domain/custom_field/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import * as rt from 'io-ts';
export enum CustomFieldTypes {
TEXT = 'text',
TOGGLE = 'toggle',
LIST = 'list',
}

export const CustomFieldTextTypeRt = rt.literal(CustomFieldTypes.TEXT);
export const CustomFieldToggleTypeRt = rt.literal(CustomFieldTypes.TOGGLE);
export const CustomFieldListTypeRt = rt.literal(CustomFieldTypes.LIST);

const CaseCustomFieldTextRt = rt.strict({
key: rt.string,
Expand All @@ -26,10 +28,21 @@ export const CaseCustomFieldToggleRt = rt.strict({
value: rt.union([rt.boolean, rt.null]),
});

export const CaseCustomFieldRt = rt.union([CaseCustomFieldTextRt, CaseCustomFieldToggleRt]);
export const CaseCustomFieldListRt = rt.strict({
key: rt.string,
type: CustomFieldListTypeRt,
value: rt.union([rt.string, rt.null]),
});

export const CaseCustomFieldRt = rt.union([
CaseCustomFieldTextRt,
CaseCustomFieldToggleRt,
CaseCustomFieldListRt,
]);
export const CaseCustomFieldsRt = rt.array(CaseCustomFieldRt);

export type CaseCustomFields = rt.TypeOf<typeof CaseCustomFieldsRt>;
export type CaseCustomField = rt.TypeOf<typeof CaseCustomFieldRt>;
export type CaseCustomFieldToggle = rt.TypeOf<typeof CaseCustomFieldToggleRt>;
export type CaseCustomFieldText = rt.TypeOf<typeof CaseCustomFieldTextRt>;
export type CaseCustomFieldList = rt.TypeOf<typeof CaseCustomFieldListRt>;
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {
PersistableStateAttachment,
Configuration,
CustomFieldTypes,
CustomFieldsConfiguration,
} from '../types/domain';
import type {
CasePatchRequest,
Expand Down Expand Up @@ -189,6 +190,7 @@ export type CaseUser = SnakeToCamelCase<User>;
export interface FetchCasesProps extends ApiProps {
queryParams?: QueryParams;
filterOptions?: FilterOptions;
customFieldsConfiguration: CustomFieldsConfiguration;
}

export interface ApiProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ export const useCustomFieldsFilterConfig = ({
return { customFieldsFilterConfig: [] };
}

for (const { key: fieldKey, type, label: buttonLabel } of customFields ?? []) {
for (const customFieldConfiguration of customFields ?? []) {
const { key: fieldKey, type, label: buttonLabel } = customFieldConfiguration;
if (customFieldsBuilder[type]) {
const { filterOptions: customFieldOptions } = customFieldsBuilder[type]();
const { getFilterOptions } = customFieldsBuilder[type]();
const customFieldOptions = getFilterOptions?.(customFieldConfiguration);

if (customFieldOptions) {
customFieldsFilterConfig.push(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import { Status } from '@kbn/cases-components/src/status/status';
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';

import type { ActionConnector } from '../../../common/types/domain';
import { CaseSeverity } from '../../../common/types/domain';
import type { CaseUI } from '../../../common/ui/types';
import { CaseSeverity, CustomFieldTypes } from '../../../common/types/domain';
import type { CaseUI, CasesConfigurationUICustomField } from '../../../common/ui/types';
import type { CasesColumnSelection } from './types';
import { getEmptyCellValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';
Expand Down Expand Up @@ -333,9 +333,15 @@ export const useCasesColumns = ({

// we need to extend the columnsDict with the columns of
// the customFields
customFields.forEach(({ key, type, label }) => {
customFields.forEach((configuration) => {
const { key, type, label } = configuration;
if (type in customFieldsBuilderMap) {
const columnDefinition = customFieldsBuilderMap[type]().getEuiTableColumn({ label });
const customFieldDefinition = customFieldsBuilderMap[type]();
const euiTableColumnProps =
type === CustomFieldTypes.LIST ? { label, options: configuration.options } : { label };
const columnDefinition = customFieldDefinition.getEuiTableColumn(
euiTableColumnProps as CasesConfigurationUICustomField
);

columnsDict[key] = {
...columnDefinition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe.skip('CustomFields', () => {
expect(screen.queryAllByTestId('create-custom-field', { exact: false }).length).toEqual(0);
});

it('should render as optional fields for text custom fields', async () => {
it('should render as optional fields for text and list custom fields', async () => {
appMockRender.render(
<FormTestComponent onSubmit={onSubmit}>
<CustomFields
Expand All @@ -78,7 +78,7 @@ describe.skip('CustomFields', () => {
</FormTestComponent>
);

expect(await screen.findAllByTestId('form-optional-field-label')).toHaveLength(2);
expect(await screen.findAllByTestId('form-optional-field-label')).toHaveLength(4);
});

it('should not set default value when in edit mode', async () => {
Expand Down Expand Up @@ -115,12 +115,14 @@ describe.skip('CustomFields', () => {

const customFields = customFieldsWrapper.querySelectorAll('.euiFormRow');

expect(customFields).toHaveLength(4);
expect(customFields).toHaveLength(6);

expect(customFields[0]).toHaveTextContent('My test label 1');
expect(customFields[1]).toHaveTextContent('My test label 2');
expect(customFields[2]).toHaveTextContent('My test label 3');
expect(customFields[3]).toHaveTextContent('My test label 4');
expect(customFields[4]).toHaveTextContent('My test label 5');
expect(customFields[5]).toHaveTextContent('My test label 6');
});

it('should update the custom fields', async () => {
Expand All @@ -132,6 +134,7 @@ describe.skip('CustomFields', () => {

const textField = customFieldsConfigurationMock[2];
const toggleField = customFieldsConfigurationMock[3];
const listField = customFieldsConfigurationMock[4];

await userEvent.type(
await screen.findByTestId(`${textField.key}-${textField.type}-create-custom-field`),
Expand All @@ -152,6 +155,7 @@ describe.skip('CustomFields', () => {
[customFieldsConfigurationMock[1].key]: customFieldsConfigurationMock[1].defaultValue,
[textField.key]: 'hello',
[toggleField.key]: true,
[listField.key]: 'option_1',
},
},
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ describe('CaseFormFields', () => {
test_key_1: 'My text test value 1',
test_key_2: false,
test_key_4: false,
test_key_5: 'option_1',
},
},
true
Expand Down Expand Up @@ -268,6 +269,7 @@ describe('CaseFormFields', () => {
test_key_1: 'Test custom filed value',
test_key_2: true,
test_key_4: false,
test_key_5: 'option_1',
},
},
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('Case View Page files tab', () => {
exact: false,
});

expect(customFields.length).toBe(4);
expect(customFields.length).toBe(6);

expect(await within(customFields[0]).findByRole('heading')).toHaveTextContent(
'My test label 1'
Expand All @@ -103,6 +103,12 @@ describe('Case View Page files tab', () => {
expect(await within(customFields[3]).findByRole('heading')).toHaveTextContent(
'My test label 4'
);
expect(await within(customFields[4]).findByRole('heading')).toHaveTextContent(
'My test label 5'
);
expect(await within(customFields[5]).findByRole('heading')).toHaveTextContent(
'My test label 6'
);
});

it('pass the permissions to custom fields correctly', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ describe('CommonFlyout ', () => {
label: 'First custom field',
required: true,
},
],
] as CustomFieldConfiguration[],
};

appMockRender = createAppMockRenderer({ license });
Expand Down Expand Up @@ -597,21 +597,13 @@ describe('CommonFlyout ', () => {
type: 'text',
value: 'this is a sample text!',
},
{
key: 'test_key_2',
type: 'toggle',
value: true,
},
{
key: 'test_key_3',
type: 'text',
value: null,
},
{
key: 'test_key_4',
type: 'toggle',
value: false,
},
...customFieldsConfigurationMock
.slice(1)
.map(({ key, type, defaultValue, required }) => ({
key,
type,
value: required ? defaultValue : type === CustomFieldTypes.TOGGLE ? false : null,
})),
],
},
});
Expand Down Expand Up @@ -691,7 +683,7 @@ describe('CommonFlyout ', () => {
label: 'First custom field',
required: true,
},
],
] as CustomFieldConfiguration[],
connector: {
id: 'servicenow-1',
name: 'My SN connector',
Expand Down
Loading