From b36f9e2c6c9ffcddb85c8f6343fb3f355b6eca02 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Thu, 14 Sep 2023 14:21:36 +0100 Subject: [PATCH 01/26] [serverless] Advanced Settings - Form component --- package.json | 1 + .../settings/components/form/README.mdx | 26 + .../components/form/bottom_bar/index.tsx | 62 + .../settings/components/form/form.tsx | 66 + .../settings/components/form/index.ts | 9 + .../settings/components/form/kibana.jsonc | 5 + .../settings/components/form/package.json | 6 + .../settings/components/form/services.tsx | 32 + .../form/storybook/form.stories.tsx | 34 + .../form/storybook/get_form_story.tsx | 41 + .../components/form/storybook/settings.json | 1843 +++++++++++++++++ tsconfig.base.json | 2 + yarn.lock | 4 + 13 files changed, 2131 insertions(+) create mode 100644 packages/kbn-management/settings/components/form/README.mdx create mode 100644 packages/kbn-management/settings/components/form/bottom_bar/index.tsx create mode 100644 packages/kbn-management/settings/components/form/form.tsx create mode 100644 packages/kbn-management/settings/components/form/index.ts create mode 100644 packages/kbn-management/settings/components/form/kibana.jsonc create mode 100644 packages/kbn-management/settings/components/form/package.json create mode 100644 packages/kbn-management/settings/components/form/services.tsx create mode 100644 packages/kbn-management/settings/components/form/storybook/form.stories.tsx create mode 100644 packages/kbn-management/settings/components/form/storybook/get_form_story.tsx create mode 100644 packages/kbn-management/settings/components/form/storybook/settings.json diff --git a/package.json b/package.json index fa7ceb4edd592..f1fca74c59b48 100644 --- a/package.json +++ b/package.json @@ -502,6 +502,7 @@ "@kbn/management-plugin": "link:src/plugins/management", "@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", "@kbn/management-settings-field-definition": "link:packages/kbn-management/settings/field_definition", "@kbn/management-settings-ids": "link:packages/kbn-management/settings/setting_ids", "@kbn/management-settings-section-registry": "link:packages/kbn-management/settings/section_registry", diff --git a/packages/kbn-management/settings/components/form/README.mdx b/packages/kbn-management/settings/components/form/README.mdx new file mode 100644 index 0000000000000..cb6718e323d9a --- /dev/null +++ b/packages/kbn-management/settings/components/form/README.mdx @@ -0,0 +1,26 @@ +--- +id: management/settings/components/form +slug: /management/settings/components/form +title: Management Settings form Component +description: A package containing a component for rendering and manipulating the form in the Advanced Settings UI. +tags: ['management', 'settings'] +date: 2023-09-12 +--- + +## Description + +This package contains a component for rendering and manipulating the form containing the setting fields in the Advanced Settings UI. + + +## Usage + +```tsx +const saveConfig = async (changes: Record>) => { + const arr = Object.entries(changes).map(([key, value]) => { + settingsStart.client.set(key, value.unsavedValue); + }); + return Promise.all(arr); +}; + +return
; +``` \ No newline at end of file diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx new file mode 100644 index 0000000000000..9932b2a4a93eb --- /dev/null +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -0,0 +1,62 @@ +/* + * 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 { EuiBottomBar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface BottomBarProps { + saveAll: () => void; + clearAllUnsaved: () => void; +} + +export const BottomBar = ({ saveAll, clearAllUnsaved }: BottomBarProps) => { + return ( + + + + + + {i18n.translate('advancedSettings.form.cancelButtonLabel', { + defaultMessage: 'Cancel changes', + })} + + + + + {i18n.translate('advancedSettings.form.saveButtonLabel', { + defaultMessage: 'Save changes', + })} + + + + + ); +}; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx new file mode 100644 index 0000000000000..aefe861809891 --- /dev/null +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -0,0 +1,66 @@ +/* + * 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, { Fragment } from 'react'; + +import type { FieldDefinition } from '@kbn/management-settings-types'; +import { FieldRow, OnChangeFn } from '@kbn/management-settings-components-field-row'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { isEmpty } from 'lodash'; +import { BottomBar } from './bottom_bar'; + +export interface FormProps { + fields: Array>; + save: (changes: Record>) => void; +} + +export const Form = (props: FormProps) => { + const { fields, save } = props; + + const [unsavedChanges, setUnsavedChanges] = React.useState< + Record> + >({}); + + const onChange: OnChangeFn = (id, change) => { + if (!change?.unsavedValue) { + const { [id]: unsavedChange, ...rest } = unsavedChanges; + setUnsavedChanges(rest); + return; + } + + setUnsavedChanges((changes) => ({ ...changes, [id]: change })); + }; + + const isSavingEnabled = true; + + const clearAllUnsaved = () => { + setUnsavedChanges({}); + }; + + const saveAll = async () => { + if (isEmpty(unsavedChanges)) { + return; + } + await save(unsavedChanges); + }; + + const fieldRows = fields.map((field) => { + const { id: key } = field; + const unsavedChange = unsavedChanges[key]; + return ; + }); + + return ( + +
{fieldRows}
+ {!isEmpty(unsavedChanges) && ( + + )} +
+ ); +}; diff --git a/packages/kbn-management/settings/components/form/index.ts b/packages/kbn-management/settings/components/form/index.ts new file mode 100644 index 0000000000000..5d218b01b0eaa --- /dev/null +++ b/packages/kbn-management/settings/components/form/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { Form } from './form'; diff --git a/packages/kbn-management/settings/components/form/kibana.jsonc b/packages/kbn-management/settings/components/form/kibana.jsonc new file mode 100644 index 0000000000000..c60e87fcfc1ac --- /dev/null +++ b/packages/kbn-management/settings/components/form/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-components-form", + "owner": "@elastic/platform-deployment-management @elastic/appex-sharedux" +} diff --git a/packages/kbn-management/settings/components/form/package.json b/packages/kbn-management/settings/components/form/package.json new file mode 100644 index 0000000000000..3e14acc7378dd --- /dev/null +++ b/packages/kbn-management/settings/components/form/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-components-form", + "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/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx new file mode 100644 index 0000000000000..4ff2c5e0ebb55 --- /dev/null +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -0,0 +1,32 @@ +/* + * 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 { + FieldRowProvider, + FieldRowKibanaProvider, + type FieldRowKibanaDependencies, + type FieldRowServices, +} from '@kbn/management-settings-components-field-row'; +import React, { FC } from 'react'; + +export type FormServices = FieldRowServices; +export type FormKibanaDependencies = FieldRowKibanaDependencies; + +/** + * React Provider that provides services to a {@link Form} component and its dependents. + */ +export const FormProvider: FC = ({ children, ...services }) => { + return {children}; +}; + +/** + * Kibana-specific Provider that maps Kibana plugins and services to a {@link FormProvider}. + */ +export const FormKibanaProvider: FC = ({ children, ...services }) => { + return {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 new file mode 100644 index 0000000000000..839e2cb278c02 --- /dev/null +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -0,0 +1,34 @@ +/* + * 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 { EuiPanel } from '@elastic/eui'; +import { action } from '@storybook/addon-actions'; +import { ComponentMeta } from '@storybook/react'; +import { FormProvider } from '../services'; +import { getFormStory } from './get_form_story'; +import { Form as Component } from '../form'; + +export default { + title: `Settings/Form/Form`, + description: 'A form with field rows', + argTypes: { save: { action: 'Saved' } }, + decorators: [ + (Story) => ( + + + + + + ), + ], +} as ComponentMeta; + +export const Form = getFormStory(); diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx new file mode 100644 index 0000000000000..161b8f8f0d568 --- /dev/null +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -0,0 +1,41 @@ +/* + * 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 { normalizeSettings } from '@kbn/management-settings-utilities'; +import { FieldDefinition, SettingType, UiSetting } from '@kbn/management-settings-types'; +import { getFieldDefinition } from '@kbn/management-settings-field-definition'; +import { action } from '@storybook/addon-actions'; +import { Form } from '../form'; +import _settings from './settings.json'; + +export const getFormStory = () => { + const Story = () => { + const settings = _settings as unknown as Record>; + const normalizedSettings = normalizeSettings(settings); + const fields: Array> = Object.entries(normalizedSettings).map( + ([id, setting]) => + getFieldDefinition({ + id, + setting, + params: { isCustom: false, isOverridden: setting.isOverridden }, + }) + ); + + const save = async () => { + alert('Saved!'); + // Not working: + action('Saved'); + }; + + return ; + }; + + return Story; +}; diff --git a/packages/kbn-management/settings/components/form/storybook/settings.json b/packages/kbn-management/settings/components/form/storybook/settings.json new file mode 100644 index 0000000000000..fa54cf96eada7 --- /dev/null +++ b/packages/kbn-management/settings/components/form/storybook/settings.json @@ -0,0 +1,1843 @@ +{ + "accessibility:disableAnimations": { + "name": "Disable Animations", + "value": false, + "description": "Turn off all unnecessary animations in the Kibana UI. Refresh the page to apply the changes.", + "category": [ + "accessibility" + ], + "requiresPageReload": true + }, + "hideAnnouncements": { + "name": "Hide announcements", + "value": false, + "description": "Stop showing messages and tours that highlight new features." + }, + "dateFormat": { + "name": "Date format", + "value": "MMM D, YYYY @ HH:mm:ss.SSS", + "description": "The format for pretty formatted dates." + }, + "dateFormat:tz": { + "name": "Time zone", + "value": "Browser", + "description": "The default time zone.", + "type": "select", + "options": [ + "Browser", + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/ComodRivadavia", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Atka", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Ciudad_Juarez", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Nelson", + "America/Fort_Wayne", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Knox_IN", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Rosario", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Shiprock", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Virgin", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/South_Pole", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Ashkhabad", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Chungking", + "Asia/Colombo", + "Asia/Dacca", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Harbin", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Istanbul", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macao", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qostanay", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimbu", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ujung_Pandang", + "Asia/Ulaanbaatar", + "Asia/Ulan_Bator", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yangon", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/ACT", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Canberra", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/LHI", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/NSW", + "Australia/North", + "Australia/Perth", + "Australia/Queensland", + "Australia/South", + "Australia/Sydney", + "Australia/Tasmania", + "Australia/Victoria", + "Australia/West", + "Australia/Yancowinna", + "Brazil/Acre", + "Brazil/DeNoronha", + "Brazil/East", + "Brazil/West", + "CET", + "CST6CDT", + "Canada/Atlantic", + "Canada/Central", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Canada/Saskatchewan", + "Canada/Yukon", + "Chile/Continental", + "Chile/EasterIsland", + "Cuba", + "EET", + "EST5EDT", + "Egypt", + "Eire", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/UTC", + "Etc/Universal", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Kyiv", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Ulyanovsk", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "GB", + "GB-Eire", + "GMT", + "GMT+0", + "GMT-0", + "GMT0", + "Greenwich", + "Hongkong", + "Iceland", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Iran", + "Israel", + "Jamaica", + "Japan", + "Kwajalein", + "Libya", + "MET", + "MST7MDT", + "Mexico/BajaNorte", + "Mexico/BajaSur", + "Mexico/General", + "NZ", + "NZ-CHAT", + "Navajo", + "PRC", + "PST8PDT", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kanton", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "ROK", + "Singapore", + "Turkey", + "UCT", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Samoa", + "UTC", + "Universal", + "W-SU", + "WET", + "Zulu" + ], + "requiresPageReload": true + }, + "dateFormat:scaled": { + "name": "Scaled date format", + "type": "json", + "value": "[\n [\"\", \"HH:mm:ss.SSS\"],\n [\"PT1S\", \"HH:mm:ss\"],\n [\"PT1M\", \"HH:mm\"],\n [\"PT1H\", \"YYYY-MM-DD HH:mm\"],\n [\"P1DT\", \"YYYY-MM-DD\"],\n [\"P1YT\", \"YYYY\"]\n]", + "description": "Values that define the format used in situations where time-based data is rendered in order, and formatted timestamps should adapt to the interval between measurements. Keys are ISO8601 intervals." + }, + "dateFormat:dow": { + "name": "Day of week", + "value": "Sunday", + "description": "The day that starts the week.", + "type": "select", + "options": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ] + }, + "dateNanosFormat": { + "name": "Date with nanoseconds format", + "value": "MMM D, YYYY @ HH:mm:ss.SSSSSSSSS", + "description": "The format for date_nanos data." + }, + "buildNum": { + "readonly": true, + "userValue": 9007199254740991 + }, + "defaultRoute": { + "name": "Default route", + "value": "/app/home", + "description": "This setting specifies the default route when opening Kibana. You can use this setting to modify the landing page when opening Kibana. The route must be a relative URL." + }, + "notifications:banner": { + "name": "Custom banner notification", + "value": "", + "type": "markdown", + "description": "A custom banner intended for temporary notices to all users. Markdown supported.", + "category": [ + "notifications" + ], + "sensitive": true + }, + "notifications:lifetime:banner": { + "name": "Banner notification lifetime", + "value": 3000000, + "description": "The time in milliseconds which a banner notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "notifications:lifetime:error": { + "name": "Error notification lifetime", + "value": 300000, + "description": "The time in milliseconds which an error notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "notifications:lifetime:warning": { + "name": "Warning notification lifetime", + "value": 10000, + "description": "The time in milliseconds which a warning notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "notifications:lifetime:info": { + "name": "Info notification lifetime", + "value": 5000, + "description": "The time in milliseconds which an information notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "theme:darkMode": { + "name": "Dark mode", + "value": false, + "description": "Enable a dark mode for the Kibana UI. A page refresh is required for the setting to be applied.", + "requiresPageReload": true + }, + "theme:version": { + "name": "Theme version", + "value": "v8", + "readonly": true + }, + "state:storeInSessionStorage": { + "name": "Store URLs in session storage", + "value": false, + "description": "The URL can sometimes grow to be too large for some browsers to handle. To counter-act this we are testing if storing parts of the URL in session storage could help. Please let us know how it goes!" + }, + "csv:separator": { + "name": "CSV separator", + "value": ",", + "description": "Separate exported values with this string", + "userValue": "|" + }, + "csv:quoteValues": { + "name": "Quote CSV values", + "value": true, + "description": "Should values be quoted in csv exports?" + }, + "banners:placement": { + "name": "Banner placement", + "description": "Display a top banner above the Elastic header. \n \n Subscription required.\n \n ", + "category": [ + "banner" + ], + "order": 1, + "type": "select", + "value": "disabled", + "options": [ + "disabled", + "top" + ], + "optionLabels": { + "disabled": "Disabled", + "top": "Top" + }, + "requiresPageReload": true + }, + "banners:textContent": { + "name": "Banner text", + "description": "Add Markdown-formatted text to the banner. \n \n Subscription required.\n \n ", + "sensitive": true, + "category": [ + "banner" + ], + "order": 2, + "type": "markdown", + "value": "", + "requiresPageReload": true + }, + "banners:textColor": { + "name": "Banner text color", + "description": "Set the color of the banner text. \n \n Subscription required.\n \n ", + "category": [ + "banner" + ], + "order": 3, + "type": "color", + "value": "#8A6A0A", + "requiresPageReload": true + }, + "banners:backgroundColor": { + "name": "Banner background color", + "description": "Set the background color for the banner. \n \n Subscription required.\n \n ", + "category": [ + "banner" + ], + "order": 4, + "type": "color", + "value": "#FFF9E8", + "requiresPageReload": true + }, + "savedObjects:perPage": { + "name": "Objects per page", + "value": 20, + "type": "number", + "description": "Number of objects to show per page in the load dialog" + }, + "savedObjects:listingLimit": { + "name": "Objects listing limit", + "type": "number", + "value": 1000, + "description": "Number of objects to fetch for the listing pages" + }, + "shortDots:enable": { + "name": "Shorten fields", + "value": false, + "description": "Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz" + }, + "format:defaultTypeMap": { + "name": "Field type format name", + "value": "{\n \"ip\": { \"id\": \"ip\", \"params\": {} },\n \"date\": { \"id\": \"date\", \"params\": {} },\n \"date_nanos\": { \"id\": \"date_nanos\", \"params\": {}, \"es\": true },\n \"geo_point\": { \"id\": \"geo_point\", \"params\": { \"transform\": \"wkt\" } },\n \"number\": { \"id\": \"number\", \"params\": {} },\n \"boolean\": { \"id\": \"boolean\", \"params\": {} },\n \"histogram\": { \"id\": \"histogram\", \"params\": {} },\n \"_source\": { \"id\": \"_source\", \"params\": {} },\n \"_default_\": { \"id\": \"string\", \"params\": {} }\n}", + "type": "json", + "description": "Map of the format name to use by default for each field type. \"_default_\" is used if the field type is not mentioned explicitly" + }, + "format:number:defaultPattern": { + "name": "Number format", + "value": "0,0.[000]", + "type": "string", + "description": "Default numeral format for the \"number\" format" + }, + "format:percent:defaultPattern": { + "name": "Percent format", + "value": "0,0.[000]%", + "type": "string", + "description": "Default numeral format for the \"percent\" format" + }, + "format:bytes:defaultPattern": { + "name": "Bytes format", + "value": "0,0.[0]b", + "type": "string", + "description": "Default numeral format for the \"bytes\" format" + }, + "format:currency:defaultPattern": { + "name": "Currency format", + "value": "($0,0.[00])", + "type": "string", + "description": "Default numeral format for the \"currency\" format" + }, + "format:number:defaultLocale": { + "name": "Formatting locale", + "value": "en", + "type": "select", + "options": [ + "en", + "be-nl", + "chs", + "cs", + "da-dk", + "de-ch", + "de", + "en-gb", + "es-ES", + "es", + "et", + "fi", + "fr-CA", + "fr-ch", + "fr", + "hu", + "it", + "ja", + "nl-nl", + "pl", + "pt-br", + "pt-pt", + "ru-UA", + "ru", + "sk", + "th", + "tr", + "uk-UA" + ], + "optionLabels": { + "be-nl": "Dutch (Belgium)", + "chs": "Simplified Chinese", + "cs": "Czech", + "da-dk": "Danish (Denmark)", + "de-ch": "German (Switzerland)", + "de": "German", + "en-gb": "English (UK)", + "es-ES": "Spanish (Spain)", + "es": "Spanish", + "et": "Estonian", + "fi": "Finnish", + "fr-CA": "French (Canada)", + "fr-ch": "French (Switzerland)", + "fr": "French", + "hu": "Hungarian", + "it": "Italian", + "ja": "Japanese", + "nl-nl": "Dutch (Netherlands)", + "pl": "Polish", + "pt-br": "Portuguese (Brazil)", + "pt-pt": "Portuguese", + "ru-UA": "Russian (Ukraine)", + "ru": "Russian", + "sk": "Slovak", + "th": "Thai", + "tr": "Turkish", + "uk-UA": "Ukrainian" + }, + "description": "Numeral language locale" + }, + "visualization:colorMapping": { + "name": "Color mapping", + "value": "{\"Count\":\"#00A69B\"}", + "type": "json", + "description": "Maps values to specific colors in charts using the Compatibility palette.", + "deprecation": { + "message": "This setting is deprecated and will not be supported in a future version.", + "docLinksKey": "visualizationSettings" + }, + "category": [ + "visualization" + ] + }, + "visualization:useLegacyTimeAxis": { + "name": "Legacy chart time axis", + "value": false, + "description": "Enables the legacy time axis for charts in Lens, Discover, Visualize and TSVB", + "deprecation": { + "message": "This setting is deprecated and will not be supported in a future version.", + "docLinksKey": "visualizationSettings" + }, + "category": [ + "visualization" + ] + }, + "bfetch:disable": { + "name": "Disable request batching", + "value": false, + "description": "Disables requests batching. This increases number of HTTP requests from Kibana, but allows to debug requests individually.", + "category": [] + }, + "bfetch:disableCompression": { + "name": "Disable batch compression", + "value": false, + "description": "Disable batch compression. This allows you to debug individual requests, but increases response size.", + "category": [] + }, + "metaFields": { + "name": "Meta fields", + "value": [ + "_source", + "_id", + "_index", + "_score" + ], + "description": "Fields that exist outside of _source to merge into our document when displaying it" + }, + "doc_table:highlight": { + "name": "Highlight results", + "value": true, + "description": "Highlight results in Discover and Saved Searches Dashboard. Highlighting makes requests slow when working on big documents.", + "category": [ + "discover" + ] + }, + "query:queryString:options": { + "name": "Query string options", + "value": "{ \"analyze_wildcard\": true }", + "description": "Options for the lucene query string parser. Is only used when \"Query language\" is set to Lucene.", + "type": "json" + }, + "query:allowLeadingWildcards": { + "name": "Allow leading wildcards in query", + "value": true, + "description": "When set, * is allowed as the first character in a query clause. To disallow leading wildcards in basic lucene queries, use query:queryString:options." + }, + "search:queryLanguage": { + "name": "Query language", + "value": "kuery", + "description": "Query language used by the query bar. KQL is a new language built specifically for Kibana.", + "type": "select", + "options": [ + "lucene", + "kuery" + ], + "optionLabels": { + "lucene": "Lucene", + "kuery": "KQL" + } + }, + "sort:options": { + "name": "Sort options", + "value": "{ \"unmapped_type\": \"boolean\" }", + "description": "Options for the Elasticsearch sort parameter", + "type": "json" + }, + "defaultIndex": { + "name": "Default data view", + "value": null, + "type": "string", + "description": "Used by discover and visualizations when a data view is not set." + }, + "courier:ignoreFilterIfFieldNotInIndex": { + "name": "Ignore filter(s)", + "value": false, + "description": "This configuration enhances support for dashboards containing visualizations accessing dissimilar indexes. When disabled, all filters are applied to all visualizations. When enabled, filter(s) will be ignored for a visualization when the visualization's index does not contain the filtering field.", + "category": [ + "search" + ] + }, + "courier:setRequestPreference": { + "name": "Request preference", + "value": "sessionId", + "options": [ + "sessionId", + "custom", + "none" + ], + "optionLabels": { + "sessionId": "Session ID", + "custom": "Custom", + "none": "None" + }, + "type": "select", + "description": "Allows you to set which shards handle your search requests.\n
    \n
  • Session ID: restricts operations to execute all search requests on the same shards.\n This has the benefit of reusing shard caches across requests.
  • \n
  • Custom: allows you to define a your own preference.\n Use 'courier:customRequestPreference' to customize your preference value.
  • \n
  • None: means do not set a preference.\n This might provide better performance because requests can be spread across all shard copies.\n However, results might be inconsistent because different shards might be in different refresh states.
  • \n
", + "category": [ + "search" + ] + }, + "courier:customRequestPreference": { + "name": "Custom request preference", + "value": "_local", + "type": "string", + "description": "Request Preference used when courier:setRequestPreference is set to \"custom\".", + "category": [ + "search" + ] + }, + "courier:maxConcurrentShardRequests": { + "name": "Max Concurrent Shard Requests", + "value": 0, + "type": "number", + "description": "Controls the max_concurrent_shard_requests setting used for _msearch requests sent by Kibana. Set to 0 to disable this config and use the Elasticsearch default.", + "category": [ + "search" + ] + }, + "search:includeFrozen": { + "name": "Search in frozen indices", + "description": "Will include frozen indices in results if enabled. Searching through frozen indices\n might increase the search time.", + "value": false, + "deprecation": { + "message": "This setting is deprecated and will be removed in Kibana 9.0.", + "docLinksKey": "kibanaSearchSettings" + }, + "category": [ + "search" + ] + }, + "histogram:barTarget": { + "name": "Target buckets", + "value": 50, + "description": "Attempt to generate around this many buckets when using \"auto\" interval in date and numeric histograms" + }, + "histogram:maxBars": { + "name": "Maximum buckets", + "value": 1000, + "description": "\n Limits the density of date and number histograms across Kibana\n for better performance using a test query. If the test query would too many buckets,\n the interval between buckets will be increased. This setting applies separately\n to each histogram aggregation, and does not apply to other types of aggregation.\n To find the maximum value of this setting, divide the Elasticsearch 'search.max_buckets'\n value by the maximum number of aggregations in each visualization.\n " + }, + "history:limit": { + "name": "History limit", + "value": 10, + "description": "In fields that have history (e.g. query inputs), show this many recent values" + }, + "timepicker:refreshIntervalDefaults": { + "name": "Time filter refresh interval", + "value": "{\n \"pause\": true,\n \"value\": 60000\n}", + "type": "json", + "description": "The timefilter's default refresh interval. The \"value\" needs to be specified in milliseconds.", + "requiresPageReload": true + }, + "timepicker:timeDefaults": { + "name": "Time filter defaults", + "value": "{\n \"from\": \"now-15m\",\n \"to\": \"now\"\n}", + "type": "json", + "description": "The timefilter selection to use when Kibana is started without one. Must be an object containing \"from\" and \"to\" (see accepted formats).", + "requiresPageReload": true + }, + "timepicker:quickRanges": { + "name": "Time filter quick ranges", + "value": "[\n {\n \"from\": \"now/d\",\n \"to\": \"now/d\",\n \"display\": \"Today\"\n },\n {\n \"from\": \"now/w\",\n \"to\": \"now/w\",\n \"display\": \"This week\"\n },\n {\n \"from\": \"now-15m\",\n \"to\": \"now\",\n \"display\": \"Last 15 minutes\"\n },\n {\n \"from\": \"now-30m\",\n \"to\": \"now\",\n \"display\": \"Last 30 minutes\"\n },\n {\n \"from\": \"now-1h\",\n \"to\": \"now\",\n \"display\": \"Last 1 hour\"\n },\n {\n \"from\": \"now-24h/h\",\n \"to\": \"now\",\n \"display\": \"Last 24 hours\"\n },\n {\n \"from\": \"now-7d/d\",\n \"to\": \"now\",\n \"display\": \"Last 7 days\"\n },\n {\n \"from\": \"now-30d/d\",\n \"to\": \"now\",\n \"display\": \"Last 30 days\"\n },\n {\n \"from\": \"now-90d/d\",\n \"to\": \"now\",\n \"display\": \"Last 90 days\"\n },\n {\n \"from\": \"now-1y/d\",\n \"to\": \"now\",\n \"display\": \"Last 1 year\"\n }\n]", + "type": "json", + "description": "The list of ranges to show in the Quick section of the time filter. This should be an array of objects, with each object containing \"from\", \"to\" (see accepted formats), and \"display\" (the title to be displayed)." + }, + "filters:pinnedByDefault": { + "name": "Pin filters by default", + "value": false, + "description": "Whether the filters should have a global state (be pinned) by default" + }, + "filterEditor:suggestValues": { + "name": "Filter editor suggest values", + "value": true, + "description": "Set this property to false to prevent the filter editor from suggesting values for fields." + }, + "autocomplete:valueSuggestionMethod": { + "name": "Autocomplete value suggestion method", + "type": "select", + "value": "terms_enum", + "description": "The method used for querying suggestions for values in KQL autocomplete. Select terms_enum to use the Elasticsearch terms enum API for improved autocomplete suggestion performance. (Note that terms_enum is incompatible with Document Level Security.) Select terms_agg to use an Elasticsearch terms aggregation. (Note that terms_agg is incompatible with IP-type fields.) Learn more.", + "options": [ + "terms_enum", + "terms_agg" + ], + "category": [ + "autocomplete" + ] + }, + "autocomplete:useTimeRange": { + "name": "Use time range", + "value": true, + "description": "Disable this property to get autocomplete suggestions from your full dataset, rather than from the current time range. Learn more.", + "category": [ + "autocomplete" + ] + }, + "search:timeout": { + "name": "Search Timeout", + "value": 600000, + "description": "Change the maximum timeout for a search session or set to 0 to disable the timeout and allow queries to run to completion.", + "type": "number", + "category": [ + "search" + ] + }, + "fileUpload:maxFileSize": { + "name": "Maximum file upload size", + "value": "100MB", + "description": "Sets the file size limit when importing files. The highest supported value for this setting is 1GB." + }, + "labs:dashboard:deferBelowFold": { + "name": "Defer loading panels below \"the fold\"", + "value": false, + "type": "boolean", + "description": "Any panels below \"the fold\"-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport", + "requiresPageReload": true, + "category": [ + "Presentation Labs" + ] + }, + "labs:dashboard:dashboardControls": { + "name": "Enable dashboard controls", + "value": true, + "type": "boolean", + "description": "Enables the controls system for dashboard, which allows dashboard authors to more easily build interactive elements for their users.", + "requiresPageReload": true, + "category": [ + "Presentation Labs" + ] + }, + "labs:canvas:byValueEmbeddable": { + "name": "By-Value Embeddables", + "value": true, + "type": "boolean", + "description": "Enables support for by-value embeddables in Canvas", + "requiresPageReload": true, + "category": [ + "Presentation Labs" + ] + }, + "visualization:heatmap:maxBuckets": { + "name": "Heatmap maximum buckets", + "value": 50, + "type": "number", + "description": "The maximum number of buckets a single datasource can return. A higher number might have negative impact on browser rendering performance", + "category": [ + "visualization" + ] + }, + "metrics:max_buckets": { + "name": "TSVB buckets limit", + "value": 2000, + "description": "Affects the TSVB histogram density. Must be set higher than \"histogram:maxBars\"." + }, + "metrics:allowStringIndices": { + "name": "Allow string indices in TSVB", + "value": false, + "requiresPageReload": true, + "description": "Enables you to query Elasticsearch indices in TSVB visualizations." + }, + "metrics:allowCheckingForFailedShards": { + "name": "Show TSVB request shard failures", + "value": true, + "description": "Show warning message for partial data in TSVB charts if the request succeeds for some shards but fails for others." + }, + "timelion:es.timefield": { + "name": "Time field", + "value": "@timestamp", + "description": "Default field containing a timestamp when using .es()", + "category": [ + "timelion" + ] + }, + "timelion:es.default_index": { + "name": "Default index", + "value": "_all", + "description": "Default elasticsearch index to search with .es()", + "category": [ + "timelion" + ] + }, + "timelion:target_buckets": { + "name": "Target buckets", + "value": 200, + "description": "The number of buckets to shoot for when using auto intervals", + "category": [ + "timelion" + ] + }, + "timelion:max_buckets": { + "name": "Maximum buckets", + "value": 2000, + "description": "The maximum number of buckets a single datasource can return", + "category": [ + "timelion" + ] + }, + "timelion:min_interval": { + "name": "Minimum interval", + "value": "1ms", + "description": "The smallest interval that will be calculated when using \"auto\"", + "category": [ + "timelion" + ] + }, + "visualization:visualize:legacyHeatmapChartsLibrary": { + "name": "Heatmap legacy charts library", + "requiresPageReload": true, + "value": false, + "description": "Enables legacy charts library for heatmap charts in visualize.", + "deprecation": { + "message": "The legacy charts library for heatmap in visualize is deprecated and will not be supported in a future version.", + "docLinksKey": "visualizationSettings" + }, + "category": [ + "visualization" + ] + }, + "labs:dashboard:enable_ui": { + "name": "Enable labs button in Dashboard", + "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Dashboard.", + "value": false, + "type": "boolean", + "category": [ + "Presentation Labs" + ], + "requiresPageReload": true + }, + "defaultColumns": { + "name": "Default columns", + "value": [], + "description": "Columns displayed by default in the Discover app. If empty, a summary of the document will be displayed.", + "category": [ + "discover" + ] + }, + "discover:maxDocFieldsDisplayed": { + "name": "Maximum document fields displayed", + "value": 200, + "description": "Maximum number of fields rendered in the document summary", + "category": [ + "discover" + ] + }, + "discover:sampleSize": { + "name": "Maximum rows per table", + "value": 500, + "description": "Sets the maximum number of rows for the entire document table.", + "category": [ + "discover" + ] + }, + "discover:sampleRowsPerPage": { + "name": "Rows per page", + "value": 100, + "options": [ + 10, + 25, + 50, + 100, + 250, + 500 + ], + "type": "select", + "description": "Limits the number of rows per page in the document table.", + "category": [ + "discover" + ] + }, + "discover:sort:defaultOrder": { + "name": "Default sort direction", + "value": "desc", + "options": [ + "desc", + "asc" + ], + "optionLabels": { + "desc": "Descending", + "asc": "Ascending" + }, + "type": "select", + "description": "Controls the default sort direction for time based data views in the Discover app.", + "category": [ + "discover" + ] + }, + "discover:searchOnPageLoad": { + "name": "Search on page load", + "value": true, + "type": "boolean", + "description": "Controls whether a search is executed when Discover first loads. This setting does not have an effect when loading a saved search.", + "category": [ + "discover" + ] + }, + "doc_table:hideTimeColumn": { + "name": "Hide 'Time' column", + "value": false, + "description": "Hide the 'Time' column in Discover and in all Saved Searches on Dashboards.", + "category": [ + "discover" + ] + }, + "fields:popularLimit": { + "name": "Popular fields limit", + "value": 10, + "description": "The top N most popular fields to show" + }, + "context:defaultSize": { + "name": "Context size", + "value": 5, + "description": "The number of surrounding entries to show in the context view", + "category": [ + "discover" + ] + }, + "context:step": { + "name": "Context size step", + "value": 5, + "description": "The step size to increment or decrement the context size by", + "category": [ + "discover" + ] + }, + "context:tieBreakerFields": { + "name": "Tie breaker fields", + "value": [ + "_doc" + ], + "description": "A comma-separated list of fields to use for tie-breaking between documents that have the same timestamp value. From this list the first field that is present and sortable in the current data view is used.", + "category": [ + "discover" + ] + }, + "doc_table:legacy": { + "name": "Document Explorer or classic view", + "value": false, + "description": "To use the new Document Explorer instead of the classic view, turn off this option. The Document Explorer offers better data sorting, resizable columns, and a full screen view.", + "requiresPageReload": true, + "category": [ + "discover" + ], + "metric": { + "type": "click", + "name": "discover:useLegacyDataGrid" + } + }, + "discover:modifyColumnsOnSwitch": { + "name": "Modify columns when changing data views", + "value": true, + "description": "Remove columns that are not available in the new data view.", + "category": [ + "discover" + ], + "metric": { + "type": "click", + "name": "discover:modifyColumnsOnSwitchTitle" + } + }, + "discover:searchFieldsFromSource": { + "name": "Read fields from _source", + "description": "When enabled will load documents directly from `_source`. This is soon going to be deprecated. When disabled, will retrieve fields via the new Fields API in the high-level search service.", + "value": false, + "category": [ + "discover" + ] + }, + "discover:showFieldStatistics": { + "name": "Show field statistics", + "description": "Enable the Field statistics view to show details such as the minimum and maximum values of a numeric field or a map of a geo field. This functionality is in beta and is subject to change.", + "value": true, + "category": [ + "discover" + ], + "metric": { + "type": "click", + "name": "discover:showFieldStatistics" + } + }, + "discover:showMultiFields": { + "name": "Show multi-fields", + "description": "Controls whether multi-fields display in the expanded document view. In most cases, multi-fields are the same as the original field. This option is only available when `searchFieldsFromSource` is off.", + "value": false, + "category": [ + "discover" + ] + }, + "discover:rowHeightOption": { + "name": "Row height in the Document Explorer", + "value": 3, + "category": [ + "discover" + ], + "description": "The number of lines to allow in a row. A value of -1 automatically adjusts the row height to fit the contents. A value of 0 displays the content in a single line." + }, + "truncate:maxHeight": { + "name": "Maximum cell height in the classic table", + "value": 115, + "category": [ + "discover" + ], + "description": "The maximum height that a cell in a table should occupy. Set to 0 to disable truncation.", + "requiresPageReload": true + }, + "discover:enableESQL": { + "name": "Enable ES|QL", + "value": true, + "description": "[technical preview] This tech preview feature is highly experimental--do not rely on this for production saved searches, visualizations or dashboards. This setting enables ES|QL in Discover. If you have feedback on this experience please reach out to us on discuss.elastic.co/c/elastic-stack/kibana", + "requiresPageReload": true, + "category": [ + "discover" + ] + }, + "xpackReporting:customPdfLogo": { + "name": "PDF footer image", + "value": null, + "description": "Custom image to use in the PDF's footer", + "sensitive": true, + "type": "image", + "category": [ + "reporting" + ] + }, + "labs:canvas:enable_ui": { + "name": "Enable labs button in Canvas", + "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Canvas.", + "value": false, + "type": "boolean", + "category": [ + "Presentation Labs" + ], + "requiresPageReload": true + }, + "rollups:enableIndexPatterns": { + "name": "Enable rollup data views", + "value": true, + "description": "Enable the creation of data views that capture rollup indices,\n which in turn enable visualizations based on rollup data.", + "category": [ + "rollups" + ], + "requiresPageReload": true + }, + "observability:enableInspectEsQueries": { + "category": [ + "observability" + ], + "name": "Inspect ES queries", + "value": false, + "description": "Inspect Elasticsearch queries in API responses.", + "requiresPageReload": true + }, + "observability:maxSuggestions": { + "category": [ + "observability" + ], + "name": "Maximum suggestions", + "value": 100, + "description": "Maximum number of suggestions fetched in autocomplete selection boxes." + }, + "observability:enableComparisonByDefault": { + "category": [ + "observability" + ], + "name": "Comparison feature", + "value": true, + "description": "Determines whether the comparison feature is enabled or disabled by default in the APM app." + }, + "observability:apmDefaultServiceEnvironment": { + "category": [ + "observability" + ], + "sensitive": true, + "name": "Default service environment", + "description": "Set the default environment for the APM app. When left empty, data from all environments will be displayed by default.", + "value": "" + }, + "observability:apmProgressiveLoading": { + "category": [ + "observability" + ], + "name": "Use progressive loading of selected APM views", + "description": "[technical preview] Whether to load data progressively for APM views. Data may be requested with a lower sampling rate first, with lower accuracy but faster response times, while the unsampled data loads in the background", + "value": "off", + "requiresPageReload": false, + "type": "select", + "options": [ + "off", + "low", + "medium", + "high" + ], + "optionLabels": { + "off": "Off", + "low": "Low sampling rate (fastest, least accurate)", + "medium": "Medium sampling rate", + "high": "High sampling rate (slower, most accurate)" + }, + "showInLabs": true + }, + "observability:apmServiceInventoryOptimizedSorting": { + "category": [ + "observability" + ], + "name": "Optimize services list load performance in APM", + "description": "[technical preview] Default APM Service Inventory and Storage Explorer pages sort (for Services without Machine Learning applied) to sort by Service Name.", + "value": false, + "requiresPageReload": false, + "type": "boolean", + "showInLabs": true + }, + "observability:apmServiceGroupMaxNumberOfServices": { + "category": [ + "observability" + ], + "name": "Maximum services in a service group", + "value": 500, + "description": "Limit the number of services in a given service group" + }, + "observability:apmTraceExplorerTab": { + "category": [ + "observability" + ], + "name": "APM Trace Explorer", + "description": "[technical preview] Enable the APM Trace Explorer feature, that allows you to search and inspect traces with KQL or EQL. Learn more.", + "value": true, + "requiresPageReload": true, + "type": "boolean", + "showInLabs": true + }, + "observability:apmLabsButton": { + "category": [ + "observability" + ], + "name": "Enable labs button in APM", + "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in APM.", + "value": false, + "requiresPageReload": true, + "type": "boolean" + }, + "observability:enableInfrastructureHostsView": { + "category": [ + "observability" + ], + "name": "Infrastructure Hosts view", + "value": true, + "description": "[beta] Enable the Hosts view in the Infrastructure app." + }, + "observability:enableAwsLambdaMetrics": { + "category": [ + "observability" + ], + "name": "AWS Lambda Metrics", + "description": "[technical preview] Display Amazon Lambda metrics in the service metrics tab.", + "value": true, + "requiresPageReload": true, + "type": "boolean", + "showInLabs": true + }, + "observability:apmAgentExplorerView": { + "category": [ + "observability" + ], + "name": "Agent explorer", + "description": "[beta] Enables Agent explorer view.", + "value": true, + "requiresPageReload": true, + "type": "boolean" + }, + "observability:apmAWSLambdaPriceFactor": { + "category": [ + "observability" + ], + "name": "AWS lambda price factor", + "type": "json", + "value": "{\n \"x86_64\": 0.0000166667,\n \"arm\": 0.0000133334\n}", + "description": "Price per Gb-second." + }, + "observability:apmAWSLambdaRequestCostPerMillion": { + "category": [ + "observability" + ], + "name": "AWS lambda price per 1M requests", + "value": 0.2 + }, + "observability:apmEnableServiceMetrics": { + "category": [ + "observability" + ], + "name": "Service transaction metrics", + "value": true, + "description": "[beta] Enables the usage of service transaction metrics, which are low cardinality metrics that can be used by certain views like the service inventory for faster loading times.", + "requiresPageReload": true + }, + "observability:apmEnableContinuousRollups": { + "category": [ + "observability" + ], + "name": "Continuous rollups", + "value": true, + "description": "[beta] When continuous rollups is enabled, the UI will select metrics with the appropriate resolution. On larger time ranges, lower resolution metrics will be used, which will improve loading times.", + "requiresPageReload": true + }, + "observability:apmEnableCriticalPath": { + "category": [ + "observability" + ], + "name": "Critical path", + "description": "[technical preview] Optionally display the critical path of a trace.", + "value": false, + "requiresPageReload": true, + "type": "boolean", + "showInLabs": true + }, + "observability:syntheticsThrottlingEnabled": { + "category": [ + "observability" + ], + "name": "Enable Synthetics throttling (Experimental)", + "value": false, + "description": "Enable the throttling setting in Synthetics monitor configurations. Note that throttling may still not be available for your monitors even if the setting is active. Intended for internal use only. read notice here.", + "requiresPageReload": true + }, + "observability:enableLegacyUptimeApp": { + "category": [ + "observability" + ], + "name": "Always show legacy Uptime app", + "value": false, + "description": "By default, the legacy Uptime app is hidden from the interface when it doesn't have any data for more than a week. Enable this option to always show it.", + "requiresPageReload": true + }, + "observability:apmEnableProfilingIntegration": { + "category": [ + "observability" + ], + "name": "Enable Universal Profiling integration in APM", + "value": false, + "requiresPageReload": false + }, + "ml:anomalyDetection:results:enableTimeDefaults": { + "name": "Enable time filter defaults for anomaly detection results", + "value": false, + "description": "Use the default time filter in the Single Metric Viewer and Anomaly Explorer. If not enabled, the results for the full time range of the job are displayed.", + "category": [ + "machineLearning" + ] + }, + "ml:anomalyDetection:results:timeDefaults": { + "name": "Time filter defaults for anomaly detection results", + "type": "json", + "value": "{\n \"from\": \"now-15m\",\n \"to\": \"now\"\n}", + "description": "The time filter selection to use when viewing anomaly detection job results.", + "requiresPageReload": true, + "category": [ + "machineLearning" + ] + }, + "securitySolution:refreshIntervalDefaults": { + "type": "json", + "name": "Time filter refresh interval", + "value": "{\n \"pause\": true,\n \"value\": 300000\n}", + "description": "

Default refresh interval for the Security time filter, in milliseconds.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 0 + }, + "securitySolution:timeDefaults": { + "type": "json", + "name": "Time filter period", + "value": "{\n \"from\": \"now/d\",\n \"to\": \"now/d\"\n}", + "description": "

Default period of time in the Security time filter.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 1 + }, + "securitySolution:defaultIndex": { + "name": "Elasticsearch indices", + "sensitive": true, + "value": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "traces-apm*", + "winlogbeat-*", + "-*elastic-cloud-logs-*" + ], + "description": "

Comma-delimited list of Elasticsearch indices from which the Security app collects events.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 2 + }, + "securitySolution:defaultThreatIndex": { + "name": "Threat indices", + "sensitive": true, + "value": [ + "logs-ti_*" + ], + "description": "

Comma-delimited list of Threat Intelligence indices from which the Security app collects indicators.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 3 + }, + "securitySolution:defaultAnomalyScore": { + "name": "Anomaly threshold", + "value": 50, + "type": "number", + "description": "

Value above which Machine Learning job anomalies are displayed in the Security app.

Valid values: 0 to 100.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 4 + }, + "securitySolution:enableNewsFeed": { + "name": "News feed", + "value": true, + "description": "

Enables the News feed

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 5 + }, + "securitySolution:enableExpandableFlyout": { + "name": "Expandable flyout", + "value": true, + "description": "

Enables the expandable flyout

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 6 + }, + "securitySolution:rulesTableRefresh": { + "name": "Rules auto refresh", + "description": "

Enables auto refresh on the rules and monitoring tables, in milliseconds

", + "type": "json", + "value": "{\n \"on\": true,\n \"value\": 60000\n}", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 7 + }, + "securitySolution:newsFeedUrl": { + "name": "News feed URL", + "value": "https://feeds.elastic.co/security-solution", + "sensitive": true, + "description": "

News feed content will be retrieved from this URL

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 8 + }, + "securitySolution:ipReputationLinks": { + "name": "IP Reputation Links", + "value": "[\n { \"name\": \"virustotal.com\", \"url_template\": \"https://www.virustotal.com/gui/search/{{ip}}\" },\n { \"name\": \"talosIntelligence.com\", \"url_template\": \"https://talosintelligence.com/reputation_center/lookup?search={{ip}}\" }\n]", + "type": "json", + "description": "Array of URL templates to build the list of reputation URLs to be displayed on the IP Details page.", + "sensitive": true, + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 9 + }, + "securitySolution:enableCcsWarning": { + "name": "CCS Rule Privileges Warning", + "value": true, + "description": "

Enables privilege check warnings in rules for CCS indices

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": false, + "order": 10 + }, + "securitySolution:showRelatedIntegrations": { + "name": "Related integrations", + "value": true, + "description": "

Shows related integrations on the rules and monitoring tables

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 11 + }, + "securitySolution:alertTags": { + "name": "Alert tagging options", + "sensitive": true, + "value": [ + "Duplicate", + "False Positive", + "Further investigation required" + ], + "description": "

List of tag options for use with alerts generated by Security Solution rules.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 12 + }, + "visualization:visualize:legacyGaugeChartsLibrary": { + "name": "Gauge legacy charts library", + "requiresPageReload": true, + "value": true, + "description": "Enables legacy charts library for gauge charts in visualize.", + "category": [ + "visualization" + ] + }, + "isDefaultIndexMigrated": { + "userValue": true + } +} \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index c345a5232b726..ae3cd55d7cad9 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -958,6 +958,8 @@ "@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"], "@kbn/management-settings-components-field-row/*": ["packages/kbn-management/settings/components/field_row/*"], + "@kbn/management-settings-components-form": ["packages/kbn-management/settings/components/form"], + "@kbn/management-settings-components-form/*": ["packages/kbn-management/settings/components/form/*"], "@kbn/management-settings-field-definition": ["packages/kbn-management/settings/field_definition"], "@kbn/management-settings-field-definition/*": ["packages/kbn-management/settings/field_definition/*"], "@kbn/management-settings-ids": ["packages/kbn-management/settings/setting_ids"], diff --git a/yarn.lock b/yarn.lock index 09c5a75781f14..48033207f2dc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4858,6 +4858,10 @@ version "0.0.0" uid "" +"@kbn/management-settings-components-form@link:packages/kbn-management/settings/components/form": + version "0.0.0" + uid "" + "@kbn/management-settings-field-definition@link:packages/kbn-management/settings/field_definition": version "0.0.0" uid "" From ad18ae37177353621d2facc1ab96c766bb9dc8f6 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Mon, 18 Sep 2023 13:00:10 +0100 Subject: [PATCH 02/26] Address feedback --- .../settings/components/form/README.mdx | 14 ----- .../components/form/bottom_bar/index.tsx | 9 ++- .../settings/components/form/form.styles.ts | 20 +++++++ .../settings/components/form/form.tsx | 8 ++- .../settings/components/form/index.ts | 2 + .../settings/components/form/services.tsx | 55 ++++++++++++++++--- .../form/storybook/form.stories.tsx | 2 + .../form/storybook/get_form_story.tsx | 9 +-- .../settings/components/form/types.ts | 43 +++++++++++++++ 9 files changed, 127 insertions(+), 35 deletions(-) create mode 100644 packages/kbn-management/settings/components/form/form.styles.ts create mode 100644 packages/kbn-management/settings/components/form/types.ts diff --git a/packages/kbn-management/settings/components/form/README.mdx b/packages/kbn-management/settings/components/form/README.mdx index cb6718e323d9a..990702a4cc6fd 100644 --- a/packages/kbn-management/settings/components/form/README.mdx +++ b/packages/kbn-management/settings/components/form/README.mdx @@ -10,17 +10,3 @@ date: 2023-09-12 ## Description This package contains a component for rendering and manipulating the form containing the setting fields in the Advanced Settings UI. - - -## Usage - -```tsx -const saveConfig = async (changes: Record>) => { - const arr = Object.entries(changes).map(([key, value]) => { - settingsStart.client.set(key, value.unsavedValue); - }); - return Promise.all(arr); -}; - -return ; -``` \ No newline at end of file diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx index 9932b2a4a93eb..84c5198154fa2 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { EuiBottomBar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useFormStyles } from '../form.styles'; interface BottomBarProps { saveAll: () => void; @@ -17,6 +18,8 @@ interface BottomBarProps { } export const BottomBar = ({ saveAll, clearAllUnsaved }: BottomBarProps) => { + const { cssFormButton } = useFormStyles(); + return ( { aria-describedby="aria-describedby.countOfUnsavedSettings" data-test-subj="advancedSetting-cancelButton" > - {i18n.translate('advancedSettings.form.cancelButtonLabel', { + {i18n.translate('management.settings.form.cancelButtonLabel', { defaultMessage: 'Cancel changes', })} { aria-describedby="aria-describedby.countOfUnsavedSettings" data-test-subj="advancedSetting-saveButton" > - {i18n.translate('advancedSettings.form.saveButtonLabel', { + {i18n.translate('management.settings.form.saveButtonLabel', { defaultMessage: 'Save changes', })} diff --git a/packages/kbn-management/settings/components/form/form.styles.ts b/packages/kbn-management/settings/components/form/form.styles.ts new file mode 100644 index 0000000000000..99290d033715e --- /dev/null +++ b/packages/kbn-management/settings/components/form/form.styles.ts @@ -0,0 +1,20 @@ +/* + * 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 { css } from '@emotion/react'; + +/** + * A React hook that provides stateful `css` classes for the {@link Form} component. + */ +export const useFormStyles = () => { + return { + cssFormButton: css` + width: 100%; + `, + }; +}; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index aefe861809891..58355f840b19c 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -13,19 +13,21 @@ import { FieldRow, OnChangeFn } from '@kbn/management-settings-components-field- import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; import { BottomBar } from './bottom_bar'; +import { useServices } from './services'; export interface FormProps { fields: Array>; - save: (changes: Record>) => void; } export const Form = (props: FormProps) => { - const { fields, save } = props; + const { fields } = props; const [unsavedChanges, setUnsavedChanges] = React.useState< Record> >({}); + const { saveChanges } = useServices(); + const onChange: OnChangeFn = (id, change) => { if (!change?.unsavedValue) { const { [id]: unsavedChange, ...rest } = unsavedChanges; @@ -46,7 +48,7 @@ export const Form = (props: FormProps) => { if (isEmpty(unsavedChanges)) { return; } - await save(unsavedChanges); + await saveChanges(unsavedChanges); }; const fieldRows = fields.map((field) => { diff --git a/packages/kbn-management/settings/components/form/index.ts b/packages/kbn-management/settings/components/form/index.ts index 5d218b01b0eaa..a763a5dd3d505 100644 --- a/packages/kbn-management/settings/components/form/index.ts +++ b/packages/kbn-management/settings/components/form/index.ts @@ -7,3 +7,5 @@ */ export { Form } from './form'; + +export type { FormKibanaDependencies, FormServices } from './types'; diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index 4ff2c5e0ebb55..cb7abc0adfa17 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -9,24 +9,65 @@ import { FieldRowProvider, FieldRowKibanaProvider, - type FieldRowKibanaDependencies, - type FieldRowServices, } from '@kbn/management-settings-components-field-row'; -import React, { FC } from 'react'; +import React, { FC, useContext } from 'react'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; -export type FormServices = FieldRowServices; -export type FormKibanaDependencies = FieldRowKibanaDependencies; +import type { FormServices, FormKibanaDependencies, Services } from './types'; + +const FormContext = React.createContext(null); /** * React Provider that provides services to a {@link Form} component and its dependents. */ export const FormProvider: FC = ({ children, ...services }) => { + const { saveChanges, showError, ...rest } = services; + + return ( + + {children} + + ); return {children}; }; /** * Kibana-specific Provider that maps Kibana plugins and services to a {@link FormProvider}. */ -export const FormKibanaProvider: FC = ({ children, ...services }) => { - return {children}; +export const FormKibanaProvider: FC = ({ children, ...deps }) => { + const { settings, ...rest } = deps; + + return ( + >) => { + const arr = Object.entries(changes).map(([key, value]) => + settings.client.set(key, value) + ); + return Promise.all(arr); + }, + // TODO: + showError: (message: string) => {}, + }} + > + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + * + * @see {@link FormServices} + */ +export const useServices = () => { + const context = useContext(FormContext); + + if (!context) { + throw new Error( + 'FormContext is missing. Ensure your component or React root is wrapped with FormProvider.' + ); + } + + return context; }; 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 839e2cb278c02..dfa2c3a6ae362 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -22,6 +22,8 @@ export default { diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index 161b8f8f0d568..5228320c78f83 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -11,7 +11,6 @@ import React from 'react'; import { normalizeSettings } from '@kbn/management-settings-utilities'; import { FieldDefinition, SettingType, UiSetting } from '@kbn/management-settings-types'; import { getFieldDefinition } from '@kbn/management-settings-field-definition'; -import { action } from '@storybook/addon-actions'; import { Form } from '../form'; import _settings from './settings.json'; @@ -28,13 +27,7 @@ export const getFormStory = () => { }) ); - const save = async () => { - alert('Saved!'); - // Not working: - action('Saved'); - }; - - return ; + return ; }; return Story; diff --git a/packages/kbn-management/settings/components/form/types.ts b/packages/kbn-management/settings/components/form/types.ts new file mode 100644 index 0000000000000..fc4b0d3775428 --- /dev/null +++ b/packages/kbn-management/settings/components/form/types.ts @@ -0,0 +1,43 @@ +/* + * 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 { + FieldRowKibanaDependencies, + FieldRowServices, +} from '@kbn/management-settings-components-field-row'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { SettingsStart } from '@kbn/core-ui-settings-browser'; + +/** + * Contextual services used by a {@link Form} component. + */ +export interface Services { + saveChanges: (changes: Record>) => void; + showError: (message: string) => void; +} + +/** + * Contextual services used by a {@link Form} component and its dependents. + */ +export type FormServices = FieldRowServices & Services; + +/** + * An interface containing a collection of Kibana plugins and services required to + * render a {@link Form} component. + */ +export interface KibanaDependencies { + settings: { + client: SettingsStart['client']; + }; +} + +/** + * An interface containing a collection of Kibana plugins and services required to + * render a {@link Form} component and its dependents. + */ +export type FormKibanaDependencies = KibanaDependencies & FieldRowKibanaDependencies; From 0787f7ab32859a725a7c2321ed387a145c7c7e8b Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Mon, 18 Sep 2023 17:16:21 +0100 Subject: [PATCH 03/26] Add remaining form functionalities --- .../settings/components/field_input/types.ts | 2 +- .../components/form/bottom_bar/bottom_bar.tsx | 97 +++++++++++++++++++ .../components/form/bottom_bar/index.tsx | 58 +---------- .../form/bottom_bar/unsaved_count.tsx | 37 +++++++ .../settings/components/form/form.styles.ts | 9 ++ .../settings/components/form/form.tsx | 55 ++++++++--- .../components/form/reload_page_toast.tsx | 44 +++++++++ .../settings/components/form/services.tsx | 13 +-- .../form/storybook/form.stories.tsx | 1 + .../form/storybook/get_form_story.tsx | 4 +- .../settings/components/form/types.ts | 5 + 11 files changed, 245 insertions(+), 80 deletions(-) create mode 100644 packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx create mode 100644 packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx create mode 100644 packages/kbn-management/settings/components/form/reload_page_toast.tsx diff --git a/packages/kbn-management/settings/components/field_input/types.ts b/packages/kbn-management/settings/components/field_input/types.ts index 73e676785e6b9..66ba6373f5b9f 100644 --- a/packages/kbn-management/settings/components/field_input/types.ts +++ b/packages/kbn-management/settings/components/field_input/types.ts @@ -27,7 +27,7 @@ export interface FieldInputServices { */ export interface FieldInputKibanaDependencies { /** The portion of the {@link ToastsStart} contract used by this component. */ - toasts: Pick; + toasts: Pick; } /** diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx new file mode 100644 index 0000000000000..0ff4648467cc2 --- /dev/null +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx @@ -0,0 +1,97 @@ +/* + * 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 { + EuiBottomBar, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { UnsavedCount } from './unsaved_count'; +import { useFormStyles } from '../form.styles'; + +interface BottomBarProps { + saveAll: () => void; + clearAllUnsaved: () => void; + hasInvalidChanges: boolean; + isLoading: boolean; + unsavedChangesCount: number; +} + +export const BottomBar = ({ + saveAll, + clearAllUnsaved, + hasInvalidChanges, + isLoading, + unsavedChangesCount, +}: BottomBarProps) => { + const { cssFormButton, cssFormUnsavedCount } = useFormStyles(); + + return ( + + + + + + + + + {i18n.translate('management.settings.form.cancelButtonLabel', { + defaultMessage: 'Cancel changes', + })} + + + + + + {i18n.translate('management.settings.form.saveButtonLabel', { + defaultMessage: 'Save changes', + })} + + + + + + ); +}; diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx index 84c5198154fa2..0abbe24520157 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -6,60 +6,4 @@ * Side Public License, v 1. */ -import React from 'react'; - -import { EuiBottomBar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useFormStyles } from '../form.styles'; - -interface BottomBarProps { - saveAll: () => void; - clearAllUnsaved: () => void; -} - -export const BottomBar = ({ saveAll, clearAllUnsaved }: BottomBarProps) => { - const { cssFormButton } = useFormStyles(); - - return ( - - - - - - {i18n.translate('management.settings.form.cancelButtonLabel', { - defaultMessage: 'Cancel changes', - })} - - - - - {i18n.translate('management.settings.form.saveButtonLabel', { - defaultMessage: 'Save changes', - })} - - - - - ); -}; +export { BottomBar } from './bottom_bar'; diff --git a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx new file mode 100644 index 0000000000000..bdd5404076fe7 --- /dev/null +++ b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx @@ -0,0 +1,37 @@ +/* + * 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 { EuiTextColor } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useFormStyles } from '../form.styles'; + +interface UnsavedCountProps { + unsavedCount: number; +} + +export const UnsavedCount = ({ unsavedCount }: UnsavedCountProps) => { + const { cssFormUnsavedCountMessage } = useFormStyles(); + return ( +

+ + + +

+ ); +}; diff --git a/packages/kbn-management/settings/components/form/form.styles.ts b/packages/kbn-management/settings/components/form/form.styles.ts index 99290d033715e..6611e9178cea0 100644 --- a/packages/kbn-management/settings/components/form/form.styles.ts +++ b/packages/kbn-management/settings/components/form/form.styles.ts @@ -16,5 +16,14 @@ export const useFormStyles = () => { cssFormButton: css` width: 100%; `, + cssFormUnsavedCount: css` + @include euiBreakpoint('xs') { + display: none; + } + `, + cssFormUnsavedCountMessage: css` + box-shadow: -$euiSizeXS 0 $euiColorWarning; + padding-left: $euiSizeS; + `, }; }; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index 58355f840b19c..540bd25cf9124 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -12,43 +12,64 @@ import type { FieldDefinition } from '@kbn/management-settings-types'; import { FieldRow, OnChangeFn } from '@kbn/management-settings-components-field-row'; import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n/target/types'; import { BottomBar } from './bottom_bar'; import { useServices } from './services'; export interface FormProps { fields: Array>; + isSavingEnabled: boolean; } export const Form = (props: FormProps) => { - const { fields } = props; + const { fields, isSavingEnabled } = props; + const { saveChanges, showError, showReloadPagePrompt } = useServices(); const [unsavedChanges, setUnsavedChanges] = React.useState< Record> >({}); - const { saveChanges } = useServices(); + const [isLoading, setIsLoading] = React.useState(false); - const onChange: OnChangeFn = (id, change) => { - if (!change?.unsavedValue) { - const { [id]: unsavedChange, ...rest } = unsavedChanges; - setUnsavedChanges(rest); + const unsavedChangesCount = Object.keys(unsavedChanges).length; + const hasInvalidChanges = Object.values(unsavedChanges).some(({ isInvalid }) => isInvalid); + + const saveAll = async () => { + setIsLoading(true); + if (isEmpty(unsavedChanges)) { return; } - - setUnsavedChanges((changes) => ({ ...changes, [id]: change })); + try { + await saveChanges(unsavedChanges); + clearAllUnsaved(); + const requiresReload = fields.some( + (setting) => unsavedChanges.hasOwnProperty(setting.id) && setting.requiresPageReload + ); + if (requiresReload) { + showReloadPagePrompt(); + } + } catch (e) { + showError( + i18n.translate('management.settings.form.saveErrorMessage', { + defaultMessage: 'Unable to save', + }) + ); + } + setIsLoading(false); }; - const isSavingEnabled = true; - const clearAllUnsaved = () => { setUnsavedChanges({}); }; - const saveAll = async () => { - if (isEmpty(unsavedChanges)) { + const onChange: OnChangeFn = (id, change) => { + if (!change?.unsavedValue) { + const { [id]: unsavedChange, ...rest } = unsavedChanges; + setUnsavedChanges(rest); return; } - await saveChanges(unsavedChanges); + + setUnsavedChanges((changes) => ({ ...changes, [id]: change })); }; const fieldRows = fields.map((field) => { @@ -61,7 +82,13 @@ export const Form = (props: FormProps) => {
{fieldRows}
{!isEmpty(unsavedChanges) && ( - + )}
); diff --git a/packages/kbn-management/settings/components/form/reload_page_toast.tsx b/packages/kbn-management/settings/components/form/reload_page_toast.tsx new file mode 100644 index 0000000000000..e082f9ac2520b --- /dev/null +++ b/packages/kbn-management/settings/components/form/reload_page_toast.tsx @@ -0,0 +1,44 @@ +/* + * 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 { i18n } from '@kbn/i18n/target/types'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ToastInput } from '@kbn/core-notifications-browser/target/types'; +import { I18nStart } from '@kbn/core-i18n-browser'; +import { ThemeServiceStart } from '@kbn/core-theme-browser'; + +export const ReloadPageToast = (theme: ThemeServiceStart, i18nStart: I18nStart): ToastInput => { + return { + title: i18n.translate('management.settings.form.requiresPageReloadToastDescription', { + defaultMessage: 'One or more settings require you to reload the page to take effect.', + }), + text: toMountPoint( + + + + window.location.reload()} + data-test-subj="windowReloadButton" + > + {i18n.translate('management.settings.form.requiresPageReloadToastButtonLabel', { + defaultMessage: 'Reload page', + })} + + + + , + { i18n: i18nStart, theme } + ), + color: 'success', + }; +}; diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index cb7abc0adfa17..401da46f58897 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -14,6 +14,7 @@ import React, { FC, useContext } from 'react'; import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import type { FormServices, FormKibanaDependencies, Services } from './types'; +import { ReloadPageToast } from './reload_page_toast'; const FormContext = React.createContext(null); @@ -21,10 +22,10 @@ const FormContext = React.createContext(null); * React Provider that provides services to a {@link Form} component and its dependents. */ export const FormProvider: FC = ({ children, ...services }) => { - const { saveChanges, showError, ...rest } = services; + const { saveChanges, showError, showReloadPagePrompt, ...rest } = services; return ( - + {children} ); @@ -35,7 +36,7 @@ export const FormProvider: FC = ({ children, ...services }) => { * Kibana-specific Provider that maps Kibana plugins and services to a {@link FormProvider}. */ export const FormKibanaProvider: FC = ({ children, ...deps }) => { - const { settings, ...rest } = deps; + const { settings, toasts, docLinks, theme, i18nStart } = deps; return ( = ({ children, ...de ); return Promise.all(arr); }, - // TODO: - showError: (message: string) => {}, + showError: (message: string) => toasts.addDanger(message), + 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 dfa2c3a6ae362..7c6a21c7da986 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -24,6 +24,7 @@ export default { links={{ deprecationKey: 'link/to/deprecation/docs' }} saveChanges={action('saveChanges')} showError={action('showError')} + showReloadPagePrompt={action('showReloadPagePrompt')} > diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index 5228320c78f83..859f1350c4830 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -23,11 +23,11 @@ export const getFormStory = () => { getFieldDefinition({ id, setting, - params: { isCustom: false, isOverridden: setting.isOverridden }, }) ); + const isSavingEnabled = true; - return ; + return ; }; return Story; diff --git a/packages/kbn-management/settings/components/form/types.ts b/packages/kbn-management/settings/components/form/types.ts index fc4b0d3775428..4d4f400d4a0fe 100644 --- a/packages/kbn-management/settings/components/form/types.ts +++ b/packages/kbn-management/settings/components/form/types.ts @@ -12,6 +12,8 @@ import type { } from '@kbn/management-settings-components-field-row'; import { SettingType, 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'; /** * Contextual services used by a {@link Form} component. @@ -19,6 +21,7 @@ import { SettingsStart } from '@kbn/core-ui-settings-browser'; export interface Services { saveChanges: (changes: Record>) => void; showError: (message: string) => void; + showReloadPagePrompt: () => void; } /** @@ -34,6 +37,8 @@ export interface KibanaDependencies { settings: { client: SettingsStart['client']; }; + theme: ThemeServiceStart; + i18nStart: I18nStart; } /** From 8fb4359b91b6a41dc5f925297f5bf01d2d539512 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Thu, 14 Sep 2023 14:21:36 +0100 Subject: [PATCH 04/26] [serverless] Advanced Settings - Form component --- package.json | 1 + .../settings/components/form/README.mdx | 26 + .../components/form/bottom_bar/index.tsx | 62 + .../settings/components/form/form.tsx | 66 + .../settings/components/form/index.ts | 9 + .../settings/components/form/kibana.jsonc | 5 + .../settings/components/form/package.json | 6 + .../settings/components/form/services.tsx | 32 + .../form/storybook/form.stories.tsx | 34 + .../form/storybook/get_form_story.tsx | 41 + .../components/form/storybook/settings.json | 1843 +++++++++++++++++ tsconfig.base.json | 2 + yarn.lock | 4 + 13 files changed, 2131 insertions(+) create mode 100644 packages/kbn-management/settings/components/form/README.mdx create mode 100644 packages/kbn-management/settings/components/form/bottom_bar/index.tsx create mode 100644 packages/kbn-management/settings/components/form/form.tsx create mode 100644 packages/kbn-management/settings/components/form/index.ts create mode 100644 packages/kbn-management/settings/components/form/kibana.jsonc create mode 100644 packages/kbn-management/settings/components/form/package.json create mode 100644 packages/kbn-management/settings/components/form/services.tsx create mode 100644 packages/kbn-management/settings/components/form/storybook/form.stories.tsx create mode 100644 packages/kbn-management/settings/components/form/storybook/get_form_story.tsx create mode 100644 packages/kbn-management/settings/components/form/storybook/settings.json diff --git a/package.json b/package.json index 2d4809d185c66..dc23e4c28f91a 100644 --- a/package.json +++ b/package.json @@ -503,6 +503,7 @@ "@kbn/management-plugin": "link:src/plugins/management", "@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", "@kbn/management-settings-field-definition": "link:packages/kbn-management/settings/field_definition", "@kbn/management-settings-ids": "link:packages/kbn-management/settings/setting_ids", "@kbn/management-settings-section-registry": "link:packages/kbn-management/settings/section_registry", diff --git a/packages/kbn-management/settings/components/form/README.mdx b/packages/kbn-management/settings/components/form/README.mdx new file mode 100644 index 0000000000000..cb6718e323d9a --- /dev/null +++ b/packages/kbn-management/settings/components/form/README.mdx @@ -0,0 +1,26 @@ +--- +id: management/settings/components/form +slug: /management/settings/components/form +title: Management Settings form Component +description: A package containing a component for rendering and manipulating the form in the Advanced Settings UI. +tags: ['management', 'settings'] +date: 2023-09-12 +--- + +## Description + +This package contains a component for rendering and manipulating the form containing the setting fields in the Advanced Settings UI. + + +## Usage + +```tsx +const saveConfig = async (changes: Record>) => { + const arr = Object.entries(changes).map(([key, value]) => { + settingsStart.client.set(key, value.unsavedValue); + }); + return Promise.all(arr); +}; + +return ; +``` \ No newline at end of file diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx new file mode 100644 index 0000000000000..9932b2a4a93eb --- /dev/null +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -0,0 +1,62 @@ +/* + * 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 { EuiBottomBar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface BottomBarProps { + saveAll: () => void; + clearAllUnsaved: () => void; +} + +export const BottomBar = ({ saveAll, clearAllUnsaved }: BottomBarProps) => { + return ( + + + + + + {i18n.translate('advancedSettings.form.cancelButtonLabel', { + defaultMessage: 'Cancel changes', + })} + + + + + {i18n.translate('advancedSettings.form.saveButtonLabel', { + defaultMessage: 'Save changes', + })} + + + + + ); +}; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx new file mode 100644 index 0000000000000..aefe861809891 --- /dev/null +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -0,0 +1,66 @@ +/* + * 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, { Fragment } from 'react'; + +import type { FieldDefinition } from '@kbn/management-settings-types'; +import { FieldRow, OnChangeFn } from '@kbn/management-settings-components-field-row'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { isEmpty } from 'lodash'; +import { BottomBar } from './bottom_bar'; + +export interface FormProps { + fields: Array>; + save: (changes: Record>) => void; +} + +export const Form = (props: FormProps) => { + const { fields, save } = props; + + const [unsavedChanges, setUnsavedChanges] = React.useState< + Record> + >({}); + + const onChange: OnChangeFn = (id, change) => { + if (!change?.unsavedValue) { + const { [id]: unsavedChange, ...rest } = unsavedChanges; + setUnsavedChanges(rest); + return; + } + + setUnsavedChanges((changes) => ({ ...changes, [id]: change })); + }; + + const isSavingEnabled = true; + + const clearAllUnsaved = () => { + setUnsavedChanges({}); + }; + + const saveAll = async () => { + if (isEmpty(unsavedChanges)) { + return; + } + await save(unsavedChanges); + }; + + const fieldRows = fields.map((field) => { + const { id: key } = field; + const unsavedChange = unsavedChanges[key]; + return ; + }); + + return ( + +
{fieldRows}
+ {!isEmpty(unsavedChanges) && ( + + )} +
+ ); +}; diff --git a/packages/kbn-management/settings/components/form/index.ts b/packages/kbn-management/settings/components/form/index.ts new file mode 100644 index 0000000000000..5d218b01b0eaa --- /dev/null +++ b/packages/kbn-management/settings/components/form/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { Form } from './form'; diff --git a/packages/kbn-management/settings/components/form/kibana.jsonc b/packages/kbn-management/settings/components/form/kibana.jsonc new file mode 100644 index 0000000000000..c60e87fcfc1ac --- /dev/null +++ b/packages/kbn-management/settings/components/form/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/management-settings-components-form", + "owner": "@elastic/platform-deployment-management @elastic/appex-sharedux" +} diff --git a/packages/kbn-management/settings/components/form/package.json b/packages/kbn-management/settings/components/form/package.json new file mode 100644 index 0000000000000..3e14acc7378dd --- /dev/null +++ b/packages/kbn-management/settings/components/form/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/management-settings-components-form", + "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/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx new file mode 100644 index 0000000000000..4ff2c5e0ebb55 --- /dev/null +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -0,0 +1,32 @@ +/* + * 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 { + FieldRowProvider, + FieldRowKibanaProvider, + type FieldRowKibanaDependencies, + type FieldRowServices, +} from '@kbn/management-settings-components-field-row'; +import React, { FC } from 'react'; + +export type FormServices = FieldRowServices; +export type FormKibanaDependencies = FieldRowKibanaDependencies; + +/** + * React Provider that provides services to a {@link Form} component and its dependents. + */ +export const FormProvider: FC = ({ children, ...services }) => { + return {children}; +}; + +/** + * Kibana-specific Provider that maps Kibana plugins and services to a {@link FormProvider}. + */ +export const FormKibanaProvider: FC = ({ children, ...services }) => { + return {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 new file mode 100644 index 0000000000000..839e2cb278c02 --- /dev/null +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -0,0 +1,34 @@ +/* + * 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 { EuiPanel } from '@elastic/eui'; +import { action } from '@storybook/addon-actions'; +import { ComponentMeta } from '@storybook/react'; +import { FormProvider } from '../services'; +import { getFormStory } from './get_form_story'; +import { Form as Component } from '../form'; + +export default { + title: `Settings/Form/Form`, + description: 'A form with field rows', + argTypes: { save: { action: 'Saved' } }, + decorators: [ + (Story) => ( + + + + + + ), + ], +} as ComponentMeta; + +export const Form = getFormStory(); diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx new file mode 100644 index 0000000000000..161b8f8f0d568 --- /dev/null +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -0,0 +1,41 @@ +/* + * 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 { normalizeSettings } from '@kbn/management-settings-utilities'; +import { FieldDefinition, SettingType, UiSetting } from '@kbn/management-settings-types'; +import { getFieldDefinition } from '@kbn/management-settings-field-definition'; +import { action } from '@storybook/addon-actions'; +import { Form } from '../form'; +import _settings from './settings.json'; + +export const getFormStory = () => { + const Story = () => { + const settings = _settings as unknown as Record>; + const normalizedSettings = normalizeSettings(settings); + const fields: Array> = Object.entries(normalizedSettings).map( + ([id, setting]) => + getFieldDefinition({ + id, + setting, + params: { isCustom: false, isOverridden: setting.isOverridden }, + }) + ); + + const save = async () => { + alert('Saved!'); + // Not working: + action('Saved'); + }; + + return ; + }; + + return Story; +}; diff --git a/packages/kbn-management/settings/components/form/storybook/settings.json b/packages/kbn-management/settings/components/form/storybook/settings.json new file mode 100644 index 0000000000000..fa54cf96eada7 --- /dev/null +++ b/packages/kbn-management/settings/components/form/storybook/settings.json @@ -0,0 +1,1843 @@ +{ + "accessibility:disableAnimations": { + "name": "Disable Animations", + "value": false, + "description": "Turn off all unnecessary animations in the Kibana UI. Refresh the page to apply the changes.", + "category": [ + "accessibility" + ], + "requiresPageReload": true + }, + "hideAnnouncements": { + "name": "Hide announcements", + "value": false, + "description": "Stop showing messages and tours that highlight new features." + }, + "dateFormat": { + "name": "Date format", + "value": "MMM D, YYYY @ HH:mm:ss.SSS", + "description": "The format for pretty formatted dates." + }, + "dateFormat:tz": { + "name": "Time zone", + "value": "Browser", + "description": "The default time zone.", + "type": "select", + "options": [ + "Browser", + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/ComodRivadavia", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Atka", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Ciudad_Juarez", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Nelson", + "America/Fort_Wayne", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Knox_IN", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Rosario", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Shiprock", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Virgin", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/South_Pole", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Ashkhabad", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Chungking", + "Asia/Colombo", + "Asia/Dacca", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Harbin", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Istanbul", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macao", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qostanay", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimbu", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ujung_Pandang", + "Asia/Ulaanbaatar", + "Asia/Ulan_Bator", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yangon", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/ACT", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Canberra", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/LHI", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/NSW", + "Australia/North", + "Australia/Perth", + "Australia/Queensland", + "Australia/South", + "Australia/Sydney", + "Australia/Tasmania", + "Australia/Victoria", + "Australia/West", + "Australia/Yancowinna", + "Brazil/Acre", + "Brazil/DeNoronha", + "Brazil/East", + "Brazil/West", + "CET", + "CST6CDT", + "Canada/Atlantic", + "Canada/Central", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Canada/Saskatchewan", + "Canada/Yukon", + "Chile/Continental", + "Chile/EasterIsland", + "Cuba", + "EET", + "EST5EDT", + "Egypt", + "Eire", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/UTC", + "Etc/Universal", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Kyiv", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Ulyanovsk", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "GB", + "GB-Eire", + "GMT", + "GMT+0", + "GMT-0", + "GMT0", + "Greenwich", + "Hongkong", + "Iceland", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Iran", + "Israel", + "Jamaica", + "Japan", + "Kwajalein", + "Libya", + "MET", + "MST7MDT", + "Mexico/BajaNorte", + "Mexico/BajaSur", + "Mexico/General", + "NZ", + "NZ-CHAT", + "Navajo", + "PRC", + "PST8PDT", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kanton", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "ROK", + "Singapore", + "Turkey", + "UCT", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Samoa", + "UTC", + "Universal", + "W-SU", + "WET", + "Zulu" + ], + "requiresPageReload": true + }, + "dateFormat:scaled": { + "name": "Scaled date format", + "type": "json", + "value": "[\n [\"\", \"HH:mm:ss.SSS\"],\n [\"PT1S\", \"HH:mm:ss\"],\n [\"PT1M\", \"HH:mm\"],\n [\"PT1H\", \"YYYY-MM-DD HH:mm\"],\n [\"P1DT\", \"YYYY-MM-DD\"],\n [\"P1YT\", \"YYYY\"]\n]", + "description": "Values that define the format used in situations where time-based data is rendered in order, and formatted timestamps should adapt to the interval between measurements. Keys are ISO8601 intervals." + }, + "dateFormat:dow": { + "name": "Day of week", + "value": "Sunday", + "description": "The day that starts the week.", + "type": "select", + "options": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ] + }, + "dateNanosFormat": { + "name": "Date with nanoseconds format", + "value": "MMM D, YYYY @ HH:mm:ss.SSSSSSSSS", + "description": "The format for date_nanos data." + }, + "buildNum": { + "readonly": true, + "userValue": 9007199254740991 + }, + "defaultRoute": { + "name": "Default route", + "value": "/app/home", + "description": "This setting specifies the default route when opening Kibana. You can use this setting to modify the landing page when opening Kibana. The route must be a relative URL." + }, + "notifications:banner": { + "name": "Custom banner notification", + "value": "", + "type": "markdown", + "description": "A custom banner intended for temporary notices to all users. Markdown supported.", + "category": [ + "notifications" + ], + "sensitive": true + }, + "notifications:lifetime:banner": { + "name": "Banner notification lifetime", + "value": 3000000, + "description": "The time in milliseconds which a banner notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "notifications:lifetime:error": { + "name": "Error notification lifetime", + "value": 300000, + "description": "The time in milliseconds which an error notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "notifications:lifetime:warning": { + "name": "Warning notification lifetime", + "value": 10000, + "description": "The time in milliseconds which a warning notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "notifications:lifetime:info": { + "name": "Info notification lifetime", + "value": 5000, + "description": "The time in milliseconds which an information notification will be displayed on-screen for. ", + "type": "number", + "category": [ + "notifications" + ] + }, + "theme:darkMode": { + "name": "Dark mode", + "value": false, + "description": "Enable a dark mode for the Kibana UI. A page refresh is required for the setting to be applied.", + "requiresPageReload": true + }, + "theme:version": { + "name": "Theme version", + "value": "v8", + "readonly": true + }, + "state:storeInSessionStorage": { + "name": "Store URLs in session storage", + "value": false, + "description": "The URL can sometimes grow to be too large for some browsers to handle. To counter-act this we are testing if storing parts of the URL in session storage could help. Please let us know how it goes!" + }, + "csv:separator": { + "name": "CSV separator", + "value": ",", + "description": "Separate exported values with this string", + "userValue": "|" + }, + "csv:quoteValues": { + "name": "Quote CSV values", + "value": true, + "description": "Should values be quoted in csv exports?" + }, + "banners:placement": { + "name": "Banner placement", + "description": "Display a top banner above the Elastic header. \n \n Subscription required.\n \n ", + "category": [ + "banner" + ], + "order": 1, + "type": "select", + "value": "disabled", + "options": [ + "disabled", + "top" + ], + "optionLabels": { + "disabled": "Disabled", + "top": "Top" + }, + "requiresPageReload": true + }, + "banners:textContent": { + "name": "Banner text", + "description": "Add Markdown-formatted text to the banner. \n \n Subscription required.\n \n ", + "sensitive": true, + "category": [ + "banner" + ], + "order": 2, + "type": "markdown", + "value": "", + "requiresPageReload": true + }, + "banners:textColor": { + "name": "Banner text color", + "description": "Set the color of the banner text. \n \n Subscription required.\n \n ", + "category": [ + "banner" + ], + "order": 3, + "type": "color", + "value": "#8A6A0A", + "requiresPageReload": true + }, + "banners:backgroundColor": { + "name": "Banner background color", + "description": "Set the background color for the banner. \n \n Subscription required.\n \n ", + "category": [ + "banner" + ], + "order": 4, + "type": "color", + "value": "#FFF9E8", + "requiresPageReload": true + }, + "savedObjects:perPage": { + "name": "Objects per page", + "value": 20, + "type": "number", + "description": "Number of objects to show per page in the load dialog" + }, + "savedObjects:listingLimit": { + "name": "Objects listing limit", + "type": "number", + "value": 1000, + "description": "Number of objects to fetch for the listing pages" + }, + "shortDots:enable": { + "name": "Shorten fields", + "value": false, + "description": "Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz" + }, + "format:defaultTypeMap": { + "name": "Field type format name", + "value": "{\n \"ip\": { \"id\": \"ip\", \"params\": {} },\n \"date\": { \"id\": \"date\", \"params\": {} },\n \"date_nanos\": { \"id\": \"date_nanos\", \"params\": {}, \"es\": true },\n \"geo_point\": { \"id\": \"geo_point\", \"params\": { \"transform\": \"wkt\" } },\n \"number\": { \"id\": \"number\", \"params\": {} },\n \"boolean\": { \"id\": \"boolean\", \"params\": {} },\n \"histogram\": { \"id\": \"histogram\", \"params\": {} },\n \"_source\": { \"id\": \"_source\", \"params\": {} },\n \"_default_\": { \"id\": \"string\", \"params\": {} }\n}", + "type": "json", + "description": "Map of the format name to use by default for each field type. \"_default_\" is used if the field type is not mentioned explicitly" + }, + "format:number:defaultPattern": { + "name": "Number format", + "value": "0,0.[000]", + "type": "string", + "description": "Default numeral format for the \"number\" format" + }, + "format:percent:defaultPattern": { + "name": "Percent format", + "value": "0,0.[000]%", + "type": "string", + "description": "Default numeral format for the \"percent\" format" + }, + "format:bytes:defaultPattern": { + "name": "Bytes format", + "value": "0,0.[0]b", + "type": "string", + "description": "Default numeral format for the \"bytes\" format" + }, + "format:currency:defaultPattern": { + "name": "Currency format", + "value": "($0,0.[00])", + "type": "string", + "description": "Default numeral format for the \"currency\" format" + }, + "format:number:defaultLocale": { + "name": "Formatting locale", + "value": "en", + "type": "select", + "options": [ + "en", + "be-nl", + "chs", + "cs", + "da-dk", + "de-ch", + "de", + "en-gb", + "es-ES", + "es", + "et", + "fi", + "fr-CA", + "fr-ch", + "fr", + "hu", + "it", + "ja", + "nl-nl", + "pl", + "pt-br", + "pt-pt", + "ru-UA", + "ru", + "sk", + "th", + "tr", + "uk-UA" + ], + "optionLabels": { + "be-nl": "Dutch (Belgium)", + "chs": "Simplified Chinese", + "cs": "Czech", + "da-dk": "Danish (Denmark)", + "de-ch": "German (Switzerland)", + "de": "German", + "en-gb": "English (UK)", + "es-ES": "Spanish (Spain)", + "es": "Spanish", + "et": "Estonian", + "fi": "Finnish", + "fr-CA": "French (Canada)", + "fr-ch": "French (Switzerland)", + "fr": "French", + "hu": "Hungarian", + "it": "Italian", + "ja": "Japanese", + "nl-nl": "Dutch (Netherlands)", + "pl": "Polish", + "pt-br": "Portuguese (Brazil)", + "pt-pt": "Portuguese", + "ru-UA": "Russian (Ukraine)", + "ru": "Russian", + "sk": "Slovak", + "th": "Thai", + "tr": "Turkish", + "uk-UA": "Ukrainian" + }, + "description": "Numeral language locale" + }, + "visualization:colorMapping": { + "name": "Color mapping", + "value": "{\"Count\":\"#00A69B\"}", + "type": "json", + "description": "Maps values to specific colors in charts using the Compatibility palette.", + "deprecation": { + "message": "This setting is deprecated and will not be supported in a future version.", + "docLinksKey": "visualizationSettings" + }, + "category": [ + "visualization" + ] + }, + "visualization:useLegacyTimeAxis": { + "name": "Legacy chart time axis", + "value": false, + "description": "Enables the legacy time axis for charts in Lens, Discover, Visualize and TSVB", + "deprecation": { + "message": "This setting is deprecated and will not be supported in a future version.", + "docLinksKey": "visualizationSettings" + }, + "category": [ + "visualization" + ] + }, + "bfetch:disable": { + "name": "Disable request batching", + "value": false, + "description": "Disables requests batching. This increases number of HTTP requests from Kibana, but allows to debug requests individually.", + "category": [] + }, + "bfetch:disableCompression": { + "name": "Disable batch compression", + "value": false, + "description": "Disable batch compression. This allows you to debug individual requests, but increases response size.", + "category": [] + }, + "metaFields": { + "name": "Meta fields", + "value": [ + "_source", + "_id", + "_index", + "_score" + ], + "description": "Fields that exist outside of _source to merge into our document when displaying it" + }, + "doc_table:highlight": { + "name": "Highlight results", + "value": true, + "description": "Highlight results in Discover and Saved Searches Dashboard. Highlighting makes requests slow when working on big documents.", + "category": [ + "discover" + ] + }, + "query:queryString:options": { + "name": "Query string options", + "value": "{ \"analyze_wildcard\": true }", + "description": "Options for the lucene query string parser. Is only used when \"Query language\" is set to Lucene.", + "type": "json" + }, + "query:allowLeadingWildcards": { + "name": "Allow leading wildcards in query", + "value": true, + "description": "When set, * is allowed as the first character in a query clause. To disallow leading wildcards in basic lucene queries, use query:queryString:options." + }, + "search:queryLanguage": { + "name": "Query language", + "value": "kuery", + "description": "Query language used by the query bar. KQL is a new language built specifically for Kibana.", + "type": "select", + "options": [ + "lucene", + "kuery" + ], + "optionLabels": { + "lucene": "Lucene", + "kuery": "KQL" + } + }, + "sort:options": { + "name": "Sort options", + "value": "{ \"unmapped_type\": \"boolean\" }", + "description": "Options for the Elasticsearch sort parameter", + "type": "json" + }, + "defaultIndex": { + "name": "Default data view", + "value": null, + "type": "string", + "description": "Used by discover and visualizations when a data view is not set." + }, + "courier:ignoreFilterIfFieldNotInIndex": { + "name": "Ignore filter(s)", + "value": false, + "description": "This configuration enhances support for dashboards containing visualizations accessing dissimilar indexes. When disabled, all filters are applied to all visualizations. When enabled, filter(s) will be ignored for a visualization when the visualization's index does not contain the filtering field.", + "category": [ + "search" + ] + }, + "courier:setRequestPreference": { + "name": "Request preference", + "value": "sessionId", + "options": [ + "sessionId", + "custom", + "none" + ], + "optionLabels": { + "sessionId": "Session ID", + "custom": "Custom", + "none": "None" + }, + "type": "select", + "description": "Allows you to set which shards handle your search requests.\n
    \n
  • Session ID: restricts operations to execute all search requests on the same shards.\n This has the benefit of reusing shard caches across requests.
  • \n
  • Custom: allows you to define a your own preference.\n Use 'courier:customRequestPreference' to customize your preference value.
  • \n
  • None: means do not set a preference.\n This might provide better performance because requests can be spread across all shard copies.\n However, results might be inconsistent because different shards might be in different refresh states.
  • \n
", + "category": [ + "search" + ] + }, + "courier:customRequestPreference": { + "name": "Custom request preference", + "value": "_local", + "type": "string", + "description": "Request Preference used when courier:setRequestPreference is set to \"custom\".", + "category": [ + "search" + ] + }, + "courier:maxConcurrentShardRequests": { + "name": "Max Concurrent Shard Requests", + "value": 0, + "type": "number", + "description": "Controls the max_concurrent_shard_requests setting used for _msearch requests sent by Kibana. Set to 0 to disable this config and use the Elasticsearch default.", + "category": [ + "search" + ] + }, + "search:includeFrozen": { + "name": "Search in frozen indices", + "description": "Will include frozen indices in results if enabled. Searching through frozen indices\n might increase the search time.", + "value": false, + "deprecation": { + "message": "This setting is deprecated and will be removed in Kibana 9.0.", + "docLinksKey": "kibanaSearchSettings" + }, + "category": [ + "search" + ] + }, + "histogram:barTarget": { + "name": "Target buckets", + "value": 50, + "description": "Attempt to generate around this many buckets when using \"auto\" interval in date and numeric histograms" + }, + "histogram:maxBars": { + "name": "Maximum buckets", + "value": 1000, + "description": "\n Limits the density of date and number histograms across Kibana\n for better performance using a test query. If the test query would too many buckets,\n the interval between buckets will be increased. This setting applies separately\n to each histogram aggregation, and does not apply to other types of aggregation.\n To find the maximum value of this setting, divide the Elasticsearch 'search.max_buckets'\n value by the maximum number of aggregations in each visualization.\n " + }, + "history:limit": { + "name": "History limit", + "value": 10, + "description": "In fields that have history (e.g. query inputs), show this many recent values" + }, + "timepicker:refreshIntervalDefaults": { + "name": "Time filter refresh interval", + "value": "{\n \"pause\": true,\n \"value\": 60000\n}", + "type": "json", + "description": "The timefilter's default refresh interval. The \"value\" needs to be specified in milliseconds.", + "requiresPageReload": true + }, + "timepicker:timeDefaults": { + "name": "Time filter defaults", + "value": "{\n \"from\": \"now-15m\",\n \"to\": \"now\"\n}", + "type": "json", + "description": "The timefilter selection to use when Kibana is started without one. Must be an object containing \"from\" and \"to\" (see accepted formats).", + "requiresPageReload": true + }, + "timepicker:quickRanges": { + "name": "Time filter quick ranges", + "value": "[\n {\n \"from\": \"now/d\",\n \"to\": \"now/d\",\n \"display\": \"Today\"\n },\n {\n \"from\": \"now/w\",\n \"to\": \"now/w\",\n \"display\": \"This week\"\n },\n {\n \"from\": \"now-15m\",\n \"to\": \"now\",\n \"display\": \"Last 15 minutes\"\n },\n {\n \"from\": \"now-30m\",\n \"to\": \"now\",\n \"display\": \"Last 30 minutes\"\n },\n {\n \"from\": \"now-1h\",\n \"to\": \"now\",\n \"display\": \"Last 1 hour\"\n },\n {\n \"from\": \"now-24h/h\",\n \"to\": \"now\",\n \"display\": \"Last 24 hours\"\n },\n {\n \"from\": \"now-7d/d\",\n \"to\": \"now\",\n \"display\": \"Last 7 days\"\n },\n {\n \"from\": \"now-30d/d\",\n \"to\": \"now\",\n \"display\": \"Last 30 days\"\n },\n {\n \"from\": \"now-90d/d\",\n \"to\": \"now\",\n \"display\": \"Last 90 days\"\n },\n {\n \"from\": \"now-1y/d\",\n \"to\": \"now\",\n \"display\": \"Last 1 year\"\n }\n]", + "type": "json", + "description": "The list of ranges to show in the Quick section of the time filter. This should be an array of objects, with each object containing \"from\", \"to\" (see accepted formats), and \"display\" (the title to be displayed)." + }, + "filters:pinnedByDefault": { + "name": "Pin filters by default", + "value": false, + "description": "Whether the filters should have a global state (be pinned) by default" + }, + "filterEditor:suggestValues": { + "name": "Filter editor suggest values", + "value": true, + "description": "Set this property to false to prevent the filter editor from suggesting values for fields." + }, + "autocomplete:valueSuggestionMethod": { + "name": "Autocomplete value suggestion method", + "type": "select", + "value": "terms_enum", + "description": "The method used for querying suggestions for values in KQL autocomplete. Select terms_enum to use the Elasticsearch terms enum API for improved autocomplete suggestion performance. (Note that terms_enum is incompatible with Document Level Security.) Select terms_agg to use an Elasticsearch terms aggregation. (Note that terms_agg is incompatible with IP-type fields.) Learn more.", + "options": [ + "terms_enum", + "terms_agg" + ], + "category": [ + "autocomplete" + ] + }, + "autocomplete:useTimeRange": { + "name": "Use time range", + "value": true, + "description": "Disable this property to get autocomplete suggestions from your full dataset, rather than from the current time range. Learn more.", + "category": [ + "autocomplete" + ] + }, + "search:timeout": { + "name": "Search Timeout", + "value": 600000, + "description": "Change the maximum timeout for a search session or set to 0 to disable the timeout and allow queries to run to completion.", + "type": "number", + "category": [ + "search" + ] + }, + "fileUpload:maxFileSize": { + "name": "Maximum file upload size", + "value": "100MB", + "description": "Sets the file size limit when importing files. The highest supported value for this setting is 1GB." + }, + "labs:dashboard:deferBelowFold": { + "name": "Defer loading panels below \"the fold\"", + "value": false, + "type": "boolean", + "description": "Any panels below \"the fold\"-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport", + "requiresPageReload": true, + "category": [ + "Presentation Labs" + ] + }, + "labs:dashboard:dashboardControls": { + "name": "Enable dashboard controls", + "value": true, + "type": "boolean", + "description": "Enables the controls system for dashboard, which allows dashboard authors to more easily build interactive elements for their users.", + "requiresPageReload": true, + "category": [ + "Presentation Labs" + ] + }, + "labs:canvas:byValueEmbeddable": { + "name": "By-Value Embeddables", + "value": true, + "type": "boolean", + "description": "Enables support for by-value embeddables in Canvas", + "requiresPageReload": true, + "category": [ + "Presentation Labs" + ] + }, + "visualization:heatmap:maxBuckets": { + "name": "Heatmap maximum buckets", + "value": 50, + "type": "number", + "description": "The maximum number of buckets a single datasource can return. A higher number might have negative impact on browser rendering performance", + "category": [ + "visualization" + ] + }, + "metrics:max_buckets": { + "name": "TSVB buckets limit", + "value": 2000, + "description": "Affects the TSVB histogram density. Must be set higher than \"histogram:maxBars\"." + }, + "metrics:allowStringIndices": { + "name": "Allow string indices in TSVB", + "value": false, + "requiresPageReload": true, + "description": "Enables you to query Elasticsearch indices in TSVB visualizations." + }, + "metrics:allowCheckingForFailedShards": { + "name": "Show TSVB request shard failures", + "value": true, + "description": "Show warning message for partial data in TSVB charts if the request succeeds for some shards but fails for others." + }, + "timelion:es.timefield": { + "name": "Time field", + "value": "@timestamp", + "description": "Default field containing a timestamp when using .es()", + "category": [ + "timelion" + ] + }, + "timelion:es.default_index": { + "name": "Default index", + "value": "_all", + "description": "Default elasticsearch index to search with .es()", + "category": [ + "timelion" + ] + }, + "timelion:target_buckets": { + "name": "Target buckets", + "value": 200, + "description": "The number of buckets to shoot for when using auto intervals", + "category": [ + "timelion" + ] + }, + "timelion:max_buckets": { + "name": "Maximum buckets", + "value": 2000, + "description": "The maximum number of buckets a single datasource can return", + "category": [ + "timelion" + ] + }, + "timelion:min_interval": { + "name": "Minimum interval", + "value": "1ms", + "description": "The smallest interval that will be calculated when using \"auto\"", + "category": [ + "timelion" + ] + }, + "visualization:visualize:legacyHeatmapChartsLibrary": { + "name": "Heatmap legacy charts library", + "requiresPageReload": true, + "value": false, + "description": "Enables legacy charts library for heatmap charts in visualize.", + "deprecation": { + "message": "The legacy charts library for heatmap in visualize is deprecated and will not be supported in a future version.", + "docLinksKey": "visualizationSettings" + }, + "category": [ + "visualization" + ] + }, + "labs:dashboard:enable_ui": { + "name": "Enable labs button in Dashboard", + "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Dashboard.", + "value": false, + "type": "boolean", + "category": [ + "Presentation Labs" + ], + "requiresPageReload": true + }, + "defaultColumns": { + "name": "Default columns", + "value": [], + "description": "Columns displayed by default in the Discover app. If empty, a summary of the document will be displayed.", + "category": [ + "discover" + ] + }, + "discover:maxDocFieldsDisplayed": { + "name": "Maximum document fields displayed", + "value": 200, + "description": "Maximum number of fields rendered in the document summary", + "category": [ + "discover" + ] + }, + "discover:sampleSize": { + "name": "Maximum rows per table", + "value": 500, + "description": "Sets the maximum number of rows for the entire document table.", + "category": [ + "discover" + ] + }, + "discover:sampleRowsPerPage": { + "name": "Rows per page", + "value": 100, + "options": [ + 10, + 25, + 50, + 100, + 250, + 500 + ], + "type": "select", + "description": "Limits the number of rows per page in the document table.", + "category": [ + "discover" + ] + }, + "discover:sort:defaultOrder": { + "name": "Default sort direction", + "value": "desc", + "options": [ + "desc", + "asc" + ], + "optionLabels": { + "desc": "Descending", + "asc": "Ascending" + }, + "type": "select", + "description": "Controls the default sort direction for time based data views in the Discover app.", + "category": [ + "discover" + ] + }, + "discover:searchOnPageLoad": { + "name": "Search on page load", + "value": true, + "type": "boolean", + "description": "Controls whether a search is executed when Discover first loads. This setting does not have an effect when loading a saved search.", + "category": [ + "discover" + ] + }, + "doc_table:hideTimeColumn": { + "name": "Hide 'Time' column", + "value": false, + "description": "Hide the 'Time' column in Discover and in all Saved Searches on Dashboards.", + "category": [ + "discover" + ] + }, + "fields:popularLimit": { + "name": "Popular fields limit", + "value": 10, + "description": "The top N most popular fields to show" + }, + "context:defaultSize": { + "name": "Context size", + "value": 5, + "description": "The number of surrounding entries to show in the context view", + "category": [ + "discover" + ] + }, + "context:step": { + "name": "Context size step", + "value": 5, + "description": "The step size to increment or decrement the context size by", + "category": [ + "discover" + ] + }, + "context:tieBreakerFields": { + "name": "Tie breaker fields", + "value": [ + "_doc" + ], + "description": "A comma-separated list of fields to use for tie-breaking between documents that have the same timestamp value. From this list the first field that is present and sortable in the current data view is used.", + "category": [ + "discover" + ] + }, + "doc_table:legacy": { + "name": "Document Explorer or classic view", + "value": false, + "description": "To use the new Document Explorer instead of the classic view, turn off this option. The Document Explorer offers better data sorting, resizable columns, and a full screen view.", + "requiresPageReload": true, + "category": [ + "discover" + ], + "metric": { + "type": "click", + "name": "discover:useLegacyDataGrid" + } + }, + "discover:modifyColumnsOnSwitch": { + "name": "Modify columns when changing data views", + "value": true, + "description": "Remove columns that are not available in the new data view.", + "category": [ + "discover" + ], + "metric": { + "type": "click", + "name": "discover:modifyColumnsOnSwitchTitle" + } + }, + "discover:searchFieldsFromSource": { + "name": "Read fields from _source", + "description": "When enabled will load documents directly from `_source`. This is soon going to be deprecated. When disabled, will retrieve fields via the new Fields API in the high-level search service.", + "value": false, + "category": [ + "discover" + ] + }, + "discover:showFieldStatistics": { + "name": "Show field statistics", + "description": "Enable the Field statistics view to show details such as the minimum and maximum values of a numeric field or a map of a geo field. This functionality is in beta and is subject to change.", + "value": true, + "category": [ + "discover" + ], + "metric": { + "type": "click", + "name": "discover:showFieldStatistics" + } + }, + "discover:showMultiFields": { + "name": "Show multi-fields", + "description": "Controls whether multi-fields display in the expanded document view. In most cases, multi-fields are the same as the original field. This option is only available when `searchFieldsFromSource` is off.", + "value": false, + "category": [ + "discover" + ] + }, + "discover:rowHeightOption": { + "name": "Row height in the Document Explorer", + "value": 3, + "category": [ + "discover" + ], + "description": "The number of lines to allow in a row. A value of -1 automatically adjusts the row height to fit the contents. A value of 0 displays the content in a single line." + }, + "truncate:maxHeight": { + "name": "Maximum cell height in the classic table", + "value": 115, + "category": [ + "discover" + ], + "description": "The maximum height that a cell in a table should occupy. Set to 0 to disable truncation.", + "requiresPageReload": true + }, + "discover:enableESQL": { + "name": "Enable ES|QL", + "value": true, + "description": "[technical preview] This tech preview feature is highly experimental--do not rely on this for production saved searches, visualizations or dashboards. This setting enables ES|QL in Discover. If you have feedback on this experience please reach out to us on discuss.elastic.co/c/elastic-stack/kibana", + "requiresPageReload": true, + "category": [ + "discover" + ] + }, + "xpackReporting:customPdfLogo": { + "name": "PDF footer image", + "value": null, + "description": "Custom image to use in the PDF's footer", + "sensitive": true, + "type": "image", + "category": [ + "reporting" + ] + }, + "labs:canvas:enable_ui": { + "name": "Enable labs button in Canvas", + "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Canvas.", + "value": false, + "type": "boolean", + "category": [ + "Presentation Labs" + ], + "requiresPageReload": true + }, + "rollups:enableIndexPatterns": { + "name": "Enable rollup data views", + "value": true, + "description": "Enable the creation of data views that capture rollup indices,\n which in turn enable visualizations based on rollup data.", + "category": [ + "rollups" + ], + "requiresPageReload": true + }, + "observability:enableInspectEsQueries": { + "category": [ + "observability" + ], + "name": "Inspect ES queries", + "value": false, + "description": "Inspect Elasticsearch queries in API responses.", + "requiresPageReload": true + }, + "observability:maxSuggestions": { + "category": [ + "observability" + ], + "name": "Maximum suggestions", + "value": 100, + "description": "Maximum number of suggestions fetched in autocomplete selection boxes." + }, + "observability:enableComparisonByDefault": { + "category": [ + "observability" + ], + "name": "Comparison feature", + "value": true, + "description": "Determines whether the comparison feature is enabled or disabled by default in the APM app." + }, + "observability:apmDefaultServiceEnvironment": { + "category": [ + "observability" + ], + "sensitive": true, + "name": "Default service environment", + "description": "Set the default environment for the APM app. When left empty, data from all environments will be displayed by default.", + "value": "" + }, + "observability:apmProgressiveLoading": { + "category": [ + "observability" + ], + "name": "Use progressive loading of selected APM views", + "description": "[technical preview] Whether to load data progressively for APM views. Data may be requested with a lower sampling rate first, with lower accuracy but faster response times, while the unsampled data loads in the background", + "value": "off", + "requiresPageReload": false, + "type": "select", + "options": [ + "off", + "low", + "medium", + "high" + ], + "optionLabels": { + "off": "Off", + "low": "Low sampling rate (fastest, least accurate)", + "medium": "Medium sampling rate", + "high": "High sampling rate (slower, most accurate)" + }, + "showInLabs": true + }, + "observability:apmServiceInventoryOptimizedSorting": { + "category": [ + "observability" + ], + "name": "Optimize services list load performance in APM", + "description": "[technical preview] Default APM Service Inventory and Storage Explorer pages sort (for Services without Machine Learning applied) to sort by Service Name.", + "value": false, + "requiresPageReload": false, + "type": "boolean", + "showInLabs": true + }, + "observability:apmServiceGroupMaxNumberOfServices": { + "category": [ + "observability" + ], + "name": "Maximum services in a service group", + "value": 500, + "description": "Limit the number of services in a given service group" + }, + "observability:apmTraceExplorerTab": { + "category": [ + "observability" + ], + "name": "APM Trace Explorer", + "description": "[technical preview] Enable the APM Trace Explorer feature, that allows you to search and inspect traces with KQL or EQL. Learn more.", + "value": true, + "requiresPageReload": true, + "type": "boolean", + "showInLabs": true + }, + "observability:apmLabsButton": { + "category": [ + "observability" + ], + "name": "Enable labs button in APM", + "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in APM.", + "value": false, + "requiresPageReload": true, + "type": "boolean" + }, + "observability:enableInfrastructureHostsView": { + "category": [ + "observability" + ], + "name": "Infrastructure Hosts view", + "value": true, + "description": "[beta] Enable the Hosts view in the Infrastructure app." + }, + "observability:enableAwsLambdaMetrics": { + "category": [ + "observability" + ], + "name": "AWS Lambda Metrics", + "description": "[technical preview] Display Amazon Lambda metrics in the service metrics tab.", + "value": true, + "requiresPageReload": true, + "type": "boolean", + "showInLabs": true + }, + "observability:apmAgentExplorerView": { + "category": [ + "observability" + ], + "name": "Agent explorer", + "description": "[beta] Enables Agent explorer view.", + "value": true, + "requiresPageReload": true, + "type": "boolean" + }, + "observability:apmAWSLambdaPriceFactor": { + "category": [ + "observability" + ], + "name": "AWS lambda price factor", + "type": "json", + "value": "{\n \"x86_64\": 0.0000166667,\n \"arm\": 0.0000133334\n}", + "description": "Price per Gb-second." + }, + "observability:apmAWSLambdaRequestCostPerMillion": { + "category": [ + "observability" + ], + "name": "AWS lambda price per 1M requests", + "value": 0.2 + }, + "observability:apmEnableServiceMetrics": { + "category": [ + "observability" + ], + "name": "Service transaction metrics", + "value": true, + "description": "[beta] Enables the usage of service transaction metrics, which are low cardinality metrics that can be used by certain views like the service inventory for faster loading times.", + "requiresPageReload": true + }, + "observability:apmEnableContinuousRollups": { + "category": [ + "observability" + ], + "name": "Continuous rollups", + "value": true, + "description": "[beta] When continuous rollups is enabled, the UI will select metrics with the appropriate resolution. On larger time ranges, lower resolution metrics will be used, which will improve loading times.", + "requiresPageReload": true + }, + "observability:apmEnableCriticalPath": { + "category": [ + "observability" + ], + "name": "Critical path", + "description": "[technical preview] Optionally display the critical path of a trace.", + "value": false, + "requiresPageReload": true, + "type": "boolean", + "showInLabs": true + }, + "observability:syntheticsThrottlingEnabled": { + "category": [ + "observability" + ], + "name": "Enable Synthetics throttling (Experimental)", + "value": false, + "description": "Enable the throttling setting in Synthetics monitor configurations. Note that throttling may still not be available for your monitors even if the setting is active. Intended for internal use only. read notice here.", + "requiresPageReload": true + }, + "observability:enableLegacyUptimeApp": { + "category": [ + "observability" + ], + "name": "Always show legacy Uptime app", + "value": false, + "description": "By default, the legacy Uptime app is hidden from the interface when it doesn't have any data for more than a week. Enable this option to always show it.", + "requiresPageReload": true + }, + "observability:apmEnableProfilingIntegration": { + "category": [ + "observability" + ], + "name": "Enable Universal Profiling integration in APM", + "value": false, + "requiresPageReload": false + }, + "ml:anomalyDetection:results:enableTimeDefaults": { + "name": "Enable time filter defaults for anomaly detection results", + "value": false, + "description": "Use the default time filter in the Single Metric Viewer and Anomaly Explorer. If not enabled, the results for the full time range of the job are displayed.", + "category": [ + "machineLearning" + ] + }, + "ml:anomalyDetection:results:timeDefaults": { + "name": "Time filter defaults for anomaly detection results", + "type": "json", + "value": "{\n \"from\": \"now-15m\",\n \"to\": \"now\"\n}", + "description": "The time filter selection to use when viewing anomaly detection job results.", + "requiresPageReload": true, + "category": [ + "machineLearning" + ] + }, + "securitySolution:refreshIntervalDefaults": { + "type": "json", + "name": "Time filter refresh interval", + "value": "{\n \"pause\": true,\n \"value\": 300000\n}", + "description": "

Default refresh interval for the Security time filter, in milliseconds.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 0 + }, + "securitySolution:timeDefaults": { + "type": "json", + "name": "Time filter period", + "value": "{\n \"from\": \"now/d\",\n \"to\": \"now/d\"\n}", + "description": "

Default period of time in the Security time filter.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 1 + }, + "securitySolution:defaultIndex": { + "name": "Elasticsearch indices", + "sensitive": true, + "value": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "traces-apm*", + "winlogbeat-*", + "-*elastic-cloud-logs-*" + ], + "description": "

Comma-delimited list of Elasticsearch indices from which the Security app collects events.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 2 + }, + "securitySolution:defaultThreatIndex": { + "name": "Threat indices", + "sensitive": true, + "value": [ + "logs-ti_*" + ], + "description": "

Comma-delimited list of Threat Intelligence indices from which the Security app collects indicators.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 3 + }, + "securitySolution:defaultAnomalyScore": { + "name": "Anomaly threshold", + "value": 50, + "type": "number", + "description": "

Value above which Machine Learning job anomalies are displayed in the Security app.

Valid values: 0 to 100.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 4 + }, + "securitySolution:enableNewsFeed": { + "name": "News feed", + "value": true, + "description": "

Enables the News feed

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 5 + }, + "securitySolution:enableExpandableFlyout": { + "name": "Expandable flyout", + "value": true, + "description": "

Enables the expandable flyout

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 6 + }, + "securitySolution:rulesTableRefresh": { + "name": "Rules auto refresh", + "description": "

Enables auto refresh on the rules and monitoring tables, in milliseconds

", + "type": "json", + "value": "{\n \"on\": true,\n \"value\": 60000\n}", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 7 + }, + "securitySolution:newsFeedUrl": { + "name": "News feed URL", + "value": "https://feeds.elastic.co/security-solution", + "sensitive": true, + "description": "

News feed content will be retrieved from this URL

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 8 + }, + "securitySolution:ipReputationLinks": { + "name": "IP Reputation Links", + "value": "[\n { \"name\": \"virustotal.com\", \"url_template\": \"https://www.virustotal.com/gui/search/{{ip}}\" },\n { \"name\": \"talosIntelligence.com\", \"url_template\": \"https://talosintelligence.com/reputation_center/lookup?search={{ip}}\" }\n]", + "type": "json", + "description": "Array of URL templates to build the list of reputation URLs to be displayed on the IP Details page.", + "sensitive": true, + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 9 + }, + "securitySolution:enableCcsWarning": { + "name": "CCS Rule Privileges Warning", + "value": true, + "description": "

Enables privilege check warnings in rules for CCS indices

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": false, + "order": 10 + }, + "securitySolution:showRelatedIntegrations": { + "name": "Related integrations", + "value": true, + "description": "

Shows related integrations on the rules and monitoring tables

", + "type": "boolean", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 11 + }, + "securitySolution:alertTags": { + "name": "Alert tagging options", + "sensitive": true, + "value": [ + "Duplicate", + "False Positive", + "Further investigation required" + ], + "description": "

List of tag options for use with alerts generated by Security Solution rules.

", + "category": [ + "securitySolution" + ], + "requiresPageReload": true, + "order": 12 + }, + "visualization:visualize:legacyGaugeChartsLibrary": { + "name": "Gauge legacy charts library", + "requiresPageReload": true, + "value": true, + "description": "Enables legacy charts library for gauge charts in visualize.", + "category": [ + "visualization" + ] + }, + "isDefaultIndexMigrated": { + "userValue": true + } +} \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index 04071f12af018..2209659985e39 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -960,6 +960,8 @@ "@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"], "@kbn/management-settings-components-field-row/*": ["packages/kbn-management/settings/components/field_row/*"], + "@kbn/management-settings-components-form": ["packages/kbn-management/settings/components/form"], + "@kbn/management-settings-components-form/*": ["packages/kbn-management/settings/components/form/*"], "@kbn/management-settings-field-definition": ["packages/kbn-management/settings/field_definition"], "@kbn/management-settings-field-definition/*": ["packages/kbn-management/settings/field_definition/*"], "@kbn/management-settings-ids": ["packages/kbn-management/settings/setting_ids"], diff --git a/yarn.lock b/yarn.lock index ed2d78a553642..5cb486026704d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4869,6 +4869,10 @@ version "0.0.0" uid "" +"@kbn/management-settings-components-form@link:packages/kbn-management/settings/components/form": + version "0.0.0" + uid "" + "@kbn/management-settings-field-definition@link:packages/kbn-management/settings/field_definition": version "0.0.0" uid "" From 1aa158477e8cea90b8be12d73c110046f7170db7 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Mon, 18 Sep 2023 13:00:10 +0100 Subject: [PATCH 05/26] Address feedback --- .../settings/components/form/README.mdx | 14 ----- .../components/form/bottom_bar/index.tsx | 9 ++- .../settings/components/form/form.styles.ts | 20 +++++++ .../settings/components/form/form.tsx | 8 ++- .../settings/components/form/index.ts | 2 + .../settings/components/form/services.tsx | 55 ++++++++++++++++--- .../form/storybook/form.stories.tsx | 2 + .../form/storybook/get_form_story.tsx | 9 +-- .../settings/components/form/types.ts | 43 +++++++++++++++ 9 files changed, 127 insertions(+), 35 deletions(-) create mode 100644 packages/kbn-management/settings/components/form/form.styles.ts create mode 100644 packages/kbn-management/settings/components/form/types.ts diff --git a/packages/kbn-management/settings/components/form/README.mdx b/packages/kbn-management/settings/components/form/README.mdx index cb6718e323d9a..990702a4cc6fd 100644 --- a/packages/kbn-management/settings/components/form/README.mdx +++ b/packages/kbn-management/settings/components/form/README.mdx @@ -10,17 +10,3 @@ date: 2023-09-12 ## Description This package contains a component for rendering and manipulating the form containing the setting fields in the Advanced Settings UI. - - -## Usage - -```tsx -const saveConfig = async (changes: Record>) => { - const arr = Object.entries(changes).map(([key, value]) => { - settingsStart.client.set(key, value.unsavedValue); - }); - return Promise.all(arr); -}; - -return ; -``` \ No newline at end of file diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx index 9932b2a4a93eb..84c5198154fa2 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { EuiBottomBar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useFormStyles } from '../form.styles'; interface BottomBarProps { saveAll: () => void; @@ -17,6 +18,8 @@ interface BottomBarProps { } export const BottomBar = ({ saveAll, clearAllUnsaved }: BottomBarProps) => { + const { cssFormButton } = useFormStyles(); + return ( { aria-describedby="aria-describedby.countOfUnsavedSettings" data-test-subj="advancedSetting-cancelButton" > - {i18n.translate('advancedSettings.form.cancelButtonLabel', { + {i18n.translate('management.settings.form.cancelButtonLabel', { defaultMessage: 'Cancel changes', })}
{ aria-describedby="aria-describedby.countOfUnsavedSettings" data-test-subj="advancedSetting-saveButton" > - {i18n.translate('advancedSettings.form.saveButtonLabel', { + {i18n.translate('management.settings.form.saveButtonLabel', { defaultMessage: 'Save changes', })} diff --git a/packages/kbn-management/settings/components/form/form.styles.ts b/packages/kbn-management/settings/components/form/form.styles.ts new file mode 100644 index 0000000000000..99290d033715e --- /dev/null +++ b/packages/kbn-management/settings/components/form/form.styles.ts @@ -0,0 +1,20 @@ +/* + * 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 { css } from '@emotion/react'; + +/** + * A React hook that provides stateful `css` classes for the {@link Form} component. + */ +export const useFormStyles = () => { + return { + cssFormButton: css` + width: 100%; + `, + }; +}; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index aefe861809891..58355f840b19c 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -13,19 +13,21 @@ import { FieldRow, OnChangeFn } from '@kbn/management-settings-components-field- import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; import { BottomBar } from './bottom_bar'; +import { useServices } from './services'; export interface FormProps { fields: Array>; - save: (changes: Record>) => void; } export const Form = (props: FormProps) => { - const { fields, save } = props; + const { fields } = props; const [unsavedChanges, setUnsavedChanges] = React.useState< Record> >({}); + const { saveChanges } = useServices(); + const onChange: OnChangeFn = (id, change) => { if (!change?.unsavedValue) { const { [id]: unsavedChange, ...rest } = unsavedChanges; @@ -46,7 +48,7 @@ export const Form = (props: FormProps) => { if (isEmpty(unsavedChanges)) { return; } - await save(unsavedChanges); + await saveChanges(unsavedChanges); }; const fieldRows = fields.map((field) => { diff --git a/packages/kbn-management/settings/components/form/index.ts b/packages/kbn-management/settings/components/form/index.ts index 5d218b01b0eaa..a763a5dd3d505 100644 --- a/packages/kbn-management/settings/components/form/index.ts +++ b/packages/kbn-management/settings/components/form/index.ts @@ -7,3 +7,5 @@ */ export { Form } from './form'; + +export type { FormKibanaDependencies, FormServices } from './types'; diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index 4ff2c5e0ebb55..cb7abc0adfa17 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -9,24 +9,65 @@ import { FieldRowProvider, FieldRowKibanaProvider, - type FieldRowKibanaDependencies, - type FieldRowServices, } from '@kbn/management-settings-components-field-row'; -import React, { FC } from 'react'; +import React, { FC, useContext } from 'react'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; -export type FormServices = FieldRowServices; -export type FormKibanaDependencies = FieldRowKibanaDependencies; +import type { FormServices, FormKibanaDependencies, Services } from './types'; + +const FormContext = React.createContext(null); /** * React Provider that provides services to a {@link Form} component and its dependents. */ export const FormProvider: FC = ({ children, ...services }) => { + const { saveChanges, showError, ...rest } = services; + + return ( + + {children} + + ); return {children}; }; /** * Kibana-specific Provider that maps Kibana plugins and services to a {@link FormProvider}. */ -export const FormKibanaProvider: FC = ({ children, ...services }) => { - return {children}; +export const FormKibanaProvider: FC = ({ children, ...deps }) => { + const { settings, ...rest } = deps; + + return ( + >) => { + const arr = Object.entries(changes).map(([key, value]) => + settings.client.set(key, value) + ); + return Promise.all(arr); + }, + // TODO: + showError: (message: string) => {}, + }} + > + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + * + * @see {@link FormServices} + */ +export const useServices = () => { + const context = useContext(FormContext); + + if (!context) { + throw new Error( + 'FormContext is missing. Ensure your component or React root is wrapped with FormProvider.' + ); + } + + return context; }; 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 839e2cb278c02..dfa2c3a6ae362 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -22,6 +22,8 @@ export default { diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index 161b8f8f0d568..5228320c78f83 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -11,7 +11,6 @@ import React from 'react'; import { normalizeSettings } from '@kbn/management-settings-utilities'; import { FieldDefinition, SettingType, UiSetting } from '@kbn/management-settings-types'; import { getFieldDefinition } from '@kbn/management-settings-field-definition'; -import { action } from '@storybook/addon-actions'; import { Form } from '../form'; import _settings from './settings.json'; @@ -28,13 +27,7 @@ export const getFormStory = () => { }) ); - const save = async () => { - alert('Saved!'); - // Not working: - action('Saved'); - }; - - return ; + return ; }; return Story; diff --git a/packages/kbn-management/settings/components/form/types.ts b/packages/kbn-management/settings/components/form/types.ts new file mode 100644 index 0000000000000..fc4b0d3775428 --- /dev/null +++ b/packages/kbn-management/settings/components/form/types.ts @@ -0,0 +1,43 @@ +/* + * 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 { + FieldRowKibanaDependencies, + FieldRowServices, +} from '@kbn/management-settings-components-field-row'; +import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; +import { SettingsStart } from '@kbn/core-ui-settings-browser'; + +/** + * Contextual services used by a {@link Form} component. + */ +export interface Services { + saveChanges: (changes: Record>) => void; + showError: (message: string) => void; +} + +/** + * Contextual services used by a {@link Form} component and its dependents. + */ +export type FormServices = FieldRowServices & Services; + +/** + * An interface containing a collection of Kibana plugins and services required to + * render a {@link Form} component. + */ +export interface KibanaDependencies { + settings: { + client: SettingsStart['client']; + }; +} + +/** + * An interface containing a collection of Kibana plugins and services required to + * render a {@link Form} component and its dependents. + */ +export type FormKibanaDependencies = KibanaDependencies & FieldRowKibanaDependencies; From 012e68b617ef194441de5db576517d48e94b1e32 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Mon, 18 Sep 2023 17:16:21 +0100 Subject: [PATCH 06/26] Add remaining form functionalities --- .../settings/components/field_input/types.ts | 2 +- .../components/form/bottom_bar/bottom_bar.tsx | 97 +++++++++++++++++++ .../components/form/bottom_bar/index.tsx | 58 +---------- .../form/bottom_bar/unsaved_count.tsx | 37 +++++++ .../settings/components/form/form.styles.ts | 9 ++ .../settings/components/form/form.tsx | 55 ++++++++--- .../components/form/reload_page_toast.tsx | 44 +++++++++ .../settings/components/form/services.tsx | 13 +-- .../form/storybook/form.stories.tsx | 1 + .../form/storybook/get_form_story.tsx | 4 +- .../settings/components/form/types.ts | 5 + 11 files changed, 245 insertions(+), 80 deletions(-) create mode 100644 packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx create mode 100644 packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx create mode 100644 packages/kbn-management/settings/components/form/reload_page_toast.tsx diff --git a/packages/kbn-management/settings/components/field_input/types.ts b/packages/kbn-management/settings/components/field_input/types.ts index e5d5c11e2f199..cbb5db35b2d1a 100644 --- a/packages/kbn-management/settings/components/field_input/types.ts +++ b/packages/kbn-management/settings/components/field_input/types.ts @@ -31,7 +31,7 @@ export interface FieldInputServices { */ export interface FieldInputKibanaDependencies { /** The portion of the {@link ToastsStart} contract used by this component. */ - toasts: Pick; + toasts: Pick; } /** diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx new file mode 100644 index 0000000000000..0ff4648467cc2 --- /dev/null +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx @@ -0,0 +1,97 @@ +/* + * 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 { + EuiBottomBar, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { UnsavedCount } from './unsaved_count'; +import { useFormStyles } from '../form.styles'; + +interface BottomBarProps { + saveAll: () => void; + clearAllUnsaved: () => void; + hasInvalidChanges: boolean; + isLoading: boolean; + unsavedChangesCount: number; +} + +export const BottomBar = ({ + saveAll, + clearAllUnsaved, + hasInvalidChanges, + isLoading, + unsavedChangesCount, +}: BottomBarProps) => { + const { cssFormButton, cssFormUnsavedCount } = useFormStyles(); + + return ( + + + + + + + + + {i18n.translate('management.settings.form.cancelButtonLabel', { + defaultMessage: 'Cancel changes', + })} + + + + + + {i18n.translate('management.settings.form.saveButtonLabel', { + defaultMessage: 'Save changes', + })} + + + + + + ); +}; diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx index 84c5198154fa2..0abbe24520157 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -6,60 +6,4 @@ * Side Public License, v 1. */ -import React from 'react'; - -import { EuiBottomBar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useFormStyles } from '../form.styles'; - -interface BottomBarProps { - saveAll: () => void; - clearAllUnsaved: () => void; -} - -export const BottomBar = ({ saveAll, clearAllUnsaved }: BottomBarProps) => { - const { cssFormButton } = useFormStyles(); - - return ( - - - - - - {i18n.translate('management.settings.form.cancelButtonLabel', { - defaultMessage: 'Cancel changes', - })} - - - - - {i18n.translate('management.settings.form.saveButtonLabel', { - defaultMessage: 'Save changes', - })} - - - - - ); -}; +export { BottomBar } from './bottom_bar'; diff --git a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx new file mode 100644 index 0000000000000..bdd5404076fe7 --- /dev/null +++ b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx @@ -0,0 +1,37 @@ +/* + * 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 { EuiTextColor } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useFormStyles } from '../form.styles'; + +interface UnsavedCountProps { + unsavedCount: number; +} + +export const UnsavedCount = ({ unsavedCount }: UnsavedCountProps) => { + const { cssFormUnsavedCountMessage } = useFormStyles(); + return ( +

+ + + +

+ ); +}; diff --git a/packages/kbn-management/settings/components/form/form.styles.ts b/packages/kbn-management/settings/components/form/form.styles.ts index 99290d033715e..6611e9178cea0 100644 --- a/packages/kbn-management/settings/components/form/form.styles.ts +++ b/packages/kbn-management/settings/components/form/form.styles.ts @@ -16,5 +16,14 @@ export const useFormStyles = () => { cssFormButton: css` width: 100%; `, + cssFormUnsavedCount: css` + @include euiBreakpoint('xs') { + display: none; + } + `, + cssFormUnsavedCountMessage: css` + box-shadow: -$euiSizeXS 0 $euiColorWarning; + padding-left: $euiSizeS; + `, }; }; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index 58355f840b19c..540bd25cf9124 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -12,43 +12,64 @@ import type { FieldDefinition } from '@kbn/management-settings-types'; import { FieldRow, OnChangeFn } from '@kbn/management-settings-components-field-row'; import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n/target/types'; import { BottomBar } from './bottom_bar'; import { useServices } from './services'; export interface FormProps { fields: Array>; + isSavingEnabled: boolean; } export const Form = (props: FormProps) => { - const { fields } = props; + const { fields, isSavingEnabled } = props; + const { saveChanges, showError, showReloadPagePrompt } = useServices(); const [unsavedChanges, setUnsavedChanges] = React.useState< Record> >({}); - const { saveChanges } = useServices(); + const [isLoading, setIsLoading] = React.useState(false); - const onChange: OnChangeFn = (id, change) => { - if (!change?.unsavedValue) { - const { [id]: unsavedChange, ...rest } = unsavedChanges; - setUnsavedChanges(rest); + const unsavedChangesCount = Object.keys(unsavedChanges).length; + const hasInvalidChanges = Object.values(unsavedChanges).some(({ isInvalid }) => isInvalid); + + const saveAll = async () => { + setIsLoading(true); + if (isEmpty(unsavedChanges)) { return; } - - setUnsavedChanges((changes) => ({ ...changes, [id]: change })); + try { + await saveChanges(unsavedChanges); + clearAllUnsaved(); + const requiresReload = fields.some( + (setting) => unsavedChanges.hasOwnProperty(setting.id) && setting.requiresPageReload + ); + if (requiresReload) { + showReloadPagePrompt(); + } + } catch (e) { + showError( + i18n.translate('management.settings.form.saveErrorMessage', { + defaultMessage: 'Unable to save', + }) + ); + } + setIsLoading(false); }; - const isSavingEnabled = true; - const clearAllUnsaved = () => { setUnsavedChanges({}); }; - const saveAll = async () => { - if (isEmpty(unsavedChanges)) { + const onChange: OnChangeFn = (id, change) => { + if (!change?.unsavedValue) { + const { [id]: unsavedChange, ...rest } = unsavedChanges; + setUnsavedChanges(rest); return; } - await saveChanges(unsavedChanges); + + setUnsavedChanges((changes) => ({ ...changes, [id]: change })); }; const fieldRows = fields.map((field) => { @@ -61,7 +82,13 @@ export const Form = (props: FormProps) => {
{fieldRows}
{!isEmpty(unsavedChanges) && ( - + )}
); diff --git a/packages/kbn-management/settings/components/form/reload_page_toast.tsx b/packages/kbn-management/settings/components/form/reload_page_toast.tsx new file mode 100644 index 0000000000000..e082f9ac2520b --- /dev/null +++ b/packages/kbn-management/settings/components/form/reload_page_toast.tsx @@ -0,0 +1,44 @@ +/* + * 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 { i18n } from '@kbn/i18n/target/types'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ToastInput } from '@kbn/core-notifications-browser/target/types'; +import { I18nStart } from '@kbn/core-i18n-browser'; +import { ThemeServiceStart } from '@kbn/core-theme-browser'; + +export const ReloadPageToast = (theme: ThemeServiceStart, i18nStart: I18nStart): ToastInput => { + return { + title: i18n.translate('management.settings.form.requiresPageReloadToastDescription', { + defaultMessage: 'One or more settings require you to reload the page to take effect.', + }), + text: toMountPoint( + + + + window.location.reload()} + data-test-subj="windowReloadButton" + > + {i18n.translate('management.settings.form.requiresPageReloadToastButtonLabel', { + defaultMessage: 'Reload page', + })} + + + + , + { i18n: i18nStart, theme } + ), + color: 'success', + }; +}; diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index cb7abc0adfa17..401da46f58897 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -14,6 +14,7 @@ import React, { FC, useContext } from 'react'; import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import type { FormServices, FormKibanaDependencies, Services } from './types'; +import { ReloadPageToast } from './reload_page_toast'; const FormContext = React.createContext(null); @@ -21,10 +22,10 @@ const FormContext = React.createContext(null); * React Provider that provides services to a {@link Form} component and its dependents. */ export const FormProvider: FC = ({ children, ...services }) => { - const { saveChanges, showError, ...rest } = services; + const { saveChanges, showError, showReloadPagePrompt, ...rest } = services; return ( - + {children} ); @@ -35,7 +36,7 @@ export const FormProvider: FC = ({ children, ...services }) => { * Kibana-specific Provider that maps Kibana plugins and services to a {@link FormProvider}. */ export const FormKibanaProvider: FC = ({ children, ...deps }) => { - const { settings, ...rest } = deps; + const { settings, toasts, docLinks, theme, i18nStart } = deps; return ( = ({ children, ...de ); return Promise.all(arr); }, - // TODO: - showError: (message: string) => {}, + showError: (message: string) => toasts.addDanger(message), + 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 dfa2c3a6ae362..7c6a21c7da986 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -24,6 +24,7 @@ export default { links={{ deprecationKey: 'link/to/deprecation/docs' }} saveChanges={action('saveChanges')} showError={action('showError')} + showReloadPagePrompt={action('showReloadPagePrompt')} > diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index 5228320c78f83..859f1350c4830 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -23,11 +23,11 @@ export const getFormStory = () => { getFieldDefinition({ id, setting, - params: { isCustom: false, isOverridden: setting.isOverridden }, }) ); + const isSavingEnabled = true; - return ; + return ; }; return Story; diff --git a/packages/kbn-management/settings/components/form/types.ts b/packages/kbn-management/settings/components/form/types.ts index fc4b0d3775428..4d4f400d4a0fe 100644 --- a/packages/kbn-management/settings/components/form/types.ts +++ b/packages/kbn-management/settings/components/form/types.ts @@ -12,6 +12,8 @@ import type { } from '@kbn/management-settings-components-field-row'; import { SettingType, 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'; /** * Contextual services used by a {@link Form} component. @@ -19,6 +21,7 @@ import { SettingsStart } from '@kbn/core-ui-settings-browser'; export interface Services { saveChanges: (changes: Record>) => void; showError: (message: string) => void; + showReloadPagePrompt: () => void; } /** @@ -34,6 +37,8 @@ export interface KibanaDependencies { settings: { client: SettingsStart['client']; }; + theme: ThemeServiceStart; + i18nStart: I18nStart; } /** From bdce1a5500aa3324ca4fc887a0f054ed198e5407 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Tue, 19 Sep 2023 18:59:31 +0100 Subject: [PATCH 07/26] Refactor changes, add initial tests --- .../form/bottom_bar/bottom_bar.test.tsx | 76 ++++++++++ .../components/form/bottom_bar/bottom_bar.tsx | 13 +- .../form/bottom_bar/unsaved_count.tsx | 2 +- .../settings/components/form/form.test.tsx | 135 ++++++++++++++++++ .../settings/components/form/form.tsx | 8 +- .../components/form/mocks/context.tsx | 56 ++++++++ .../settings/components/form/mocks/index.ts | 9 ++ .../settings/components/form/tsconfig.json | 30 ++++ 8 files changed, 318 insertions(+), 11 deletions(-) create mode 100644 packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx create mode 100644 packages/kbn-management/settings/components/form/form.test.tsx create mode 100644 packages/kbn-management/settings/components/form/mocks/context.tsx create mode 100644 packages/kbn-management/settings/components/form/mocks/index.ts create mode 100644 packages/kbn-management/settings/components/form/tsconfig.json diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx new file mode 100644 index 0000000000000..0239947b86773 --- /dev/null +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx @@ -0,0 +1,76 @@ +/* + * 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 { fireEvent, render } from '@testing-library/react'; +import { + BottomBar, + BottomBarProps, + DATA_TEST_SUBJ_SAVE_BUTTON, + DATA_TEST_SUBJ_CANCEL_BUTTON, +} from './bottom_bar'; +import { wrap } from '../mocks'; + +const saveAll = jest.fn(); +const clearAllUnsaved = jest.fn(); + +const defaultProps: BottomBarProps = { + saveAll, + clearAllUnsaved, + hasInvalidChanges: false, + unsavedChangesCount: 3, + isLoading: false, +}; + +describe('BottomBar', () => { + it('renders without errors', () => { + const { container } = render(wrap()); + expect(container).toBeInTheDocument(); + }); + + it('fires saveAll when the Save button is clicked', () => { + const { getByTestId } = render(wrap()); + + const input = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); + fireEvent.click(input); + expect(saveAll).toHaveBeenCalled(); + }); + + it('fires clearAllUnsaved when the Cancel button is clicked', () => { + const { getByTestId } = render(wrap()); + + const input = getByTestId(DATA_TEST_SUBJ_CANCEL_BUTTON); + fireEvent.click(input); + expect(saveAll).toHaveBeenCalled(); + }); + + // TODO: fix this + it.skip('renders unsaved changes count', () => { + // const { getByTestId } = render(wrap()); + + // const input = getByTestId(DATA_TEST_SUBJ_UNSAVED_COUNT); + // expect(input).toBe('3 unsaved settings'); + }); + + it('save button is disabled when there are invalid changes', () => { + const { getByTestId } = render( + wrap() + ); + + const input = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); + expect(input).toBeDisabled(); + }); + + // TODO: fix this + it.skip('save button is loading when in loading state', () => { + const { getByTestId } = render(wrap()); + + const input = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); + expect(input).toBeDisabled(); + }); +}); diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx index 0ff4648467cc2..28d77d853f8b8 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx @@ -20,7 +20,10 @@ import { i18n } from '@kbn/i18n'; import { UnsavedCount } from './unsaved_count'; import { useFormStyles } from '../form.styles'; -interface BottomBarProps { +export const DATA_TEST_SUBJ_SAVE_BUTTON = 'settings-save-button'; +export const DATA_TEST_SUBJ_CANCEL_BUTTON = 'settings-cancel-button'; + +export interface BottomBarProps { saveAll: () => void; clearAllUnsaved: () => void; hasInvalidChanges: boolean; @@ -56,8 +59,7 @@ export const BottomBar = ({ size="s" iconType="cross" onClick={clearAllUnsaved} - aria-describedby="aria-describedby.countOfUnsavedSettings" - data-test-subj="advancedSetting-cancelButton" + data-test-subj={DATA_TEST_SUBJ_CANCEL_BUTTON} > {i18n.translate('management.settings.form.cancelButtonLabel', { defaultMessage: 'Cancel changes', @@ -68,7 +70,7 @@ export const BottomBar = ({ {i18n.translate('management.settings.form.saveButtonLabel', { defaultMessage: 'Save changes', diff --git a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx index bdd5404076fe7..fb664a1dc1c42 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx @@ -22,7 +22,7 @@ export const UnsavedCount = ({ unsavedCount }: UnsavedCountProps) => {

; +}; + +const settings: Omit = { + array: { + description: 'Description for Array test setting', + name: 'array:test:setting', + type: 'array', + userValue: null, + value: defaultValues.array, + ...defaults, + }, + boolean: { + description: 'Description for Boolean test setting', + name: 'boolean:test:setting', + type: 'boolean', + userValue: null, + value: defaultValues.boolean, + ...defaults, + }, + color: { + description: 'Description for Color test setting', + name: 'color:test:setting', + type: 'color', + userValue: null, + value: defaultValues.color, + ...defaults, + }, + image: { + description: 'Description for Image test setting', + name: 'image:test:setting', + type: 'image', + userValue: null, + value: defaultValues.image, + ...defaults, + }, + number: { + description: 'Description for Number test setting', + name: 'number:test:setting', + type: 'number', + userValue: null, + value: defaultValues.number, + ...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: defaultValues.select, + ...defaults, + }, + string: { + description: 'Description for String test setting', + name: 'string:test:setting', + type: 'string', + userValue: null, + value: defaultValues.string, + ...defaults, + }, + undefined: { + description: 'Description for Undefined test setting', + name: 'undefined:test:setting', + type: 'undefined', + userValue: null, + value: defaultValues.undefined, + ...defaults, + }, +}; + +const fields: Array> = Object.entries(settings).map(([id, setting]) => + getFieldDefinition({ + id, + setting, + params: { isCustom: false, isOverridden: setting.isOverridden }, + }) +); + +describe('Form', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders without errors', () => { + const { container } = render(wrap()); + + expect(container).toBeInTheDocument(); + }); + + // TODO: Add more test cases +}); diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index 540bd25cf9124..a8c26e71c46e1 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -9,12 +9,12 @@ import React, { Fragment } from 'react'; import type { FieldDefinition } from '@kbn/management-settings-types'; -import { FieldRow, OnChangeFn } from '@kbn/management-settings-components-field-row'; +import { FieldRow, RowOnChangeFn } from '@kbn/management-settings-components-field-row'; import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import { isEmpty } from 'lodash'; -import { i18n } from '@kbn/i18n/target/types'; import { BottomBar } from './bottom_bar'; import { useServices } from './services'; +import { i18n } from '@kbn/i18n'; export interface FormProps { fields: Array>; @@ -62,8 +62,8 @@ export const Form = (props: FormProps) => { setUnsavedChanges({}); }; - const onChange: OnChangeFn = (id, change) => { - if (!change?.unsavedValue) { + const onChange: RowOnChangeFn = (id, change) => { + if (!change) { const { [id]: unsavedChange, ...rest } = unsavedChanges; setUnsavedChanges(rest); return; diff --git a/packages/kbn-management/settings/components/form/mocks/context.tsx b/packages/kbn-management/settings/components/form/mocks/context.tsx new file mode 100644 index 0000000000000..11024930612ea --- /dev/null +++ b/packages/kbn-management/settings/components/form/mocks/context.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, { ReactChild } from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; + +import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root'; +import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { I18nStart } from '@kbn/core-i18n-browser'; + +import { createFieldRowServicesMock } from '@kbn/management-settings-components-field-row/mocks'; +import type { FieldRowServices } from '@kbn/management-settings-components-field-row'; +import { FormProvider } from '../services'; +import type { FormServices } from '../types'; + +const createRootMock = () => { + const i18n: I18nStart = { + Context: ({ children }) => {children}, + }; + const theme = themeServiceMock.createStartContract(); + return { + i18n, + theme, + }; +}; + +export const createFormServicesMock = (): FormServices => ({ + ...createFieldRowServicesMock(), + saveChanges: jest.fn(), + showError: jest.fn(), + showReloadPagePrompt: jest.fn(), +}); + +export const TestWrapper = ({ + children, + services = createFormServicesMock(), +}: { + children: ReactChild; + services?: FormServices; +}) => { + return ( + + {children} + + ); +}; + +export const wrap = ( + component: JSX.Element, + services: FieldRowServices = createFieldRowServicesMock() +) => {component}; diff --git a/packages/kbn-management/settings/components/form/mocks/index.ts b/packages/kbn-management/settings/components/form/mocks/index.ts new file mode 100644 index 0000000000000..3fc27f74446b1 --- /dev/null +++ b/packages/kbn-management/settings/components/form/mocks/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { TestWrapper, createFormServicesMock, wrap } from './context'; diff --git a/packages/kbn-management/settings/components/form/tsconfig.json b/packages/kbn-management/settings/components/form/tsconfig.json new file mode 100644 index 0000000000000..73de070f9b5a1 --- /dev/null +++ b/packages/kbn-management/settings/components/form/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/management-settings-types", + "@kbn/management-settings-field-definition", + "@kbn/i18n", + "@kbn/i18n-react", + "@kbn/management-settings-utilities", + "@kbn/management-settings-components-field-row", + "@kbn/core-doc-links-browser", + "@kbn/react-kibana-context-root", + "@kbn/core-theme-browser-mocks", + "@kbn/core-i18n-browser", + ] +} From 5ffebd2ed1b83c4b0c5362c0a53ed06455cc9b17 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:22:43 +0000 Subject: [PATCH 08/26] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../settings/components/form/bottom_bar/bottom_bar.test.tsx | 1 - packages/kbn-management/settings/components/form/form.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx index 0239947b86773..0d5ae58d536ef 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx @@ -52,7 +52,6 @@ describe('BottomBar', () => { // TODO: fix this it.skip('renders unsaved changes count', () => { // const { getByTestId } = render(wrap()); - // const input = getByTestId(DATA_TEST_SUBJ_UNSAVED_COUNT); // expect(input).toBe('3 unsaved settings'); }); diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index a8c26e71c46e1..5cea7868ed8c0 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -12,9 +12,9 @@ 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 { isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n'; import { BottomBar } from './bottom_bar'; import { useServices } from './services'; -import { i18n } from '@kbn/i18n'; export interface FormProps { fields: Array>; From 359d9fadca3a5ef53b0bb91360ab8aa6a09fa795 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:29:45 +0000 Subject: [PATCH 09/26] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../kbn-management/settings/components/form/tsconfig.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kbn-management/settings/components/form/tsconfig.json b/packages/kbn-management/settings/components/form/tsconfig.json index 73de070f9b5a1..2c027ec1614e6 100644 --- a/packages/kbn-management/settings/components/form/tsconfig.json +++ b/packages/kbn-management/settings/components/form/tsconfig.json @@ -22,9 +22,13 @@ "@kbn/i18n-react", "@kbn/management-settings-utilities", "@kbn/management-settings-components-field-row", - "@kbn/core-doc-links-browser", "@kbn/react-kibana-context-root", "@kbn/core-theme-browser-mocks", "@kbn/core-i18n-browser", + "@kbn/react-kibana-context-theme", + "@kbn/react-kibana-mount", + "@kbn/core-notifications-browser", + "@kbn/core-theme-browser", + "@kbn/core-ui-settings-browser", ] } From d79f977f63750f59710db45b7d34705c1425b8c1 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:36:45 +0000 Subject: [PATCH 10/26] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 85c901fd8659e..dce2977c338d8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -483,6 +483,7 @@ packages/kbn-management/cards_navigation @elastic/platform-deployment-management src/plugins/management @elastic/platform-deployment-management packages/kbn-management/settings/components/field_input @elastic/platform-deployment-management packages/kbn-management/settings/components/field_row @elastic/platform-deployment-management +packages/kbn-management/settings/components/form @elastic/platform-deployment-management @elastic/appex-sharedux packages/kbn-management/settings/field_definition @elastic/platform-deployment-management packages/kbn-management/settings/setting_ids @elastic/appex-sharedux @elastic/platform-deployment-management packages/kbn-management/settings/section_registry @elastic/appex-sharedux @elastic/platform-deployment-management From 75c941ee5d83c00ea6d34a2cdae01a0a4ee938a3 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 08:00:06 +0100 Subject: [PATCH 11/26] Address feedback --- .github/CODEOWNERS | 2 +- .../settings/components/form/kibana.jsonc | 2 +- .../components/form/reload_page_toast.tsx | 29 +- .../form/storybook/form.stories.tsx | 7 +- .../form/storybook/get_form_story.tsx | 118 +- .../components/form/storybook/settings.json | 1843 ----------------- .../settings/components/form/types.ts | 5 +- 7 files changed, 134 insertions(+), 1872 deletions(-) delete mode 100644 packages/kbn-management/settings/components/form/storybook/settings.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dce2977c338d8..adfa50519595c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -483,7 +483,7 @@ packages/kbn-management/cards_navigation @elastic/platform-deployment-management src/plugins/management @elastic/platform-deployment-management packages/kbn-management/settings/components/field_input @elastic/platform-deployment-management packages/kbn-management/settings/components/field_row @elastic/platform-deployment-management -packages/kbn-management/settings/components/form @elastic/platform-deployment-management @elastic/appex-sharedux +packages/kbn-management/settings/components/form @elastic/platform-deployment-management packages/kbn-management/settings/field_definition @elastic/platform-deployment-management packages/kbn-management/settings/setting_ids @elastic/appex-sharedux @elastic/platform-deployment-management packages/kbn-management/settings/section_registry @elastic/appex-sharedux @elastic/platform-deployment-management diff --git a/packages/kbn-management/settings/components/form/kibana.jsonc b/packages/kbn-management/settings/components/form/kibana.jsonc index c60e87fcfc1ac..5db9d203e37f3 100644 --- a/packages/kbn-management/settings/components/form/kibana.jsonc +++ b/packages/kbn-management/settings/components/form/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/management-settings-components-form", - "owner": "@elastic/platform-deployment-management @elastic/appex-sharedux" + "owner": "@elastic/platform-deployment-management" } diff --git a/packages/kbn-management/settings/components/form/reload_page_toast.tsx b/packages/kbn-management/settings/components/form/reload_page_toast.tsx index e082f9ac2520b..d12c6d0fe77f0 100644 --- a/packages/kbn-management/settings/components/form/reload_page_toast.tsx +++ b/packages/kbn-management/settings/components/form/reload_page_toast.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n/target/types'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { ToastInput } from '@kbn/core-notifications-browser/target/types'; @@ -22,21 +21,19 @@ export const ReloadPageToast = (theme: ThemeServiceStart, i18nStart: I18nStart): defaultMessage: 'One or more settings require you to reload the page to take effect.', }), text: toMountPoint( - - - - window.location.reload()} - data-test-subj="windowReloadButton" - > - {i18n.translate('management.settings.form.requiresPageReloadToastButtonLabel', { - defaultMessage: 'Reload page', - })} - - - - , + + + window.location.reload()} + data-test-subj="windowReloadButton" + > + {i18n.translate('management.settings.form.requiresPageReloadToastButtonLabel', { + defaultMessage: 'Reload page', + })} + + + , { i18n: i18nStart, theme } ), color: 'success', 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 7c6a21c7da986..67121f80b8bc4 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -16,7 +16,12 @@ import { Form as Component } from '../form'; export default { title: `Settings/Form/Form`, description: 'A form with field rows', - argTypes: { save: { action: 'Saved' } }, + argTypes: { + isSavingEnabled: { + name: 'Saving is enabled?', + control: { type: 'boolean' }, + }, + }, decorators: [ (Story) => ( ; +}; + +const settings: Settings = { + array: { + description: 'Description for Array test setting', + name: 'array:test:setting', + type: 'array', + userValue: null, + value: ['example_value'], + ...defaults, + }, + boolean: { + description: 'Description for Boolean test setting', + name: 'boolean:test:setting', + type: 'boolean', + userValue: null, + value: true, + ...defaults, + }, + color: { + description: 'Description for Color test setting', + name: 'color:test:setting', + type: 'color', + userValue: null, + value: '#FF00CC', + ...defaults, + }, + image: { + description: 'Description for Image test setting', + name: 'image:test:setting', + type: 'image', + userValue: null, + value: '', + ...defaults, + }, + number: { + description: 'Description for Number test setting', + name: 'number:test:setting', + type: 'number', + userValue: null, + value: 1, + ...defaults, + }, + json: { + name: 'json:test:setting', + description: 'Description for Json test setting', + type: 'json', + userValue: '{"foo": "bar"}', + 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', + ...defaults, + }, + string: { + description: 'Description for String test setting', + name: 'string:test:setting', + type: 'string', + userValue: null, + value: 'hello world', + ...defaults, + }, + undefined: { + description: 'Description for Undefined test setting', + name: 'undefined:test:setting', + type: 'undefined', + userValue: null, + value: undefined, + ...defaults, + }, +}; + +export type StoryProps = Pick; export const getFormStory = () => { - const Story = () => { - const settings = _settings as unknown as Record>; - const normalizedSettings = normalizeSettings(settings); - const fields: Array> = Object.entries(normalizedSettings).map( + const Story = ({ isSavingEnabled }: StoryProps) => { + const fields: Array> = Object.entries(settings).map( ([id, setting]) => getFieldDefinition({ id, setting, }) ); - const isSavingEnabled = true; return ; }; + Story.args = { + isSavingEnabled: true, + }; + return Story; }; diff --git a/packages/kbn-management/settings/components/form/storybook/settings.json b/packages/kbn-management/settings/components/form/storybook/settings.json deleted file mode 100644 index fa54cf96eada7..0000000000000 --- a/packages/kbn-management/settings/components/form/storybook/settings.json +++ /dev/null @@ -1,1843 +0,0 @@ -{ - "accessibility:disableAnimations": { - "name": "Disable Animations", - "value": false, - "description": "Turn off all unnecessary animations in the Kibana UI. Refresh the page to apply the changes.", - "category": [ - "accessibility" - ], - "requiresPageReload": true - }, - "hideAnnouncements": { - "name": "Hide announcements", - "value": false, - "description": "Stop showing messages and tours that highlight new features." - }, - "dateFormat": { - "name": "Date format", - "value": "MMM D, YYYY @ HH:mm:ss.SSS", - "description": "The format for pretty formatted dates." - }, - "dateFormat:tz": { - "name": "Time zone", - "value": "Browser", - "description": "The default time zone.", - "type": "select", - "options": [ - "Browser", - "Africa/Abidjan", - "Africa/Accra", - "Africa/Addis_Ababa", - "Africa/Algiers", - "Africa/Asmara", - "Africa/Asmera", - "Africa/Bamako", - "Africa/Bangui", - "Africa/Banjul", - "Africa/Bissau", - "Africa/Blantyre", - "Africa/Brazzaville", - "Africa/Bujumbura", - "Africa/Cairo", - "Africa/Casablanca", - "Africa/Ceuta", - "Africa/Conakry", - "Africa/Dakar", - "Africa/Dar_es_Salaam", - "Africa/Djibouti", - "Africa/Douala", - "Africa/El_Aaiun", - "Africa/Freetown", - "Africa/Gaborone", - "Africa/Harare", - "Africa/Johannesburg", - "Africa/Juba", - "Africa/Kampala", - "Africa/Khartoum", - "Africa/Kigali", - "Africa/Kinshasa", - "Africa/Lagos", - "Africa/Libreville", - "Africa/Lome", - "Africa/Luanda", - "Africa/Lubumbashi", - "Africa/Lusaka", - "Africa/Malabo", - "Africa/Maputo", - "Africa/Maseru", - "Africa/Mbabane", - "Africa/Mogadishu", - "Africa/Monrovia", - "Africa/Nairobi", - "Africa/Ndjamena", - "Africa/Niamey", - "Africa/Nouakchott", - "Africa/Ouagadougou", - "Africa/Porto-Novo", - "Africa/Sao_Tome", - "Africa/Timbuktu", - "Africa/Tripoli", - "Africa/Tunis", - "Africa/Windhoek", - "America/Adak", - "America/Anchorage", - "America/Anguilla", - "America/Antigua", - "America/Araguaina", - "America/Argentina/Buenos_Aires", - "America/Argentina/Catamarca", - "America/Argentina/ComodRivadavia", - "America/Argentina/Cordoba", - "America/Argentina/Jujuy", - "America/Argentina/La_Rioja", - "America/Argentina/Mendoza", - "America/Argentina/Rio_Gallegos", - "America/Argentina/Salta", - "America/Argentina/San_Juan", - "America/Argentina/San_Luis", - "America/Argentina/Tucuman", - "America/Argentina/Ushuaia", - "America/Aruba", - "America/Asuncion", - "America/Atikokan", - "America/Atka", - "America/Bahia", - "America/Bahia_Banderas", - "America/Barbados", - "America/Belem", - "America/Belize", - "America/Blanc-Sablon", - "America/Boa_Vista", - "America/Bogota", - "America/Boise", - "America/Buenos_Aires", - "America/Cambridge_Bay", - "America/Campo_Grande", - "America/Cancun", - "America/Caracas", - "America/Catamarca", - "America/Cayenne", - "America/Cayman", - "America/Chicago", - "America/Chihuahua", - "America/Ciudad_Juarez", - "America/Coral_Harbour", - "America/Cordoba", - "America/Costa_Rica", - "America/Creston", - "America/Cuiaba", - "America/Curacao", - "America/Danmarkshavn", - "America/Dawson", - "America/Dawson_Creek", - "America/Denver", - "America/Detroit", - "America/Dominica", - "America/Edmonton", - "America/Eirunepe", - "America/El_Salvador", - "America/Ensenada", - "America/Fort_Nelson", - "America/Fort_Wayne", - "America/Fortaleza", - "America/Glace_Bay", - "America/Godthab", - "America/Goose_Bay", - "America/Grand_Turk", - "America/Grenada", - "America/Guadeloupe", - "America/Guatemala", - "America/Guayaquil", - "America/Guyana", - "America/Halifax", - "America/Havana", - "America/Hermosillo", - "America/Indiana/Indianapolis", - "America/Indiana/Knox", - "America/Indiana/Marengo", - "America/Indiana/Petersburg", - "America/Indiana/Tell_City", - "America/Indiana/Vevay", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Indianapolis", - "America/Inuvik", - "America/Iqaluit", - "America/Jamaica", - "America/Jujuy", - "America/Juneau", - "America/Kentucky/Louisville", - "America/Kentucky/Monticello", - "America/Knox_IN", - "America/Kralendijk", - "America/La_Paz", - "America/Lima", - "America/Los_Angeles", - "America/Louisville", - "America/Lower_Princes", - "America/Maceio", - "America/Managua", - "America/Manaus", - "America/Marigot", - "America/Martinique", - "America/Matamoros", - "America/Mazatlan", - "America/Mendoza", - "America/Menominee", - "America/Merida", - "America/Metlakatla", - "America/Mexico_City", - "America/Miquelon", - "America/Moncton", - "America/Monterrey", - "America/Montevideo", - "America/Montreal", - "America/Montserrat", - "America/Nassau", - "America/New_York", - "America/Nipigon", - "America/Nome", - "America/Noronha", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Ojinaga", - "America/Panama", - "America/Pangnirtung", - "America/Paramaribo", - "America/Phoenix", - "America/Port-au-Prince", - "America/Port_of_Spain", - "America/Porto_Acre", - "America/Porto_Velho", - "America/Puerto_Rico", - "America/Punta_Arenas", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Recife", - "America/Regina", - "America/Resolute", - "America/Rio_Branco", - "America/Rosario", - "America/Santa_Isabel", - "America/Santarem", - "America/Santiago", - "America/Santo_Domingo", - "America/Sao_Paulo", - "America/Scoresbysund", - "America/Shiprock", - "America/Sitka", - "America/St_Barthelemy", - "America/St_Johns", - "America/St_Kitts", - "America/St_Lucia", - "America/St_Thomas", - "America/St_Vincent", - "America/Swift_Current", - "America/Tegucigalpa", - "America/Thule", - "America/Thunder_Bay", - "America/Tijuana", - "America/Toronto", - "America/Tortola", - "America/Vancouver", - "America/Virgin", - "America/Whitehorse", - "America/Winnipeg", - "America/Yakutat", - "America/Yellowknife", - "Antarctica/Casey", - "Antarctica/Davis", - "Antarctica/DumontDUrville", - "Antarctica/Macquarie", - "Antarctica/Mawson", - "Antarctica/McMurdo", - "Antarctica/Palmer", - "Antarctica/Rothera", - "Antarctica/South_Pole", - "Antarctica/Syowa", - "Antarctica/Troll", - "Antarctica/Vostok", - "Arctic/Longyearbyen", - "Asia/Aden", - "Asia/Almaty", - "Asia/Amman", - "Asia/Anadyr", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Ashgabat", - "Asia/Ashkhabad", - "Asia/Atyrau", - "Asia/Baghdad", - "Asia/Bahrain", - "Asia/Baku", - "Asia/Bangkok", - "Asia/Barnaul", - "Asia/Beirut", - "Asia/Bishkek", - "Asia/Brunei", - "Asia/Calcutta", - "Asia/Chita", - "Asia/Choibalsan", - "Asia/Chongqing", - "Asia/Chungking", - "Asia/Colombo", - "Asia/Dacca", - "Asia/Damascus", - "Asia/Dhaka", - "Asia/Dili", - "Asia/Dubai", - "Asia/Dushanbe", - "Asia/Famagusta", - "Asia/Gaza", - "Asia/Harbin", - "Asia/Hebron", - "Asia/Ho_Chi_Minh", - "Asia/Hong_Kong", - "Asia/Hovd", - "Asia/Irkutsk", - "Asia/Istanbul", - "Asia/Jakarta", - "Asia/Jayapura", - "Asia/Jerusalem", - "Asia/Kabul", - "Asia/Kamchatka", - "Asia/Karachi", - "Asia/Kashgar", - "Asia/Kathmandu", - "Asia/Katmandu", - "Asia/Khandyga", - "Asia/Kolkata", - "Asia/Krasnoyarsk", - "Asia/Kuala_Lumpur", - "Asia/Kuching", - "Asia/Kuwait", - "Asia/Macao", - "Asia/Macau", - "Asia/Magadan", - "Asia/Makassar", - "Asia/Manila", - "Asia/Muscat", - "Asia/Nicosia", - "Asia/Novokuznetsk", - "Asia/Novosibirsk", - "Asia/Omsk", - "Asia/Oral", - "Asia/Phnom_Penh", - "Asia/Pontianak", - "Asia/Pyongyang", - "Asia/Qatar", - "Asia/Qostanay", - "Asia/Qyzylorda", - "Asia/Rangoon", - "Asia/Riyadh", - "Asia/Saigon", - "Asia/Sakhalin", - "Asia/Samarkand", - "Asia/Seoul", - "Asia/Shanghai", - "Asia/Singapore", - "Asia/Srednekolymsk", - "Asia/Taipei", - "Asia/Tashkent", - "Asia/Tbilisi", - "Asia/Tehran", - "Asia/Tel_Aviv", - "Asia/Thimbu", - "Asia/Thimphu", - "Asia/Tokyo", - "Asia/Tomsk", - "Asia/Ujung_Pandang", - "Asia/Ulaanbaatar", - "Asia/Ulan_Bator", - "Asia/Urumqi", - "Asia/Ust-Nera", - "Asia/Vientiane", - "Asia/Vladivostok", - "Asia/Yakutsk", - "Asia/Yangon", - "Asia/Yekaterinburg", - "Asia/Yerevan", - "Atlantic/Azores", - "Atlantic/Bermuda", - "Atlantic/Canary", - "Atlantic/Cape_Verde", - "Atlantic/Faeroe", - "Atlantic/Faroe", - "Atlantic/Jan_Mayen", - "Atlantic/Madeira", - "Atlantic/Reykjavik", - "Atlantic/South_Georgia", - "Atlantic/St_Helena", - "Atlantic/Stanley", - "Australia/ACT", - "Australia/Adelaide", - "Australia/Brisbane", - "Australia/Broken_Hill", - "Australia/Canberra", - "Australia/Currie", - "Australia/Darwin", - "Australia/Eucla", - "Australia/Hobart", - "Australia/LHI", - "Australia/Lindeman", - "Australia/Lord_Howe", - "Australia/Melbourne", - "Australia/NSW", - "Australia/North", - "Australia/Perth", - "Australia/Queensland", - "Australia/South", - "Australia/Sydney", - "Australia/Tasmania", - "Australia/Victoria", - "Australia/West", - "Australia/Yancowinna", - "Brazil/Acre", - "Brazil/DeNoronha", - "Brazil/East", - "Brazil/West", - "CET", - "CST6CDT", - "Canada/Atlantic", - "Canada/Central", - "Canada/Eastern", - "Canada/Mountain", - "Canada/Newfoundland", - "Canada/Pacific", - "Canada/Saskatchewan", - "Canada/Yukon", - "Chile/Continental", - "Chile/EasterIsland", - "Cuba", - "EET", - "EST5EDT", - "Egypt", - "Eire", - "Etc/GMT", - "Etc/GMT+0", - "Etc/GMT+1", - "Etc/GMT+10", - "Etc/GMT+11", - "Etc/GMT+12", - "Etc/GMT+2", - "Etc/GMT+3", - "Etc/GMT+4", - "Etc/GMT+5", - "Etc/GMT+6", - "Etc/GMT+7", - "Etc/GMT+8", - "Etc/GMT+9", - "Etc/GMT-0", - "Etc/GMT-1", - "Etc/GMT-10", - "Etc/GMT-11", - "Etc/GMT-12", - "Etc/GMT-13", - "Etc/GMT-14", - "Etc/GMT-2", - "Etc/GMT-3", - "Etc/GMT-4", - "Etc/GMT-5", - "Etc/GMT-6", - "Etc/GMT-7", - "Etc/GMT-8", - "Etc/GMT-9", - "Etc/GMT0", - "Etc/Greenwich", - "Etc/UCT", - "Etc/UTC", - "Etc/Universal", - "Etc/Zulu", - "Europe/Amsterdam", - "Europe/Andorra", - "Europe/Astrakhan", - "Europe/Athens", - "Europe/Belfast", - "Europe/Belgrade", - "Europe/Berlin", - "Europe/Bratislava", - "Europe/Brussels", - "Europe/Bucharest", - "Europe/Budapest", - "Europe/Busingen", - "Europe/Chisinau", - "Europe/Copenhagen", - "Europe/Dublin", - "Europe/Gibraltar", - "Europe/Guernsey", - "Europe/Helsinki", - "Europe/Isle_of_Man", - "Europe/Istanbul", - "Europe/Jersey", - "Europe/Kaliningrad", - "Europe/Kiev", - "Europe/Kirov", - "Europe/Kyiv", - "Europe/Lisbon", - "Europe/Ljubljana", - "Europe/London", - "Europe/Luxembourg", - "Europe/Madrid", - "Europe/Malta", - "Europe/Mariehamn", - "Europe/Minsk", - "Europe/Monaco", - "Europe/Moscow", - "Europe/Nicosia", - "Europe/Oslo", - "Europe/Paris", - "Europe/Podgorica", - "Europe/Prague", - "Europe/Riga", - "Europe/Rome", - "Europe/Samara", - "Europe/San_Marino", - "Europe/Sarajevo", - "Europe/Saratov", - "Europe/Simferopol", - "Europe/Skopje", - "Europe/Sofia", - "Europe/Stockholm", - "Europe/Tallinn", - "Europe/Tirane", - "Europe/Tiraspol", - "Europe/Ulyanovsk", - "Europe/Uzhgorod", - "Europe/Vaduz", - "Europe/Vatican", - "Europe/Vienna", - "Europe/Vilnius", - "Europe/Volgograd", - "Europe/Warsaw", - "Europe/Zagreb", - "Europe/Zaporozhye", - "Europe/Zurich", - "GB", - "GB-Eire", - "GMT", - "GMT+0", - "GMT-0", - "GMT0", - "Greenwich", - "Hongkong", - "Iceland", - "Indian/Antananarivo", - "Indian/Chagos", - "Indian/Christmas", - "Indian/Cocos", - "Indian/Comoro", - "Indian/Kerguelen", - "Indian/Mahe", - "Indian/Maldives", - "Indian/Mauritius", - "Indian/Mayotte", - "Indian/Reunion", - "Iran", - "Israel", - "Jamaica", - "Japan", - "Kwajalein", - "Libya", - "MET", - "MST7MDT", - "Mexico/BajaNorte", - "Mexico/BajaSur", - "Mexico/General", - "NZ", - "NZ-CHAT", - "Navajo", - "PRC", - "PST8PDT", - "Pacific/Apia", - "Pacific/Auckland", - "Pacific/Bougainville", - "Pacific/Chatham", - "Pacific/Chuuk", - "Pacific/Easter", - "Pacific/Efate", - "Pacific/Enderbury", - "Pacific/Fakaofo", - "Pacific/Fiji", - "Pacific/Funafuti", - "Pacific/Galapagos", - "Pacific/Gambier", - "Pacific/Guadalcanal", - "Pacific/Guam", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Kanton", - "Pacific/Kiritimati", - "Pacific/Kosrae", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Marquesas", - "Pacific/Midway", - "Pacific/Nauru", - "Pacific/Niue", - "Pacific/Norfolk", - "Pacific/Noumea", - "Pacific/Pago_Pago", - "Pacific/Palau", - "Pacific/Pitcairn", - "Pacific/Pohnpei", - "Pacific/Ponape", - "Pacific/Port_Moresby", - "Pacific/Rarotonga", - "Pacific/Saipan", - "Pacific/Samoa", - "Pacific/Tahiti", - "Pacific/Tarawa", - "Pacific/Tongatapu", - "Pacific/Truk", - "Pacific/Wake", - "Pacific/Wallis", - "Pacific/Yap", - "Poland", - "Portugal", - "ROK", - "Singapore", - "Turkey", - "UCT", - "US/Alaska", - "US/Aleutian", - "US/Arizona", - "US/Central", - "US/East-Indiana", - "US/Eastern", - "US/Hawaii", - "US/Indiana-Starke", - "US/Michigan", - "US/Mountain", - "US/Pacific", - "US/Samoa", - "UTC", - "Universal", - "W-SU", - "WET", - "Zulu" - ], - "requiresPageReload": true - }, - "dateFormat:scaled": { - "name": "Scaled date format", - "type": "json", - "value": "[\n [\"\", \"HH:mm:ss.SSS\"],\n [\"PT1S\", \"HH:mm:ss\"],\n [\"PT1M\", \"HH:mm\"],\n [\"PT1H\", \"YYYY-MM-DD HH:mm\"],\n [\"P1DT\", \"YYYY-MM-DD\"],\n [\"P1YT\", \"YYYY\"]\n]", - "description": "Values that define the format used in situations where time-based data is rendered in order, and formatted timestamps should adapt to the interval between measurements. Keys are ISO8601 intervals." - }, - "dateFormat:dow": { - "name": "Day of week", - "value": "Sunday", - "description": "The day that starts the week.", - "type": "select", - "options": [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday" - ] - }, - "dateNanosFormat": { - "name": "Date with nanoseconds format", - "value": "MMM D, YYYY @ HH:mm:ss.SSSSSSSSS", - "description": "The format for date_nanos data." - }, - "buildNum": { - "readonly": true, - "userValue": 9007199254740991 - }, - "defaultRoute": { - "name": "Default route", - "value": "/app/home", - "description": "This setting specifies the default route when opening Kibana. You can use this setting to modify the landing page when opening Kibana. The route must be a relative URL." - }, - "notifications:banner": { - "name": "Custom banner notification", - "value": "", - "type": "markdown", - "description": "A custom banner intended for temporary notices to all users. Markdown supported.", - "category": [ - "notifications" - ], - "sensitive": true - }, - "notifications:lifetime:banner": { - "name": "Banner notification lifetime", - "value": 3000000, - "description": "The time in milliseconds which a banner notification will be displayed on-screen for. ", - "type": "number", - "category": [ - "notifications" - ] - }, - "notifications:lifetime:error": { - "name": "Error notification lifetime", - "value": 300000, - "description": "The time in milliseconds which an error notification will be displayed on-screen for. ", - "type": "number", - "category": [ - "notifications" - ] - }, - "notifications:lifetime:warning": { - "name": "Warning notification lifetime", - "value": 10000, - "description": "The time in milliseconds which a warning notification will be displayed on-screen for. ", - "type": "number", - "category": [ - "notifications" - ] - }, - "notifications:lifetime:info": { - "name": "Info notification lifetime", - "value": 5000, - "description": "The time in milliseconds which an information notification will be displayed on-screen for. ", - "type": "number", - "category": [ - "notifications" - ] - }, - "theme:darkMode": { - "name": "Dark mode", - "value": false, - "description": "Enable a dark mode for the Kibana UI. A page refresh is required for the setting to be applied.", - "requiresPageReload": true - }, - "theme:version": { - "name": "Theme version", - "value": "v8", - "readonly": true - }, - "state:storeInSessionStorage": { - "name": "Store URLs in session storage", - "value": false, - "description": "The URL can sometimes grow to be too large for some browsers to handle. To counter-act this we are testing if storing parts of the URL in session storage could help. Please let us know how it goes!" - }, - "csv:separator": { - "name": "CSV separator", - "value": ",", - "description": "Separate exported values with this string", - "userValue": "|" - }, - "csv:quoteValues": { - "name": "Quote CSV values", - "value": true, - "description": "Should values be quoted in csv exports?" - }, - "banners:placement": { - "name": "Banner placement", - "description": "Display a top banner above the Elastic header. \n \n Subscription required.\n \n ", - "category": [ - "banner" - ], - "order": 1, - "type": "select", - "value": "disabled", - "options": [ - "disabled", - "top" - ], - "optionLabels": { - "disabled": "Disabled", - "top": "Top" - }, - "requiresPageReload": true - }, - "banners:textContent": { - "name": "Banner text", - "description": "Add Markdown-formatted text to the banner. \n \n Subscription required.\n \n ", - "sensitive": true, - "category": [ - "banner" - ], - "order": 2, - "type": "markdown", - "value": "", - "requiresPageReload": true - }, - "banners:textColor": { - "name": "Banner text color", - "description": "Set the color of the banner text. \n \n Subscription required.\n \n ", - "category": [ - "banner" - ], - "order": 3, - "type": "color", - "value": "#8A6A0A", - "requiresPageReload": true - }, - "banners:backgroundColor": { - "name": "Banner background color", - "description": "Set the background color for the banner. \n \n Subscription required.\n \n ", - "category": [ - "banner" - ], - "order": 4, - "type": "color", - "value": "#FFF9E8", - "requiresPageReload": true - }, - "savedObjects:perPage": { - "name": "Objects per page", - "value": 20, - "type": "number", - "description": "Number of objects to show per page in the load dialog" - }, - "savedObjects:listingLimit": { - "name": "Objects listing limit", - "type": "number", - "value": 1000, - "description": "Number of objects to fetch for the listing pages" - }, - "shortDots:enable": { - "name": "Shorten fields", - "value": false, - "description": "Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz" - }, - "format:defaultTypeMap": { - "name": "Field type format name", - "value": "{\n \"ip\": { \"id\": \"ip\", \"params\": {} },\n \"date\": { \"id\": \"date\", \"params\": {} },\n \"date_nanos\": { \"id\": \"date_nanos\", \"params\": {}, \"es\": true },\n \"geo_point\": { \"id\": \"geo_point\", \"params\": { \"transform\": \"wkt\" } },\n \"number\": { \"id\": \"number\", \"params\": {} },\n \"boolean\": { \"id\": \"boolean\", \"params\": {} },\n \"histogram\": { \"id\": \"histogram\", \"params\": {} },\n \"_source\": { \"id\": \"_source\", \"params\": {} },\n \"_default_\": { \"id\": \"string\", \"params\": {} }\n}", - "type": "json", - "description": "Map of the format name to use by default for each field type. \"_default_\" is used if the field type is not mentioned explicitly" - }, - "format:number:defaultPattern": { - "name": "Number format", - "value": "0,0.[000]", - "type": "string", - "description": "Default numeral format for the \"number\" format" - }, - "format:percent:defaultPattern": { - "name": "Percent format", - "value": "0,0.[000]%", - "type": "string", - "description": "Default numeral format for the \"percent\" format" - }, - "format:bytes:defaultPattern": { - "name": "Bytes format", - "value": "0,0.[0]b", - "type": "string", - "description": "Default numeral format for the \"bytes\" format" - }, - "format:currency:defaultPattern": { - "name": "Currency format", - "value": "($0,0.[00])", - "type": "string", - "description": "Default numeral format for the \"currency\" format" - }, - "format:number:defaultLocale": { - "name": "Formatting locale", - "value": "en", - "type": "select", - "options": [ - "en", - "be-nl", - "chs", - "cs", - "da-dk", - "de-ch", - "de", - "en-gb", - "es-ES", - "es", - "et", - "fi", - "fr-CA", - "fr-ch", - "fr", - "hu", - "it", - "ja", - "nl-nl", - "pl", - "pt-br", - "pt-pt", - "ru-UA", - "ru", - "sk", - "th", - "tr", - "uk-UA" - ], - "optionLabels": { - "be-nl": "Dutch (Belgium)", - "chs": "Simplified Chinese", - "cs": "Czech", - "da-dk": "Danish (Denmark)", - "de-ch": "German (Switzerland)", - "de": "German", - "en-gb": "English (UK)", - "es-ES": "Spanish (Spain)", - "es": "Spanish", - "et": "Estonian", - "fi": "Finnish", - "fr-CA": "French (Canada)", - "fr-ch": "French (Switzerland)", - "fr": "French", - "hu": "Hungarian", - "it": "Italian", - "ja": "Japanese", - "nl-nl": "Dutch (Netherlands)", - "pl": "Polish", - "pt-br": "Portuguese (Brazil)", - "pt-pt": "Portuguese", - "ru-UA": "Russian (Ukraine)", - "ru": "Russian", - "sk": "Slovak", - "th": "Thai", - "tr": "Turkish", - "uk-UA": "Ukrainian" - }, - "description": "Numeral language locale" - }, - "visualization:colorMapping": { - "name": "Color mapping", - "value": "{\"Count\":\"#00A69B\"}", - "type": "json", - "description": "Maps values to specific colors in charts using the Compatibility palette.", - "deprecation": { - "message": "This setting is deprecated and will not be supported in a future version.", - "docLinksKey": "visualizationSettings" - }, - "category": [ - "visualization" - ] - }, - "visualization:useLegacyTimeAxis": { - "name": "Legacy chart time axis", - "value": false, - "description": "Enables the legacy time axis for charts in Lens, Discover, Visualize and TSVB", - "deprecation": { - "message": "This setting is deprecated and will not be supported in a future version.", - "docLinksKey": "visualizationSettings" - }, - "category": [ - "visualization" - ] - }, - "bfetch:disable": { - "name": "Disable request batching", - "value": false, - "description": "Disables requests batching. This increases number of HTTP requests from Kibana, but allows to debug requests individually.", - "category": [] - }, - "bfetch:disableCompression": { - "name": "Disable batch compression", - "value": false, - "description": "Disable batch compression. This allows you to debug individual requests, but increases response size.", - "category": [] - }, - "metaFields": { - "name": "Meta fields", - "value": [ - "_source", - "_id", - "_index", - "_score" - ], - "description": "Fields that exist outside of _source to merge into our document when displaying it" - }, - "doc_table:highlight": { - "name": "Highlight results", - "value": true, - "description": "Highlight results in Discover and Saved Searches Dashboard. Highlighting makes requests slow when working on big documents.", - "category": [ - "discover" - ] - }, - "query:queryString:options": { - "name": "Query string options", - "value": "{ \"analyze_wildcard\": true }", - "description": "Options for the lucene query string parser. Is only used when \"Query language\" is set to Lucene.", - "type": "json" - }, - "query:allowLeadingWildcards": { - "name": "Allow leading wildcards in query", - "value": true, - "description": "When set, * is allowed as the first character in a query clause. To disallow leading wildcards in basic lucene queries, use query:queryString:options." - }, - "search:queryLanguage": { - "name": "Query language", - "value": "kuery", - "description": "Query language used by the query bar. KQL is a new language built specifically for Kibana.", - "type": "select", - "options": [ - "lucene", - "kuery" - ], - "optionLabels": { - "lucene": "Lucene", - "kuery": "KQL" - } - }, - "sort:options": { - "name": "Sort options", - "value": "{ \"unmapped_type\": \"boolean\" }", - "description": "Options for the Elasticsearch sort parameter", - "type": "json" - }, - "defaultIndex": { - "name": "Default data view", - "value": null, - "type": "string", - "description": "Used by discover and visualizations when a data view is not set." - }, - "courier:ignoreFilterIfFieldNotInIndex": { - "name": "Ignore filter(s)", - "value": false, - "description": "This configuration enhances support for dashboards containing visualizations accessing dissimilar indexes. When disabled, all filters are applied to all visualizations. When enabled, filter(s) will be ignored for a visualization when the visualization's index does not contain the filtering field.", - "category": [ - "search" - ] - }, - "courier:setRequestPreference": { - "name": "Request preference", - "value": "sessionId", - "options": [ - "sessionId", - "custom", - "none" - ], - "optionLabels": { - "sessionId": "Session ID", - "custom": "Custom", - "none": "None" - }, - "type": "select", - "description": "Allows you to set which shards handle your search requests.\n

    \n
  • Session ID: restricts operations to execute all search requests on the same shards.\n This has the benefit of reusing shard caches across requests.
  • \n
  • Custom: allows you to define a your own preference.\n Use 'courier:customRequestPreference' to customize your preference value.
  • \n
  • None: means do not set a preference.\n This might provide better performance because requests can be spread across all shard copies.\n However, results might be inconsistent because different shards might be in different refresh states.
  • \n
", - "category": [ - "search" - ] - }, - "courier:customRequestPreference": { - "name": "Custom request preference", - "value": "_local", - "type": "string", - "description": "Request Preference used when courier:setRequestPreference is set to \"custom\".", - "category": [ - "search" - ] - }, - "courier:maxConcurrentShardRequests": { - "name": "Max Concurrent Shard Requests", - "value": 0, - "type": "number", - "description": "Controls the max_concurrent_shard_requests setting used for _msearch requests sent by Kibana. Set to 0 to disable this config and use the Elasticsearch default.", - "category": [ - "search" - ] - }, - "search:includeFrozen": { - "name": "Search in frozen indices", - "description": "Will include frozen indices in results if enabled. Searching through frozen indices\n might increase the search time.", - "value": false, - "deprecation": { - "message": "This setting is deprecated and will be removed in Kibana 9.0.", - "docLinksKey": "kibanaSearchSettings" - }, - "category": [ - "search" - ] - }, - "histogram:barTarget": { - "name": "Target buckets", - "value": 50, - "description": "Attempt to generate around this many buckets when using \"auto\" interval in date and numeric histograms" - }, - "histogram:maxBars": { - "name": "Maximum buckets", - "value": 1000, - "description": "\n Limits the density of date and number histograms across Kibana\n for better performance using a test query. If the test query would too many buckets,\n the interval between buckets will be increased. This setting applies separately\n to each histogram aggregation, and does not apply to other types of aggregation.\n To find the maximum value of this setting, divide the Elasticsearch 'search.max_buckets'\n value by the maximum number of aggregations in each visualization.\n " - }, - "history:limit": { - "name": "History limit", - "value": 10, - "description": "In fields that have history (e.g. query inputs), show this many recent values" - }, - "timepicker:refreshIntervalDefaults": { - "name": "Time filter refresh interval", - "value": "{\n \"pause\": true,\n \"value\": 60000\n}", - "type": "json", - "description": "The timefilter's default refresh interval. The \"value\" needs to be specified in milliseconds.", - "requiresPageReload": true - }, - "timepicker:timeDefaults": { - "name": "Time filter defaults", - "value": "{\n \"from\": \"now-15m\",\n \"to\": \"now\"\n}", - "type": "json", - "description": "The timefilter selection to use when Kibana is started without one. Must be an object containing \"from\" and \"to\" (see accepted formats).", - "requiresPageReload": true - }, - "timepicker:quickRanges": { - "name": "Time filter quick ranges", - "value": "[\n {\n \"from\": \"now/d\",\n \"to\": \"now/d\",\n \"display\": \"Today\"\n },\n {\n \"from\": \"now/w\",\n \"to\": \"now/w\",\n \"display\": \"This week\"\n },\n {\n \"from\": \"now-15m\",\n \"to\": \"now\",\n \"display\": \"Last 15 minutes\"\n },\n {\n \"from\": \"now-30m\",\n \"to\": \"now\",\n \"display\": \"Last 30 minutes\"\n },\n {\n \"from\": \"now-1h\",\n \"to\": \"now\",\n \"display\": \"Last 1 hour\"\n },\n {\n \"from\": \"now-24h/h\",\n \"to\": \"now\",\n \"display\": \"Last 24 hours\"\n },\n {\n \"from\": \"now-7d/d\",\n \"to\": \"now\",\n \"display\": \"Last 7 days\"\n },\n {\n \"from\": \"now-30d/d\",\n \"to\": \"now\",\n \"display\": \"Last 30 days\"\n },\n {\n \"from\": \"now-90d/d\",\n \"to\": \"now\",\n \"display\": \"Last 90 days\"\n },\n {\n \"from\": \"now-1y/d\",\n \"to\": \"now\",\n \"display\": \"Last 1 year\"\n }\n]", - "type": "json", - "description": "The list of ranges to show in the Quick section of the time filter. This should be an array of objects, with each object containing \"from\", \"to\" (see accepted formats), and \"display\" (the title to be displayed)." - }, - "filters:pinnedByDefault": { - "name": "Pin filters by default", - "value": false, - "description": "Whether the filters should have a global state (be pinned) by default" - }, - "filterEditor:suggestValues": { - "name": "Filter editor suggest values", - "value": true, - "description": "Set this property to false to prevent the filter editor from suggesting values for fields." - }, - "autocomplete:valueSuggestionMethod": { - "name": "Autocomplete value suggestion method", - "type": "select", - "value": "terms_enum", - "description": "The method used for querying suggestions for values in KQL autocomplete. Select terms_enum to use the Elasticsearch terms enum API for improved autocomplete suggestion performance. (Note that terms_enum is incompatible with Document Level Security.) Select terms_agg to use an Elasticsearch terms aggregation. (Note that terms_agg is incompatible with IP-type fields.) Learn more.", - "options": [ - "terms_enum", - "terms_agg" - ], - "category": [ - "autocomplete" - ] - }, - "autocomplete:useTimeRange": { - "name": "Use time range", - "value": true, - "description": "Disable this property to get autocomplete suggestions from your full dataset, rather than from the current time range. Learn more.", - "category": [ - "autocomplete" - ] - }, - "search:timeout": { - "name": "Search Timeout", - "value": 600000, - "description": "Change the maximum timeout for a search session or set to 0 to disable the timeout and allow queries to run to completion.", - "type": "number", - "category": [ - "search" - ] - }, - "fileUpload:maxFileSize": { - "name": "Maximum file upload size", - "value": "100MB", - "description": "Sets the file size limit when importing files. The highest supported value for this setting is 1GB." - }, - "labs:dashboard:deferBelowFold": { - "name": "Defer loading panels below \"the fold\"", - "value": false, - "type": "boolean", - "description": "Any panels below \"the fold\"-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport", - "requiresPageReload": true, - "category": [ - "Presentation Labs" - ] - }, - "labs:dashboard:dashboardControls": { - "name": "Enable dashboard controls", - "value": true, - "type": "boolean", - "description": "Enables the controls system for dashboard, which allows dashboard authors to more easily build interactive elements for their users.", - "requiresPageReload": true, - "category": [ - "Presentation Labs" - ] - }, - "labs:canvas:byValueEmbeddable": { - "name": "By-Value Embeddables", - "value": true, - "type": "boolean", - "description": "Enables support for by-value embeddables in Canvas", - "requiresPageReload": true, - "category": [ - "Presentation Labs" - ] - }, - "visualization:heatmap:maxBuckets": { - "name": "Heatmap maximum buckets", - "value": 50, - "type": "number", - "description": "The maximum number of buckets a single datasource can return. A higher number might have negative impact on browser rendering performance", - "category": [ - "visualization" - ] - }, - "metrics:max_buckets": { - "name": "TSVB buckets limit", - "value": 2000, - "description": "Affects the TSVB histogram density. Must be set higher than \"histogram:maxBars\"." - }, - "metrics:allowStringIndices": { - "name": "Allow string indices in TSVB", - "value": false, - "requiresPageReload": true, - "description": "Enables you to query Elasticsearch indices in TSVB visualizations." - }, - "metrics:allowCheckingForFailedShards": { - "name": "Show TSVB request shard failures", - "value": true, - "description": "Show warning message for partial data in TSVB charts if the request succeeds for some shards but fails for others." - }, - "timelion:es.timefield": { - "name": "Time field", - "value": "@timestamp", - "description": "Default field containing a timestamp when using .es()", - "category": [ - "timelion" - ] - }, - "timelion:es.default_index": { - "name": "Default index", - "value": "_all", - "description": "Default elasticsearch index to search with .es()", - "category": [ - "timelion" - ] - }, - "timelion:target_buckets": { - "name": "Target buckets", - "value": 200, - "description": "The number of buckets to shoot for when using auto intervals", - "category": [ - "timelion" - ] - }, - "timelion:max_buckets": { - "name": "Maximum buckets", - "value": 2000, - "description": "The maximum number of buckets a single datasource can return", - "category": [ - "timelion" - ] - }, - "timelion:min_interval": { - "name": "Minimum interval", - "value": "1ms", - "description": "The smallest interval that will be calculated when using \"auto\"", - "category": [ - "timelion" - ] - }, - "visualization:visualize:legacyHeatmapChartsLibrary": { - "name": "Heatmap legacy charts library", - "requiresPageReload": true, - "value": false, - "description": "Enables legacy charts library for heatmap charts in visualize.", - "deprecation": { - "message": "The legacy charts library for heatmap in visualize is deprecated and will not be supported in a future version.", - "docLinksKey": "visualizationSettings" - }, - "category": [ - "visualization" - ] - }, - "labs:dashboard:enable_ui": { - "name": "Enable labs button in Dashboard", - "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Dashboard.", - "value": false, - "type": "boolean", - "category": [ - "Presentation Labs" - ], - "requiresPageReload": true - }, - "defaultColumns": { - "name": "Default columns", - "value": [], - "description": "Columns displayed by default in the Discover app. If empty, a summary of the document will be displayed.", - "category": [ - "discover" - ] - }, - "discover:maxDocFieldsDisplayed": { - "name": "Maximum document fields displayed", - "value": 200, - "description": "Maximum number of fields rendered in the document summary", - "category": [ - "discover" - ] - }, - "discover:sampleSize": { - "name": "Maximum rows per table", - "value": 500, - "description": "Sets the maximum number of rows for the entire document table.", - "category": [ - "discover" - ] - }, - "discover:sampleRowsPerPage": { - "name": "Rows per page", - "value": 100, - "options": [ - 10, - 25, - 50, - 100, - 250, - 500 - ], - "type": "select", - "description": "Limits the number of rows per page in the document table.", - "category": [ - "discover" - ] - }, - "discover:sort:defaultOrder": { - "name": "Default sort direction", - "value": "desc", - "options": [ - "desc", - "asc" - ], - "optionLabels": { - "desc": "Descending", - "asc": "Ascending" - }, - "type": "select", - "description": "Controls the default sort direction for time based data views in the Discover app.", - "category": [ - "discover" - ] - }, - "discover:searchOnPageLoad": { - "name": "Search on page load", - "value": true, - "type": "boolean", - "description": "Controls whether a search is executed when Discover first loads. This setting does not have an effect when loading a saved search.", - "category": [ - "discover" - ] - }, - "doc_table:hideTimeColumn": { - "name": "Hide 'Time' column", - "value": false, - "description": "Hide the 'Time' column in Discover and in all Saved Searches on Dashboards.", - "category": [ - "discover" - ] - }, - "fields:popularLimit": { - "name": "Popular fields limit", - "value": 10, - "description": "The top N most popular fields to show" - }, - "context:defaultSize": { - "name": "Context size", - "value": 5, - "description": "The number of surrounding entries to show in the context view", - "category": [ - "discover" - ] - }, - "context:step": { - "name": "Context size step", - "value": 5, - "description": "The step size to increment or decrement the context size by", - "category": [ - "discover" - ] - }, - "context:tieBreakerFields": { - "name": "Tie breaker fields", - "value": [ - "_doc" - ], - "description": "A comma-separated list of fields to use for tie-breaking between documents that have the same timestamp value. From this list the first field that is present and sortable in the current data view is used.", - "category": [ - "discover" - ] - }, - "doc_table:legacy": { - "name": "Document Explorer or classic view", - "value": false, - "description": "To use the new Document Explorer instead of the classic view, turn off this option. The Document Explorer offers better data sorting, resizable columns, and a full screen view.", - "requiresPageReload": true, - "category": [ - "discover" - ], - "metric": { - "type": "click", - "name": "discover:useLegacyDataGrid" - } - }, - "discover:modifyColumnsOnSwitch": { - "name": "Modify columns when changing data views", - "value": true, - "description": "Remove columns that are not available in the new data view.", - "category": [ - "discover" - ], - "metric": { - "type": "click", - "name": "discover:modifyColumnsOnSwitchTitle" - } - }, - "discover:searchFieldsFromSource": { - "name": "Read fields from _source", - "description": "When enabled will load documents directly from `_source`. This is soon going to be deprecated. When disabled, will retrieve fields via the new Fields API in the high-level search service.", - "value": false, - "category": [ - "discover" - ] - }, - "discover:showFieldStatistics": { - "name": "Show field statistics", - "description": "Enable the Field statistics view to show details such as the minimum and maximum values of a numeric field or a map of a geo field. This functionality is in beta and is subject to change.", - "value": true, - "category": [ - "discover" - ], - "metric": { - "type": "click", - "name": "discover:showFieldStatistics" - } - }, - "discover:showMultiFields": { - "name": "Show multi-fields", - "description": "Controls whether multi-fields display in the expanded document view. In most cases, multi-fields are the same as the original field. This option is only available when `searchFieldsFromSource` is off.", - "value": false, - "category": [ - "discover" - ] - }, - "discover:rowHeightOption": { - "name": "Row height in the Document Explorer", - "value": 3, - "category": [ - "discover" - ], - "description": "The number of lines to allow in a row. A value of -1 automatically adjusts the row height to fit the contents. A value of 0 displays the content in a single line." - }, - "truncate:maxHeight": { - "name": "Maximum cell height in the classic table", - "value": 115, - "category": [ - "discover" - ], - "description": "The maximum height that a cell in a table should occupy. Set to 0 to disable truncation.", - "requiresPageReload": true - }, - "discover:enableESQL": { - "name": "Enable ES|QL", - "value": true, - "description": "[technical preview] This tech preview feature is highly experimental--do not rely on this for production saved searches, visualizations or dashboards. This setting enables ES|QL in Discover. If you have feedback on this experience please reach out to us on discuss.elastic.co/c/elastic-stack/kibana", - "requiresPageReload": true, - "category": [ - "discover" - ] - }, - "xpackReporting:customPdfLogo": { - "name": "PDF footer image", - "value": null, - "description": "Custom image to use in the PDF's footer", - "sensitive": true, - "type": "image", - "category": [ - "reporting" - ] - }, - "labs:canvas:enable_ui": { - "name": "Enable labs button in Canvas", - "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Canvas.", - "value": false, - "type": "boolean", - "category": [ - "Presentation Labs" - ], - "requiresPageReload": true - }, - "rollups:enableIndexPatterns": { - "name": "Enable rollup data views", - "value": true, - "description": "Enable the creation of data views that capture rollup indices,\n which in turn enable visualizations based on rollup data.", - "category": [ - "rollups" - ], - "requiresPageReload": true - }, - "observability:enableInspectEsQueries": { - "category": [ - "observability" - ], - "name": "Inspect ES queries", - "value": false, - "description": "Inspect Elasticsearch queries in API responses.", - "requiresPageReload": true - }, - "observability:maxSuggestions": { - "category": [ - "observability" - ], - "name": "Maximum suggestions", - "value": 100, - "description": "Maximum number of suggestions fetched in autocomplete selection boxes." - }, - "observability:enableComparisonByDefault": { - "category": [ - "observability" - ], - "name": "Comparison feature", - "value": true, - "description": "Determines whether the comparison feature is enabled or disabled by default in the APM app." - }, - "observability:apmDefaultServiceEnvironment": { - "category": [ - "observability" - ], - "sensitive": true, - "name": "Default service environment", - "description": "Set the default environment for the APM app. When left empty, data from all environments will be displayed by default.", - "value": "" - }, - "observability:apmProgressiveLoading": { - "category": [ - "observability" - ], - "name": "Use progressive loading of selected APM views", - "description": "[technical preview] Whether to load data progressively for APM views. Data may be requested with a lower sampling rate first, with lower accuracy but faster response times, while the unsampled data loads in the background", - "value": "off", - "requiresPageReload": false, - "type": "select", - "options": [ - "off", - "low", - "medium", - "high" - ], - "optionLabels": { - "off": "Off", - "low": "Low sampling rate (fastest, least accurate)", - "medium": "Medium sampling rate", - "high": "High sampling rate (slower, most accurate)" - }, - "showInLabs": true - }, - "observability:apmServiceInventoryOptimizedSorting": { - "category": [ - "observability" - ], - "name": "Optimize services list load performance in APM", - "description": "[technical preview] Default APM Service Inventory and Storage Explorer pages sort (for Services without Machine Learning applied) to sort by Service Name.", - "value": false, - "requiresPageReload": false, - "type": "boolean", - "showInLabs": true - }, - "observability:apmServiceGroupMaxNumberOfServices": { - "category": [ - "observability" - ], - "name": "Maximum services in a service group", - "value": 500, - "description": "Limit the number of services in a given service group" - }, - "observability:apmTraceExplorerTab": { - "category": [ - "observability" - ], - "name": "APM Trace Explorer", - "description": "[technical preview] Enable the APM Trace Explorer feature, that allows you to search and inspect traces with KQL or EQL. Learn more.", - "value": true, - "requiresPageReload": true, - "type": "boolean", - "showInLabs": true - }, - "observability:apmLabsButton": { - "category": [ - "observability" - ], - "name": "Enable labs button in APM", - "description": "This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in APM.", - "value": false, - "requiresPageReload": true, - "type": "boolean" - }, - "observability:enableInfrastructureHostsView": { - "category": [ - "observability" - ], - "name": "Infrastructure Hosts view", - "value": true, - "description": "[beta] Enable the Hosts view in the Infrastructure app." - }, - "observability:enableAwsLambdaMetrics": { - "category": [ - "observability" - ], - "name": "AWS Lambda Metrics", - "description": "[technical preview] Display Amazon Lambda metrics in the service metrics tab.", - "value": true, - "requiresPageReload": true, - "type": "boolean", - "showInLabs": true - }, - "observability:apmAgentExplorerView": { - "category": [ - "observability" - ], - "name": "Agent explorer", - "description": "[beta] Enables Agent explorer view.", - "value": true, - "requiresPageReload": true, - "type": "boolean" - }, - "observability:apmAWSLambdaPriceFactor": { - "category": [ - "observability" - ], - "name": "AWS lambda price factor", - "type": "json", - "value": "{\n \"x86_64\": 0.0000166667,\n \"arm\": 0.0000133334\n}", - "description": "Price per Gb-second." - }, - "observability:apmAWSLambdaRequestCostPerMillion": { - "category": [ - "observability" - ], - "name": "AWS lambda price per 1M requests", - "value": 0.2 - }, - "observability:apmEnableServiceMetrics": { - "category": [ - "observability" - ], - "name": "Service transaction metrics", - "value": true, - "description": "[beta] Enables the usage of service transaction metrics, which are low cardinality metrics that can be used by certain views like the service inventory for faster loading times.", - "requiresPageReload": true - }, - "observability:apmEnableContinuousRollups": { - "category": [ - "observability" - ], - "name": "Continuous rollups", - "value": true, - "description": "[beta] When continuous rollups is enabled, the UI will select metrics with the appropriate resolution. On larger time ranges, lower resolution metrics will be used, which will improve loading times.", - "requiresPageReload": true - }, - "observability:apmEnableCriticalPath": { - "category": [ - "observability" - ], - "name": "Critical path", - "description": "[technical preview] Optionally display the critical path of a trace.", - "value": false, - "requiresPageReload": true, - "type": "boolean", - "showInLabs": true - }, - "observability:syntheticsThrottlingEnabled": { - "category": [ - "observability" - ], - "name": "Enable Synthetics throttling (Experimental)", - "value": false, - "description": "Enable the throttling setting in Synthetics monitor configurations. Note that throttling may still not be available for your monitors even if the setting is active. Intended for internal use only. read notice here.", - "requiresPageReload": true - }, - "observability:enableLegacyUptimeApp": { - "category": [ - "observability" - ], - "name": "Always show legacy Uptime app", - "value": false, - "description": "By default, the legacy Uptime app is hidden from the interface when it doesn't have any data for more than a week. Enable this option to always show it.", - "requiresPageReload": true - }, - "observability:apmEnableProfilingIntegration": { - "category": [ - "observability" - ], - "name": "Enable Universal Profiling integration in APM", - "value": false, - "requiresPageReload": false - }, - "ml:anomalyDetection:results:enableTimeDefaults": { - "name": "Enable time filter defaults for anomaly detection results", - "value": false, - "description": "Use the default time filter in the Single Metric Viewer and Anomaly Explorer. If not enabled, the results for the full time range of the job are displayed.", - "category": [ - "machineLearning" - ] - }, - "ml:anomalyDetection:results:timeDefaults": { - "name": "Time filter defaults for anomaly detection results", - "type": "json", - "value": "{\n \"from\": \"now-15m\",\n \"to\": \"now\"\n}", - "description": "The time filter selection to use when viewing anomaly detection job results.", - "requiresPageReload": true, - "category": [ - "machineLearning" - ] - }, - "securitySolution:refreshIntervalDefaults": { - "type": "json", - "name": "Time filter refresh interval", - "value": "{\n \"pause\": true,\n \"value\": 300000\n}", - "description": "

Default refresh interval for the Security time filter, in milliseconds.

", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 0 - }, - "securitySolution:timeDefaults": { - "type": "json", - "name": "Time filter period", - "value": "{\n \"from\": \"now/d\",\n \"to\": \"now/d\"\n}", - "description": "

Default period of time in the Security time filter.

", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 1 - }, - "securitySolution:defaultIndex": { - "name": "Elasticsearch indices", - "sensitive": true, - "value": [ - "apm-*-transaction*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "traces-apm*", - "winlogbeat-*", - "-*elastic-cloud-logs-*" - ], - "description": "

Comma-delimited list of Elasticsearch indices from which the Security app collects events.

", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 2 - }, - "securitySolution:defaultThreatIndex": { - "name": "Threat indices", - "sensitive": true, - "value": [ - "logs-ti_*" - ], - "description": "

Comma-delimited list of Threat Intelligence indices from which the Security app collects indicators.

", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 3 - }, - "securitySolution:defaultAnomalyScore": { - "name": "Anomaly threshold", - "value": 50, - "type": "number", - "description": "

Value above which Machine Learning job anomalies are displayed in the Security app.

Valid values: 0 to 100.

", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 4 - }, - "securitySolution:enableNewsFeed": { - "name": "News feed", - "value": true, - "description": "

Enables the News feed

", - "type": "boolean", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 5 - }, - "securitySolution:enableExpandableFlyout": { - "name": "Expandable flyout", - "value": true, - "description": "

Enables the expandable flyout

", - "type": "boolean", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 6 - }, - "securitySolution:rulesTableRefresh": { - "name": "Rules auto refresh", - "description": "

Enables auto refresh on the rules and monitoring tables, in milliseconds

", - "type": "json", - "value": "{\n \"on\": true,\n \"value\": 60000\n}", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 7 - }, - "securitySolution:newsFeedUrl": { - "name": "News feed URL", - "value": "https://feeds.elastic.co/security-solution", - "sensitive": true, - "description": "

News feed content will be retrieved from this URL

", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 8 - }, - "securitySolution:ipReputationLinks": { - "name": "IP Reputation Links", - "value": "[\n { \"name\": \"virustotal.com\", \"url_template\": \"https://www.virustotal.com/gui/search/{{ip}}\" },\n { \"name\": \"talosIntelligence.com\", \"url_template\": \"https://talosintelligence.com/reputation_center/lookup?search={{ip}}\" }\n]", - "type": "json", - "description": "Array of URL templates to build the list of reputation URLs to be displayed on the IP Details page.", - "sensitive": true, - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 9 - }, - "securitySolution:enableCcsWarning": { - "name": "CCS Rule Privileges Warning", - "value": true, - "description": "

Enables privilege check warnings in rules for CCS indices

", - "type": "boolean", - "category": [ - "securitySolution" - ], - "requiresPageReload": false, - "order": 10 - }, - "securitySolution:showRelatedIntegrations": { - "name": "Related integrations", - "value": true, - "description": "

Shows related integrations on the rules and monitoring tables

", - "type": "boolean", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 11 - }, - "securitySolution:alertTags": { - "name": "Alert tagging options", - "sensitive": true, - "value": [ - "Duplicate", - "False Positive", - "Further investigation required" - ], - "description": "

List of tag options for use with alerts generated by Security Solution rules.

", - "category": [ - "securitySolution" - ], - "requiresPageReload": true, - "order": 12 - }, - "visualization:visualize:legacyGaugeChartsLibrary": { - "name": "Gauge legacy charts library", - "requiresPageReload": true, - "value": true, - "description": "Enables legacy charts library for gauge charts in visualize.", - "category": [ - "visualization" - ] - }, - "isDefaultIndexMigrated": { - "userValue": true - } -} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/form/types.ts b/packages/kbn-management/settings/components/form/types.ts index 4d4f400d4a0fe..2e803cbd74cb5 100644 --- a/packages/kbn-management/settings/components/form/types.ts +++ b/packages/kbn-management/settings/components/form/types.ts @@ -14,6 +14,7 @@ import { SettingType, 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'; +import { ToastsStart } from '@kbn/core-notifications-browser'; /** * Contextual services used by a {@link Form} component. @@ -33,12 +34,14 @@ export type FormServices = FieldRowServices & Services; * An interface containing a collection of Kibana plugins and services required to * render a {@link Form} component. */ -export interface KibanaDependencies { +interface KibanaDependencies { settings: { client: SettingsStart['client']; }; theme: ThemeServiceStart; i18nStart: I18nStart; + /** The portion of the {@link ToastsStart} contract used by this component. */ + toasts: Pick; } /** From cb9e3570d48a552aeb36dcd86cf65ff6c0899c58 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 08:20:54 +0100 Subject: [PATCH 12/26] Add settings mock and fix types --- .../settings/components/field_input/types.ts | 2 +- .../settings/components/form/mocks/index.ts | 1 + .../components/form/mocks/settings.ts | 108 ++++++++++++++++++ .../components/form/reload_page_toast.tsx | 4 +- .../form/storybook/get_form_story.tsx | 104 +---------------- 5 files changed, 115 insertions(+), 104 deletions(-) create mode 100644 packages/kbn-management/settings/components/form/mocks/settings.ts diff --git a/packages/kbn-management/settings/components/field_input/types.ts b/packages/kbn-management/settings/components/field_input/types.ts index cbb5db35b2d1a..e5d5c11e2f199 100644 --- a/packages/kbn-management/settings/components/field_input/types.ts +++ b/packages/kbn-management/settings/components/field_input/types.ts @@ -31,7 +31,7 @@ export interface FieldInputServices { */ export interface FieldInputKibanaDependencies { /** The portion of the {@link ToastsStart} contract used by this component. */ - toasts: Pick; + toasts: Pick; } /** diff --git a/packages/kbn-management/settings/components/form/mocks/index.ts b/packages/kbn-management/settings/components/form/mocks/index.ts index 3fc27f74446b1..416cc2c7a47e3 100644 --- a/packages/kbn-management/settings/components/form/mocks/index.ts +++ b/packages/kbn-management/settings/components/form/mocks/index.ts @@ -7,3 +7,4 @@ */ export { TestWrapper, createFormServicesMock, wrap } from './context'; +export { settingsMock } from './settings'; diff --git a/packages/kbn-management/settings/components/form/mocks/settings.ts b/packages/kbn-management/settings/components/form/mocks/settings.ts new file mode 100644 index 0000000000000..10a4f021d4be7 --- /dev/null +++ b/packages/kbn-management/settings/components/form/mocks/settings.ts @@ -0,0 +1,108 @@ +/* + * 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, + category: ['category'], +}; + +type Settings = { + [key in SettingType]: KnownTypeToMetadata; +}; + +export const settingsMock: Settings = { + array: { + description: 'Description for Array test setting', + name: 'array:test:setting', + type: 'array', + userValue: null, + value: ['example_value'], + ...defaults, + }, + boolean: { + description: 'Description for Boolean test setting', + name: 'boolean:test:setting', + type: 'boolean', + userValue: null, + value: true, + ...defaults, + }, + color: { + description: 'Description for Color test setting', + name: 'color:test:setting', + type: 'color', + userValue: null, + value: '#FF00CC', + ...defaults, + }, + image: { + description: 'Description for Image test setting', + name: 'image:test:setting', + type: 'image', + userValue: null, + value: '', + ...defaults, + }, + number: { + description: 'Description for Number test setting', + name: 'number:test:setting', + type: 'number', + userValue: null, + value: 1, + ...defaults, + }, + json: { + name: 'json:test:setting', + description: 'Description for Json test setting', + type: 'json', + userValue: '{"foo": "bar"}', + 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', + ...defaults, + }, + string: { + description: 'Description for String test setting', + name: 'string:test:setting', + type: 'string', + userValue: null, + value: 'hello world', + ...defaults, + }, + undefined: { + description: 'Description for Undefined test setting', + name: 'undefined:test:setting', + type: 'undefined', + userValue: null, + value: undefined, + ...defaults, + }, +}; diff --git a/packages/kbn-management/settings/components/form/reload_page_toast.tsx b/packages/kbn-management/settings/components/form/reload_page_toast.tsx index d12c6d0fe77f0..0e557e3a6d93a 100644 --- a/packages/kbn-management/settings/components/form/reload_page_toast.tsx +++ b/packages/kbn-management/settings/components/form/reload_page_toast.tsx @@ -8,10 +8,10 @@ import React from 'react'; -import { i18n } from '@kbn/i18n/target/types'; +import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ToastInput } from '@kbn/core-notifications-browser/target/types'; +import { ToastInput } from '@kbn/core-notifications-browser'; import { I18nStart } from '@kbn/core-i18n-browser'; import { ThemeServiceStart } from '@kbn/core-theme-browser'; diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index 9170947b759ff..7f160a09956a1 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -8,114 +8,16 @@ import React from 'react'; -import { FieldDefinition, KnownTypeToMetadata, SettingType } from '@kbn/management-settings-types'; +import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinition } from '@kbn/management-settings-field-definition'; import { Form, FormProps } from '../form'; - -const defaults = { - requiresPageReload: false, - readonly: false, - category: ['category'], -}; - -type Settings = { - [key in SettingType]: KnownTypeToMetadata; -}; - -const settings: Settings = { - array: { - description: 'Description for Array test setting', - name: 'array:test:setting', - type: 'array', - userValue: null, - value: ['example_value'], - ...defaults, - }, - boolean: { - description: 'Description for Boolean test setting', - name: 'boolean:test:setting', - type: 'boolean', - userValue: null, - value: true, - ...defaults, - }, - color: { - description: 'Description for Color test setting', - name: 'color:test:setting', - type: 'color', - userValue: null, - value: '#FF00CC', - ...defaults, - }, - image: { - description: 'Description for Image test setting', - name: 'image:test:setting', - type: 'image', - userValue: null, - value: '', - ...defaults, - }, - number: { - description: 'Description for Number test setting', - name: 'number:test:setting', - type: 'number', - userValue: null, - value: 1, - ...defaults, - }, - json: { - name: 'json:test:setting', - description: 'Description for Json test setting', - type: 'json', - userValue: '{"foo": "bar"}', - 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', - ...defaults, - }, - string: { - description: 'Description for String test setting', - name: 'string:test:setting', - type: 'string', - userValue: null, - value: 'hello world', - ...defaults, - }, - undefined: { - description: 'Description for Undefined test setting', - name: 'undefined:test:setting', - type: 'undefined', - userValue: null, - value: undefined, - ...defaults, - }, -}; +import { settingsMock } from '../mocks'; export type StoryProps = Pick; export const getFormStory = () => { const Story = ({ isSavingEnabled }: StoryProps) => { - const fields: Array> = Object.entries(settings).map( + const fields: Array> = Object.entries(settingsMock).map( ([id, setting]) => getFieldDefinition({ id, From 46e5652a86ba688baa9c7e4f320ec3e82569702c Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 20 Sep 2023 07:30:10 +0000 Subject: [PATCH 13/26] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/kbn-management/settings/components/form/tsconfig.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kbn-management/settings/components/form/tsconfig.json b/packages/kbn-management/settings/components/form/tsconfig.json index 2c027ec1614e6..a4bfc74006397 100644 --- a/packages/kbn-management/settings/components/form/tsconfig.json +++ b/packages/kbn-management/settings/components/form/tsconfig.json @@ -20,12 +20,10 @@ "@kbn/management-settings-field-definition", "@kbn/i18n", "@kbn/i18n-react", - "@kbn/management-settings-utilities", "@kbn/management-settings-components-field-row", "@kbn/react-kibana-context-root", "@kbn/core-theme-browser-mocks", "@kbn/core-i18n-browser", - "@kbn/react-kibana-context-theme", "@kbn/react-kibana-mount", "@kbn/core-notifications-browser", "@kbn/core-theme-browser", From de2eb28f294e5bad65d9f25913f652579b35bfa7 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 09:11:43 +0100 Subject: [PATCH 14/26] Fix bottom bar tests --- .../form/bottom_bar/bottom_bar.test.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx index 0d5ae58d536ef..ff57bffb4d420 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx @@ -18,15 +18,18 @@ import { wrap } from '../mocks'; const saveAll = jest.fn(); const clearAllUnsaved = jest.fn(); +const unsavedChangesCount = 3; const defaultProps: BottomBarProps = { saveAll, clearAllUnsaved, hasInvalidChanges: false, - unsavedChangesCount: 3, + unsavedChangesCount, isLoading: false, }; +const unsavedChangesCountText = unsavedChangesCount + ' unsaved settings'; + describe('BottomBar', () => { it('renders without errors', () => { const { container } = render(wrap()); @@ -49,11 +52,10 @@ describe('BottomBar', () => { expect(saveAll).toHaveBeenCalled(); }); - // TODO: fix this - it.skip('renders unsaved changes count', () => { - // const { getByTestId } = render(wrap()); - // const input = getByTestId(DATA_TEST_SUBJ_UNSAVED_COUNT); - // expect(input).toBe('3 unsaved settings'); + it('renders unsaved changes count', () => { + const { getByText } = render(wrap()); + + expect(getByText(unsavedChangesCountText)).toBeInTheDocument(); }); it('save button is disabled when there are invalid changes', () => { @@ -65,11 +67,13 @@ describe('BottomBar', () => { expect(input).toBeDisabled(); }); - // TODO: fix this - it.skip('save button is loading when in loading state', () => { - const { getByTestId } = render(wrap()); + it('save button is loading when in loading state', () => { + const { getByTestId, getByLabelText } = render( + wrap() + ); const input = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); expect(input).toBeDisabled(); + expect(getByLabelText('Loading')).toBeInTheDocument(); }); }); From cefe38cec28cdbcd4ca2499052ab2149a6bf712f Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 12:39:52 +0100 Subject: [PATCH 15/26] Add form tests --- .../components/form/bottom_bar/index.tsx | 2 +- .../settings/components/form/form.test.tsx | 226 ++++++++++-------- .../components/form/mocks/context.tsx | 8 +- .../settings/components/form/services.tsx | 2 +- .../settings/components/form/tsconfig.json | 1 + 5 files changed, 127 insertions(+), 112 deletions(-) diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx index 0abbe24520157..9fc2f7093fa80 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { BottomBar } from './bottom_bar'; +export { BottomBar, DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar'; diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index e98a10364a7c9..e035d2982997e 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -7,119 +7,34 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinition } from '@kbn/management-settings-field-definition'; -import { KnownTypeToMetadata } from '@kbn/management-settings-types/metadata'; import { Form } from './form'; -import { wrap } from './mocks'; +import { wrap, settingsMock } from './mocks'; +import { TEST_SUBJ_PREFIX_FIELD } from '@kbn/management-settings-components-field-input/input'; +import { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar'; +import { FormServices } from './types'; -const defaults = { - requiresPageReload: false, - readonly: false, - category: ['category'], -}; - -const defaultValues: Record = { - array: ['example_value'], - boolean: true, - color: '#FF00CC', - image: '', - json: "{ foo: 'bar2' }", - markdown: 'Hello World', - number: 1, - select: 'apple', - string: 'hello world', - undefined: 'undefined', -}; - -type Settings = { - [key in SettingType]: KnownTypeToMetadata; -}; +const fields: Array> = Object.entries(settingsMock).map( + ([id, setting]) => + getFieldDefinition({ + id, + setting, + params: { isCustom: false, isOverridden: setting.isOverridden }, + }) +); -const settings: Omit = { - array: { - description: 'Description for Array test setting', - name: 'array:test:setting', - type: 'array', - userValue: null, - value: defaultValues.array, - ...defaults, - }, - boolean: { - description: 'Description for Boolean test setting', - name: 'boolean:test:setting', - type: 'boolean', - userValue: null, - value: defaultValues.boolean, - ...defaults, - }, - color: { - description: 'Description for Color test setting', - name: 'color:test:setting', - type: 'color', - userValue: null, - value: defaultValues.color, - ...defaults, - }, - image: { - description: 'Description for Image test setting', - name: 'image:test:setting', - type: 'image', - userValue: null, - value: defaultValues.image, - ...defaults, - }, - number: { - description: 'Description for Number test setting', - name: 'number:test:setting', - type: 'number', - userValue: null, - value: defaultValues.number, - ...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: defaultValues.select, - ...defaults, - }, - string: { - description: 'Description for String test setting', - name: 'string:test:setting', - type: 'string', - userValue: null, - value: defaultValues.string, - ...defaults, - }, - undefined: { - description: 'Description for Undefined test setting', - name: 'undefined:test:setting', - type: 'undefined', - userValue: null, - value: defaultValues.undefined, - ...defaults, - }, +const services: FormServices = { + showDanger: jest.fn(), + links: {}, + saveChanges: jest.fn(), + showError: jest.fn(), + showReloadPagePrompt: jest.fn(), }; -const fields: Array> = Object.entries(settings).map(([id, setting]) => - getFieldDefinition({ - id, - setting, - params: { isCustom: false, isOverridden: setting.isOverridden }, - }) -); - describe('Form', () => { beforeEach(() => { jest.clearAllMocks(); @@ -131,5 +46,106 @@ describe('Form', () => { expect(container).toBeInTheDocument(); }); - // TODO: Add more test cases + it('renders as read only if saving is disabled', () => { + const { getByTestId } = render(wrap()); + + (Object.keys(settingsMock) as SettingType[]).forEach((type) => { + if (type === 'json' || type === 'markdown') { + return; + } + + const inputTestSubj = `${TEST_SUBJ_PREFIX_FIELD}-${type}`; + + if (type === 'color') { + expect(getByTestId(`euiColorPickerAnchor ${inputTestSubj}`)).toBeDisabled(); + } else { + expect(getByTestId(inputTestSubj)).toBeDisabled(); + } + }); + }); + + it('renders bottom bar when a field is changed', () => { + const { getByTestId, queryByTestId } = render( + wrap() + ); + + expect(queryByTestId(DATA_TEST_SUBJ_SAVE_BUTTON)).not.toBeInTheDocument(); + expect(queryByTestId(DATA_TEST_SUBJ_CANCEL_BUTTON)).not.toBeInTheDocument(); + + const testFieldType = 'string'; + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${testFieldType}`); + fireEvent.change(input, { target: { value: 'test' } }); + + expect(getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON)).toBeInTheDocument(); + expect(getByTestId(DATA_TEST_SUBJ_CANCEL_BUTTON)).toBeInTheDocument(); + }); + + // TODO: fix + it.skip('fires saveChanges when Save button is clicked', () => { + const { getByTestId } = render(wrap(, services)); + + const testFieldType = 'string'; + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${testFieldType}`); + fireEvent.change(input, { target: { value: 'test' } }); + + const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); + fireEvent.click(saveButton); + + expect(services.saveChanges).toHaveBeenCalledTimes(1); + }); + + it('clears changes when Cancel button is clicked', () => { + const { getByTestId } = render(wrap()); + + const testFieldType = 'string'; + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${testFieldType}`); + fireEvent.change(input, { target: { value: 'test' } }); + + const cancelButton = getByTestId(DATA_TEST_SUBJ_CANCEL_BUTTON); + fireEvent.click(cancelButton); + + expect(input).toHaveValue(settingsMock[testFieldType].value); + }); + + // TODO: fix + it.skip('fires showError when saving is unsuccessful', () => { + const saveChangesWithError = jest.fn(() => { + throw new Error('Unable to save'); + }); + const testServices = { ...services, saveChanges: saveChangesWithError }; + + const { getByTestId } = render( + wrap(, testServices) + ); + + const testFieldType = 'string'; + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${testFieldType}`); + fireEvent.change(input, { target: { value: 'test' } }); + + const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); + fireEvent.click(saveButton); + + expect(services.showError).toHaveBeenCalled(); + }); + + // TODO: fix + it.skip('fires showReloadPagePrompt when changing a reloadPageRequired setting', () => { + // Make all settings require a page reload + const testFields = fields.map((field) => { + return { ...field, requiresPageReload: true }; + }); + + const { getByTestId } = render( + wrap(, services) + ); + + const testFieldType = 'string'; + const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${testFieldType}`); + fireEvent.change(input, { target: { value: 'test' } }); + + const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); + fireEvent.click(saveButton); + + expect(services.showReloadPagePrompt).toHaveBeenCalled(); + }); }); diff --git a/packages/kbn-management/settings/components/form/mocks/context.tsx b/packages/kbn-management/settings/components/form/mocks/context.tsx index 11024930612ea..a280e09bda765 100644 --- a/packages/kbn-management/settings/components/form/mocks/context.tsx +++ b/packages/kbn-management/settings/components/form/mocks/context.tsx @@ -14,7 +14,6 @@ import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; import { I18nStart } from '@kbn/core-i18n-browser'; import { createFieldRowServicesMock } from '@kbn/management-settings-components-field-row/mocks'; -import type { FieldRowServices } from '@kbn/management-settings-components-field-row'; import { FormProvider } from '../services'; import type { FormServices } from '../types'; @@ -50,7 +49,6 @@ export const TestWrapper = ({ ); }; -export const wrap = ( - component: JSX.Element, - services: FieldRowServices = createFieldRowServicesMock() -) => {component}; +export const wrap = (component: JSX.Element, services: FormServices = createFormServicesMock()) => ( + {component} +); diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index 401da46f58897..d28a524419e4c 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -66,7 +66,7 @@ export const useServices = () => { if (!context) { throw new Error( - 'FormContext is missing. Ensure your component or React root is wrapped with FormProvider.' + 'FormContext is missing. Ensure your component or React root is wrapped with FormProvider.' ); } diff --git a/packages/kbn-management/settings/components/form/tsconfig.json b/packages/kbn-management/settings/components/form/tsconfig.json index a4bfc74006397..5e6e13668b16e 100644 --- a/packages/kbn-management/settings/components/form/tsconfig.json +++ b/packages/kbn-management/settings/components/form/tsconfig.json @@ -6,6 +6,7 @@ "jest", "node", "react", + "@testing-library/jest-dom", ] }, "include": [ From 2f27856759b37830efcdc7088ab0aa13ea28961f Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:47:53 +0000 Subject: [PATCH 16/26] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/kbn-management/settings/components/form/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kbn-management/settings/components/form/tsconfig.json b/packages/kbn-management/settings/components/form/tsconfig.json index 5e6e13668b16e..359e5560fd2e4 100644 --- a/packages/kbn-management/settings/components/form/tsconfig.json +++ b/packages/kbn-management/settings/components/form/tsconfig.json @@ -29,5 +29,6 @@ "@kbn/core-notifications-browser", "@kbn/core-theme-browser", "@kbn/core-ui-settings-browser", + "@kbn/management-settings-components-field-input", ] } From acd943908ea6a3e0f8f5e3b74e904a4fc7a862a1 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 15:29:16 +0100 Subject: [PATCH 17/26] Add documentation --- .../kbn-management/settings/components/form/README.mdx | 10 ++++++++-- .../settings/components/form/bottom_bar/bottom_bar.tsx | 6 ++++++ .../components/form/bottom_bar/unsaved_count.tsx | 6 ++++++ .../settings/components/form/form.test.tsx | 6 +++--- .../kbn-management/settings/components/form/form.tsx | 9 +++++++++ .../settings/components/form/mocks/settings.ts | 3 +++ .../settings/components/form/reload_page_toast.tsx | 8 +++++++- .../settings/components/form/services.tsx | 4 ++-- .../components/form/storybook/get_form_story.tsx | 4 ++++ 9 files changed, 48 insertions(+), 8 deletions(-) diff --git a/packages/kbn-management/settings/components/form/README.mdx b/packages/kbn-management/settings/components/form/README.mdx index 990702a4cc6fd..f4c17450c733b 100644 --- a/packages/kbn-management/settings/components/form/README.mdx +++ b/packages/kbn-management/settings/components/form/README.mdx @@ -2,11 +2,17 @@ id: management/settings/components/form slug: /management/settings/components/form title: Management Settings form Component -description: A package containing a component for rendering and manipulating the form in the Advanced Settings UI. +description: A package containing a component for rendering the form in the Advanced Settings UI. tags: ['management', 'settings'] date: 2023-09-12 --- ## Description -This package contains a component for rendering and manipulating the form containing the setting fields in the Advanced Settings UI. +This package contains a component for rendering the Advanced Settings UI form that contains `FieldRow` components, each of which displays a single UiSetting field row. +The form also handles the logic for saving any changes to the UiSettings values by directly communicating with the uiSettings service. + + +## Notes + +- This implementation was extracted from the `Form` component in the `advancedSettings` plugin. \ No newline at end of file diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx index 28d77d853f8b8..8a4398ab38f0d 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx @@ -23,6 +23,9 @@ import { useFormStyles } from '../form.styles'; export const DATA_TEST_SUBJ_SAVE_BUTTON = 'settings-save-button'; export const DATA_TEST_SUBJ_CANCEL_BUTTON = 'settings-cancel-button'; +/** + * Props for a {@link BottomBar} component. + */ export interface BottomBarProps { saveAll: () => void; clearAllUnsaved: () => void; @@ -31,6 +34,9 @@ export interface BottomBarProps { unsavedChangesCount: number; } +/** + * Component for displaying the bottom bar of a {@link Form}. + */ export const BottomBar = ({ saveAll, clearAllUnsaved, diff --git a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx index fb664a1dc1c42..a68262db69df6 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx @@ -12,10 +12,16 @@ import { EuiTextColor } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useFormStyles } from '../form.styles'; +/** + * Props for a {@link UnsavedCount} component. + */ interface UnsavedCountProps { unsavedCount: number; } +/** + * Component for displaying the count of unsaved changes in a {@link BottomBar}. + */ export const UnsavedCount = ({ unsavedCount }: UnsavedCountProps) => { const { cssFormUnsavedCountMessage } = useFormStyles(); return ( diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index e035d2982997e..4516d040fe5fc 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -91,7 +91,7 @@ describe('Form', () => { const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); fireEvent.click(saveButton); - expect(services.saveChanges).toHaveBeenCalledTimes(1); + expect(services.saveChanges).toHaveBeenCalled(); }); it('clears changes when Cancel button is clicked', () => { @@ -115,7 +115,7 @@ describe('Form', () => { const testServices = { ...services, saveChanges: saveChangesWithError }; const { getByTestId } = render( - wrap(, testServices) + wrap(, testServices) ); const testFieldType = 'string'; @@ -136,7 +136,7 @@ describe('Form', () => { }); const { getByTestId } = render( - wrap(, services) + wrap(, services) ); const testFieldType = 'string'; diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index 5cea7868ed8c0..90bd679ef96be 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -16,11 +16,20 @@ import { i18n } from '@kbn/i18n'; import { BottomBar } from './bottom_bar'; import { useServices } from './services'; +/** + * Props for a {@link Form} component. + */ export interface FormProps { + /** A list of {@link FieldDefinition} corresponding to settings to be displayed in the form. */ fields: Array>; + /** True if saving settings is enabled, false otherwise. */ isSavingEnabled: boolean; } +/** + * Component for displaying a set of {@link FieldRow} in a form. + * @param props The {@link FormProps} for the {@link Form} component. + */ export const Form = (props: FormProps) => { const { fields, isSavingEnabled } = props; const { saveChanges, showError, showReloadPagePrompt } = useServices(); diff --git a/packages/kbn-management/settings/components/form/mocks/settings.ts b/packages/kbn-management/settings/components/form/mocks/settings.ts index 10a4f021d4be7..8770828f1cbe2 100644 --- a/packages/kbn-management/settings/components/form/mocks/settings.ts +++ b/packages/kbn-management/settings/components/form/mocks/settings.ts @@ -18,6 +18,9 @@ type Settings = { [key in SettingType]: KnownTypeToMetadata; }; +/** + * A representative set of UiSettings. + */ export const settingsMock: Settings = { array: { description: 'Description for Array test setting', diff --git a/packages/kbn-management/settings/components/form/reload_page_toast.tsx b/packages/kbn-management/settings/components/form/reload_page_toast.tsx index 0e557e3a6d93a..663e1ed2900a2 100644 --- a/packages/kbn-management/settings/components/form/reload_page_toast.tsx +++ b/packages/kbn-management/settings/components/form/reload_page_toast.tsx @@ -15,7 +15,13 @@ import { ToastInput } from '@kbn/core-notifications-browser'; import { I18nStart } from '@kbn/core-i18n-browser'; import { ThemeServiceStart } from '@kbn/core-theme-browser'; -export const ReloadPageToast = (theme: ThemeServiceStart, i18nStart: I18nStart): ToastInput => { +/** + * Utility function for returning a {@link ToastInput} for displaying a prompt for reloading the page. + * @param theme The {@link ThemeServiceStart} contract. + * @param i18nStart The {@link I18nStart} contract. + * @returns A toast. + */ +export const reloadPageToast = (theme: ThemeServiceStart, i18nStart: I18nStart): ToastInput => { return { title: i18n.translate('management.settings.form.requiresPageReloadToastDescription', { defaultMessage: 'One or more settings require you to reload the page to take effect.', diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index d28a524419e4c..eb03d3563b567 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -14,7 +14,7 @@ import React, { FC, useContext } from 'react'; import { SettingType, UnsavedFieldChange } from '@kbn/management-settings-types'; import type { FormServices, FormKibanaDependencies, Services } from './types'; -import { ReloadPageToast } from './reload_page_toast'; +import { reloadPageToast } from './reload_page_toast'; const FormContext = React.createContext(null); @@ -48,7 +48,7 @@ export const FormKibanaProvider: FC = ({ children, ...de return Promise.all(arr); }, showError: (message: string) => toasts.addDanger(message), - showReloadPagePrompt: () => toasts.add(ReloadPageToast(theme, i18nStart)), + showReloadPagePrompt: () => toasts.add(reloadPageToast(theme, i18nStart)), }} > {children} diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index 7f160a09956a1..a2c3d5585c0cd 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -15,6 +15,10 @@ import { settingsMock } from '../mocks'; export type StoryProps = Pick; +/** + * Utility function for returning a {@link Form} Storybook story. + * @returns A Storybook Story. + */ export const getFormStory = () => { const Story = ({ isSavingEnabled }: StoryProps) => { const fields: Array> = Object.entries(settingsMock).map( From 9b428266ba986662e2598337394e0d56a5eff098 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 16:45:51 +0100 Subject: [PATCH 18/26] Refactor form tests --- .../settings/components/form/form.test.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index 4516d040fe5fc..72e6a00509bae 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -13,7 +13,7 @@ import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinition } from '@kbn/management-settings-field-definition'; import { Form } from './form'; -import { wrap, settingsMock } from './mocks'; +import { wrap, settingsMock, createFormServicesMock } from './mocks'; import { TEST_SUBJ_PREFIX_FIELD } from '@kbn/management-settings-components-field-input/input'; import { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar'; import { FormServices } from './types'; @@ -27,14 +27,6 @@ const fields: Array> = Object.entries(settingsMock) }) ); -const services: FormServices = { - showDanger: jest.fn(), - links: {}, - saveChanges: jest.fn(), - showError: jest.fn(), - showReloadPagePrompt: jest.fn(), -}; - describe('Form', () => { beforeEach(() => { jest.clearAllMocks(); @@ -82,6 +74,7 @@ describe('Form', () => { // TODO: fix it.skip('fires saveChanges when Save button is clicked', () => { + const services: FormServices = createFormServicesMock(); const { getByTestId } = render(wrap(, services)); const testFieldType = 'string'; @@ -91,7 +84,9 @@ describe('Form', () => { const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); fireEvent.click(saveButton); - expect(services.saveChanges).toHaveBeenCalled(); + expect(services.saveChanges).toHaveBeenCalledWith({ + string: { type: 'string', unsavedValue: 'test' }, + }); }); it('clears changes when Cancel button is clicked', () => { @@ -109,6 +104,7 @@ describe('Form', () => { // TODO: fix it.skip('fires showError when saving is unsuccessful', () => { + const services: FormServices = createFormServicesMock(); const saveChangesWithError = jest.fn(() => { throw new Error('Unable to save'); }); @@ -130,6 +126,7 @@ describe('Form', () => { // TODO: fix it.skip('fires showReloadPagePrompt when changing a reloadPageRequired setting', () => { + const services: FormServices = createFormServicesMock(); // Make all settings require a page reload const testFields = fields.map((field) => { return { ...field, requiresPageReload: true }; From 41f0838ec8b6f8b75e5db8328cc4ba2cc3a87fad Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Wed, 20 Sep 2023 11:46:09 -0400 Subject: [PATCH 19/26] Update get_definitions.ts This should help with Storybook mocks. --- .../settings/field_definition/get_definitions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kbn-management/settings/field_definition/get_definitions.ts b/packages/kbn-management/settings/field_definition/get_definitions.ts index c42613c8c2ce1..83d604db294cf 100644 --- a/packages/kbn-management/settings/field_definition/get_definitions.ts +++ b/packages/kbn-management/settings/field_definition/get_definitions.ts @@ -10,6 +10,8 @@ import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { FieldDefinition, SettingType, UiSettingMetadata } from '@kbn/management-settings-types'; import { getFieldDefinition } from './get_definition'; +type SettingsClient = Pick; + /** * Convenience function to convert settings taken from a UiSettingsClient into * {@link FieldDefinition} objects. @@ -20,7 +22,7 @@ import { getFieldDefinition } from './get_definition'; */ export const getFieldDefinitions = ( settings: Record>, - client: IUiSettingsClient + client: SettingsClient ): Array> => Object.entries(settings).map(([id, setting]) => getFieldDefinition({ From 6397470f9d187adbabb8a140e34abad9772c03f9 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 18:01:54 +0100 Subject: [PATCH 20/26] Use getDefintions to create fields --- .../settings/components/form/README.mdx | 2 +- .../settings/components/form/form.test.tsx | 16 ++++++---------- .../settings/components/form/mocks/index.ts | 1 + .../settings/components/form/mocks/settings.ts | 2 +- .../components/form/mocks/settings_client.ts | 17 +++++++++++++++++ .../form/storybook/get_form_story.tsx | 13 +++++-------- 6 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 packages/kbn-management/settings/components/form/mocks/settings_client.ts diff --git a/packages/kbn-management/settings/components/form/README.mdx b/packages/kbn-management/settings/components/form/README.mdx index f4c17450c733b..163f476284f89 100644 --- a/packages/kbn-management/settings/components/form/README.mdx +++ b/packages/kbn-management/settings/components/form/README.mdx @@ -1,7 +1,7 @@ --- id: management/settings/components/form slug: /management/settings/components/form -title: Management Settings form Component +title: Management Settings Form Component description: A package containing a component for rendering the form in the Advanced Settings UI. tags: ['management', 'settings'] date: 2023-09-12 diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index 72e6a00509bae..8e597f2acf4c8 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -10,21 +10,17 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; -import { getFieldDefinition } from '@kbn/management-settings-field-definition'; +import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; import { Form } from './form'; -import { wrap, settingsMock, createFormServicesMock } from './mocks'; +import { wrap, settingsMock, createFormServicesMock, uiSettingsClientMock } from './mocks'; import { TEST_SUBJ_PREFIX_FIELD } from '@kbn/management-settings-components-field-input/input'; import { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar'; import { FormServices } from './types'; -const fields: Array> = Object.entries(settingsMock).map( - ([id, setting]) => - getFieldDefinition({ - id, - setting, - params: { isCustom: false, isOverridden: setting.isOverridden }, - }) +const fields: Array> = getFieldDefinitions( + settingsMock, + uiSettingsClientMock ); describe('Form', () => { @@ -121,7 +117,7 @@ describe('Form', () => { const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); fireEvent.click(saveButton); - expect(services.showError).toHaveBeenCalled(); + expect(testServices.showError).toHaveBeenCalled(); }); // TODO: fix diff --git a/packages/kbn-management/settings/components/form/mocks/index.ts b/packages/kbn-management/settings/components/form/mocks/index.ts index 416cc2c7a47e3..8bbf8709eadd0 100644 --- a/packages/kbn-management/settings/components/form/mocks/index.ts +++ b/packages/kbn-management/settings/components/form/mocks/index.ts @@ -8,3 +8,4 @@ export { TestWrapper, createFormServicesMock, wrap } from './context'; export { settingsMock } 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 index 8770828f1cbe2..807104f7bebd7 100644 --- a/packages/kbn-management/settings/components/form/mocks/settings.ts +++ b/packages/kbn-management/settings/components/form/mocks/settings.ts @@ -66,7 +66,7 @@ export const settingsMock: Settings = { name: 'json:test:setting', description: 'Description for Json test setting', type: 'json', - userValue: '{"foo": "bar"}', + userValue: null, value: '{"foo": "bar"}', ...defaults, }, diff --git a/packages/kbn-management/settings/components/form/mocks/settings_client.ts b/packages/kbn-management/settings/components/form/mocks/settings_client.ts new file mode 100644 index 0000000000000..7087add88a943 --- /dev/null +++ b/packages/kbn-management/settings/components/form/mocks/settings_client.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. + */ + +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; + +/** + * Mock of the portion of the {@link IUiSettingsClient} used as a parameter in the {@link getFieldDefinitions} function. + */ +export const uiSettingsClientMock: Pick = { + isCustom: () => false, + isOverridden: () => false, +}; diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index a2c3d5585c0cd..0e4dc6f6fc06c 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; -import { getFieldDefinition } from '@kbn/management-settings-field-definition'; +import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; import { Form, FormProps } from '../form'; -import { settingsMock } from '../mocks'; +import { settingsMock, uiSettingsClientMock } from '../mocks'; export type StoryProps = Pick; @@ -21,12 +21,9 @@ export type StoryProps = Pick; */ export const getFormStory = () => { const Story = ({ isSavingEnabled }: StoryProps) => { - const fields: Array> = Object.entries(settingsMock).map( - ([id, setting]) => - getFieldDefinition({ - id, - setting, - }) + const fields: Array> = getFieldDefinitions( + settingsMock, + uiSettingsClientMock ); return ; From 866d1d80707aa45d14ca6314b28784e42a9872eb Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Wed, 20 Sep 2023 19:10:22 +0100 Subject: [PATCH 21/26] Add storybook control for requirePageReload --- .../settings/components/form/form.test.tsx | 3 +- .../settings/components/form/mocks/index.ts | 2 +- .../components/form/mocks/settings.ts | 180 +++++++++--------- .../form/storybook/form.stories.tsx | 4 + .../form/storybook/get_form_story.tsx | 16 +- 5 files changed, 111 insertions(+), 94 deletions(-) diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index 8e597f2acf4c8..aa1080f0fcc6e 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -13,11 +13,12 @@ import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; import { Form } from './form'; -import { wrap, settingsMock, createFormServicesMock, uiSettingsClientMock } from './mocks'; +import { wrap, getSettingsMock, createFormServicesMock, uiSettingsClientMock } from './mocks'; import { TEST_SUBJ_PREFIX_FIELD } from '@kbn/management-settings-components-field-input/input'; import { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar'; import { FormServices } from './types'; +const settingsMock = getSettingsMock(); const fields: Array> = getFieldDefinitions( settingsMock, uiSettingsClientMock diff --git a/packages/kbn-management/settings/components/form/mocks/index.ts b/packages/kbn-management/settings/components/form/mocks/index.ts index 8bbf8709eadd0..80e92448a3bb4 100644 --- a/packages/kbn-management/settings/components/form/mocks/index.ts +++ b/packages/kbn-management/settings/components/form/mocks/index.ts @@ -7,5 +7,5 @@ */ export { TestWrapper, createFormServicesMock, wrap } from './context'; -export { settingsMock } from './settings'; +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 index 807104f7bebd7..6a87fc06fdb30 100644 --- a/packages/kbn-management/settings/components/form/mocks/settings.ts +++ b/packages/kbn-management/settings/components/form/mocks/settings.ts @@ -19,93 +19,99 @@ type Settings = { }; /** - * A representative set of UiSettings. + * A utility function returning a representative set of UiSettings. + * @param requirePageReload The value of the `requirePageReload` param for all settings. */ -export const settingsMock: Settings = { - array: { - description: 'Description for Array test setting', - name: 'array:test:setting', - type: 'array', - userValue: null, - value: ['example_value'], - ...defaults, - }, - boolean: { - description: 'Description for Boolean test setting', - name: 'boolean:test:setting', - type: 'boolean', - userValue: null, - value: true, - ...defaults, - }, - color: { - description: 'Description for Color test setting', - name: 'color:test:setting', - type: 'color', - userValue: null, - value: '#FF00CC', - ...defaults, - }, - image: { - description: 'Description for Image test setting', - name: 'image:test:setting', - type: 'image', - userValue: null, - value: '', - ...defaults, - }, - number: { - description: 'Description for Number test setting', - name: 'number:test:setting', - type: 'number', - userValue: null, - 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, - }, - select: { - description: 'Description for Select test setting', - name: 'select:test:setting', - options: ['apple', 'orange', 'banana'], - optionLabels: { - apple: 'Apple', - orange: 'Orange', - banana: 'Banana', +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: ['example_value'], + ...defaults, }, - type: 'select', - userValue: null, - value: 'apple', - ...defaults, - }, - string: { - description: 'Description for String test setting', - name: 'string:test:setting', - type: 'string', - userValue: null, - value: 'hello world', - ...defaults, - }, - undefined: { - description: 'Description for Undefined test setting', - name: 'undefined:test:setting', - type: 'undefined', - userValue: null, - value: undefined, - ...defaults, - }, + boolean: { + description: 'Description for Boolean test setting', + name: 'boolean:test:setting', + type: 'boolean', + userValue: null, + value: true, + ...defaults, + }, + color: { + description: 'Description for Color test setting', + name: 'color:test:setting', + type: 'color', + userValue: null, + value: '#FF00CC', + ...defaults, + }, + image: { + description: 'Description for Image test setting', + name: 'image:test:setting', + type: 'image', + userValue: null, + value: '', + ...defaults, + }, + number: { + description: 'Description for Number test setting', + name: 'number:test:setting', + type: 'number', + userValue: null, + 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, + }, + 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', + ...defaults, + }, + string: { + description: 'Description for String test setting', + name: 'string:test:setting', + type: 'string', + userValue: null, + value: 'hello world', + ...defaults, + }, + undefined: { + description: 'Description for Undefined test setting', + name: 'undefined:test:setting', + type: 'undefined', + userValue: null, + value: undefined, + ...defaults, + }, + }; }; 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 67121f80b8bc4..217bc7be7dee9 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -21,6 +21,10 @@ export default { name: 'Saving is enabled?', control: { type: 'boolean' }, }, + requirePageReload: { + name: 'Settings require page reload?', + control: { type: 'boolean' }, + }, }, decorators: [ (Story) => ( diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx index 0e4dc6f6fc06c..7401558a3570f 100644 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx @@ -10,19 +10,24 @@ import React from 'react'; import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; -import { Form, FormProps } from '../form'; -import { settingsMock, uiSettingsClientMock } from '../mocks'; +import { Form } from '../form'; +import { getSettingsMock, uiSettingsClientMock } from '../mocks'; -export type StoryProps = Pick; +export interface StoryProps { + /** True if saving settings is enabled, false otherwise. */ + isSavingEnabled: boolean; + /** True if settings require page reload, false otherwise. */ + requirePageReload: boolean; +} /** * Utility function for returning a {@link Form} Storybook story. * @returns A Storybook Story. */ export const getFormStory = () => { - const Story = ({ isSavingEnabled }: StoryProps) => { + const Story = ({ isSavingEnabled, requirePageReload }: StoryProps) => { const fields: Array> = getFieldDefinitions( - settingsMock, + getSettingsMock(requirePageReload), uiSettingsClientMock ); @@ -31,6 +36,7 @@ export const getFormStory = () => { Story.args = { isSavingEnabled: true, + requirePageReload: false, }; return Story; From a24ea1fcbc22a44acce541712bbdae50041fb5fb Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Thu, 21 Sep 2023 12:42:31 +0100 Subject: [PATCH 22/26] Refactor changes --- .../kbn-management/settings/components/form/index.ts | 1 + .../settings/components/form/services.tsx | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/kbn-management/settings/components/form/index.ts b/packages/kbn-management/settings/components/form/index.ts index a763a5dd3d505..d674990322a09 100644 --- a/packages/kbn-management/settings/components/form/index.ts +++ b/packages/kbn-management/settings/components/form/index.ts @@ -9,3 +9,4 @@ export { Form } from './form'; export type { FormKibanaDependencies, FormServices } from './types'; +export { FormProvider, FormKibanaProvider } from './services'; diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index eb03d3563b567..e788ced776f2c 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -18,10 +18,17 @@ import { reloadPageToast } from './reload_page_toast'; const FormContext = React.createContext(null); +/** + * Props for {@link FormProvider}. + */ +export interface FormProviderProps extends FormServices { + children: React.ReactNode; +} + /** * React Provider that provides services to a {@link Form} component and its dependents. */ -export const FormProvider: FC = ({ children, ...services }) => { +export const FormProvider = ({ children, ...services }: FormProviderProps) => { const { saveChanges, showError, showReloadPagePrompt, ...rest } = services; return ( @@ -29,7 +36,6 @@ export const FormProvider: FC = ({ children, ...services }) => { {children}
); - return {children}; }; /** @@ -58,8 +64,6 @@ export const FormKibanaProvider: FC = ({ children, ...de /** * React hook for accessing pre-wired services. - * - * @see {@link FormServices} */ export const useServices = () => { const context = useContext(FormContext); From f3d6c360a7d12f20a88e4388f93a5548d55d8fe5 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Thu, 21 Sep 2023 18:07:51 +0100 Subject: [PATCH 23/26] Fix skipped tests --- .../settings/components/form/form.test.tsx | 23 +++++++++---------- .../components/form/mocks/context.tsx | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index aa1080f0fcc6e..cd6bf4284ee33 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 } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; @@ -69,8 +69,7 @@ describe('Form', () => { expect(getByTestId(DATA_TEST_SUBJ_CANCEL_BUTTON)).toBeInTheDocument(); }); - // TODO: fix - it.skip('fires saveChanges when Save button is clicked', () => { + it('fires saveChanges when Save button is clicked', async () => { const services: FormServices = createFormServicesMock(); const { getByTestId } = render(wrap(, services)); @@ -99,8 +98,7 @@ describe('Form', () => { expect(input).toHaveValue(settingsMock[testFieldType].value); }); - // TODO: fix - it.skip('fires showError when saving is unsuccessful', () => { + it('fires showError when saving is unsuccessful', () => { const services: FormServices = createFormServicesMock(); const saveChangesWithError = jest.fn(() => { throw new Error('Unable to save'); @@ -121,14 +119,13 @@ describe('Form', () => { expect(testServices.showError).toHaveBeenCalled(); }); - // TODO: fix - it.skip('fires showReloadPagePrompt when changing a reloadPageRequired setting', () => { + it('fires showReloadPagePrompt when changing a reloadPageRequired setting', async () => { const services: FormServices = createFormServicesMock(); // Make all settings require a page reload - const testFields = fields.map((field) => { - return { ...field, requiresPageReload: true }; - }); - + const testFields: Array> = getFieldDefinitions( + getSettingsMock(true), + uiSettingsClientMock + ); const { getByTestId } = render( wrap(, services) ); @@ -140,6 +137,8 @@ describe('Form', () => { const saveButton = getByTestId(DATA_TEST_SUBJ_SAVE_BUTTON); fireEvent.click(saveButton); - expect(services.showReloadPagePrompt).toHaveBeenCalled(); + await waitFor(() => { + expect(services.showReloadPagePrompt).toHaveBeenCalled(); + }); }); }); diff --git a/packages/kbn-management/settings/components/form/mocks/context.tsx b/packages/kbn-management/settings/components/form/mocks/context.tsx index a280e09bda765..2af26a8f0aaf1 100644 --- a/packages/kbn-management/settings/components/form/mocks/context.tsx +++ b/packages/kbn-management/settings/components/form/mocks/context.tsx @@ -50,5 +50,5 @@ export const TestWrapper = ({ }; export const wrap = (component: JSX.Element, services: FormServices = createFormServicesMock()) => ( - {component} + ); From 72230e1e0f3bc044d77b32a67fefab3e951245e2 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Fri, 22 Sep 2023 12:51:28 +0100 Subject: [PATCH 24/26] Small fix --- packages/kbn-management/settings/components/form/services.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-management/settings/components/form/services.tsx b/packages/kbn-management/settings/components/form/services.tsx index e788ced776f2c..bdbfbdc88c33b 100644 --- a/packages/kbn-management/settings/components/form/services.tsx +++ b/packages/kbn-management/settings/components/form/services.tsx @@ -49,7 +49,7 @@ export const FormKibanaProvider: FC = ({ children, ...de value={{ saveChanges: (changes: Record>) => { const arr = Object.entries(changes).map(([key, value]) => - settings.client.set(key, value) + settings.client.set(key, value.unsavedValue) ); return Promise.all(arr); }, From d67ccda9627025a7cc95c93cd43428d554c74b54 Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Mon, 25 Sep 2023 17:03:04 +0100 Subject: [PATCH 25/26] Address feedback --- .../components/form/bottom_bar/bottom_bar.tsx | 12 ++--- .../components/form/bottom_bar/index.tsx | 2 +- .../form/bottom_bar/unsaved_count.tsx | 22 ++++---- .../settings/components/form/form.styles.ts | 10 ++-- .../settings/components/form/form.test.tsx | 2 +- .../settings/components/form/form.tsx | 38 ++++---------- .../components/form/mocks/settings.ts | 15 +++--- .../components/form/reload_page_toast.tsx | 4 +- .../form/storybook/form.stories.tsx | 27 ++++++++-- .../form/storybook/get_form_story.tsx | 43 --------------- .../settings/components/form/use_save.ts | 52 +++++++++++++++++++ 11 files changed, 120 insertions(+), 107 deletions(-) delete mode 100644 packages/kbn-management/settings/components/form/storybook/get_form_story.tsx create mode 100644 packages/kbn-management/settings/components/form/use_save.ts diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx index 8a4398ab38f0d..818c86b78109a 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.tsx @@ -27,8 +27,8 @@ export const DATA_TEST_SUBJ_CANCEL_BUTTON = 'settings-cancel-button'; * Props for a {@link BottomBar} component. */ export interface BottomBarProps { - saveAll: () => void; - clearAllUnsaved: () => void; + onSaveAll: () => void; + onClearAllUnsaved: () => void; hasInvalidChanges: boolean; isLoading: boolean; unsavedChangesCount: number; @@ -38,8 +38,8 @@ export interface BottomBarProps { * Component for displaying the bottom bar of a {@link Form}. */ export const BottomBar = ({ - saveAll, - clearAllUnsaved, + onSaveAll, + onClearAllUnsaved, hasInvalidChanges, isLoading, unsavedChangesCount, @@ -64,7 +64,7 @@ export const BottomBar = ({ color="ghost" size="s" iconType="cross" - onClick={clearAllUnsaved} + onClick={onClearAllUnsaved} data-test-subj={DATA_TEST_SUBJ_CANCEL_BUTTON} > {i18n.translate('management.settings.form.cancelButtonLabel', { @@ -88,7 +88,7 @@ export const BottomBar = ({ fill size="s" iconType="check" - onClick={saveAll} + onClick={onSaveAll} isLoading={isLoading} data-test-subj={DATA_TEST_SUBJ_SAVE_BUTTON} > diff --git a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx index 9fc2f7093fa80..0abbe24520157 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/index.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/index.tsx @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { BottomBar, DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar'; +export { BottomBar } from './bottom_bar'; diff --git a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx index a68262db69df6..c5bb501ce98f4 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/unsaved_count.tsx @@ -8,7 +8,7 @@ import React from 'react'; -import { EuiTextColor } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useFormStyles } from '../form.styles'; @@ -25,19 +25,17 @@ interface UnsavedCountProps { export const UnsavedCount = ({ unsavedCount }: UnsavedCountProps) => { const { cssFormUnsavedCountMessage } = useFormStyles(); return ( -

- - + - -

+ values={{ + unsavedCount, + }} + /> + ); }; diff --git a/packages/kbn-management/settings/components/form/form.styles.ts b/packages/kbn-management/settings/components/form/form.styles.ts index 6611e9178cea0..994bab4530f1f 100644 --- a/packages/kbn-management/settings/components/form/form.styles.ts +++ b/packages/kbn-management/settings/components/form/form.styles.ts @@ -6,24 +6,28 @@ * Side Public License, v 1. */ +import { useEuiTheme, euiBreakpoint } from '@elastic/eui'; import { css } from '@emotion/react'; /** * A React hook that provides stateful `css` classes for the {@link Form} component. */ export const useFormStyles = () => { + const euiTheme = useEuiTheme(); + const { size, colors } = euiTheme.euiTheme; + return { cssFormButton: css` width: 100%; `, cssFormUnsavedCount: css` - @include euiBreakpoint('xs') { + ${euiBreakpoint(euiTheme, ['xs'])} { display: none; } `, cssFormUnsavedCountMessage: css` - box-shadow: -$euiSizeXS 0 $euiColorWarning; - padding-left: $euiSizeS; + box-shadow: -${size.xs} 0 ${colors.warning}; + padding-left: ${size.s}; `, }; }; diff --git a/packages/kbn-management/settings/components/form/form.test.tsx b/packages/kbn-management/settings/components/form/form.test.tsx index cd6bf4284ee33..2f1cbdb80bcac 100644 --- a/packages/kbn-management/settings/components/form/form.test.tsx +++ b/packages/kbn-management/settings/components/form/form.test.tsx @@ -15,7 +15,7 @@ import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; 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 { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar'; +import { DATA_TEST_SUBJ_SAVE_BUTTON, DATA_TEST_SUBJ_CANCEL_BUTTON } from './bottom_bar/bottom_bar'; import { FormServices } from './types'; const settingsMock = getSettingsMock(); diff --git a/packages/kbn-management/settings/components/form/form.tsx b/packages/kbn-management/settings/components/form/form.tsx index 90bd679ef96be..fabc80755cad8 100644 --- a/packages/kbn-management/settings/components/form/form.tsx +++ b/packages/kbn-management/settings/components/form/form.tsx @@ -12,9 +12,8 @@ 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 { isEmpty } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { BottomBar } from './bottom_bar'; -import { useServices } from './services'; +import { useSave } from './use_save'; /** * Props for a {@link Form} component. @@ -32,7 +31,6 @@ export interface FormProps { */ export const Form = (props: FormProps) => { const { fields, isSavingEnabled } = props; - const { saveChanges, showError, showReloadPagePrompt } = useServices(); const [unsavedChanges, setUnsavedChanges] = React.useState< Record> @@ -43,34 +41,18 @@ export const Form = (props: FormProps) => { const unsavedChangesCount = Object.keys(unsavedChanges).length; const hasInvalidChanges = Object.values(unsavedChanges).some(({ isInvalid }) => isInvalid); + const clearAllUnsaved = () => { + setUnsavedChanges({}); + }; + + const saveChanges = useSave({ fields, clearChanges: clearAllUnsaved }); + const saveAll = async () => { setIsLoading(true); - if (isEmpty(unsavedChanges)) { - return; - } - try { - await saveChanges(unsavedChanges); - clearAllUnsaved(); - const requiresReload = fields.some( - (setting) => unsavedChanges.hasOwnProperty(setting.id) && setting.requiresPageReload - ); - if (requiresReload) { - showReloadPagePrompt(); - } - } catch (e) { - showError( - i18n.translate('management.settings.form.saveErrorMessage', { - defaultMessage: 'Unable to save', - }) - ); - } + await saveChanges(unsavedChanges); setIsLoading(false); }; - const clearAllUnsaved = () => { - setUnsavedChanges({}); - }; - const onChange: RowOnChangeFn = (id, change) => { if (!change) { const { [id]: unsavedChange, ...rest } = unsavedChanges; @@ -92,8 +74,8 @@ export const Form = (props: FormProps) => {
{fieldRows}
{!isEmpty(unsavedChanges) && ( ; }; @@ -23,9 +17,12 @@ type Settings = { * @param requirePageReload The value of the `requirePageReload` param for all settings. */ export const getSettingsMock = (requirePageReload: boolean = false): Settings => { - if (requirePageReload) { - defaults.requiresPageReload = true; - } + const defaults = { + requiresPageReload: requirePageReload, + readonly: false, + category: ['category'], + }; + return { array: { description: 'Description for Array test setting', diff --git a/packages/kbn-management/settings/components/form/reload_page_toast.tsx b/packages/kbn-management/settings/components/form/reload_page_toast.tsx index 663e1ed2900a2..a8414dd4ccbe8 100644 --- a/packages/kbn-management/settings/components/form/reload_page_toast.tsx +++ b/packages/kbn-management/settings/components/form/reload_page_toast.tsx @@ -15,6 +15,8 @@ import { ToastInput } from '@kbn/core-notifications-browser'; import { I18nStart } from '@kbn/core-i18n-browser'; import { ThemeServiceStart } from '@kbn/core-theme-browser'; +export const DATA_TEST_SUBJ_PAGE_RELOAD_BUTTON = 'pageReloadButton'; + /** * Utility function for returning a {@link ToastInput} for displaying a prompt for reloading the page. * @param theme The {@link ThemeServiceStart} contract. @@ -32,7 +34,7 @@ export const reloadPageToast = (theme: ThemeServiceStart, i18nStart: I18nStart): window.location.reload()} - data-test-subj="windowReloadButton" + data-test-subj={DATA_TEST_SUBJ_PAGE_RELOAD_BUTTON} > {i18n.translate('management.settings.form.requiresPageReloadToastButtonLabel', { defaultMessage: 'Reload page', 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 217bc7be7dee9..5ba4a57d8a2f3 100644 --- a/packages/kbn-management/settings/components/form/storybook/form.stories.tsx +++ b/packages/kbn-management/settings/components/form/storybook/form.stories.tsx @@ -9,9 +9,11 @@ import React from 'react'; import { EuiPanel } from '@elastic/eui'; import { action } from '@storybook/addon-actions'; import { ComponentMeta } from '@storybook/react'; -import { FormProvider } from '../services'; -import { getFormStory } from './get_form_story'; +import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; +import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; +import { getSettingsMock, uiSettingsClientMock } from '../mocks'; import { Form as Component } from '../form'; +import { FormProvider } from '../services'; export default { title: `Settings/Form/Form`, @@ -43,4 +45,23 @@ export default { ], } as ComponentMeta; -export const Form = getFormStory(); +interface FormStoryProps { + /** True if saving settings is enabled, false otherwise. */ + isSavingEnabled: boolean; + /** True if settings require page reload, false otherwise. */ + requirePageReload: boolean; +} + +export const Form = ({ isSavingEnabled, requirePageReload }: FormStoryProps) => { + const fields: Array> = getFieldDefinitions( + getSettingsMock(requirePageReload), + uiSettingsClientMock + ); + + return ; +}; + +Form.args = { + isSavingEnabled: true, + requirePageReload: false, +}; diff --git a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx b/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx deleted file mode 100644 index 7401558a3570f..0000000000000 --- a/packages/kbn-management/settings/components/form/storybook/get_form_story.tsx +++ /dev/null @@ -1,43 +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 React from 'react'; - -import { FieldDefinition, SettingType } from '@kbn/management-settings-types'; -import { getFieldDefinitions } from '@kbn/management-settings-field-definition'; -import { Form } from '../form'; -import { getSettingsMock, uiSettingsClientMock } from '../mocks'; - -export interface StoryProps { - /** True if saving settings is enabled, false otherwise. */ - isSavingEnabled: boolean; - /** True if settings require page reload, false otherwise. */ - requirePageReload: boolean; -} - -/** - * Utility function for returning a {@link Form} Storybook story. - * @returns A Storybook Story. - */ -export const getFormStory = () => { - const Story = ({ isSavingEnabled, requirePageReload }: StoryProps) => { - const fields: Array> = getFieldDefinitions( - getSettingsMock(requirePageReload), - uiSettingsClientMock - ); - - return ; - }; - - Story.args = { - isSavingEnabled: true, - requirePageReload: false, - }; - - return Story; -}; diff --git a/packages/kbn-management/settings/components/form/use_save.ts b/packages/kbn-management/settings/components/form/use_save.ts new file mode 100644 index 0000000000000..ebd1981eb57d9 --- /dev/null +++ b/packages/kbn-management/settings/components/form/use_save.ts @@ -0,0 +1,52 @@ +/* + * 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 { FieldDefinition, SettingType } from '@kbn/management-settings-types'; +import { isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { UnsavedFieldChange } from '@kbn/management-settings-types'; +import { useServices } from './services'; + +export interface UseSaveParameters { + /** All {@link FieldDefinition} in the form. */ + fields: Array>; + /** The function to invoke for clearing all unsaved changes. */ + clearChanges: () => void; +} + +/** + * Hook to provide a function that will save all given {@link UnsavedFieldChange}. + * + * @param params The {@link UseSaveParameters} to use. + * @returns A function that will save all {@link UnsavedFieldChange} that are passed as an argument. + */ +export const useSave = (params: UseSaveParameters) => { + const { saveChanges, showError, showReloadPagePrompt } = useServices(); + + return async (changes: Record>) => { + if (isEmpty(changes)) { + return; + } + try { + await saveChanges(changes); + params.clearChanges(); + const requiresReload = params.fields.some( + (setting) => changes.hasOwnProperty(setting.id) && setting.requiresPageReload + ); + if (requiresReload) { + showReloadPagePrompt(); + } + } catch (e) { + showError( + i18n.translate('management.settings.form.saveErrorMessage', { + defaultMessage: 'Unable to save', + }) + ); + } + }; +}; From d4e89b1a4104e939962ba20df968202ff255600a Mon Sep 17 00:00:00 2001 From: Elena Stoeva Date: Mon, 25 Sep 2023 19:02:09 +0100 Subject: [PATCH 26/26] Fix bottom bar tests --- .../settings/components/form/bottom_bar/bottom_bar.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx index ff57bffb4d420..ddb3502bb2009 100644 --- a/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx +++ b/packages/kbn-management/settings/components/form/bottom_bar/bottom_bar.test.tsx @@ -21,8 +21,8 @@ const clearAllUnsaved = jest.fn(); const unsavedChangesCount = 3; const defaultProps: BottomBarProps = { - saveAll, - clearAllUnsaved, + onSaveAll: saveAll, + onClearAllUnsaved: clearAllUnsaved, hasInvalidChanges: false, unsavedChangesCount, isLoading: false,