Skip to content

Commit

Permalink
feat: add grouping to configuration page (#1432)
Browse files Browse the repository at this point in the history
  • Loading branch information
soleksy-splunk authored Nov 13, 2024
1 parent 6b89e55 commit b94b228
Show file tree
Hide file tree
Showing 10 changed files with 389 additions and 19 deletions.
1 change: 1 addition & 0 deletions docs/configurations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ proxy configuration, and logging level configuration.
| name<span class="required-asterisk">\*</span> | string | To define the particular tab name. |
| title<span class="required-asterisk">\*</span> | string | To show the title of the tab. |
| [entity](../entity/index.md)<span class="required-asterisk">\*</span> | array | A list of fields and their properties. |
| [groups](../advanced/groups_feature.md) | array | It is used to divide forms into distinct sections, each comprising relevant fields. |
| [table](../table.md) | object | To display accounts stanza in table |
| style | string | By specifying this property in the global config file, the forms can either be opened as a new page or in a dialog. <br>Supported values are "page" or "dialog". <br> Default value is **dialog**. |
| options | object | This property allows you to enable the [saveValidator](../advanced/save_validator.md) feature. |
Expand Down
3 changes: 3 additions & 0 deletions splunk_add_on_ucc_framework/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2476,6 +2476,9 @@
},
"hideForPlatform": {
"$ref": "#/definitions/HideForPlatform"
},
"groups": {
"$ref": "#/definitions/Groups"
}
},
"anyOf": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,21 @@
}
}
],
"groups": [
{
"label": "Hidden entities",
"fields": [
"text_field_hidden_for_cloud",
"text_field_hidden_for_enterprise",
"field_no_validators",
"field_no_validators_suppressed"
],
"options": {
"isExpandable": true,
"expand": false
}
}
],
"title": "Account",
"restHandlerModule": "splunk_ta_uccexample_validate_account_rh",
"restHandlerClass": "CustomAccountValidator"
Expand Down Expand Up @@ -1891,10 +1906,10 @@
"meta": {
"name": "Splunk_TA_UCCExample",
"restRoot": "splunk_ta_uccexample",
"version": "5.51.1+3536793cd",
"version": "5.52.0+70c7e9d6b",
"displayName": "Splunk UCC test Add-on",
"schemaVersion": "0.0.9",
"_uccVersion": "5.51.1",
"_uccVersion": "5.52.0",
"supportedThemes": [
"light",
"dark"
Expand Down
234 changes: 233 additions & 1 deletion ui/src/components/BaseFormView/BaseFormConfigMock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from 'zod';
import { GlobalConfigSchema } from '../../types/globalConfig/globalConfig';
import { GlobalConfig, GlobalConfigSchema } from '../../types/globalConfig/globalConfig';
import { AnyOfEntity } from '../../types/globalConfig/entities';

const globalConfigMockCustomControl = {
pages: {
Expand Down Expand Up @@ -174,3 +175,234 @@ const globalConfigMockCustomControl = {
export function getGlobalConfigMockCustomControl() {
return GlobalConfigSchema.parse(globalConfigMockCustomControl);
}

const EXAMPLE_GROUPS_ENTITIES = [
{
type: 'text',
label: 'Text 1 Group 2',
field: 'text_field_1_group_2',
required: false,
},
{
type: 'text',
label: 'Text 2 Group 2',
field: 'text_field_2_group_2',
required: false,
},
{
type: 'text',
label: 'Text 1 Group 1',
field: 'text_field_1_group_1',
required: false,
},
{
type: 'text',
label: 'Text 2 Group 1',
field: 'text_field_2_group_1',
required: false,
},
{
type: 'text',
label: 'Text 1 Group 3',
field: 'text_field_1_group_3',
required: false,
},
{
type: 'text',
label: 'Text 2 Group 3',
field: 'text_field_2_group_3',
required: false,
},
] satisfies z.input<typeof AnyOfEntity>[];

const GROUPS_FOR_EXAMPLE_ENTITIES = [
{
label: 'Group 1',
fields: ['text_field_1_group_1', 'text_field_2_group_1'],
},
{
label: 'Group 2',
fields: ['text_field_1_group_2', 'text_field_2_group_2'],
options: {
isExpandable: true,
expand: true,
},
},
{
label: 'Group 3',
fields: ['text_field_1_group_3', 'text_field_2_group_3'],
options: {
isExpandable: true,
expand: false,
},
},
];

const globalConfigMockGroupsForConfigPage = {
pages: {
configuration: {
tabs: [
{
name: 'account',
table: {
actions: ['edit', 'delete', 'clone'],
header: [
{
label: 'Name',
field: 'name',
},
],
},
entity: [
{
type: 'text',
label: 'Name',
validators: [
{
type: 'regex',
errorMsg:
'Account Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.',
pattern: '^[a-zA-Z]\\w*$',
},
],
field: 'name',
help: 'A unique name for the account.',
required: true,
},
...EXAMPLE_GROUPS_ENTITIES,
],
groups: GROUPS_FOR_EXAMPLE_ENTITIES,
title: 'Accounts',
},
],
title: 'Configuration',
description: 'Set up your add-on',
},
inputs: {
services: [],
title: 'Inputs',
description: 'Manage your data inputs',
table: {
actions: ['edit', 'delete', 'clone'],
header: [
{
label: 'Name',
field: 'name',
},
],
},
},
},
meta: {
name: 'demo_addon_for_splunk',
restRoot: 'demo_addon_for_splunk',
version: '5.31.1R85f0e18e',
displayName: 'Demo Add-on for Splunk',
schemaVersion: '0.0.3',
checkForUpdates: false,
searchViewDefault: false,
},
} satisfies z.input<typeof GlobalConfigSchema>;

export function getGlobalConfigMockGroupsForConfigPage(): GlobalConfig {
return GlobalConfigSchema.parse(globalConfigMockGroupsForConfigPage);
}

const globalConfigMockGroupsForInputPage = {
pages: {
configuration: {
tabs: [
{
name: 'account',
table: {
actions: ['edit', 'delete', 'clone'],
header: [
{
label: 'Name',
field: 'name',
},
],
},
entity: [
{
type: 'text',
label: 'Name',
validators: [
{
type: 'regex',
errorMsg:
'Account Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.',
pattern: '^[a-zA-Z]\\w*$',
},
],
field: 'name',
help: 'A unique name for the account.',
required: true,
},
],
title: 'Accounts',
},
],
title: 'Configuration',
description: 'Set up your add-on',
},
inputs: {
services: [
{
name: 'demo_input',
entity: [
{
type: 'text',
label: 'Name',
validators: [
{
type: 'regex',
errorMsg:
'Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.',
pattern: '^[a-zA-Z]\\w*$',
},
{
type: 'string',
errorMsg: 'Length of input name should be between 1 and 100',
minLength: 1,
maxLength: 100,
},
],
field: 'name',
help: 'A unique name for the data input.',
required: true,
encrypted: false,
},
...EXAMPLE_GROUPS_ENTITIES,
],
groups: GROUPS_FOR_EXAMPLE_ENTITIES,
title: 'demo_input',
},
],
title: 'Inputs',
description: 'Manage your data inputs',
table: {
actions: ['edit', 'delete', 'clone'],
header: [
{
label: 'Name',
field: 'name',
},
],
},
},
},
meta: {
name: 'demo_addon_for_splunk',
restRoot: 'demo_addon_for_splunk',
version: '5.31.1R85f0e18e',
displayName: 'Demo Add-on for Splunk',
schemaVersion: '0.0.3',
checkForUpdates: false,
searchViewDefault: false,
},
} satisfies z.input<typeof GlobalConfigSchema>;

export function getGlobalConfigMockGroupsFoInputPage(): GlobalConfig {
return GlobalConfigSchema.parse(globalConfigMockGroupsForInputPage);
}
82 changes: 81 additions & 1 deletion ui/src/components/BaseFormView/BaseFormView.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import userEvent from '@testing-library/user-event';

import { getGlobalConfigMock } from '../../mocks/globalConfigMock';
import { setUnifiedConfig } from '../../util/util';
import BaseFormView from './BaseFormView';
import { getBuildDirPath } from '../../util/script';
import mockCustomControlMockForTest from '../CustomControl/CustomControlMockForTest';
import { getGlobalConfigMockCustomControl } from './BaseFormConfigMock';
import {
getGlobalConfigMockCustomControl,
getGlobalConfigMockGroupsFoInputPage,
getGlobalConfigMockGroupsForConfigPage,
} from './BaseFormConfigMock';

const handleFormSubmit = jest.fn();

Expand All @@ -14,6 +20,22 @@ const SERVICE_NAME = 'account';
const STANZA_NAME = 'stanzaName';
const CUSTOM_MODULE = 'CustomControl';

const getElementsByGroup = (group: string) => {
const firstField = screen.queryByText(`Text 1 Group ${group}`);
const secondField = screen.queryByText(`Text 2 Group ${group}`);
return { firstField, secondField };
};
const verifyDisplayedGroup = (group: string) => {
const { firstField, secondField } = getElementsByGroup(group);
expect(firstField).toBeInTheDocument();
expect(secondField).toBeInTheDocument();
};
const verifyNotDisplayedElement = (group: string) => {
const { firstField, secondField } = getElementsByGroup(group);
expect(firstField).not.toBeInTheDocument();
expect(secondField).not.toBeInTheDocument();
};

it('should render base form correctly with name and File fields', async () => {
const mockConfig = getGlobalConfigMock();
setUnifiedConfig(mockConfig);
Expand Down Expand Up @@ -66,3 +88,61 @@ it('should pass default values to custom component correctly', async () => {

expect((customModal as HTMLSelectElement)?.value).toEqual('input_three');
});

it.each([
{
page: 'configuration' as const,
config: getGlobalConfigMockGroupsForConfigPage(),
service: 'account',
},
{
page: 'inputs' as const,
config: getGlobalConfigMockGroupsFoInputPage(),
service: 'demo_input',
},
])('entities grouping for page works properly %s', async ({ config, page, service }) => {
setUnifiedConfig(config);

render(
<BaseFormView
page={page}
stanzaName={STANZA_NAME}
serviceName={service}
mode="create"
currentServiceState={{}}
handleFormSubmit={handleFormSubmit}
/>
);
const group1Header = await screen.findByText('Group 1', { exact: true });

const group2Header = await screen.findByRole('button', { name: 'Group 2' });

const group3Header = await screen.findByRole('button', { name: 'Group 3' });

verifyDisplayedGroup('1');
verifyDisplayedGroup('2');
verifyNotDisplayedElement('3'); // group 3 is not expanded by default

expect(group3Header).toHaveAttribute('aria-expanded', 'false');
await userEvent.click(group3Header);
verifyDisplayedGroup('3');
expect(group3Header).toHaveAttribute('aria-expanded', 'true');

await userEvent.click(group1Header); // does not change anything
verifyDisplayedGroup('1');

expect(group2Header).toHaveAttribute('aria-expanded', 'true');
await userEvent.click(group2Header);
expect(group2Header).toHaveAttribute('aria-expanded', 'false');

/**
* verifying aria-expanded attribute as in tests
* child elements are not removed from the DOM
* they are removed in browser
* todo: verify behaviour
*/
await userEvent.click(group2Header);
verifyDisplayedGroup('1');
verifyDisplayedGroup('2');
verifyDisplayedGroup('3'); // after modifications all groups should be displayed
});
Loading

0 comments on commit b94b228

Please sign in to comment.