Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -481,13 +481,13 @@ packages/kbn-managed-vscode-config @elastic/kibana-operations
packages/kbn-managed-vscode-config-cli @elastic/kibana-operations
packages/kbn-management/cards_navigation @elastic/platform-deployment-management
src/plugins/management @elastic/platform-deployment-management
packages/kbn-management/settings/components/field_input @elastic/platform-deployment-management @elastic/appex-sharedux
packages/kbn-management/settings/components/field_row @elastic/platform-deployment-management @elastic/appex-sharedux
packages/kbn-management/settings/field_definition @elastic/platform-deployment-management @elastic/appex-sharedux
packages/kbn-management/settings/components/field_input @elastic/platform-deployment-management
packages/kbn-management/settings/components/field_row @elastic/platform-deployment-management
packages/kbn-management/settings/field_definition @elastic/platform-deployment-management
packages/kbn-management/settings/setting_ids @elastic/appex-sharedux @elastic/platform-deployment-management
packages/kbn-management/settings/section_registry @elastic/appex-sharedux @elastic/platform-deployment-management
packages/kbn-management/settings/types @elastic/platform-deployment-management @elastic/appex-sharedux
packages/kbn-management/settings/utilities @elastic/platform-deployment-management @elastic/appex-sharedux
packages/kbn-management/settings/types @elastic/platform-deployment-management
packages/kbn-management/settings/utilities @elastic/platform-deployment-management
packages/kbn-management/storybook/config @elastic/platform-deployment-management
test/plugin_functional/plugins/management_test_plugin @elastic/kibana-app-services
packages/kbn-mapbox-gl @elastic/kibana-gis
Expand Down
11 changes: 11 additions & 0 deletions packages/kbn-management/settings/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Management Settings

These packages comprise the Management Advanced Settings application. The source of these components and related types and utilities origingated in the `advancedSettings` plugin. We've abstracted it away into packages first, for Serverless, and later, as a drop-in replacement in the plugin.

## Notes

**Be aware**: the functional flow logic we've adopted for these components is not one I would encourage, specifically, using "drilled" onChange handlers and utilizing a composing-component-based store. Ideally, we'd use a Redux store, or, at the very least, a React reducer.

In the interest of time and compatibility, we've opted to use the pattern from the original components in `advancedSettings`. We plan to revisit the state management and prop-drilling when `advancedSettings` is refactored with these components.

This is being tracked with https://github.com/elastic/kibana/issues/166579
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,30 @@
* Side Public License, v 1.
*/

import React from 'react';
import React, { useState } from 'react';
import type { ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';

import { EuiPanel } from '@elastic/eui';
import { UiSettingsType } from '@kbn/core-ui-settings-common';
import { SettingType, UiSettingMetadata } from '@kbn/management-settings-types';
import {
useFieldDefinition,
getDefaultValue,
} from '@kbn/management-settings-field-definition/storybook';
OnChangeFn,
SettingType,
UiSettingMetadata,
UnsavedFieldChange,
} from '@kbn/management-settings-types';

import { getFieldDefinition } from '@kbn/management-settings-field-definition';
import { getDefaultValue, getUserValue } from '@kbn/management-settings-utilities/storybook';
import { FieldInputProvider } from '../services';
import { FieldInput as Component, FieldInput } from '../field_input';
import { InputProps, OnChangeFn } from '../types';
import { InputProps } from '../types';

/**
* Props for a {@link FieldInput} Storybook story.
*/
export type StoryProps<T extends SettingType> = Pick<InputProps<T>, 'value' | 'isDisabled'>;
export type StoryProps<T extends SettingType> = Pick<InputProps<T>, 'isSavingEnabled'> &
Pick<UiSettingMetadata<T>, 'value' | 'userValue'>;

/**
* Interface defining available {@link https://storybook.js.org/docs/react/writing-stories/parameters parameters}
Expand All @@ -42,17 +46,11 @@ interface Params {
*/
export interface Args {
/** True if the field is disabled, false otherwise. */
isDisabled: boolean;
isSavingEnabled: boolean;
userValue: unknown;
value: unknown;
}

/**
* Default argument values for a {@link FieldInput} Storybook story.
*/
export const storyArgs = {
/** True if the field is disabled, false otherwise. */
isDisabled: false,
};

/**
* Utility function for returning a {@link FieldInput} Storybook story
* definition.
Expand All @@ -65,10 +63,13 @@ export const getStory = (title: string, description: string) =>
title: `Settings/Field Input/${title}`,
description,
argTypes: {
isDisabled: {
name: 'Is field disabled?',
isSavingEnabled: {
name: 'Is saving enabled?',
},
value: {
name: 'Default value',
},
userValue: {
name: 'Current saved value',
},
},
Expand All @@ -90,30 +91,45 @@ export const getStory = (title: string, description: string) =>
* @returns A Storybook Story.
*/
export const getInputStory = (type: SettingType, params: Params = {}) => {
const Story = ({ value, isDisabled = false }: StoryProps<typeof type>) => {
const Story = ({ userValue, value, isSavingEnabled }: StoryProps<typeof type>) => {
const [unsavedChange, setUnsavedChange] = useState<
UnsavedFieldChange<typeof type> | undefined
>();

const setting: UiSettingMetadata<typeof type> = {
type,
value,
userValue: value,
userValue,
...params.settingFields,
};

const [field, unsavedChange, onChangeFn] = useFieldDefinition(setting);
const field = getFieldDefinition({
id: setting.name?.split(' ').join(':').toLowerCase() || setting.type,
setting,
});

const onChange: OnChangeFn<typeof type> = (newChange) => {
onChangeFn(newChange);
setUnsavedChange(newChange);

action('onChange')({
type,
unsavedValue: newChange?.unsavedValue,
savedValue: field.savedValue,
});
};
return (
<FieldInput
{...{ field, isInvalid: unsavedChange.isInvalid, unsavedChange, onChange, isDisabled }}
/>
);

return <FieldInput {...{ field, unsavedChange, onChange, isSavingEnabled }} />;
};

Story.argTypes = {
...params.argTypes,
};

Story.args = {
isSavingEnabled: true,
value: getDefaultValue(type),
userValue: getUserValue(type),
...params.argTypes,
...storyArgs,
};

return Story;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { getInputStory, getStory } from './common';

const argTypes = {
value: {
name: 'Default value',
control: {
type: 'select',
options: ['option1', 'option2', 'option3'],
},
},
userValue: {
name: 'Current saved value',
control: {
type: 'select',
Expand All @@ -25,3 +32,9 @@ const settingFields = {

export default getStory('Select Input', 'An input with multiple values.');
export const SelectInput = getInputStory('select' as const, { argTypes, settingFields });

SelectInput.args = {
isSavingEnabled: true,
value: 'option1',
userValue: 'option2',
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ describe('FieldInput', () => {
options,
} as FieldDefinition<typeof type>,
onChange: jest.fn(),
isSavingEnabled: true,
};

return props;
Expand Down Expand Up @@ -131,12 +132,12 @@ describe('FieldInput', () => {
const { getByTestId } = render(wrap(<FieldInput {...props} />));
const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`);
fireEvent.change(input, { target: { value: 'new value' } });
expect(props.onChange).toHaveBeenCalledWith({ value: 'new value' });
expect(props.onChange).toHaveBeenCalledWith({ type: 'string', unsavedValue: 'new value' });
});

it('disables the input when isDisabled prop is true', () => {
const props = getDefaultProps('string');
const { getByTestId } = render(wrap(<FieldInput {...props} isDisabled />));
const { getByTestId } = render(wrap(<FieldInput {...props} isSavingEnabled={false} />));
const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`);
expect(input).toBeDisabled();
});
Expand Down Expand Up @@ -190,7 +191,7 @@ describe('FieldInput', () => {
...defaultProps.field,
type: 'foobar',
},
} as unknown as FieldInputProps<'string'>;
} as unknown as FieldInputProps<SettingType>;

expect(() => render(wrap(<FieldInput {...props} />))).toThrowError(
'Unknown or incompatible field type: foobar'
Expand Down
Loading