From 05779294469d77c7addd29c2fceb56c4eccb8674 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Wed, 27 Sep 2023 10:57:58 -0400 Subject: [PATCH 1/6] Make SettingType pre-defined to clean up references --- .../field_input/field_input.test.tsx | 2 +- .../components/field_input/field_input.tsx | 210 +++++++++--------- .../field_row/__stories__/common.tsx | 5 +- .../components/field_row/field_row.tsx | 10 +- .../field_row/footer/reset_link.test.tsx | 4 +- .../field_row/footer/reset_link.tsx | 2 +- .../settings/components/field_row/index.ts | 2 +- .../settings/components/field_row/types.ts | 2 +- .../settings/components/form/form.test.tsx | 7 +- .../settings/components/form/form.tsx | 12 +- .../settings/components/form/services.tsx | 4 +- .../form/storybook/form.stories.tsx | 4 +- .../settings/components/form/types.ts | 4 +- .../settings/components/form/use_save.ts | 6 +- .../field_definition/get_definitions.ts | 6 +- .../field_definition/is/field_definition.ts | 3 +- .../field_definition/is/unsaved_change.ts | 3 +- .../settings/types/field_definition.ts | 5 +- .../kbn-management/settings/types/index.ts | 5 +- .../kbn-management/settings/types/metadata.ts | 6 +- .../settings/types/setting_type.ts | 2 +- .../settings/types/unsaved_change.ts | 4 +- .../utilities/setting/is_default_value.ts | 4 +- .../utilities/setting/normalize_settings.ts | 6 +- 24 files changed, 156 insertions(+), 162 deletions(-) diff --git a/packages/kbn-management/settings/components/field_input/field_input.test.tsx b/packages/kbn-management/settings/components/field_input/field_input.test.tsx index 0305636fd35e6..49f2135dbb121 100644 --- a/packages/kbn-management/settings/components/field_input/field_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/field_input.test.tsx @@ -191,7 +191,7 @@ describe('FieldInput', () => { ...defaultProps.field, type: 'foobar', }, - } as unknown as FieldInputProps; + } as unknown as FieldInputProps; expect(() => render(wrap())).toThrowError( 'Unknown or incompatible field type: foobar' diff --git a/packages/kbn-management/settings/components/field_input/field_input.tsx b/packages/kbn-management/settings/components/field_input/field_input.tsx index ae9e3c5c6c536..bc481927f089e 100644 --- a/packages/kbn-management/settings/components/field_input/field_input.tsx +++ b/packages/kbn-management/settings/components/field_input/field_input.tsx @@ -56,7 +56,7 @@ import { /** * The props that are passed to the {@link FieldInput} component. */ -export interface FieldInputProps { +export interface FieldInputProps { /** The {@link FieldDefinition} for the component. */ field: Pick, 'type' | 'id' | 'name' | 'ariaAttributes'>; /** An {@link UnsavedFieldChange} for the component, if any. */ @@ -81,140 +81,136 @@ const getMismatchError = (type: SettingType, unsavedType?: SettingType) => * * @param props The props for the {@link FieldInput} component. */ -export const FieldInput = React.forwardRef>( - (props, ref) => { - const { field, unsavedChange, onChange, isSavingEnabled } = props; - - // Create a ref for those input fields that require an imperative handle. - const inputRef = useRef(null); - - // Create an imperative handle that passes the invocation to any internal input that - // may require it. - useImperativeHandle(ref, () => ({ - reset: () => { - if (inputRef.current) { - inputRef.current.reset(); - } - }, - })); - - const inputProps = { isSavingEnabled, onChange }; - - // These checks might seem excessive or redundant, but they are necessary to ensure that - // the types are honored correctly using type guards. These checks get compiled down to - // checks against the `type` property-- which we were doing in the previous code, albeit - // in an unenforceable way. - // - // Based on the success of a check, we can render the `FieldInput` in a indempotent and - // type-safe way. - // - if (isArrayFieldDefinition(field)) { - // If the composing component mistakenly provides an incompatible `UnsavedFieldChange`, - // we can throw an `Error`. We might consider switching to a `console.error` and not - // rendering the input, but that might be less helpful. - if (!isArrayFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); +export const FieldInput = React.forwardRef((props, ref) => { + const { field, unsavedChange, onChange, isSavingEnabled } = props; + + // Create a ref for those input fields that require an imperative handle. + const inputRef = useRef(null); + + // Create an imperative handle that passes the invocation to any internal input that + // may require it. + useImperativeHandle(ref, () => ({ + reset: () => { + if (inputRef.current) { + inputRef.current.reset(); } - - return ; + }, + })); + + const inputProps = { isSavingEnabled, onChange }; + + // These checks might seem excessive or redundant, but they are necessary to ensure that + // the types are honored correctly using type guards. These checks get compiled down to + // checks against the `type` property-- which we were doing in the previous code, albeit + // in an unenforceable way. + // + // Based on the success of a check, we can render the `FieldInput` in a indempotent and + // type-safe way. + // + if (isArrayFieldDefinition(field)) { + // If the composing component mistakenly provides an incompatible `UnsavedFieldChange`, + // we can throw an `Error`. We might consider switching to a `console.error` and not + // rendering the input, but that might be less helpful. + if (!isArrayFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isBooleanFieldDefinition(field)) { - if (!isBooleanFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + return ; + } - return ; + if (isBooleanFieldDefinition(field)) { + if (!isBooleanFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isColorFieldDefinition(field)) { - if (!isColorFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + return ; + } - return ; + if (isColorFieldDefinition(field)) { + if (!isColorFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isImageFieldDefinition(field)) { - if (!isImageFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + return ; + } - return ; + if (isImageFieldDefinition(field)) { + if (!isImageFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isJsonFieldDefinition(field)) { - if (!isJsonFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + return ; + } - return ( - - ); + if (isJsonFieldDefinition(field)) { + if (!isJsonFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isMarkdownFieldDefinition(field)) { - if (!isMarkdownFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + return ( + + ); + } - return ( - - ); + if (isMarkdownFieldDefinition(field)) { + if (!isMarkdownFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isNumberFieldDefinition(field)) { - if (!isNumberFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + return ( + + ); + } - return ; + if (isNumberFieldDefinition(field)) { + if (!isNumberFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isSelectFieldDefinition(field)) { - if (!isSelectFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } - - const { - options: { values: optionValues, labels: optionLabels }, - } = field; + return ; + } - return ( - - ); + if (isSelectFieldDefinition(field)) { + if (!isSelectFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isStringFieldDefinition(field)) { - if (!isStringFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + const { + options: { values: optionValues, labels: optionLabels }, + } = field; - return ; + return ; + } + + if (isStringFieldDefinition(field)) { + if (!isStringFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - if (isUndefinedFieldDefinition(field)) { - if (!isUndefinedFieldUnsavedChange(unsavedChange)) { - throw getMismatchError(field.type, unsavedChange?.type); - } + return ; + } - return ( - } - unsavedChange={unsavedChange as unknown as UnsavedFieldChange<'string'>} - {...inputProps} - /> - ); + if (isUndefinedFieldDefinition(field)) { + if (!isUndefinedFieldUnsavedChange(unsavedChange)) { + throw getMismatchError(field.type, unsavedChange?.type); } - throw new Error(`Unknown or incompatible field type: ${field.type}`); + return ( + } + unsavedChange={unsavedChange as unknown as UnsavedFieldChange<'string'>} + {...inputProps} + /> + ); } -); + + throw new Error(`Unknown or incompatible field type: ${field.type}`); +}); diff --git a/packages/kbn-management/settings/components/field_row/__stories__/common.tsx b/packages/kbn-management/settings/components/field_row/__stories__/common.tsx index 58e145146cc40..de0fd7dd29120 100644 --- a/packages/kbn-management/settings/components/field_row/__stories__/common.tsx +++ b/packages/kbn-management/settings/components/field_row/__stories__/common.tsx @@ -103,10 +103,7 @@ export const storyArgs = { * @param type The type of the UiSetting for this {@link FieldRow}. * @returns A Storybook Story. */ -export const getFieldRowStory = ( - type: SettingType, - settingFields?: Partial> -) => { +export const getFieldRowStory = (type: SettingType, settingFields?: Partial) => { const Story = ({ isCustom, isDeprecated, diff --git a/packages/kbn-management/settings/components/field_row/field_row.tsx b/packages/kbn-management/settings/components/field_row/field_row.tsx index 9058511c955d1..51d844c41b84c 100644 --- a/packages/kbn-management/settings/components/field_row/field_row.tsx +++ b/packages/kbn-management/settings/components/field_row/field_row.tsx @@ -35,7 +35,7 @@ import { FieldInputFooter } from './footer'; export const DATA_TEST_SUBJ_SCREEN_READER_MESSAGE = 'fieldRowScreenReaderMessage'; -type Definition = Pick< +type Definition = Pick< FieldDefinition, | 'ariaAttributes' | 'defaultValue' @@ -57,18 +57,18 @@ type Definition = Pick< */ export interface FieldRowProps { /** The {@link FieldDefinition} corresponding the setting. */ - field: Definition; + field: Definition; /** True if saving settings is enabled, false otherwise. */ isSavingEnabled: boolean; /** The {@link OnChangeFn} handler. */ - onChange: RowOnChangeFn; + onChange: RowOnChangeFn; /** * The onClear handler, if a value is cleared to an empty or default state. * @param id The id relating to the field to clear. */ onClear?: (id: string) => void; /** The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. */ - unsavedChange?: UnsavedFieldChange; + unsavedChange?: UnsavedFieldChange; } /** @@ -87,7 +87,7 @@ export const FieldRow = (props: FieldRowProps) => { const ref = useRef(null); // Route any change to the `onChange` handler, along with the field id. - const onChange: OnChangeFn = (update) => { + const onChange: OnChangeFn = (update) => { onChangeProp(id, update); }; diff --git a/packages/kbn-management/settings/components/field_row/footer/reset_link.test.tsx b/packages/kbn-management/settings/components/field_row/footer/reset_link.test.tsx index 2704e9e33d743..74f87aadca816 100644 --- a/packages/kbn-management/settings/components/field_row/footer/reset_link.test.tsx +++ b/packages/kbn-management/settings/components/field_row/footer/reset_link.test.tsx @@ -9,13 +9,11 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; -import { SettingType } from '@kbn/management-settings-types'; - import { wrap } from '../mocks'; import { InputResetLink, InputResetLinkProps } from './reset_link'; describe('InputResetLink', () => { - const defaultProps: InputResetLinkProps = { + const defaultProps: InputResetLinkProps = { field: { type: 'string', id: 'test', diff --git a/packages/kbn-management/settings/components/field_row/footer/reset_link.tsx b/packages/kbn-management/settings/components/field_row/footer/reset_link.tsx index 35a34350e0be8..778995085d981 100644 --- a/packages/kbn-management/settings/components/field_row/footer/reset_link.tsx +++ b/packages/kbn-management/settings/components/field_row/footer/reset_link.tsx @@ -21,7 +21,7 @@ import { isFieldDefaultValue } from '@kbn/management-settings-utilities'; /** * Props for a {@link InputResetLink} component. */ -export interface InputResetLinkProps { +export interface InputResetLinkProps { /** The {@link FieldDefinition} corresponding the setting. */ field: Pick< FieldDefinition, diff --git a/packages/kbn-management/settings/components/field_row/index.ts b/packages/kbn-management/settings/components/field_row/index.ts index 98c64f1cd494d..f86982141f75a 100644 --- a/packages/kbn-management/settings/components/field_row/index.ts +++ b/packages/kbn-management/settings/components/field_row/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { FieldRow, type FieldRowProps as FieldProps } from './field_row'; +export { FieldRow, type FieldRowProps } from './field_row'; export { FieldRowProvider, FieldRowKibanaProvider, type FieldRowProviderProps } from './services'; export type { FieldRowServices, diff --git a/packages/kbn-management/settings/components/field_row/types.ts b/packages/kbn-management/settings/components/field_row/types.ts index d353cd91fba49..d7b33fa8f972f 100644 --- a/packages/kbn-management/settings/components/field_row/types.ts +++ b/packages/kbn-management/settings/components/field_row/types.ts @@ -49,7 +49,7 @@ export type FieldRowKibanaDependencies = KibanaDependencies & FieldInputKibanaDe * @param id A unique id corresponding to the particular setting being changed. * @param change The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. */ -export type RowOnChangeFn = ( +export type RowOnChangeFn = ( id: string, change?: UnsavedFieldChange ) => void; diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index 2f1cbdb80bcac..387c70113f36b 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -19,10 +19,7 @@ import { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bott import { FormServices } from './types'; const settingsMock = getSettingsMock(); -const fields: Array> = getFieldDefinitions( - settingsMock, - uiSettingsClientMock -); +const fields: FieldDefinition[] = getFieldDefinitions(settingsMock, uiSettingsClientMock); describe('Form', () => { beforeEach(() => { @@ -122,7 +119,7 @@ describe('Form', () => { it('fires showReloadPagePrompt when changing a reloadPageRequired setting', async () => { const services: FormServices = createFormServicesMock(); // Make all settings require a page reload - const testFields: Array> = getFieldDefinitions( + const testFields: FieldDefinition[] = getFieldDefinitions( getSettingsMock(true), uiSettingsClientMock ); diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index fabc80755cad8..79396dd860b6a 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -10,7 +10,7 @@ import React, { Fragment } from 'react'; import type { FieldDefinition } from '@kbn/management-settings-types'; import { FieldRow, RowOnChangeFn } from '@kbn/management-settings-components-field-row'; -import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { UnsavedFieldChange } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; import { BottomBar } from './bottom_bar'; import { useSave } from './use_save'; @@ -20,7 +20,7 @@ import { useSave } from './use_save'; */ export interface FormProps { /** A list of {@link FieldDefinition} corresponding to settings to be displayed in the form. */ - fields: Array>; + fields: FieldDefinition[]; /** True if saving settings is enabled, false otherwise. */ isSavingEnabled: boolean; } @@ -32,9 +32,9 @@ export interface FormProps { export const Form = (props: FormProps) => { const { fields, isSavingEnabled } = props; - const [unsavedChanges, setUnsavedChanges] = React.useState< - Record> - >({}); + const [unsavedChanges, setUnsavedChanges] = React.useState>( + {} + ); const [isLoading, setIsLoading] = React.useState(false); @@ -53,7 +53,7 @@ export const Form = (props: FormProps) => { setIsLoading(false); }; - const onChange: RowOnChangeFn = (id, change) => { + const onChange: RowOnChangeFn = (id, change) => { if (!change) { const { [id]: unsavedChange, ...rest } = unsavedChanges; setUnsavedChanges(rest); diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index bdbfbdc88c33b..7e36b42245dd2 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -11,7 +11,7 @@ import { FieldRowKibanaProvider, } from '@kbn/management-settings-components-field-row'; import React, { FC, useContext } from 'react'; -import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { UnsavedFieldChange } from '@kbn/management-settings-types'; import type { FormServices, FormKibanaDependencies, Services } from './types'; import { reloadPageToast } from './reload_page_toast'; @@ -47,7 +47,7 @@ export const FormKibanaProvider: FC = ({ children, ...de return ( >) => { + saveChanges: (changes: Record) => { const arr = Object.entries(changes).map(([key, value]) => settings.client.set(key, value.unsavedValue) ); diff --git a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx index 5ba4a57d8a2f3..f2ff27eacd9aa 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiPanel } from '@elastic/eui'; import { action } from '@storybook/addon-actions'; import { ComponentMeta } from '@storybook/react'; -import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; +import { FieldDefinition } from '@kbn/management-settings-types'; import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; import { getSettingsMock, uiSettingsClientMock } from '../mocks'; import { Form as Component } from '../form'; @@ -53,7 +53,7 @@ interface FormStoryProps { } export const Form = ({ isSavingEnabled, requirePageReload }: FormStoryProps) => { - const fields: Array> = getFieldDefinitions( + const fields: FieldDefinition[] = getFieldDefinitions( getSettingsMock(requirePageReload), uiSettingsClientMock ); diff --git a/packages/kbn-management/settings/components/form/types.ts b/packages/kbn-management/settings/components/form/types.ts index 2e803cbd74cb5..155420f5effe2 100644 --- a/packages/kbn-management/settings/components/form/types.ts +++ b/packages/kbn-management/settings/components/form/types.ts @@ -10,7 +10,7 @@ import type { FieldRowKibanaDependencies, FieldRowServices, } from '@kbn/management-settings-components-field-row'; -import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { UnsavedFieldChange } from '@kbn/management-settings-types'; import { SettingsStart } from '@kbn/core-ui-settings-browser'; import { I18nStart } from '@kbn/core-i18n-browser'; import { ThemeServiceStart } from '@kbn/core-theme-browser'; @@ -20,7 +20,7 @@ import { ToastsStart } from '@kbn/core-notifications-browser'; * Contextual services used by a {@link Form} component. */ export interface Services { - saveChanges: (changes: Record>) => void; + saveChanges: (changes: Record) => void; showError: (message: string) => void; showReloadPagePrompt: () => void; } diff --git a/packages/kbn-management/settings/components/form/use_save.ts b/packages/kbn-management/settings/components/form/use_save.ts index ebd1981eb57d9..f0679f31aca20 100644 --- a/packages/kbn-management/settings/components/form/use_save.ts +++ b/packages/kbn-management/settings/components/form/use_save.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { FieldDefinition, SettingType } from '@kbn/management-settings-types'; +import type { FieldDefinition } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import { UnsavedFieldChange } from '@kbn/management-settings-types'; @@ -14,7 +14,7 @@ import { useServices } from './services'; export interface UseSaveParameters { /** All {@link FieldDefinition} in the form. */ - fields: Array>; + fields: FieldDefinition[]; /** The function to invoke for clearing all unsaved changes. */ clearChanges: () => void; } @@ -28,7 +28,7 @@ export interface UseSaveParameters { export const useSave = (params: UseSaveParameters) => { const { saveChanges, showError, showReloadPagePrompt } = useServices(); - return async (changes: Record>) => { + return async (changes: Record) => { if (isEmpty(changes)) { return; } diff --git a/packages/kbn-management/settings/field_definition/get_definitions.ts b/packages/kbn-management/settings/field_definition/get_definitions.ts index 83d604db294cf..e84c05b03f07d 100644 --- a/packages/kbn-management/settings/field_definition/get_definitions.ts +++ b/packages/kbn-management/settings/field_definition/get_definitions.ts @@ -7,7 +7,7 @@ */ import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import { FieldDefinition, SettingType, UiSettingMetadata } from '@kbn/management-settings-types'; +import { FieldDefinition, UiSettingMetadata } from '@kbn/management-settings-types'; import { getFieldDefinition } from './get_definition'; type SettingsClient = Pick; @@ -21,9 +21,9 @@ type SettingsClient = Pick; * @returns An array of {@link FieldDefinition} objects. */ export const getFieldDefinitions = ( - settings: Record>, + settings: Record, client: SettingsClient -): Array> => +): FieldDefinition[] => Object.entries(settings).map(([id, setting]) => getFieldDefinition({ id, diff --git a/packages/kbn-management/settings/field_definition/is/field_definition.ts b/packages/kbn-management/settings/field_definition/is/field_definition.ts index 52c6e83468177..364cb4940f0d2 100644 --- a/packages/kbn-management/settings/field_definition/is/field_definition.ts +++ b/packages/kbn-management/settings/field_definition/is/field_definition.ts @@ -26,13 +26,12 @@ import { MarkdownFieldDefinition, NumberFieldDefinition, SelectFieldDefinition, - SettingType, StringFieldDefinition, UndefinedFieldDefinition, } from '@kbn/management-settings-types'; /** Simplifed type for a {@link FieldDefinition} */ -type Definition = Pick, 'type'>; +type Definition = Pick; /** * Returns `true` if the given {@link FieldDefinition} is an {@link ArrayFieldDefinition}, diff --git a/packages/kbn-management/settings/field_definition/is/unsaved_change.ts b/packages/kbn-management/settings/field_definition/is/unsaved_change.ts index 6af63db17e36a..a88e0842bb095 100644 --- a/packages/kbn-management/settings/field_definition/is/unsaved_change.ts +++ b/packages/kbn-management/settings/field_definition/is/unsaved_change.ts @@ -27,12 +27,11 @@ import { SelectUnsavedFieldChange, StringUnsavedFieldChange, UndefinedUnsavedFieldChange, - SettingType, UnsavedFieldChange, } from '@kbn/management-settings-types'; /** Simplifed type for a {@link UnsavedFieldChange} */ -type Change = UnsavedFieldChange; +type Change = UnsavedFieldChange; /** * Returns `true` if the given {@link FieldUnsavedChange} is an {@link ArrayUnsavedFieldChange}, diff --git a/packages/kbn-management/settings/types/field_definition.ts b/packages/kbn-management/settings/types/field_definition.ts index eb34df3b67868..2da9168b6401e 100644 --- a/packages/kbn-management/settings/types/field_definition.ts +++ b/packages/kbn-management/settings/types/field_definition.ts @@ -20,7 +20,10 @@ import { KnownTypeToValue, SettingType } from './setting_type'; * representing a UiSetting). * @public */ -export interface FieldDefinition | null> { +export interface FieldDefinition< + T extends SettingType = SettingType, + V = KnownTypeToValue | null +> { /** UX ARIA attributes derived from the setting. */ ariaAttributes: { /** The `aria-label` attribute for the field input. */ diff --git a/packages/kbn-management/settings/types/index.ts b/packages/kbn-management/settings/types/index.ts index 08cd1ae1df3bb..86106a3663537 100644 --- a/packages/kbn-management/settings/types/index.ts +++ b/packages/kbn-management/settings/types/index.ts @@ -51,6 +51,7 @@ export type { StringUnsavedFieldChange, UndefinedUnsavedFieldChange, UnsavedFieldChange, + UnsavedFieldChanges, } from './unsaved_change'; export type { @@ -76,4 +77,6 @@ export type ResetInputRef = { * A function that is called when the value of a {@link FieldInput} changes. * @param change The {@link UnsavedFieldChange} passed to the handler. */ -export type OnChangeFn = (change?: UnsavedFieldChange) => void; +export type OnChangeFn = ( + change?: UnsavedFieldChange +) => void; diff --git a/packages/kbn-management/settings/types/metadata.ts b/packages/kbn-management/settings/types/metadata.ts index c0a79549039de..48ff816230ce1 100644 --- a/packages/kbn-management/settings/types/metadata.ts +++ b/packages/kbn-management/settings/types/metadata.ts @@ -21,8 +21,10 @@ export type UiSetting = PublicUiSettingsParams & UserProvidedValues; * * @public */ -export interface UiSettingMetadata | null> - extends UiSetting { +export interface UiSettingMetadata< + T extends SettingType = SettingType, + V = KnownTypeToValue | null +> extends UiSetting { /** * The type of setting being represented. * @see{@link SettingType} diff --git a/packages/kbn-management/settings/types/setting_type.ts b/packages/kbn-management/settings/types/setting_type.ts index da297c6d94171..92db0744ac83e 100644 --- a/packages/kbn-management/settings/types/setting_type.ts +++ b/packages/kbn-management/settings/types/setting_type.ts @@ -63,7 +63,7 @@ export type Value = string | boolean | number | Array | undefin * given {@link SettingType}. * @public */ -export type KnownTypeToValue = +export type KnownTypeToValue = T extends 'color' | 'image' | 'json' | 'markdown' | 'select' | 'string' ? string : T extends 'boolean' ? boolean : T extends 'number' | 'bigint' ? number : diff --git a/packages/kbn-management/settings/types/unsaved_change.ts b/packages/kbn-management/settings/types/unsaved_change.ts index 3bd815187f70a..0b5a02e241931 100644 --- a/packages/kbn-management/settings/types/unsaved_change.ts +++ b/packages/kbn-management/settings/types/unsaved_change.ts @@ -13,7 +13,7 @@ import { KnownTypeToValue, SettingType } from './setting_type'; * yet been saved. * @public */ -export interface UnsavedFieldChange { +export interface UnsavedFieldChange { /** * The type of setting. * @see {@link SettingType} @@ -125,3 +125,5 @@ export type KnownTypeToUnsavedChange = T extends 'string' ? StringUnsavedFieldChange: T extends 'undefined' ? UndefinedUnsavedFieldChange : never; + +export type UnsavedFieldChanges = Record; diff --git a/packages/kbn-management/settings/utilities/setting/is_default_value.ts b/packages/kbn-management/settings/utilities/setting/is_default_value.ts index b59467b7410ac..9c6dbe0077521 100644 --- a/packages/kbn-management/settings/utilities/setting/is_default_value.ts +++ b/packages/kbn-management/settings/utilities/setting/is_default_value.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SettingType, UiSettingMetadata, Value } from '@kbn/management-settings-types'; +import { UiSettingMetadata, Value } from '@kbn/management-settings-types'; import isEqual from 'lodash/isEqual'; /** @@ -17,7 +17,7 @@ import isEqual from 'lodash/isEqual'; * @returns True if the provided value is equal to the setting's default value, false otherwise. */ export const isSettingDefaultValue = ( - setting: UiSettingMetadata, + setting: UiSettingMetadata, userValue: Value = setting.userValue ) => { const { value } = setting; diff --git a/packages/kbn-management/settings/utilities/setting/normalize_settings.ts b/packages/kbn-management/settings/utilities/setting/normalize_settings.ts index fa247151c7751..e4083ab58c96b 100644 --- a/packages/kbn-management/settings/utilities/setting/normalize_settings.ts +++ b/packages/kbn-management/settings/utilities/setting/normalize_settings.ts @@ -92,10 +92,8 @@ const deriveValue = (type: SettingType, value: unknown): Value => { * may be missing the `type` or `value` properties. * @returns A mapped collection of normalized {@link UiSetting} objects. */ -export const normalizeSettings = ( - rawSettings: RawSettings -): Record> => { - const normalizedSettings: Record> = {}; +export const normalizeSettings = (rawSettings: RawSettings): Record => { + const normalizedSettings: Record = {}; const entries = Object.entries(rawSettings); From 579e65542d33871f6fc1940c8a25f53e2bff953a Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Wed, 27 Sep 2023 12:18:31 -0400 Subject: [PATCH 2/6] [codemod] Make onFieldChange and onInputChange more distinct --- packages/kbn-management/settings/README.mdx | 2 +- .../field_input/__stories__/common.tsx | 8 ++-- .../field_input/field_input.test.tsx | 4 +- .../components/field_input/field_input.tsx | 10 ++--- .../field_input/input/array_input.test.tsx | 12 +++--- .../field_input/input/array_input.tsx | 4 +- .../field_input/input/boolean_input.test.tsx | 12 +++--- .../field_input/input/boolean_input.tsx | 6 +-- .../field_input/input/code_editor_input.tsx | 4 +- .../input/color_picker_input.test.tsx | 17 +++++---- .../field_input/input/color_picker_input.tsx | 4 +- .../field_input/input/image_input.test.tsx | 10 ++--- .../field_input/input/image_input.tsx | 4 +- .../input/json_editor_input.test.tsx | 30 +++++++-------- .../input/markdown_editor_input.test.tsx | 10 ++--- .../field_input/input/number_input.test.tsx | 13 ++++--- .../field_input/input/number_input.tsx | 4 +- .../field_input/input/select_input.test.tsx | 13 ++++--- .../field_input/input/select_input.tsx | 4 +- .../field_input/input/text_input.test.tsx | 10 ++--- .../field_input/input/text_input.tsx | 4 +- .../settings/components/field_input/types.ts | 6 +-- .../settings/components/field_row/README.mdx | 2 +- .../field_row/__stories__/common.tsx | 13 ++++--- .../components/field_row/field_row.test.tsx | 38 ++++++++++--------- .../components/field_row/field_row.tsx | 26 ++++++------- .../settings/components/field_row/index.ts | 1 - .../settings/components/field_row/types.ts | 11 ------ .../settings/components/form/form.tsx | 8 ++-- .../kbn-management/settings/types/index.ts | 12 +++++- .../settings/utilities/field/use_update.ts | 20 +++++----- 31 files changed, 169 insertions(+), 153 deletions(-) diff --git a/packages/kbn-management/settings/README.mdx b/packages/kbn-management/settings/README.mdx index fa541b6719091..5e867dd59d490 100644 --- a/packages/kbn-management/settings/README.mdx +++ b/packages/kbn-management/settings/README.mdx @@ -4,7 +4,7 @@ These packages comprise the Management Advanced Settings application. The sourc ## 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. +**Be aware**: the functional flow logic we've adopted for these components is not one I would encourage, specifically, using "drilled" onFieldChange 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. diff --git a/packages/kbn-management/settings/components/field_input/__stories__/common.tsx b/packages/kbn-management/settings/components/field_input/__stories__/common.tsx index 399a125822a35..39daed80f40b8 100644 --- a/packages/kbn-management/settings/components/field_input/__stories__/common.tsx +++ b/packages/kbn-management/settings/components/field_input/__stories__/common.tsx @@ -13,7 +13,7 @@ import { action } from '@storybook/addon-actions'; import { EuiPanel } from '@elastic/eui'; import { UiSettingsType } from '@kbn/core-ui-settings-common'; import { - OnChangeFn, + OnInputChangeFn, SettingType, UiSettingMetadata, UnsavedFieldChange, @@ -108,17 +108,17 @@ export const getInputStory = (type: SettingType, params: Params = {}) => { setting, }); - const onChange: OnChangeFn = (newChange) => { + const onInputChange: OnInputChangeFn = (newChange) => { setUnsavedChange(newChange); - action('onChange')({ + action('onInputChange')({ type, unsavedValue: newChange?.unsavedValue, savedValue: field.savedValue, }); }; - return ; + return ; }; Story.argTypes = { diff --git a/packages/kbn-management/settings/components/field_input/field_input.test.tsx b/packages/kbn-management/settings/components/field_input/field_input.test.tsx index 49f2135dbb121..a946f259cab96 100644 --- a/packages/kbn-management/settings/components/field_input/field_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/field_input.test.tsx @@ -55,7 +55,7 @@ describe('FieldInput', () => { }, options, } as FieldDefinition, - onChange: jest.fn(), + onInputChange: jest.fn(), isSavingEnabled: true, }; @@ -132,7 +132,7 @@ describe('FieldInput', () => { const { getByTestId } = render(wrap()); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${name}`); fireEvent.change(input, { target: { value: 'new value' } }); - expect(props.onChange).toHaveBeenCalledWith({ type: 'string', unsavedValue: 'new value' }); + expect(props.onInputChange).toHaveBeenCalledWith({ type: 'string', unsavedValue: 'new value' }); }); it('disables the input when isDisabled prop is true', () => { diff --git a/packages/kbn-management/settings/components/field_input/field_input.tsx b/packages/kbn-management/settings/components/field_input/field_input.tsx index bc481927f089e..a5b3fdb4aac55 100644 --- a/packages/kbn-management/settings/components/field_input/field_input.tsx +++ b/packages/kbn-management/settings/components/field_input/field_input.tsx @@ -10,7 +10,7 @@ import React, { useImperativeHandle, useRef } from 'react'; import type { FieldDefinition, - OnChangeFn, + OnInputChangeFn, ResetInputRef, SettingType, UnsavedFieldChange, @@ -61,8 +61,8 @@ export interface FieldInputProps { field: Pick, 'type' | 'id' | 'name' | 'ariaAttributes'>; /** An {@link UnsavedFieldChange} for the component, if any. */ unsavedChange?: UnsavedFieldChange; - /** The `onChange` handler for the input. */ - onChange: OnChangeFn; + /** The `onInputChange` handler for the input. */ + onInputChange: OnInputChangeFn; /** True if the input can be saved, false otherwise. */ isSavingEnabled: boolean; /** True if the value within the input is invalid, false otherwise. */ @@ -82,7 +82,7 @@ const getMismatchError = (type: SettingType, unsavedType?: SettingType) => * @param props The props for the {@link FieldInput} component. */ export const FieldInput = React.forwardRef((props, ref) => { - const { field, unsavedChange, onChange, isSavingEnabled } = props; + const { field, unsavedChange, onInputChange, isSavingEnabled } = props; // Create a ref for those input fields that require an imperative handle. const inputRef = useRef(null); @@ -97,7 +97,7 @@ export const FieldInput = React.forwardRef((prop }, })); - const inputProps = { isSavingEnabled, onChange }; + const inputProps = { isSavingEnabled, onInputChange }; // These checks might seem excessive or redundant, but they are necessary to ensure that // the types are honored correctly using type guards. These checks get compiled down to diff --git a/packages/kbn-management/settings/components/field_input/input/array_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/array_input.test.tsx index 6f80de3039b70..c954035e9c639 100644 --- a/packages/kbn-management/settings/components/field_input/input/array_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/array_input.test.tsx @@ -18,9 +18,9 @@ const name = 'Some array field'; const id = 'some:array:field'; describe('ArrayInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: InputProps<'array'> = { - onChange, + onInputChange, field: { name, type: 'array', @@ -35,7 +35,7 @@ describe('ArrayInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -70,7 +70,7 @@ describe('ArrayInput', () => { expect(input).toHaveValue('foo, bar, baz'); }); - it('only calls onChange when blurred ', () => { + it('only calls onInputChange when blurred ', () => { render(wrap()); const input = screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); @@ -78,13 +78,13 @@ describe('ArrayInput', () => { userEvent.type(input, ',baz'); expect(input).toHaveValue('foo, bar,baz'); - expect(defaultProps.onChange).not.toHaveBeenCalled(); + expect(defaultProps.onInputChange).not.toHaveBeenCalled(); act(() => { input.blur(); }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'array', unsavedValue: ['foo', 'bar', 'baz'], }); diff --git a/packages/kbn-management/settings/components/field_input/input/array_input.tsx b/packages/kbn-management/settings/components/field_input/input/array_input.tsx index dfc92dc980bbd..f2ce42051eb04 100644 --- a/packages/kbn-management/settings/components/field_input/input/array_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/array_input.tsx @@ -29,7 +29,7 @@ export const ArrayInput = ({ field, unsavedChange, isSavingEnabled, - onChange: onChangeProp, + onInputChange, }: ArrayInputProps) => { const [inputValue] = getFieldInputValue(field, unsavedChange) || []; const [value, setValue] = useState(inputValue?.join(', ')); @@ -39,7 +39,7 @@ export const ArrayInput = ({ setValue(newValue); }; - const onUpdate = useUpdate({ onChange: onChangeProp, field }); + const onUpdate = useUpdate({ onInputChange, field }); useEffect(() => { setValue(inputValue?.join(', ')); diff --git a/packages/kbn-management/settings/components/field_input/input/boolean_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/boolean_input.test.tsx index b9f3ac883421b..49bc4e9367a65 100644 --- a/packages/kbn-management/settings/components/field_input/input/boolean_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/boolean_input.test.tsx @@ -19,9 +19,9 @@ const name = 'Some boolean field'; const id = 'some:boolean:field'; describe('BooleanInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: InputProps<'boolean'> = { - onChange, + onInputChange, field: { name, type: 'boolean', @@ -36,7 +36,7 @@ describe('BooleanInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders false', () => { @@ -60,16 +60,16 @@ describe('BooleanInput', () => { expect(screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`)).toBeChecked(); }); - it('calls onChange when toggled', () => { + it('calls onInputChange when toggled', () => { render(wrap()); const input = screen.getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); - expect(defaultProps.onChange).not.toHaveBeenCalled(); + expect(defaultProps.onInputChange).not.toHaveBeenCalled(); act(() => { fireEvent.click(input); }); - expect(defaultProps.onChange).toBeCalledWith({ type: 'boolean', unsavedValue: true }); + expect(defaultProps.onInputChange).toBeCalledWith({ type: 'boolean', unsavedValue: true }); act(() => { fireEvent.click(input); diff --git a/packages/kbn-management/settings/components/field_input/input/boolean_input.tsx b/packages/kbn-management/settings/components/field_input/input/boolean_input.tsx index 4f523da8067eb..782bff10a1ee3 100644 --- a/packages/kbn-management/settings/components/field_input/input/boolean_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/boolean_input.tsx @@ -28,15 +28,15 @@ export const BooleanInput = ({ field, unsavedChange, isSavingEnabled, - onChange: onChangeProp, + onInputChange, }: BooleanInputProps) => { + const onUpdate = useUpdate({ onInputChange, field }); + const onChange: EuiSwitchProps['onChange'] = (event) => { const inputValue = event.target.checked; onUpdate({ type: field.type, unsavedValue: inputValue }); }; - const onUpdate = useUpdate({ onChange: onChangeProp, field }); - const { id, name, ariaAttributes } = field; const { ariaLabel, ariaDescribedBy } = ariaAttributes; const [value] = getFieldInputValue(field, unsavedChange); diff --git a/packages/kbn-management/settings/components/field_input/input/code_editor_input.tsx b/packages/kbn-management/settings/components/field_input/input/code_editor_input.tsx index dc6c1e15043aa..5b9d90154d087 100644 --- a/packages/kbn-management/settings/components/field_input/input/code_editor_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/code_editor_input.tsx @@ -43,9 +43,9 @@ export const CodeEditorInput = ({ type, isSavingEnabled, defaultValue, - onChange: onChangeProp, + onInputChange, }: CodeEditorInputProps) => { - const onUpdate = useUpdate({ onChange: onChangeProp, field }); + const onUpdate = useUpdate({ onInputChange, field }); const onChange: CodeEditorProps['onChange'] = (inputValue) => { let newUnsavedValue; diff --git a/packages/kbn-management/settings/components/field_input/input/color_picker_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/color_picker_input.test.tsx index 0bd73ad51645c..4214b4b37bdf1 100644 --- a/packages/kbn-management/settings/components/field_input/input/color_picker_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/color_picker_input.test.tsx @@ -15,9 +15,9 @@ const name = 'Some color field'; const id = 'some:color:field'; describe('ColorPickerInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: ColorPickerInputProps = { - onChange, + onInputChange, field: { name, type: 'color', @@ -32,7 +32,7 @@ describe('ColorPickerInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -42,20 +42,23 @@ describe('ColorPickerInput', () => { expect(input).toHaveValue('#000000'); }); - it('calls the onChange prop when the value changes', () => { + it('calls the onInputChange prop when the value changes', () => { const { getByRole } = render(wrap()); const input = getByRole('textbox'); const newValue = '#ffffff'; fireEvent.change(input, { target: { value: newValue } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'color', unsavedValue: newValue }); + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ + type: 'color', + unsavedValue: newValue, + }); }); - it('calls the onChange prop with an error when the value is malformed', () => { + it('calls the onInputChange prop with an error when the value is malformed', () => { const { getByRole } = render(wrap()); const input = getByRole('textbox'); const newValue = '#1234'; fireEvent.change(input, { target: { value: newValue } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'color', unsavedValue: newValue, isInvalid: true, diff --git a/packages/kbn-management/settings/components/field_input/input/color_picker_input.tsx b/packages/kbn-management/settings/components/field_input/input/color_picker_input.tsx index 8533fc0545eab..41f41ea0638fc 100644 --- a/packages/kbn-management/settings/components/field_input/input/color_picker_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/color_picker_input.tsx @@ -32,9 +32,9 @@ export const ColorPickerInput = ({ field, unsavedChange, isSavingEnabled, - onChange: onChangeProp, + onInputChange, }: ColorPickerInputProps) => { - const onUpdate = useUpdate({ onChange: onChangeProp, field }); + const onUpdate = useUpdate({ onInputChange, field }); const onChange: EuiColorPickerProps['onChange'] = (newColor, { isValid }) => { const update: UnsavedFieldChange<'color'> = { type: field.type, unsavedValue: newColor }; diff --git a/packages/kbn-management/settings/components/field_input/input/image_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/image_input.test.tsx index cbbec332330d1..3d0c47d177822 100644 --- a/packages/kbn-management/settings/components/field_input/input/image_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/image_input.test.tsx @@ -18,9 +18,9 @@ const name = 'Some image field'; const id = 'some:image:field'; describe('ImageInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: ImageInputProps = { - onChange, + onInputChange, field: { name, type: 'image', @@ -35,7 +35,7 @@ describe('ImageInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -43,7 +43,7 @@ describe('ImageInput', () => { expect(container).toBeInTheDocument(); }); - it('calls the onChange prop when a file is selected', async () => { + it('calls the onInputChange prop when a file is selected', async () => { const { getByTestId } = render(wrap()); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`) as HTMLInputElement; const file = new File(['(⌐□_□)'], 'test.png', { type: 'image/png' }); @@ -55,7 +55,7 @@ describe('ImageInput', () => { expect(input.files?.length).toBe(1); // This doesn't work for some reason. - // expect(defaultProps.onChange).toHaveBeenCalledWith({ value: file }); + // expect(defaultProps.onInputChange).toHaveBeenCalledWith({ value: file }); }); it('disables the input when isDisabled prop is true', () => { diff --git a/packages/kbn-management/settings/components/field_input/input/image_input.tsx b/packages/kbn-management/settings/components/field_input/input/image_input.tsx index 286cba2c49d4c..9bcacf620c1fe 100644 --- a/packages/kbn-management/settings/components/field_input/input/image_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/image_input.tsx @@ -44,7 +44,7 @@ const errorMessage = i18n.translate('management.settings.field.imageChangeErrorM * Component for manipulating an `image` field. */ export const ImageInput = React.forwardRef( - ({ field, unsavedChange, isSavingEnabled, onChange: onChangeProp }, ref) => { + ({ field, unsavedChange, isSavingEnabled, onInputChange }, ref) => { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ @@ -53,7 +53,7 @@ export const ImageInput = React.forwardRef( const { showDanger } = useServices(); - const onUpdate = useUpdate({ onChange: onChangeProp, field }); + const onUpdate = useUpdate({ onInputChange, field }); const onChange: EuiFilePickerProps['onChange'] = async (files: FileList | null) => { if (files === null || !files.length) { diff --git a/packages/kbn-management/settings/components/field_input/input/json_editor_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/json_editor_input.test.tsx index 2cd34de067ffc..800a807f8103a 100644 --- a/packages/kbn-management/settings/components/field_input/input/json_editor_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/json_editor_input.test.tsx @@ -33,9 +33,9 @@ jest.mock('../code_editor', () => ({ })); describe('JsonEditorInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: CodeEditorInputProps = { - onChange, + onInputChange, type: 'json', field: { name, @@ -51,7 +51,7 @@ describe('JsonEditorInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -65,28 +65,28 @@ describe('JsonEditorInput', () => { expect(input).toHaveValue(initialValue); }); - it('calls the onChange prop when the object value changes', () => { + it('calls the onInputChange prop when the object value changes', () => { const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '{"bar":"foo"}' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '{"bar":"foo"}', }); }); - it('calls the onChange prop when the object value changes with no value', () => { + it('calls the onInputChange prop when the object value changes with no value', () => { const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '' }); + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '' }); }); - it('calls the onChange prop with an error when the object value changes to invalid JSON', () => { + it('calls the onInputChange prop with an error when the object value changes to invalid JSON', () => { const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '{"bar" "foo"}' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '{"bar" "foo"}', error: 'Invalid JSON syntax', @@ -94,20 +94,20 @@ describe('JsonEditorInput', () => { }); }); - it('calls the onChange prop when the array value changes', () => { + it('calls the onInputChange prop when the array value changes', () => { const props = { ...defaultProps, defaultValue: '["bar", "foo"]', value: undefined }; const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '["foo", "bar", "baz"]' } }); waitFor(() => - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '["foo", "bar", "baz"]', }) ); }); - it('calls the onChange prop when the array value changes with no value', () => { + it('calls the onInputChange prop when the array value changes with no value', () => { const props = { ...defaultProps, defaultValue: '["bar", "foo"]', @@ -116,15 +116,15 @@ describe('JsonEditorInput', () => { const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '' }); + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '' }); }); - it('calls the onChange prop with an array when the array value changes to invalid JSON', () => { + it('calls the onInputChange prop with an array when the array value changes to invalid JSON', () => { const props = { ...defaultProps, defaultValue: '["bar", "foo"]', value: undefined }; const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '["bar", "foo" | "baz"]' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'json', unsavedValue: '["bar", "foo" | "baz"]', error: 'Invalid JSON syntax', diff --git a/packages/kbn-management/settings/components/field_input/input/markdown_editor_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/markdown_editor_input.test.tsx index dd15b250cf1e0..291585e5f149a 100644 --- a/packages/kbn-management/settings/components/field_input/input/markdown_editor_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/markdown_editor_input.test.tsx @@ -33,9 +33,9 @@ jest.mock('../code_editor', () => ({ })); describe('MarkdownEditorInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: CodeEditorInputProps = { - onChange, + onInputChange, type: 'markdown', field: { name, @@ -51,7 +51,7 @@ describe('MarkdownEditorInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -65,11 +65,11 @@ describe('MarkdownEditorInput', () => { expect(input).toHaveValue(initialValue); }); - it('calls the onChange prop when the value changes', () => { + it('calls the onInputChange prop when the value changes', () => { const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '# New Markdown Title' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'markdown', unsavedValue: '# New Markdown Title', }); diff --git a/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx index 3fd6518102a46..fa228a48b721e 100644 --- a/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx @@ -16,9 +16,9 @@ const name = 'Some number field'; const id = 'some:number:field'; describe('NumberInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: NumberInputProps = { - onChange, + onInputChange, field: { name, type: 'number', @@ -33,7 +33,7 @@ describe('NumberInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -65,11 +65,14 @@ describe('NumberInput', () => { expect(input).toHaveValue(4321); }); - it('calls the onChange prop when the value changes', () => { + it('calls the onInputChange prop when the value changes', () => { const { getByTestId } = render(wrap()); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: '54321' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'number', unsavedValue: 54321 }); + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ + type: 'number', + unsavedValue: 54321, + }); }); it('disables the input when isDisabled prop is true', () => { diff --git a/packages/kbn-management/settings/components/field_input/input/number_input.tsx b/packages/kbn-management/settings/components/field_input/input/number_input.tsx index f67e27be505e2..a1929593e3cb5 100644 --- a/packages/kbn-management/settings/components/field_input/input/number_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/number_input.tsx @@ -26,14 +26,14 @@ export const NumberInput = ({ field, unsavedChange, isSavingEnabled, - onChange: onChangeProp, + onInputChange, }: NumberInputProps) => { const onChange: EuiFieldNumberProps['onChange'] = (event) => { const inputValue = Number(event.target.value); onUpdate({ type: field.type, unsavedValue: inputValue }); }; - const onUpdate = useUpdate({ onChange: onChangeProp, field }); + const onUpdate = useUpdate({ onInputChange, field }); const { id, name, ariaAttributes } = field; const { ariaLabel, ariaDescribedBy } = ariaAttributes; diff --git a/packages/kbn-management/settings/components/field_input/input/select_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/select_input.test.tsx index ca2e875a65604..e77d2a2cbfe49 100644 --- a/packages/kbn-management/settings/components/field_input/input/select_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/select_input.test.tsx @@ -16,9 +16,9 @@ const name = 'Some select field'; const id = 'some:select:field'; describe('SelectInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: SelectInputProps = { - onChange, + onInputChange, field: { name, type: 'select', @@ -39,7 +39,7 @@ describe('SelectInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -49,11 +49,14 @@ describe('SelectInput', () => { expect(input).toHaveValue('option2'); }); - it('calls the onChange prop when the value changes', () => { + it('calls the onInputChange prop when the value changes', () => { const { getByTestId } = render(wrap()); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: 'option3' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ type: 'select', unsavedValue: 'option3' }); + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ + type: 'select', + unsavedValue: 'option3', + }); }); it('disables the input when isDisabled prop is true', () => { diff --git a/packages/kbn-management/settings/components/field_input/input/select_input.tsx b/packages/kbn-management/settings/components/field_input/input/select_input.tsx index bd53fb9913ec5..9421d4d3e83b1 100644 --- a/packages/kbn-management/settings/components/field_input/input/select_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/select_input.tsx @@ -30,7 +30,7 @@ export interface SelectInputProps extends InputProps<'select'> { export const SelectInput = ({ field, unsavedChange, - onChange: onChangeProp, + onInputChange, optionLabels = {}, optionValues: optionsProp, isSavingEnabled, @@ -53,7 +53,7 @@ export const SelectInput = ({ onUpdate({ type: field.type, unsavedValue: inputValue }); }; - const onUpdate = useUpdate({ onChange: onChangeProp, field }); + const onUpdate = useUpdate({ onInputChange, field }); const { id, ariaAttributes } = field; const { ariaLabel, ariaDescribedBy } = ariaAttributes; diff --git a/packages/kbn-management/settings/components/field_input/input/text_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/text_input.test.tsx index 9dcdb8a04d5ea..e0d2eb75fca90 100644 --- a/packages/kbn-management/settings/components/field_input/input/text_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/text_input.test.tsx @@ -16,9 +16,9 @@ const name = 'Some text field'; const id = 'some:text:field'; describe('TextInput', () => { - const onChange = jest.fn(); + const onInputChange = jest.fn(); const defaultProps: TextInputProps = { - onChange, + onInputChange, field: { name, type: 'string', @@ -33,7 +33,7 @@ describe('TextInput', () => { }; beforeEach(() => { - onChange.mockClear(); + onInputChange.mockClear(); }); it('renders without errors', () => { @@ -47,11 +47,11 @@ describe('TextInput', () => { expect(input).toHaveValue('initial value'); }); - it('calls the onChange prop when the value changes', () => { + it('calls the onInputChange prop when the value changes', () => { const { getByTestId } = render(); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); fireEvent.change(input, { target: { value: 'new value' } }); - expect(defaultProps.onChange).toHaveBeenCalledWith({ + expect(defaultProps.onInputChange).toHaveBeenCalledWith({ type: 'string', unsavedValue: 'new value', }); diff --git a/packages/kbn-management/settings/components/field_input/input/text_input.tsx b/packages/kbn-management/settings/components/field_input/input/text_input.tsx index f4f9450e9577f..711d7aa808344 100644 --- a/packages/kbn-management/settings/components/field_input/input/text_input.tsx +++ b/packages/kbn-management/settings/components/field_input/input/text_input.tsx @@ -26,14 +26,14 @@ export const TextInput = ({ field, unsavedChange, isSavingEnabled, - onChange: onChangeProp, + onInputChange, }: TextInputProps) => { const onChange: EuiFieldTextProps['onChange'] = (event) => { const inputValue = event.target.value; onUpdate({ type: field.type, unsavedValue: inputValue }); }; - const onUpdate = useUpdate({ onChange: onChangeProp, field }); + const onUpdate = useUpdate({ onInputChange, field }); const { id, name, ariaAttributes } = field; const { ariaLabel, ariaDescribedBy } = ariaAttributes; diff --git a/packages/kbn-management/settings/components/field_input/types.ts b/packages/kbn-management/settings/components/field_input/types.ts index e5d5c11e2f199..fc4d311a3d210 100644 --- a/packages/kbn-management/settings/components/field_input/types.ts +++ b/packages/kbn-management/settings/components/field_input/types.ts @@ -8,7 +8,7 @@ import { FieldDefinition, - OnChangeFn, + OnInputChangeFn, SettingType, UnsavedFieldChange, } from '@kbn/management-settings-types'; @@ -44,6 +44,6 @@ export interface InputProps { >; unsavedChange?: UnsavedFieldChange; isSavingEnabled: boolean; - /** The `onChange` handler. */ - onChange: OnChangeFn; + /** The `onInputChange` handler. */ + onInputChange: OnInputChangeFn; } diff --git a/packages/kbn-management/settings/components/field_row/README.mdx b/packages/kbn-management/settings/components/field_row/README.mdx index 6fe238938407c..d8febe27dd674 100644 --- a/packages/kbn-management/settings/components/field_row/README.mdx +++ b/packages/kbn-management/settings/components/field_row/README.mdx @@ -19,7 +19,7 @@ For reference, this is an example of the current Advanced Settings UI: ## Implementation -A `FormRow` represents a single UiSetting, and is responsible for rendering the UiSetting's label, description, and equivalent value input. It displays the state of any unsaved change, (e.g. error). It also handles the logic for updating the UiSetting's value in a consuming component through the `onChange` handler. +A `FormRow` represents a single UiSetting, and is responsible for rendering the UiSetting's label, description, and equivalent value input. It displays the state of any unsaved change, (e.g. error). It also handles the logic for updating the UiSetting's value in a consuming component through the `onFieldChange` handler.
Anatomy of a `FormRow`
diff --git a/packages/kbn-management/settings/components/field_row/__stories__/common.tsx b/packages/kbn-management/settings/components/field_row/__stories__/common.tsx index de0fd7dd29120..e8c51d075c21d 100644 --- a/packages/kbn-management/settings/components/field_row/__stories__/common.tsx +++ b/packages/kbn-management/settings/components/field_row/__stories__/common.tsx @@ -12,12 +12,15 @@ import { action } from '@storybook/addon-actions'; import { EuiPanel } from '@elastic/eui'; import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; -import { KnownTypeToMetadata, UiSettingMetadata } from '@kbn/management-settings-types/metadata'; +import { + KnownTypeToMetadata, + UiSettingMetadata, + OnFieldChangeFn, +} from '@kbn/management-settings-types'; import { getDefaultValue, getUserValue } from '@kbn/management-settings-utilities/storybook'; import { getFieldDefinition } from '@kbn/management-settings-field-definition'; import { FieldRow as Component, FieldRow } from '../field_row'; import { FieldRowProvider } from '../services'; -import { RowOnChangeFn } from '../types'; /** * Props for a {@link FieldInput} Storybook story. @@ -140,17 +143,17 @@ export const getFieldRowStory = (type: SettingType, settingFields?: Partial = (_id, newChange) => { + const onFieldChange: OnFieldChangeFn = (_id, newChange) => { setUnsavedChange(newChange); - action('onChange')({ + action('onFieldChange')({ type, unsavedValue: newChange?.unsavedValue, savedValue: field.savedValue, }); }; - return ; + return ; }; // In Kibana, the image default value is never anything other than null. There would be a number diff --git a/packages/kbn-management/settings/components/field_row/field_row.test.tsx b/packages/kbn-management/settings/components/field_row/field_row.test.tsx index afa425d2a459a..e64d32366409e 100644 --- a/packages/kbn-management/settings/components/field_row/field_row.test.tsx +++ b/packages/kbn-management/settings/components/field_row/field_row.test.tsx @@ -198,7 +198,7 @@ describe('Field', () => { wrap( ) @@ -212,7 +212,7 @@ describe('Field', () => { wrap( ) @@ -243,7 +243,7 @@ describe('Field', () => { setting, params: { isOverridden: true }, })} - onChange={handleChange} + onFieldChange={handleChange} isSavingEnabled={true} /> ) @@ -265,7 +265,7 @@ describe('Field', () => { id, setting, })} - onChange={handleChange} + onFieldChange={handleChange} isSavingEnabled={false} /> ) @@ -288,7 +288,7 @@ describe('Field', () => { userValue: userValues[type] as any, }, })} - onChange={handleChange} + onFieldChange={handleChange} isSavingEnabled={true} /> ) @@ -319,7 +319,7 @@ describe('Field', () => { setting, params: { isCustom: true }, })} - onChange={handleChange} + onFieldChange={handleChange} isSavingEnabled={true} /> ) @@ -341,7 +341,7 @@ describe('Field', () => { type, unsavedValue: userValues[type] as any, }} - onChange={handleChange} + onFieldChange={handleChange} isSavingEnabled={true} /> ) @@ -373,7 +373,7 @@ describe('Field', () => { }); const { getByTestId } = render( - wrap() + wrap() ); const input = getByTestId(`${DATA_TEST_SUBJ_RESET_PREFIX}-${field.id}`); @@ -395,7 +395,7 @@ describe('Field', () => { ) @@ -408,12 +408,12 @@ describe('Field', () => { }); }); - it('should fire onChange when input changes', () => { + it('should fire onFieldChange when input changes', () => { const setting = settings.string; const field = getFieldDefinition({ id: setting.name || setting.type, setting }); const { getByTestId } = render( - wrap() + wrap() ); const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${field.id}`); @@ -424,12 +424,12 @@ describe('Field', () => { }); }); - it('should fire onChange with an error when input changes with invalid value', () => { + it('should fire onFieldChange with an error when input changes with invalid value', () => { const setting = settings.color; const field = getFieldDefinition({ id: setting.name || setting.type, setting }); const { getByTestId } = render( - wrap() + wrap() ); const input = getByTestId(`euiColorPickerAnchor ${TEST_SUBJ_PREFIX_FIELD}-${field.id}`); @@ -451,7 +451,7 @@ describe('Field', () => { wrap( { const { getByTestId } = render( wrap( - + ) ); @@ -512,7 +516,7 @@ describe('Field', () => { }); const { getByTestId, getByAltText } = render( - wrap() + wrap() ); const link = getByTestId(`${DATA_TEST_SUBJ_CHANGE_LINK_PREFIX}-${field.id}`); @@ -534,7 +538,7 @@ describe('Field', () => { wrap( diff --git a/packages/kbn-management/settings/components/field_row/field_row.tsx b/packages/kbn-management/settings/components/field_row/field_row.tsx index 51d844c41b84c..3592e634ade5e 100644 --- a/packages/kbn-management/settings/components/field_row/field_row.tsx +++ b/packages/kbn-management/settings/components/field_row/field_row.tsx @@ -21,7 +21,8 @@ import type { ResetInputRef, SettingType, UnsavedFieldChange, - OnChangeFn, + OnInputChangeFn, + OnFieldChangeFn, } from '@kbn/management-settings-types'; import { isImageFieldDefinition } from '@kbn/management-settings-field-definition'; import { FieldInput } from '@kbn/management-settings-components-field-input'; @@ -30,7 +31,6 @@ import { hasUnsavedChange } from '@kbn/management-settings-utilities'; import { FieldDescription } from './description'; import { FieldTitle } from './title'; import { useFieldStyles } from './field_row.styles'; -import { RowOnChangeFn } from './types'; import { FieldInputFooter } from './footer'; export const DATA_TEST_SUBJ_SCREEN_READER_MESSAGE = 'fieldRowScreenReaderMessage'; @@ -60,8 +60,8 @@ export interface FieldRowProps { field: Definition; /** True if saving settings is enabled, false otherwise. */ isSavingEnabled: boolean; - /** The {@link OnChangeFn} handler. */ - onChange: RowOnChangeFn; + /** The {@link OnInputChangeFn} handler. */ + onFieldChange: OnFieldChangeFn; /** * The onClear handler, if a value is cleared to an empty or default state. * @param id The id relating to the field to clear. @@ -76,7 +76,7 @@ export interface FieldRowProps { * @param props The {@link FieldRowProps} for the {@link FieldRow} component. */ export const FieldRow = (props: FieldRowProps) => { - const { isSavingEnabled, onChange: onChangeProp, field, unsavedChange } = props; + const { isSavingEnabled, onFieldChange, field, unsavedChange } = props; const { id, groupId, isOverridden, unsavedFieldId } = field; const { cssFieldFormGroup } = useFieldStyles({ field, @@ -86,9 +86,9 @@ export const FieldRow = (props: FieldRowProps) => { // Create a ref for those input fields that use a `reset` handle. const ref = useRef(null); - // Route any change to the `onChange` handler, along with the field id. - const onChange: OnChangeFn = (update) => { - onChangeProp(id, update); + // Route any change to the `onFieldChange` handler, along with the field id. + const onInputChange: OnInputChangeFn = (update) => { + onFieldChange(id, update); }; const onReset = () => { @@ -97,9 +97,9 @@ export const FieldRow = (props: FieldRowProps) => { const update = { type: field.type, unsavedValue: field.defaultValue }; if (hasUnsavedChange(field, update)) { - onChange(update); + onInputChange(update); } else { - onChange(); + onInputChange(); } }; @@ -111,9 +111,9 @@ export const FieldRow = (props: FieldRowProps) => { // Indicate a field is being cleared for a new value by setting its unchanged // value to`undefined`. Currently, this only applies to `image` fields. if (field.savedValue !== undefined && field.savedValue !== null) { - onChange({ type: field.type, unsavedValue: undefined }); + onInputChange({ type: field.type, unsavedValue: undefined }); } else { - onChange(); + onInputChange(); } }; @@ -162,7 +162,7 @@ export const FieldRow = (props: FieldRowProps) => { {unsavedScreenReaderMessage} diff --git a/packages/kbn-management/settings/components/field_row/index.ts b/packages/kbn-management/settings/components/field_row/index.ts index f86982141f75a..6bcbcf409d1be 100644 --- a/packages/kbn-management/settings/components/field_row/index.ts +++ b/packages/kbn-management/settings/components/field_row/index.ts @@ -11,7 +11,6 @@ export { FieldRowProvider, FieldRowKibanaProvider, type FieldRowProviderProps } export type { FieldRowServices, FieldRowKibanaDependencies, - RowOnChangeFn, KibanaDependencies, Services, } from './types'; diff --git a/packages/kbn-management/settings/components/field_row/types.ts b/packages/kbn-management/settings/components/field_row/types.ts index d7b33fa8f972f..a483763e79571 100644 --- a/packages/kbn-management/settings/components/field_row/types.ts +++ b/packages/kbn-management/settings/components/field_row/types.ts @@ -12,7 +12,6 @@ import type { FieldInputServices, FieldInputKibanaDependencies, } from '@kbn/management-settings-components-field-input'; -import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; /** * Contextual services used by a {@link FieldRow} component. @@ -43,13 +42,3 @@ export interface KibanaDependencies { * render a {@link FieldRow} component and its dependents. */ export type FieldRowKibanaDependencies = KibanaDependencies & FieldInputKibanaDependencies; - -/** - * An `onChange` handler for a {@link FieldRow} component. - * @param id A unique id corresponding to the particular setting being changed. - * @param change The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. - */ -export type RowOnChangeFn = ( - id: string, - change?: UnsavedFieldChange -) => void; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index 79396dd860b6a..47e5bd91d4af9 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -9,8 +9,8 @@ import React, { Fragment } from 'react'; import type { FieldDefinition } from '@kbn/management-settings-types'; -import { FieldRow, RowOnChangeFn } from '@kbn/management-settings-components-field-row'; -import { UnsavedFieldChange } from '@kbn/management-settings-types'; +import { FieldRow } from '@kbn/management-settings-components-field-row'; +import { UnsavedFieldChange, OnFieldChangeFn } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; import { BottomBar } from './bottom_bar'; import { useSave } from './use_save'; @@ -53,7 +53,7 @@ export const Form = (props: FormProps) => { setIsLoading(false); }; - const onChange: RowOnChangeFn = (id, change) => { + const onFieldChange: OnFieldChangeFn = (id, change) => { if (!change) { const { [id]: unsavedChange, ...rest } = unsavedChanges; setUnsavedChanges(rest); @@ -66,7 +66,7 @@ export const Form = (props: FormProps) => { const fieldRows = fields.map((field) => { const { id: key } = field; const unsavedChange = unsavedChanges[key]; - return ; + return ; }); return ( diff --git a/packages/kbn-management/settings/types/index.ts b/packages/kbn-management/settings/types/index.ts index 86106a3663537..6f315807c84b5 100644 --- a/packages/kbn-management/settings/types/index.ts +++ b/packages/kbn-management/settings/types/index.ts @@ -77,6 +77,16 @@ export type ResetInputRef = { * A function that is called when the value of a {@link FieldInput} changes. * @param change The {@link UnsavedFieldChange} passed to the handler. */ -export type OnChangeFn = ( +export type OnInputChangeFn = ( + change?: UnsavedFieldChange +) => void; + +/** + * An `onFieldChange` handler when a Field changes. + * @param id A unique id corresponding to the particular setting being changed. + * @param change The {@link UnsavedFieldChange} corresponding to any unsaved change to the field. + */ +export type OnFieldChangeFn = ( + id: string, change?: UnsavedFieldChange ) => void; diff --git a/packages/kbn-management/settings/utilities/field/use_update.ts b/packages/kbn-management/settings/utilities/field/use_update.ts index 4744d59dd90e7..c330ff25d7f4d 100644 --- a/packages/kbn-management/settings/utilities/field/use_update.ts +++ b/packages/kbn-management/settings/utilities/field/use_update.ts @@ -6,31 +6,33 @@ * Side Public License, v 1. */ -import type { FieldDefinition, SettingType, OnChangeFn } from '@kbn/management-settings-types'; +import type { FieldDefinition, SettingType, OnInputChangeFn } from '@kbn/management-settings-types'; import { hasUnsavedChange } from './has_unsaved_change'; export interface UseUpdateParameters { - /** The {@link OnChangeFn} to invoke. */ - onChange: OnChangeFn; + /** The {@link OnInputChangeFn} to invoke. */ + onInputChange: OnInputChangeFn; /** The {@link FieldDefinition} to use to create an update. */ field: Pick, 'defaultValue' | 'savedValue'>; } /** - * Hook to provide a standard {@link OnChangeFn} that will send an update to the + * Hook to provide a standard {@link OnInputChangeFn} that will send an update to the * field. * * @param params The {@link UseUpdateParameters} to use. - * @returns An {@link OnChangeFn} that will send an update to the field. + * @returns An {@link OnInputChangeFn} that will send an update to the field. */ -export const useUpdate = (params: UseUpdateParameters): OnChangeFn => { - const { onChange, field } = params; +export const useUpdate = ( + params: UseUpdateParameters +): OnInputChangeFn => { + const { onInputChange, field } = params; return (update) => { if (hasUnsavedChange(field, update)) { - onChange(update); + onInputChange(update); } else { - onChange(); + onInputChange(); } }; }; From e1c53eefcceb94454e5a673d8840db8ccd140e59 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Wed, 27 Sep 2023 13:20:51 -0400 Subject: [PATCH 3/6] [fix] Fix logged errors in form tests --- .../settings/components/form/form.test.tsx | 36 +++++++++++++------ .../components/form/mocks/settings.ts | 34 +++++++++--------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index 387c70113f36b..b014fa188f7d5 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { fireEvent, render, waitFor } from '@testing-library/react'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; @@ -75,14 +75,18 @@ describe('Form', () => { fireEvent.change(input, { target: { value: 'test' } }); const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); - fireEvent.click(saveButton); + act(() => { + fireEvent.click(saveButton); + }); - expect(services.saveChanges).toHaveBeenCalledWith({ - string: { type: 'string', unsavedValue: 'test' }, + await waitFor(() => { + expect(services.saveChanges).toHaveBeenCalledWith({ + string: { type: 'string', unsavedValue: 'test' }, + }); }); }); - it('clears changes when Cancel button is clicked', () => { + it('clears changes when Cancel button is clicked', async () => { const { getByTestId } = render(wrap(
)); const testFieldType = 'string'; @@ -90,12 +94,16 @@ describe('Form', () => { fireEvent.change(input, { target: { value: 'test' } }); const cancelButton = getByTestId(DATA_TEST_SUBJ_CANCEL_BUTTON); - fireEvent.click(cancelButton); + act(() => { + fireEvent.click(cancelButton); + }); - expect(input).toHaveValue(settingsMock[testFieldType].value); + await waitFor(() => { + expect(input).toHaveValue(settingsMock[testFieldType].value); + }); }); - it('fires showError when saving is unsuccessful', () => { + it('fires showError when saving is unsuccessful', async () => { const services: FormServices = createFormServicesMock(); const saveChangesWithError = jest.fn(() => { throw new Error('Unable to save'); @@ -111,9 +119,13 @@ describe('Form', () => { fireEvent.change(input, { target: { value: 'test' } }); const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); - fireEvent.click(saveButton); + act(() => { + fireEvent.click(saveButton); + }); - expect(testServices.showError).toHaveBeenCalled(); + await waitFor(() => { + expect(testServices.showError).toHaveBeenCalled(); + }); }); it('fires showReloadPagePrompt when changing a reloadPageRequired setting', async () => { @@ -132,7 +144,9 @@ describe('Form', () => { fireEvent.change(input, { target: { value: 'test' } }); const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); - fireEvent.click(saveButton); + act(() => { + fireEvent.click(saveButton); + }); await waitFor(() => { expect(services.showReloadPagePrompt).toHaveBeenCalled(); diff --git a/packages/kbn-management/settings/components/form/mocks/settings.ts b/packages/kbn-management/settings/components/form/mocks/settings.ts index e22f24e4a1a09..5601622683132 100644 --- a/packages/kbn-management/settings/components/form/mocks/settings.ts +++ b/packages/kbn-management/settings/components/form/mocks/settings.ts @@ -9,7 +9,7 @@ import { KnownTypeToMetadata, SettingType } from '@kbn/management-settings-types'; type Settings = { - [key in SettingType]: KnownTypeToMetadata; + [key in Exclude]: KnownTypeToMetadata; }; /** @@ -64,22 +64,22 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => value: 1, ...defaults, }, - json: { - name: 'json:test:setting', - description: 'Description for Json test setting', - type: 'json', - userValue: null, - value: '{"foo": "bar"}', - ...defaults, - }, - markdown: { - name: 'markdown:test:setting', - description: 'Description for Markdown test setting', - type: 'markdown', - userValue: null, - value: '', - ...defaults, - }, + // json: { + // name: 'json:test:setting', + // description: 'Description for Json test setting', + // type: 'json', + // userValue: null, + // value: '{"foo": "bar"}', + // ...defaults, + // }, + // markdown: { + // name: 'markdown:test:setting', + // description: 'Description for Markdown test setting', + // type: 'markdown', + // userValue: null, + // value: '', + // ...defaults, + // }, select: { description: 'Description for Select test setting', name: 'select:test:setting', From cc711518477f9402a406010aefeccfc3cf5ce05e Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Wed, 27 Sep 2023 14:02:27 -0400 Subject: [PATCH 4/6] [feature] Implement Setting Categories in Advanced Settings in serverless --- .github/CODEOWNERS | 1 + package.json | 1 + .../components/field_category/README.mdx | 12 ++ .../__stories__/categories.stories.tsx | 56 ++++++++ .../__stories__/category.stories.tsx | 83 ++++++++++++ .../__stories__/use_category_story.tsx | 63 +++++++++ .../components/field_category/categories.tsx | 54 ++++++++ .../components/field_category/category.tsx | 67 ++++++++++ .../field_category/clear_query_link.tsx | 59 ++++++++ .../components/field_category/index.ts | 17 +++ .../components/field_category/jest.config.js | 13 ++ .../components/field_category/kibana.jsonc | 5 + .../components/field_category/package.json | 6 + .../components/field_category/services.tsx | 33 +++++ .../components/field_category/tsconfig.json | 25 ++++ .../components/field_category/types.ts | 23 ++++ .../settings/components/form/form.tsx | 16 ++- .../components/form/mocks/settings.ts | 9 +- .../settings/components/form/services.tsx | 14 +- .../form/storybook/form.stories.tsx | 10 +- .../settings/components/form/tsconfig.json | 2 + .../kbn-management/settings/types/category.ts | 16 +++ .../kbn-management/settings/types/index.ts | 2 + .../utilities/category/categorize_fields.ts | 22 +++ .../utilities/category/get_category_name.ts | 54 ++++++++ .../settings/utilities/category/index.ts | 10 ++ .../settings/utilities/index.ts | 3 + .../settings/utilities/mocks/settings.mock.ts | 126 ++++++++++++++++++ .../settings/utilities/tsconfig.json | 1 + .../storybook/config/preview.ts | 16 +++ tsconfig.base.json | 2 + yarn.lock | 4 + 32 files changed, 807 insertions(+), 18 deletions(-) create mode 100644 packages/kbn-management/settings/components/field_category/README.mdx create mode 100644 packages/kbn-management/settings/components/field_category/__stories__/categories.stories.tsx create mode 100644 packages/kbn-management/settings/components/field_category/__stories__/category.stories.tsx create mode 100644 packages/kbn-management/settings/components/field_category/__stories__/use_category_story.tsx create mode 100644 packages/kbn-management/settings/components/field_category/categories.tsx create mode 100644 packages/kbn-management/settings/components/field_category/category.tsx create mode 100644 packages/kbn-management/settings/components/field_category/clear_query_link.tsx create mode 100644 packages/kbn-management/settings/components/field_category/index.ts create mode 100644 packages/kbn-management/settings/components/field_category/jest.config.js create mode 100644 packages/kbn-management/settings/components/field_category/kibana.jsonc create mode 100644 packages/kbn-management/settings/components/field_category/package.json create mode 100644 packages/kbn-management/settings/components/field_category/services.tsx create mode 100644 packages/kbn-management/settings/components/field_category/tsconfig.json create mode 100644 packages/kbn-management/settings/components/field_category/types.ts create mode 100644 packages/kbn-management/settings/types/category.ts create mode 100644 packages/kbn-management/settings/utilities/category/categorize_fields.ts create mode 100644 packages/kbn-management/settings/utilities/category/get_category_name.ts create mode 100644 packages/kbn-management/settings/utilities/category/index.ts create mode 100644 packages/kbn-management/settings/utilities/mocks/settings.mock.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index be401c26dc0d2..2ad748bb6a304 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -483,6 +483,7 @@ 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_category @elastic/platform-deployment-management 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/components/form @elastic/platform-deployment-management diff --git a/package.json b/package.json index 3b7f8c030fc8f..74a16210edfd0 100644 --- a/package.json +++ b/package.json @@ -505,6 +505,7 @@ "@kbn/logstash-plugin": "link:x-pack/plugins/logstash", "@kbn/management-cards-navigation": "link:packages/kbn-management/cards_navigation", "@kbn/management-plugin": "link:src/plugins/management", + "@kbn/management-settings-components-field-category": "link:packages/kbn-management/settings/components/field_category", "@kbn/management-settings-components-field-input": "link:packages/kbn-management/settings/components/field_input", "@kbn/management-settings-components-field-row": "link:packages/kbn-management/settings/components/field_row", "@kbn/management-settings-components-form": "link:packages/kbn-management/settings/components/form", diff --git a/packages/kbn-management/settings/components/field_category/README.mdx b/packages/kbn-management/settings/components/field_category/README.mdx new file mode 100644 index 0000000000000..64f57e84b1b99 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/README.mdx @@ -0,0 +1,12 @@ +--- +id: management/settings/components/fieldCategory +slug: /management/settings/components/field-category +title: Management Settings Field Category Component +description: A package containing components for rendering field rows in collections organized by their category. +tags: ['management', 'settings'] +date: 2023-10-25 +--- + +## Description + +This package contains a component for rendering field rows of `UiSetting` objects in collections organized by their category. It's used primarily by the `Form` component. \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_category/__stories__/categories.stories.tsx b/packages/kbn-management/settings/components/field_category/__stories__/categories.stories.tsx new file mode 100644 index 0000000000000..6ea4d96fd2b4b --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/__stories__/categories.stories.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { ComponentMeta, Story } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { FieldCategories as Component } from '../categories'; +import { Params, useCategoryStory } from './use_category_story'; +import { FieldCategoryProvider } from '../services'; + +export default { + title: 'Settings/Field Category/Categories', + description: '', + args: { + isFiltered: false, + isSavingEnabled: true, + }, + argTypes: { + isFiltered: { + control: { + type: 'boolean', + }, + }, + isSavingEnabled: { + control: { + type: 'boolean', + }, + }, + }, + parameters: { + backgrounds: { + default: 'ghost', + }, + }, +} as ComponentMeta; + +export const Categories: Story = (params) => { + const { onClearQuery, isSavingEnabled, onFieldChange, unsavedChanges, categorizedFields } = + useCategoryStory(params); + + return ( + + + + ); +}; diff --git a/packages/kbn-management/settings/components/field_category/__stories__/category.stories.tsx b/packages/kbn-management/settings/components/field_category/__stories__/category.stories.tsx new file mode 100644 index 0000000000000..180f3d0b4ce49 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/__stories__/category.stories.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { ComponentMeta } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock'; +import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; +import { categorizeFields } from '@kbn/management-settings-utilities'; +import { FieldRow } from '@kbn/management-settings-components-field-row'; + +import { FieldCategory as Component, type FieldCategoryProps as ComponentProps } from '../category'; +import { Params, useCategoryStory } from './use_category_story'; +import { FieldCategoryProvider } from '../services'; + +const settings = getSettingsMock(); + +// Markdown and JSON fields require Monaco, which are *notoriously* slow in Storybook due +// to the lack of a webworker. Until we can resolve it, filter out those fields. +const definitions = getFieldDefinitions(settings, { + isCustom: () => { + return false; + }, + isOverridden: () => { + return false; + }, +}).filter((field) => field.type !== 'json' && field.type !== 'markdown'); + +const categories = Object.keys(categorizeFields(definitions)); + +export default { + title: 'Settings/Field Category/Category', + description: '', + args: { + category: categories[0], + isFiltered: false, + isSavingEnabled: true, + }, + argTypes: { + category: { + control: { + type: 'select', + options: categories, + }, + }, + }, +} as ComponentMeta; + +type FieldCategoryParams = Pick & Params; + +export const Category = ({ isFiltered, category, isSavingEnabled }: FieldCategoryParams) => { + const { onClearQuery, onFieldChange, unsavedChanges } = useCategoryStory({ + isFiltered, + isSavingEnabled, + }); + + const { count, fields } = categorizeFields(definitions)[category]; + const rows = isFiltered ? [fields[0]] : fields; + + return ( + + + {rows.map((field) => ( + + ))} + + + ); +}; diff --git a/packages/kbn-management/settings/components/field_category/__stories__/use_category_story.tsx b/packages/kbn-management/settings/components/field_category/__stories__/use_category_story.tsx new file mode 100644 index 0000000000000..73962fe9ed16c --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/__stories__/use_category_story.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { useArgs } from '@storybook/client-api'; +import { action } from '@storybook/addon-actions'; + +import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock'; +import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; +import { categorizeFields } from '@kbn/management-settings-utilities'; +import { UnsavedFieldChanges, OnFieldChangeFn } from '@kbn/management-settings-types'; + +export interface Params { + isFiltered: boolean; + isSavingEnabled: boolean; +} + +export const useCategoryStory = ({ isFiltered, isSavingEnabled }: Params) => { + const [_args, updateArgs] = useArgs(); + const settings = getSettingsMock(); + + // Markdown and JSON fields require Monaco, which are *notoriously* slow in Storybook due + // to the lack of a webworker. Until we can resolve it, filter out those fields. + const definitions = getFieldDefinitions(settings, { + isCustom: () => { + return false; + }, + isOverridden: () => { + return false; + }, + }).filter((field) => field.type !== 'json' && field.type !== 'markdown'); + + const categorizedFields = categorizeFields(definitions); + + if (isFiltered) { + Object.keys(categorizedFields).forEach((category) => { + categorizedFields[category].fields = categorizedFields[category].fields.slice(0, 1); + }); + } + + const onClearQuery = () => updateArgs({ isFiltered: false }); + + const [unsavedChanges, setUnsavedChanges] = React.useState({}); + + const onFieldChange: OnFieldChangeFn = (id, change) => { + action('onFieldChange')(id, change); + + if (!change) { + const { [id]: unsavedChange, ...rest } = unsavedChanges; + setUnsavedChanges(rest); + return; + } + + setUnsavedChanges((changes) => ({ ...changes, [id]: change })); + }; + + return { onClearQuery, onFieldChange, isSavingEnabled, unsavedChanges, categorizedFields }; +}; diff --git a/packages/kbn-management/settings/components/field_category/categories.tsx b/packages/kbn-management/settings/components/field_category/categories.tsx new file mode 100644 index 0000000000000..c732c29c4e4a2 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/categories.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { CategorizedFields, UnsavedFieldChanges } from '@kbn/management-settings-types'; + +import { FieldRow, FieldRowProps } from '@kbn/management-settings-components-field-row'; +import { FieldCategory, type FieldCategoryProps } from './category'; + +/** + * Props for the {@link FieldCategories} component. + */ +export interface FieldCategoriesProps + extends Pick, + Pick { + /** Categorized fields for display. */ + categorizedFields: CategorizedFields; + /** And unsaved changes currently managed by the parent component. */ + unsavedChanges?: UnsavedFieldChanges; +} + +/** + * Convenience component for displaying a set of {@link FieldCategory} components, given + * a set of categorized fields. + * + * @param {FieldCategoriesProps} props props to pass to the {@link FieldCategories} component. + */ +export const FieldCategories = ({ + categorizedFields, + unsavedChanges = {}, + onClearQuery, + isSavingEnabled, + onFieldChange, +}: FieldCategoriesProps) => ( + <> + {Object.entries(categorizedFields).map(([category, { count, fields }]) => ( + + {fields.map((field) => ( + + ))} + + ))} + +); diff --git a/packages/kbn-management/settings/components/field_category/category.tsx b/packages/kbn-management/settings/components/field_category/category.tsx new file mode 100644 index 0000000000000..47465c2ab0288 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/category.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { ReactElement, Children } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiSplitPanel, EuiTitle, useEuiTheme } from '@elastic/eui'; + +import { getCategoryName } from '@kbn/management-settings-utilities'; +import type { FieldRowProps } from '@kbn/management-settings-components-field-row'; +import { css } from '@emotion/react'; +import { ClearQueryLink, ClearQueryLinkProps } from './clear_query_link'; + +/** + * Props for a {@link FieldCategory} component. + */ +export interface FieldCategoryProps + extends Pick { + /** The name of the category. */ + category: string; + /** Children-- should be {@link FieldRow} components. */ + children: + | ReactElement + | Array>; +} + +/** + * Component for displaying a container of fields pertaining to a single + * category. + * @param props - the props to pass to the {@link FieldCategory} component. + */ +export const FieldCategory = (props: FieldCategoryProps) => { + const { category, fieldCount, onClearQuery, children } = props; + const { + euiTheme: { size }, + } = useEuiTheme(); + + const displayCount = Children.count(children); + + const panelCSS = css` + & + & { + margin-top: ${size.l}; + } + `; + + return ( + + + + + +

{getCategoryName(category)}

+
+
+ + + +
+
+ {children} +
+ ); +}; diff --git a/packages/kbn-management/settings/components/field_category/clear_query_link.tsx b/packages/kbn-management/settings/components/field_category/clear_query_link.tsx new file mode 100644 index 0000000000000..c54b2c9ad52dc --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/clear_query_link.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { css } from '@emotion/react'; + +import { EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +/** + * Props for the {@link ClearQueryLink} component. + */ +export interface ClearQueryLinkProps { + /** The total number of fields in the category. */ + fieldCount: number; + /** The number of fields currently being displayed. */ + displayCount: number; + /** Handler to invoke when clearing the current filtering query. */ + onClearQuery: () => void; +} + +/** + * Component for displaying a link to clear the current filtering query. + */ +export const ClearQueryLink = ({ fieldCount, displayCount, onClearQuery }: ClearQueryLinkProps) => { + if (fieldCount === displayCount) { + return null; + } + + const linkCSS = css` + font-style: italic; + `; + + return ( + + + + + + + ), + }} + /> + + ); +}; diff --git a/packages/kbn-management/settings/components/field_category/index.ts b/packages/kbn-management/settings/components/field_category/index.ts new file mode 100644 index 0000000000000..38e0e914bfa89 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldCategories, type FieldCategoriesProps } from './categories'; +export { FieldCategory, type FieldCategoryProps } from './category'; +export type { ClearQueryLinkProps } from './clear_query_link'; +export type { FieldCategoryKibanaDependencies, FieldCategoryServices } from './types'; +export { + FieldCategoryKibanaProvider, + FieldCategoryProvider, + type FieldCategoryProviderProps, +} from './services'; diff --git a/packages/kbn-management/settings/components/field_category/jest.config.js b/packages/kbn-management/settings/components/field_category/jest.config.js new file mode 100644 index 0000000000000..6569a209f7277 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['/packages/kbn-management/settings/components/field_category'], +}; diff --git a/packages/kbn-management/settings/components/field_category/kibana.jsonc b/packages/kbn-management/settings/components/field_category/kibana.jsonc new file mode 100644 index 0000000000000..ac8859b05df4b --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-components-field-category", + "owner": "@elastic/platform-deployment-management" +} diff --git a/packages/kbn-management/settings/components/field_category/package.json b/packages/kbn-management/settings/components/field_category/package.json new file mode 100644 index 0000000000000..d8b9345da3086 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-components-field-category", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_category/services.tsx b/packages/kbn-management/settings/components/field_category/services.tsx new file mode 100644 index 0000000000000..2ff805196ec02 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/services.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { + FieldRowProvider, + FieldRowKibanaProvider, +} from '@kbn/management-settings-components-field-row'; + +import type { FieldCategoryServices } from './types'; + +/** + * Props for {@link FieldCategoryProvider}. + */ +export interface FieldCategoryProviderProps extends FieldCategoryServices { + children: React.ReactNode; +} + +/** + * React Provider that provides services to a {@link FieldCategory} component and its dependents. + */ +export const FieldCategoryProvider = FieldRowProvider; + +/** + * Kibana-specific Provider that maps Kibana plugins and services to a {@link FieldCategoryProvider}. + */ +export const FieldCategoryKibanaProvider = FieldRowKibanaProvider; diff --git a/packages/kbn-management/settings/components/field_category/tsconfig.json b/packages/kbn-management/settings/components/field_category/tsconfig.json new file mode 100644 index 0000000000000..8b4e9b1566b31 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-utilities", + "@kbn/management-settings-field-definition", + "@kbn/management-settings-components-field-row", + "@kbn/management-settings-types", + "@kbn/i18n-react", + ] +} diff --git a/packages/kbn-management/settings/components/field_category/types.ts b/packages/kbn-management/settings/components/field_category/types.ts new file mode 100644 index 0000000000000..3f726f18300b9 --- /dev/null +++ b/packages/kbn-management/settings/components/field_category/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { + FieldRowServices, + FieldRowKibanaDependencies, +} from '@kbn/management-settings-components-field-row'; + +/** + * Contextual services used by a {@link FieldCategory} component and its dependents. + */ +export type FieldCategoryServices = FieldRowServices; + +/** + * An interface containing a collection of Kibana plugins and services required to + * render a {@link FieldCategory} component and its dependents. + */ +export type FieldCategoryKibanaDependencies = FieldRowKibanaDependencies; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index 47e5bd91d4af9..0bc9d2c96fb29 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -9,9 +9,10 @@ import React, { Fragment } from 'react'; import type { FieldDefinition } from '@kbn/management-settings-types'; -import { FieldRow } from '@kbn/management-settings-components-field-row'; +import { FieldCategories } from '@kbn/management-settings-components-field-category'; import { UnsavedFieldChange, OnFieldChangeFn } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; +import { categorizeFields } from '@kbn/management-settings-utilities'; import { BottomBar } from './bottom_bar'; import { useSave } from './use_save'; @@ -63,15 +64,16 @@ export const Form = (props: FormProps) => { setUnsavedChanges((changes) => ({ ...changes, [id]: change })); }; - const fieldRows = fields.map((field) => { - const { id: key } = field; - const unsavedChange = unsavedChanges[key]; - return ; - }); + const categorizedFields = categorizeFields(fields); + + /** TODO - Querying is not enabled yet. */ + const onClearQuery = () => {}; return ( -
{fieldRows}
+ {!isEmpty(unsavedChanges) && ( const defaults = { requiresPageReload: requirePageReload, readonly: false, - category: ['category'], }; return { @@ -30,6 +29,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'array', userValue: null, value: ['example_value'], + category: ['general'], ...defaults, }, boolean: { @@ -38,6 +38,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'boolean', userValue: null, value: true, + category: ['general'], ...defaults, }, color: { @@ -46,6 +47,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'color', userValue: null, value: '#FF00CC', + category: ['general'], ...defaults, }, image: { @@ -54,6 +56,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'image', userValue: null, value: '', + category: ['dashboard'], ...defaults, }, number: { @@ -62,6 +65,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'number', userValue: null, value: 1, + category: ['dashboard'], ...defaults, }, // json: { @@ -92,6 +96,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'select', userValue: null, value: 'apple', + category: ['securitySolution'], ...defaults, }, string: { @@ -100,6 +105,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'string', userValue: null, value: 'hello world', + category: ['securitySolution'], ...defaults, }, undefined: { @@ -108,6 +114,7 @@ export const getSettingsMock = (requirePageReload: boolean = false): Settings => type: 'undefined', userValue: null, value: undefined, + category: ['someCategory'], ...defaults, }, }; diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index 7e36b42245dd2..73100c1729da0 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { - FieldRowProvider, - FieldRowKibanaProvider, -} from '@kbn/management-settings-components-field-row'; import React, { FC, useContext } from 'react'; import { UnsavedFieldChange } from '@kbn/management-settings-types'; +import { + FieldCategoryKibanaProvider, + FieldCategoryProvider, +} from '@kbn/management-settings-components-field-category'; import type { FormServices, FormKibanaDependencies, Services } from './types'; import { reloadPageToast } from './reload_page_toast'; @@ -33,7 +33,7 @@ export const FormProvider = ({ children, ...services }: FormProviderProps) => { return ( - {children} + {children} ); }; @@ -57,7 +57,9 @@ export const FormKibanaProvider: FC = ({ children, ...de showReloadPagePrompt: () => toasts.add(reloadPageToast(theme, i18nStart)), }} > - {children} + + {children} + ); }; diff --git a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx index f2ff27eacd9aa..ff9f561c5c501 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -6,7 +6,6 @@ * Side Public License, v 1. */ import React from 'react'; -import { EuiPanel } from '@elastic/eui'; import { action } from '@storybook/addon-actions'; import { ComponentMeta } from '@storybook/react'; import { FieldDefinition } from '@kbn/management-settings-types'; @@ -37,12 +36,15 @@ export default { showError={action('showError')} showReloadPagePrompt={action('showReloadPagePrompt')} > - - - + ), ], + parameters: { + backgrounds: { + default: 'ghost', + }, + }, } as ComponentMeta; interface FormStoryProps { diff --git a/packages/kbn-management/settings/components/form/tsconfig.json b/packages/kbn-management/settings/components/form/tsconfig.json index 359e5560fd2e4..cfd8fe3ac53b0 100644 --- a/packages/kbn-management/settings/components/form/tsconfig.json +++ b/packages/kbn-management/settings/components/form/tsconfig.json @@ -30,5 +30,7 @@ "@kbn/core-theme-browser", "@kbn/core-ui-settings-browser", "@kbn/management-settings-components-field-input", + "@kbn/management-settings-components-field-category", + "@kbn/management-settings-utilities", ] } diff --git a/packages/kbn-management/settings/types/category.ts b/packages/kbn-management/settings/types/category.ts new file mode 100644 index 0000000000000..fe5d4910e426f --- /dev/null +++ b/packages/kbn-management/settings/types/category.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FieldDefinition } from './field_definition'; + +export interface CategorizedFields { + [category: string]: { + count: number; + fields: FieldDefinition[]; + }; +} diff --git a/packages/kbn-management/settings/types/index.ts b/packages/kbn-management/settings/types/index.ts index 6f315807c84b5..7f8afb8073a5c 100644 --- a/packages/kbn-management/settings/types/index.ts +++ b/packages/kbn-management/settings/types/index.ts @@ -65,6 +65,8 @@ export type { Value, } from './setting_type'; +export type { CategorizedFields } from './category'; + /** * A React `ref` that indicates an input can be reset using an * imperative handle. diff --git a/packages/kbn-management/settings/utilities/category/categorize_fields.ts b/packages/kbn-management/settings/utilities/category/categorize_fields.ts new file mode 100644 index 0000000000000..a705d5f872266 --- /dev/null +++ b/packages/kbn-management/settings/utilities/category/categorize_fields.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CategorizedFields, FieldDefinition } from '@kbn/management-settings-types'; + +export const categorizeFields = (fields: FieldDefinition[]): CategorizedFields => { + // Group settings by category + return fields.reduce((grouped: CategorizedFields, field) => { + const category = field.categories[0]; + const group = grouped[category] || { count: 0, fields: [] }; + group.fields = [...group.fields, field]; + group.count = group.fields.length; + grouped[category] = group; + + return grouped; + }, {}); +}; diff --git a/packages/kbn-management/settings/utilities/category/get_category_name.ts b/packages/kbn-management/settings/utilities/category/get_category_name.ts new file mode 100644 index 0000000000000..8355f001f7bbd --- /dev/null +++ b/packages/kbn-management/settings/utilities/category/get_category_name.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +const upperFirst = (str = '') => str.replace(/^./, (strng) => strng.toUpperCase()); + +const names: Record = { + general: i18n.translate('management.settings.categoryNames.generalLabel', { + defaultMessage: 'General', + }), + machineLearning: i18n.translate('management.settings.categoryNames.machineLearningLabel', { + defaultMessage: 'Machine Learning', + }), + observability: i18n.translate('management.settings.categoryNames.observabilityLabel', { + defaultMessage: 'Observability', + }), + timelion: i18n.translate('management.settings.categoryNames.timelionLabel', { + defaultMessage: 'Timelion', + }), + notifications: i18n.translate('management.settings.categoryNames.notificationsLabel', { + defaultMessage: 'Notifications', + }), + visualizations: i18n.translate('management.settings.categoryNames.visualizationsLabel', { + defaultMessage: 'Visualizations', + }), + discover: i18n.translate('management.settings.categoryNames.discoverLabel', { + defaultMessage: 'Discover', + }), + dashboard: i18n.translate('management.settings.categoryNames.dashboardLabel', { + defaultMessage: 'Dashboard', + }), + reporting: i18n.translate('management.settings.categoryNames.reportingLabel', { + defaultMessage: 'Reporting', + }), + search: i18n.translate('management.settings.categoryNames.searchLabel', { + defaultMessage: 'Search', + }), + securitySolution: i18n.translate('management.settings.categoryNames.securitySolutionLabel', { + defaultMessage: 'Security Solution', + }), + enterpriseSearch: i18n.translate('management.settings.categoryNames.enterpriseSearchLabel', { + defaultMessage: 'Enterprise Search', + }), +}; + +export function getCategoryName(category?: string) { + return category ? names[category] || upperFirst(category) : ''; +} diff --git a/packages/kbn-management/settings/utilities/category/index.ts b/packages/kbn-management/settings/utilities/category/index.ts new file mode 100644 index 0000000000000..c562a888e0cbe --- /dev/null +++ b/packages/kbn-management/settings/utilities/category/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { categorizeFields } from './categorize_fields'; +export { getCategoryName } from './get_category_name'; diff --git a/packages/kbn-management/settings/utilities/index.ts b/packages/kbn-management/settings/utilities/index.ts index 4e4523f66eb59..60cb17e47206f 100644 --- a/packages/kbn-management/settings/utilities/index.ts +++ b/packages/kbn-management/settings/utilities/index.ts @@ -7,6 +7,7 @@ */ export { isSettingDefaultValue, normalizeSettings } from './setting'; + export { getFieldInputValue, hasUnsavedChange, @@ -14,3 +15,5 @@ export { useUpdate, type UseUpdateParameters, } from './field'; + +export { categorizeFields, getCategoryName } from './category'; diff --git a/packages/kbn-management/settings/utilities/mocks/settings.mock.ts b/packages/kbn-management/settings/utilities/mocks/settings.mock.ts new file mode 100644 index 0000000000000..18b247df67430 --- /dev/null +++ b/packages/kbn-management/settings/utilities/mocks/settings.mock.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { KnownTypeToMetadata, SettingType } from '@kbn/management-settings-types'; + +const defaults = { + requiresPageReload: false, + readonly: false, +}; + +type Settings = { + [key in SettingType]: KnownTypeToMetadata; +}; + +/** + * A utility function returning a representative set of UiSettings. + * @param requirePageReload The value of the `requirePageReload` param for all settings. + */ +export const getSettingsMock = (requirePageReload: boolean = false): Settings => { + if (requirePageReload) { + defaults.requiresPageReload = true; + } + return { + array: { + description: 'Description for Array test setting', + name: 'array:test:setting', + type: 'array', + userValue: null, + value: ['foo', 'bar', 'baz'], + category: ['general', 'dashboard'], + ...defaults, + }, + boolean: { + description: 'Description for Boolean test setting', + name: 'boolean:test:setting', + type: 'boolean', + userValue: null, + value: true, + category: ['general', 'dashboard'], + ...defaults, + }, + color: { + description: 'Description for Color test setting', + name: 'color:test:setting', + type: 'color', + userValue: null, + value: '#FF00CC', + category: ['general', 'dashboard'], + ...defaults, + }, + image: { + description: 'Description for Image test setting', + name: 'image:test:setting', + type: 'image', + userValue: null, + value: '', + category: ['dashboard', 'discover'], + ...defaults, + }, + number: { + description: 'Description for Number test setting', + name: 'number:test:setting', + type: 'number', + userValue: null, + value: 1, + category: ['dashboard', 'discover'], + ...defaults, + }, + json: { + name: 'json:test:setting', + description: 'Description for Json test setting', + type: 'json', + userValue: null, + value: '{"foo": "bar"}', + category: ['dashboard', 'discover'], + ...defaults, + }, + markdown: { + name: 'markdown:test:setting', + description: 'Description for Markdown test setting', + type: 'markdown', + userValue: null, + value: '', + category: ['notifications', 'search'], + ...defaults, + }, + select: { + description: 'Description for Select test setting', + name: 'select:test:setting', + options: ['apple', 'orange', 'banana'], + optionLabels: { + apple: 'Apple', + orange: 'Orange', + banana: 'Banana', + }, + type: 'select', + userValue: null, + value: 'apple', + category: ['notifications', 'search'], + ...defaults, + }, + string: { + description: 'Description for String test setting', + name: 'string:test:setting', + type: 'string', + userValue: null, + value: 'hello world', + category: ['notifications', 'search'], + ...defaults, + }, + undefined: { + description: 'Description for Undefined test setting', + name: 'undefined:test:setting', + type: 'undefined', + userValue: null, + value: undefined, + category: ['notifications', 'search'], + ...defaults, + }, + }; +}; diff --git a/packages/kbn-management/settings/utilities/tsconfig.json b/packages/kbn-management/settings/utilities/tsconfig.json index 1247d2cd18707..c97a0448689a3 100644 --- a/packages/kbn-management/settings/utilities/tsconfig.json +++ b/packages/kbn-management/settings/utilities/tsconfig.json @@ -17,5 +17,6 @@ ], "kbn_references": [ "@kbn/management-settings-types", + "@kbn/i18n", ] } diff --git a/packages/kbn-management/storybook/config/preview.ts b/packages/kbn-management/storybook/config/preview.ts index ee65b88614fb9..032af90b7dd5b 100644 --- a/packages/kbn-management/storybook/config/preview.ts +++ b/packages/kbn-management/storybook/config/preview.ts @@ -20,3 +20,19 @@ import jest from 'jest-mock'; /* @ts-expect-error TS doesn't see jest as a property of window, and I don't want to edit our global config. */ window.jest = jest; + +export const parameters = { + backgrounds: { + default: 'body', + values: [ + { + name: 'body', + value: '##f7f8fc', + }, + { + name: 'ghost', + value: '#fff', + }, + ], + }, +}; diff --git a/tsconfig.base.json b/tsconfig.base.json index 030b5c9bbed4c..9f8798d64fbb6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -960,6 +960,8 @@ "@kbn/management-cards-navigation/*": ["packages/kbn-management/cards_navigation/*"], "@kbn/management-plugin": ["src/plugins/management"], "@kbn/management-plugin/*": ["src/plugins/management/*"], + "@kbn/management-settings-components-field-category": ["packages/kbn-management/settings/components/field_category"], + "@kbn/management-settings-components-field-category/*": ["packages/kbn-management/settings/components/field_category/*"], "@kbn/management-settings-components-field-input": ["packages/kbn-management/settings/components/field_input"], "@kbn/management-settings-components-field-input/*": ["packages/kbn-management/settings/components/field_input/*"], "@kbn/management-settings-components-field-row": ["packages/kbn-management/settings/components/field_row"], diff --git a/yarn.lock b/yarn.lock index 41769ceb9a6ef..308df4e86f347 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4863,6 +4863,10 @@ version "0.0.0" uid "" +"@kbn/management-settings-components-field-category@link:packages/kbn-management/settings/components/field_category": + version "0.0.0" + uid "" + "@kbn/management-settings-components-field-input@link:packages/kbn-management/settings/components/field_input": version "0.0.0" uid "" From 79144d55632454f926f609f5e344e36906e2339b Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Thu, 28 Sep 2023 11:47:47 -0400 Subject: [PATCH 5/6] Addressing review feedback --- .../settings/components/form/form.test.tsx | 5 +- .../settings/components/form/mocks/index.ts | 1 - .../components/form/mocks/settings.ts | 121 ------------------ .../settings/utilities/mocks/settings.mock.ts | 20 +-- 4 files changed, 13 insertions(+), 134 deletions(-) delete mode 100644 packages/kbn-management/settings/components/form/mocks/settings.ts diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index b014fa188f7d5..0e149938e2146 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -11,10 +11,11 @@ import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; +import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock'; +import { TEST_SUBJ_PREFIX_FIELD } from '@kbn/management-settings-components-field-input/input'; import { Form } from './form'; -import { wrap, getSettingsMock, createFormServicesMock, uiSettingsClientMock } from './mocks'; -import { TEST_SUBJ_PREFIX_FIELD } from '@kbn/management-settings-components-field-input/input'; +import { wrap, createFormServicesMock, uiSettingsClientMock } from './mocks'; import { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar/bottom_bar'; import { FormServices } from './types'; diff --git a/packages/kbn-management/settings/components/form/mocks/index.ts b/packages/kbn-management/settings/components/form/mocks/index.ts index 80e92448a3bb4..03cca50115a23 100644 --- a/packages/kbn-management/settings/components/form/mocks/index.ts +++ b/packages/kbn-management/settings/components/form/mocks/index.ts @@ -7,5 +7,4 @@ */ export { TestWrapper, createFormServicesMock, wrap } from './context'; -export { getSettingsMock } from './settings'; export { uiSettingsClientMock } from './settings_client'; diff --git a/packages/kbn-management/settings/components/form/mocks/settings.ts b/packages/kbn-management/settings/components/form/mocks/settings.ts deleted file mode 100644 index d441415de7c77..0000000000000 --- a/packages/kbn-management/settings/components/form/mocks/settings.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { KnownTypeToMetadata, SettingType } from '@kbn/management-settings-types'; - -type Settings = { - [key in Exclude]: KnownTypeToMetadata; -}; - -/** - * A utility function returning a representative set of UiSettings. - * @param requirePageReload The value of the `requirePageReload` param for all settings. - */ -export const getSettingsMock = (requirePageReload: boolean = false): Settings => { - const defaults = { - requiresPageReload: requirePageReload, - readonly: false, - }; - - return { - array: { - description: 'Description for Array test setting', - name: 'array:test:setting', - type: 'array', - userValue: null, - value: ['example_value'], - category: ['general'], - ...defaults, - }, - boolean: { - description: 'Description for Boolean test setting', - name: 'boolean:test:setting', - type: 'boolean', - userValue: null, - value: true, - category: ['general'], - ...defaults, - }, - color: { - description: 'Description for Color test setting', - name: 'color:test:setting', - type: 'color', - userValue: null, - value: '#FF00CC', - category: ['general'], - ...defaults, - }, - image: { - description: 'Description for Image test setting', - name: 'image:test:setting', - type: 'image', - userValue: null, - value: '', - category: ['dashboard'], - ...defaults, - }, - number: { - description: 'Description for Number test setting', - name: 'number:test:setting', - type: 'number', - userValue: null, - value: 1, - category: ['dashboard'], - ...defaults, - }, - // json: { - // name: 'json:test:setting', - // description: 'Description for Json test setting', - // type: 'json', - // userValue: null, - // value: '{"foo": "bar"}', - // ...defaults, - // }, - // markdown: { - // name: 'markdown:test:setting', - // description: 'Description for Markdown test setting', - // type: 'markdown', - // userValue: null, - // value: '', - // ...defaults, - // }, - select: { - description: 'Description for Select test setting', - name: 'select:test:setting', - options: ['apple', 'orange', 'banana'], - optionLabels: { - apple: 'Apple', - orange: 'Orange', - banana: 'Banana', - }, - type: 'select', - userValue: null, - value: 'apple', - category: ['securitySolution'], - ...defaults, - }, - string: { - description: 'Description for String test setting', - name: 'string:test:setting', - type: 'string', - userValue: null, - value: 'hello world', - category: ['securitySolution'], - ...defaults, - }, - undefined: { - description: 'Description for Undefined test setting', - name: 'undefined:test:setting', - type: 'undefined', - userValue: null, - value: undefined, - category: ['someCategory'], - ...defaults, - }, - }; -}; diff --git a/packages/kbn-management/settings/utilities/mocks/settings.mock.ts b/packages/kbn-management/settings/utilities/mocks/settings.mock.ts index 18b247df67430..ca2a2892836ce 100644 --- a/packages/kbn-management/settings/utilities/mocks/settings.mock.ts +++ b/packages/kbn-management/settings/utilities/mocks/settings.mock.ts @@ -8,23 +8,23 @@ import { KnownTypeToMetadata, SettingType } from '@kbn/management-settings-types'; -const defaults = { - requiresPageReload: false, - readonly: false, -}; - type Settings = { [key in SettingType]: KnownTypeToMetadata; }; /** * A utility function returning a representative set of UiSettings. - * @param requirePageReload The value of the `requirePageReload` param for all settings. + * @param requiresPageReload The value of the `requirePageReload` param for all settings. */ -export const getSettingsMock = (requirePageReload: boolean = false): Settings => { - if (requirePageReload) { - defaults.requiresPageReload = true; - } +export const getSettingsMock = ( + requiresPageReload: boolean = false, + readonly: boolean = false +): Settings => { + const defaults = { + requiresPageReload, + readonly, + }; + return { array: { description: 'Description for Array test setting', From d68dc53d04b12c377cf23fb3636f0a0f2be36210 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Thu, 28 Sep 2023 11:47:47 -0400 Subject: [PATCH 6/6] Addressing review feedback --- .../settings/components/form/storybook/form.stories.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx index ff9f561c5c501..060ddd6355340 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -10,7 +10,9 @@ import { action } from '@storybook/addon-actions'; import { ComponentMeta } from '@storybook/react'; import { FieldDefinition } from '@kbn/management-settings-types'; import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; -import { getSettingsMock, uiSettingsClientMock } from '../mocks'; +import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock'; + +import { uiSettingsClientMock } from '../mocks'; import { Form as Component } from '../form'; import { FormProvider } from '../services';