From 4eb35783d14f274e5c50926b1a118c57835b07f9 Mon Sep 17 00:00:00 2001 From: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com> Date: Tue, 30 Jan 2024 20:47:35 +0000 Subject: [PATCH] [Advanced Settings] Integrate new Settings application into stateful Kibana (#175255) Closes https://github.com/elastic/kibana/issues/172922 ## Summary This PR: - Integrates the new Settings application (`packages/kbn-management/settings/application`) into stateful Kibana and removes the old `management_app` from the `src/plugins/advanced_settings` plugin. - Adds support for section registry in the new Settings application, so that other plugins can add their own sections to the Advanced settings app. - Adds functionality for disabling saving of settings based on the provided capabilities of the current user. Screenshot 2024-01-23 at 16 46 03

"Usage collection" section in Global settings: Screenshot 2024-01-23 at 16 48 24 ### How to test **Testing Advanced settings in stateful Kibana:** 1. Start Es with `yarn es snapshot` and Kibana with `yarn start` 2. Go to Stack Management -> Advanced Settings 3. Verify that the app functions correctly. Both tabs (for space and global settings) should be displayed, setting fields should be editable and saveable, etc. **Testing the section registry** Currently, `telemetry_management_section` is the only plugin that registers a section - the "Usage collection" section under the "Global settings" tab. This should work correctly in stateful Kibana. 1. Start Es with `yarn es snapshot --license=trial` and Kibana with `yarn start` 2. Go to Stack Management -> Advanced Settings and select the "Global settings" tab 3. Scroll down and verify that the "Usage collection" section is displayed and works as expected. **Testing with different capabilities:** 1. Start Es with `yarn es snapshot` and Kibana with `yarn start` 2. Go to Stack Management -> Roles 3. Create a role that has "Read" access to Advanced settings and one that doesn't have any access. 4. Create users with each of these two roles. 5. Log in with these users and verify that the user with "Read" access can see the app but cannot edit it, and the user with no privileges cannot access the app. **Testing Advanced settings in serverless Kibana:** The Advanced settings app in serverless shouldn't be affected by these changes. 1. Start Es with `yarn es serverless` and Kibana with `yarn serverless-{es/oblt/security}` 2. Go to Management -> Advanced Settings 3. Verify that the app functions correctly. There shouldn't be any tabs as there are no spaces. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- config/serverless.yml | 2 +- docs/developer/plugin-list.asciidoc | 2 +- .../__stories__/application.stories.tsx | 6 + .../settings/application/application.tsx | 57 +- .../settings/application/index.tsx | 15 +- .../settings/application/mocks/context.tsx | 5 + .../settings/application/read_only_badge.ts | 21 +- .../settings/application/services.tsx | 61 +- .../settings/application/tsconfig.json | 3 + .../settings/types/capabilities.ts | 11 +- .../kbn-management/settings/types/index.ts | 2 + packages/kbn-management/settings/types/tab.ts | 3 + .../settings/types/tsconfig.json | 1 + .../utilities/mocks/capabilities.mock.ts | 19 +- src/plugins/advanced_settings/README.md | 2 +- src/plugins/advanced_settings/jest.config.js | 16 - src/plugins/advanced_settings/kibana.jsonc | 4 +- src/plugins/advanced_settings/public/index.ts | 13 - .../management_app/_advanced_settings.scss | 43 - .../management_app/advanced_settings.tsx | 99 - ..._settings_voice_announcement.test.tsx.snap | 52 - ...anced_settings_voice_announcement.test.tsx | 73 - .../advanced_settings_voice_announcement.tsx | 92 - .../index.ts | 9 - .../__snapshots__/call_outs.test.tsx.snap | 25 - .../components/call_outs/call_outs.tsx | 41 - .../components/call_outs/index.ts | 9 - .../field/__snapshots__/field.test.tsx.snap | 4480 ----------------- .../components/field/field.test.tsx | 494 -- .../management_app/components/field/field.tsx | 667 --- .../components/field/field_code_editor.tsx | 107 - .../form/__snapshots__/form.test.tsx.snap | 1077 ---- .../components/form/form.test.tsx | 321 -- .../management_app/components/form/form.tsx | 432 -- .../management_app/components/form/index.ts | 9 - .../search/__snapshots__/search.test.tsx.snap | 63 - .../management_app/components/search/index.ts | 9 - .../components/search/search.test.tsx | 63 - .../components/search/search.tsx | 100 - .../public/management_app/i18n_texts.ts | 35 - .../public/management_app/index.scss | 1 - .../management_app/lib/default_category.ts | 9 - .../management_app/lib/get_aria_name.test.ts | 36 - .../management_app/lib/get_aria_name.ts | 53 - .../lib/get_category_name.test.ts | 25 - .../management_app/lib/get_category_name.ts | 54 - .../management_app/lib/get_val_type.test.ts | 39 - .../public/management_app/lib/get_val_type.ts | 40 - .../public/management_app/lib/index.ts | 14 - .../lib/is_default_value.test.ts | 76 - .../management_app/lib/sort_fields.test.ts | 56 - .../public/management_app/lib/sort_fields.ts | 31 - .../lib/to_editable_config.test.ts | 106 - .../management_app/lib/to_editable_config.ts | 59 - .../mount_management_section.tsx | 109 - .../public/management_app/settings.test.tsx | 376 -- .../public/management_app/settings.tsx | 369 -- .../management_app/settings_helper.test.ts | 155 - .../public/management_app/settings_helper.ts | 56 - .../public/management_app/types.ts | 51 - src/plugins/advanced_settings/public/mocks.ts | 26 - .../public/{plugin.ts => plugin.tsx} | 33 +- .../server/capabilities_provider.ts | 8 +- .../advanced_settings/server/config.ts | 3 +- .../advanced_settings/server/plugin.ts | 6 +- src/plugins/advanced_settings/tsconfig.json | 19 +- src/plugins/management/public/plugin.tsx | 37 - src/plugins/management/tsconfig.json | 5 +- .../apps/management/data_views/_cache.ts | 7 +- test/functional/page_objects/settings_page.ts | 39 +- .../translations/translations/fr-FR.json | 58 - .../translations/translations/ja-JP.json | 58 - .../translations/translations/zh-CN.json | 58 - .../apps/group1/advanced_settings.ts | 6 +- .../cypress/screens/sourcerer.ts | 2 +- .../cypress/tasks/sourcerer.ts | 6 +- .../common/management/data_views/_cache.ts | 2 +- 77 files changed, 242 insertions(+), 10389 deletions(-) rename src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.test.tsx => packages/kbn-management/settings/application/read_only_badge.ts (51%) rename src/plugins/advanced_settings/public/management_app/components/field/index.ts => packages/kbn-management/settings/types/capabilities.ts (66%) rename src/plugins/advanced_settings/public/management_app/lib/is_default_value.ts => packages/kbn-management/settings/utilities/mocks/capabilities.mock.ts (57%) delete mode 100644 src/plugins/advanced_settings/jest.config.js delete mode 100644 src/plugins/advanced_settings/public/management_app/_advanced_settings.scss delete mode 100644 src/plugins/advanced_settings/public/management_app/advanced_settings.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/__snapshots__/advanced_settings_voice_announcement.test.tsx.snap delete mode 100644 src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.test.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/index.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap delete mode 100644 src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/call_outs/index.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap delete mode 100644 src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/field/field.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/field/field_code_editor.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/form/__snapshots__/form.test.tsx.snap delete mode 100644 src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/form/form.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/form/index.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/components/search/__snapshots__/search.test.tsx.snap delete mode 100644 src/plugins/advanced_settings/public/management_app/components/search/index.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/search/search.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/i18n_texts.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/index.scss delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/default_category.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/get_val_type.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/index.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/sort_fields.test.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/sort_fields.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/mount_management_section.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/settings.test.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/settings.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/settings_helper.test.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/settings_helper.ts delete mode 100644 src/plugins/advanced_settings/public/management_app/types.ts delete mode 100644 src/plugins/advanced_settings/public/mocks.ts rename src/plugins/advanced_settings/public/{plugin.ts => plugin.tsx} (65%) diff --git a/config/serverless.yml b/config/serverless.yml index 575f411466c79..0a1dd4aff8ca9 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -58,7 +58,7 @@ guided_onboarding.enabled: false # Other disabled plugins xpack.canvas.enabled: false data.search.sessions.enabled: false -advanced_settings.enabled: false +advanced_settings.globalSettingsEnabled: false # Disable the browser-side functionality that depends on SecurityCheckupGetStateRoutes xpack.security.showInsecureClusterWarning: false diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index c3ffa6339f320..b2a1823e87443 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -23,7 +23,7 @@ NOTE: |{kib-repo}blob/{branch}/src/plugins/advanced_settings/README.md[advancedSettings] -|This plugin contains the advanced settings management section +|This plugin registers the management settings application allowing users to configure their advanced settings, also known as uiSettings within the code. diff --git a/packages/kbn-management/settings/application/__stories__/application.stories.tsx b/packages/kbn-management/settings/application/__stories__/application.stories.tsx index ced0c0dbcdef8..bc4048ce590f8 100644 --- a/packages/kbn-management/settings/application/__stories__/application.stories.tsx +++ b/packages/kbn-management/settings/application/__stories__/application.stories.tsx @@ -16,6 +16,7 @@ import { getSettingsMock, } from '@kbn/management-settings-utilities/mocks/settings.mock'; import { UiSettingsScope } from '@kbn/core-ui-settings-common'; +import { getSettingsCapabilitiesMock } from '@kbn/management-settings-utilities/mocks/capabilities.mock'; import { SettingsApplication as Component } from '../application'; import { SettingsApplicationProvider } from '../services'; @@ -43,6 +44,11 @@ const getSettingsApplicationStory = ({ hasGlobalSettings }: StoryProps) => ( getAllowlistedSettings={(scope: UiSettingsScope) => scope === 'namespace' ? getSettingsMock() : hasGlobalSettings ? getGlobalSettingsMock() : {} } + getSections={() => []} + // @ts-ignore + getToastsService={() => null} + getCapabilities={getSettingsCapabilitiesMock} + setBadge={() => {}} isCustomSetting={() => false} isOverriddenSetting={() => false} saveChanges={action('saveChanges')} diff --git a/packages/kbn-management/settings/application/application.tsx b/packages/kbn-management/settings/application/application.tsx index e877bae184ec1..90d28f8e5af1b 100644 --- a/packages/kbn-management/settings/application/application.tsx +++ b/packages/kbn-management/settings/application/application.tsx @@ -22,6 +22,7 @@ import { SettingsTabs } from '@kbn/management-settings-types/tab'; import { EmptyState } from './empty_state'; import { i18nTexts } from './i18n_texts'; import { Tab } from './tab'; +import { readOnlyBadge } from './read_only_badge'; import { useScopeFields } from './hooks/use_scope_fields'; import { QueryInput, QueryInputProps } from './query_input'; import { useServices } from './services'; @@ -53,7 +54,8 @@ function getQueryParam(url: string) { * Component for displaying the {@link SettingsApplication} component. */ export const SettingsApplication = () => { - const { addUrlToHistory } = useServices(); + const { addUrlToHistory, getSections, getToastsService, getCapabilities, setBadge } = + useServices(); const queryParam = getQueryParam(window.location.href); const [query, setQuery] = useState(Query.parse(queryParam)); @@ -68,7 +70,17 @@ export const SettingsApplication = () => { const [spaceAllFields, globalAllFields] = useScopeFields(); const [spaceFilteredFields, globalFilteredFields] = useScopeFields(query); - const globalSettingsEnabled = globalAllFields.length > 0; + const { + spaceSettings: { save: canSaveSpaceSettings }, + globalSettings: { save: canSaveGlobalSettings, show: canShowGlobalSettings }, + } = getCapabilities(); + if (!canSaveSpaceSettings || (!canSaveGlobalSettings && canShowGlobalSettings)) { + setBadge(readOnlyBadge); + } + + // Only enabled the Global settings tab if there are any global settings + // and if global settings can be shown + const globalTabEnabled = globalAllFields.length > 0 && canShowGlobalSettings; const tabs: SettingsTabs = { [SPACE_SETTINGS_TAB_ID]: { @@ -77,16 +89,19 @@ export const SettingsApplication = () => { categoryCounts: getCategoryCounts(spaceAllFields), callOutTitle: i18nTexts.spaceCalloutTitle, callOutText: i18nTexts.spaceCalloutText, + sections: getSections('namespace'), + isSavingEnabled: canSaveSpaceSettings, }, }; - // Only add a Global settings tab if there are any global settings - if (globalSettingsEnabled) { + if (globalTabEnabled) { tabs[GLOBAL_SETTINGS_TAB_ID] = { name: i18nTexts.globalTabTitle, fields: globalFilteredFields, categoryCounts: getCategoryCounts(globalAllFields), callOutTitle: i18nTexts.globalCalloutTitle, callOutText: i18nTexts.globalCalloutText, + sections: getSections('global'), + isSavingEnabled: canSaveGlobalSettings, }; } @@ -110,7 +125,7 @@ export const SettingsApplication = () => { - {globalSettingsEnabled && ( + {globalTabEnabled && ( <> {Object.keys(tabs).map((id) => ( @@ -130,13 +145,31 @@ export const SettingsApplication = () => { )} {selectedTab.fields.length ? ( -
onQueryChange()} - scope={selectedTabId === SPACE_SETTINGS_TAB_ID ? 'namespace' : 'global'} - /> + <> + onQueryChange()} + scope={selectedTabId === SPACE_SETTINGS_TAB_ID ? 'namespace' : 'global'} + /> + + {selectedTab.sections.length > 0 && + selectedTab.sections.map(({ Component, queryMatch }, index) => { + if (queryMatch(query.text)) { + return ( + + ); + } + })} + ) : ( onQueryChange() }} /> )} diff --git a/packages/kbn-management/settings/application/index.tsx b/packages/kbn-management/settings/application/index.tsx index cfdef8f174723..b7a6df38d4db1 100644 --- a/packages/kbn-management/settings/application/index.tsx +++ b/packages/kbn-management/settings/application/index.tsx @@ -28,9 +28,22 @@ export const KibanaSettingsApplication = ({ settings, theme, history, + sectionRegistry, + application, + chrome, }: SettingsApplicationKibanaDependencies) => ( diff --git a/packages/kbn-management/settings/application/mocks/context.tsx b/packages/kbn-management/settings/application/mocks/context.tsx index 85f5d1ba1ada4..5ad4a7edfb2a6 100644 --- a/packages/kbn-management/settings/application/mocks/context.tsx +++ b/packages/kbn-management/settings/application/mocks/context.tsx @@ -21,6 +21,7 @@ import { getSettingsMock, } from '@kbn/management-settings-utilities/mocks/settings.mock'; import { UiSettingsScope } from '@kbn/core-ui-settings-common'; +import { getSettingsCapabilitiesMock } from '@kbn/management-settings-utilities/mocks/capabilities.mock'; import { SettingsApplicationProvider, SettingsApplicationServices } from '../services'; const createRootMock = () => { @@ -42,10 +43,14 @@ export const createSettingsApplicationServicesMock = ( ...createFormServicesMock(), getAllowlistedSettings: (scope: UiSettingsScope) => scope === 'namespace' ? getSettingsMock() : hasGlobalSettings ? getGlobalSettingsMock() : {}, + getSections: () => [], + getCapabilities: getSettingsCapabilitiesMock, + setBadge: jest.fn(), isCustomSetting: () => false, isOverriddenSetting: () => false, subscribeToUpdates: () => new Subscription(), addUrlToHistory: jest.fn(), + getToastsService: jest.fn(), }); export const TestWrapper = ({ diff --git a/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.test.tsx b/packages/kbn-management/settings/application/read_only_badge.ts similarity index 51% rename from src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.test.tsx rename to packages/kbn-management/settings/application/read_only_badge.ts index 8cdf8bc5c6b54..f285ff7e7349b 100644 --- a/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.test.tsx +++ b/packages/kbn-management/settings/application/read_only_badge.ts @@ -6,15 +6,14 @@ * Side Public License, v 1. */ -import React from 'react'; -import { shallow } from 'enzyme'; +import { i18n } from '@kbn/i18n'; -import { CallOuts } from './call_outs'; - -describe('CallOuts', () => { - it('should render normally', async () => { - const component = shallow(); - - expect(component).toMatchSnapshot(); - }); -}); +export const readOnlyBadge = { + text: i18n.translate('management.settings.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('management.settings.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save advanced settings', + }), + iconType: 'glasses', +}; diff --git a/packages/kbn-management/settings/application/services.tsx b/packages/kbn-management/settings/application/services.tsx index c963e6bcbe8ac..11454fbab932a 100644 --- a/packages/kbn-management/settings/application/services.tsx +++ b/packages/kbn-management/settings/application/services.tsx @@ -14,15 +14,22 @@ import { type FormKibanaDependencies, type FormServices, } from '@kbn/management-settings-components-form'; -import { UiSettingMetadata } from '@kbn/management-settings-types'; +import { SettingsCapabilities, UiSettingMetadata } from '@kbn/management-settings-types'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { normalizeSettings } from '@kbn/management-settings-utilities'; import { Subscription } from 'rxjs'; -import { ScopedHistory } from '@kbn/core-application-browser'; +import { ApplicationStart, ScopedHistory } from '@kbn/core-application-browser'; import { UiSettingsScope } from '@kbn/core-ui-settings-common'; +import { RegistryEntry, SectionRegistryStart } from '@kbn/management-settings-section-registry'; +import { ToastsStart } from '@kbn/core-notifications-browser'; +import { ChromeBadge, ChromeStart } from '@kbn/core-chrome-browser'; export interface Services { getAllowlistedSettings: (scope: UiSettingsScope) => Record; + getSections: (scope: UiSettingsScope) => RegistryEntry[]; + getToastsService: () => ToastsStart; + getCapabilities: () => SettingsCapabilities; + setBadge: (badge: ChromeBadge) => void; subscribeToUpdates: (fn: () => void, scope: UiSettingsScope) => Subscription; isCustomSetting: (key: string, scope: UiSettingsScope) => boolean; isOverriddenSetting: (key: string, scope: UiSettingsScope) => boolean; @@ -43,6 +50,12 @@ export interface KibanaDependencies { >; }; history: ScopedHistory; + sectionRegistry: SectionRegistryStart; + notifications: { + toasts: ToastsStart; + }; + application: Pick; + chrome: Pick; } export type SettingsApplicationKibanaDependencies = KibanaDependencies & FormKibanaDependencies; @@ -65,6 +78,10 @@ export const SettingsApplicationProvider: FC = ({ links, showDanger, getAllowlistedSettings, + getSections, + getCapabilities, + setBadge, + getToastsService, subscribeToUpdates, isCustomSetting, isOverriddenSetting, @@ -75,6 +92,10 @@ export const SettingsApplicationProvider: FC = ({ { - const { docLinks, notifications, theme, i18n, settings, history } = dependencies; + const { + docLinks, + notifications, + theme, + i18n, + settings, + history, + sectionRegistry, + application, + chrome, + } = dependencies; const { client, globalClient } = settings; const getScopeClient = (scope: UiSettingsScope) => { @@ -114,6 +145,26 @@ export const SettingsApplicationKibanaProvider: FC { + return scope === 'namespace' + ? sectionRegistry.getSpacesSections() + : sectionRegistry.getGlobalSections(); + }; + + const getCapabilities = () => { + const { advancedSettings, globalSettings } = application.capabilities; + return { + spaceSettings: { + show: advancedSettings.show as boolean, + save: advancedSettings.save as boolean, + }, + globalSettings: { + show: globalSettings.show as boolean, + save: globalSettings.save as boolean, + }, + }; + }; + const isCustomSetting = (key: string, scope: UiSettingsScope) => { const scopeClient = getScopeClient(scope); return scopeClient.isCustom(key); @@ -131,6 +182,10 @@ export const SettingsApplicationKibanaProvider: FC notifications.toasts, + getCapabilities, + setBadge: (badge: ChromeBadge) => chrome.setBadge(badge), isCustomSetting, isOverriddenSetting, subscribeToUpdates, diff --git a/packages/kbn-management/settings/application/tsconfig.json b/packages/kbn-management/settings/application/tsconfig.json index cc1e11c585774..182131a7e8714 100644 --- a/packages/kbn-management/settings/application/tsconfig.json +++ b/packages/kbn-management/settings/application/tsconfig.json @@ -32,5 +32,8 @@ "@kbn/core-i18n-browser", "@kbn/core-analytics-browser-mocks", "@kbn/core-ui-settings-common", + "@kbn/management-settings-section-registry", + "@kbn/core-notifications-browser", + "@kbn/core-chrome-browser", ] } diff --git a/src/plugins/advanced_settings/public/management_app/components/field/index.ts b/packages/kbn-management/settings/types/capabilities.ts similarity index 66% rename from src/plugins/advanced_settings/public/management_app/components/field/index.ts rename to packages/kbn-management/settings/types/capabilities.ts index 6616264cc058b..dcd1b5cebb32c 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/index.ts +++ b/packages/kbn-management/settings/types/capabilities.ts @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -export { Field, getEditableValue } from './field'; +export interface SettingsCapabilities { + spaceSettings: SettingCapability; + globalSettings: SettingCapability; +} -// eslint-disable-next-line import/no-default-export -export { Field as default } from './field'; +interface SettingCapability { + show: boolean; + save: boolean; +} diff --git a/packages/kbn-management/settings/types/index.ts b/packages/kbn-management/settings/types/index.ts index 9a8a27e4ae244..59e034a22057f 100644 --- a/packages/kbn-management/settings/types/index.ts +++ b/packages/kbn-management/settings/types/index.ts @@ -66,6 +66,8 @@ export type { } from './setting_type'; export type { CategorizedFields, CategoryCounts } from './category'; +export type { SettingsTabs } from './tab'; +export type { SettingsCapabilities } from './capabilities'; /** * A React `ref` that indicates an input can be reset using an diff --git a/packages/kbn-management/settings/types/tab.ts b/packages/kbn-management/settings/types/tab.ts index cf87c9f066fd0..8cbf1a51619bc 100644 --- a/packages/kbn-management/settings/types/tab.ts +++ b/packages/kbn-management/settings/types/tab.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { RegistryEntry } from '@kbn/management-settings-section-registry'; import { CategoryCounts } from './category'; import { FieldDefinition } from '.'; @@ -16,5 +17,7 @@ export interface SettingsTabs { categoryCounts: CategoryCounts; callOutTitle: string; callOutText: string; + sections: RegistryEntry[]; + isSavingEnabled: boolean; }; } diff --git a/packages/kbn-management/settings/types/tsconfig.json b/packages/kbn-management/settings/types/tsconfig.json index 345fbe3125a79..2753fb935acaa 100644 --- a/packages/kbn-management/settings/types/tsconfig.json +++ b/packages/kbn-management/settings/types/tsconfig.json @@ -14,5 +14,6 @@ "@kbn/analytics", "@kbn/core", "@kbn/core-ui-settings-common", + "@kbn/management-settings-section-registry", ] } diff --git a/src/plugins/advanced_settings/public/management_app/lib/is_default_value.ts b/packages/kbn-management/settings/utilities/mocks/capabilities.mock.ts similarity index 57% rename from src/plugins/advanced_settings/public/management_app/lib/is_default_value.ts rename to packages/kbn-management/settings/utilities/mocks/capabilities.mock.ts index f00b137e44ca5..5b0049577ae43 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/is_default_value.ts +++ b/packages/kbn-management/settings/utilities/mocks/capabilities.mock.ts @@ -6,12 +6,15 @@ * Side Public License, v 1. */ -import { FieldSetting } from '../types'; +import { SettingsCapabilities } from '@kbn/management-settings-types'; -export function isDefaultValue(setting: FieldSetting) { - return ( - setting.isCustom || - setting.value === undefined || - String(setting.value) === String(setting.defVal) - ); -} +export const getSettingsCapabilitiesMock = (): SettingsCapabilities => ({ + spaceSettings: { + show: true, + save: true, + }, + globalSettings: { + show: true, + save: true, + }, +}); diff --git a/src/plugins/advanced_settings/README.md b/src/plugins/advanced_settings/README.md index a364348a8e99a..5403974bfe686 100644 --- a/src/plugins/advanced_settings/README.md +++ b/src/plugins/advanced_settings/README.md @@ -1,5 +1,5 @@ # Advanced Settings -This plugin contains the advanced settings management section +This plugin registers the [management settings application](packages/kbn-management/settings/application/application.tsx) allowing users to configure their advanced settings, also known as uiSettings within the code. \ No newline at end of file diff --git a/src/plugins/advanced_settings/jest.config.js b/src/plugins/advanced_settings/jest.config.js deleted file mode 100644 index 7900d7f39b6c6..0000000000000 --- a/src/plugins/advanced_settings/jest.config.js +++ /dev/null @@ -1,16 +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. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/src/plugins/advanced_settings'], - coverageDirectory: '/target/kibana-coverage/jest/src/plugins/advanced_settings', - coverageReporters: ['text', 'html'], - collectCoverageFrom: ['/src/plugins/advanced_settings/{public,server}/**/*.{ts,tsx}'], -}; diff --git a/src/plugins/advanced_settings/kibana.jsonc b/src/plugins/advanced_settings/kibana.jsonc index c0106e24c50f4..1997baa90d97a 100644 --- a/src/plugins/advanced_settings/kibana.jsonc +++ b/src/plugins/advanced_settings/kibana.jsonc @@ -14,8 +14,6 @@ "usageCollection" ], "requiredBundles": [ - "kibanaReact", - "kibanaUtils" ] } -} \ No newline at end of file +} diff --git a/src/plugins/advanced_settings/public/index.ts b/src/plugins/advanced_settings/public/index.ts index e07c1aa1d72d1..46608b9d94509 100644 --- a/src/plugins/advanced_settings/public/index.ts +++ b/src/plugins/advanced_settings/public/index.ts @@ -6,23 +6,10 @@ * Side Public License, v 1. */ -import React from 'react'; import { PluginInitializerContext } from '@kbn/core/public'; import { AdvancedSettingsPlugin } from './plugin'; export type { AdvancedSettingsSetup, AdvancedSettingsStart } from './types'; -/** - * Exports the field component as a React.lazy component. We're explicitly naming it lazy here - * so any plugin that would import that can clearly see it's lazy loaded and can only be used - * inside a suspense context. - */ -const LazyField = React.lazy(() => import('./management_app/components/field')); -export { LazyField }; - -export { toEditableConfig } from './management_app/lib/to_editable_config'; - export function plugin(initializerContext: PluginInitializerContext) { return new AdvancedSettingsPlugin(); } - -export type { FieldState } from './management_app/types'; diff --git a/src/plugins/advanced_settings/public/management_app/_advanced_settings.scss b/src/plugins/advanced_settings/public/management_app/_advanced_settings.scss deleted file mode 100644 index a5f7e774f9abe..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/_advanced_settings.scss +++ /dev/null @@ -1,43 +0,0 @@ -.mgtAdvancedSettings__field { - + * { - margin-top: $euiSize; - } - - .mgtAdvancedSettings__fieldTitle { - padding-left: $euiSizeS; - margin-left: -$euiSizeS; - } - - &--unsaved .mgtAdvancedSettings__fieldTitle { - // Simulates a left side border without shifting content - box-shadow: -$euiSizeXS 0 $euiColorWarning; - } - &--invalid .mgtAdvancedSettings__fieldTitle { - // Simulates a left side border without shifting content - box-shadow: -$euiSizeXS 0 $euiColorDanger; - } -} - -.mgtAdvancedSettings__fieldTitleUnsavedIcon { - margin-left: $euiSizeS; -} - -.mgtAdvancedSettingsForm__unsavedCount { - @include euiBreakpoint('xs') { - display: none; - } -} - -.mgtAdvancedSettingsForm__unsavedCountMessage { - // Simulates a left side border without shifting content - box-shadow: -$euiSizeXS 0 $euiColorWarning; - padding-left: $euiSizeS; -} - -.mgtAdvancedSettingsForm__button { - width: 100%; -} - -.kbnBody--mgtAdvancedSettingsHasBottomBar .mgtPage__body { - padding-bottom: $euiSizeXL * 2; -} diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx deleted file mode 100644 index 347a04b2df81a..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ /dev/null @@ -1,99 +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, { Component } from 'react'; - -import { UiCounterMetricType } from '@kbn/analytics'; - -import { DocLinksStart, ToastsStart, ThemeServiceStart } from '@kbn/core/public'; - -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { IUiSettingsClient, SettingsStart } from '@kbn/core-ui-settings-browser'; -import { AdvancedSettingsVoiceAnnouncement } from './components/advanced_settings_voice_announcement'; -import { Form } from './components/form'; - -import { FieldSetting, SettingsChanges } from './types'; - -export const QUERY = 'query'; - -interface AdvancedSettingsProps { - enableSaving: boolean; - settingsService: SettingsStart; - /** TODO: remove once use_ui_setting is changed to use the settings service - * https://github.com/elastic/kibana/issues/149347 */ - uiSettingsClient: IUiSettingsClient; - docLinks: DocLinksStart['links']; - toasts: ToastsStart; - theme: ThemeServiceStart['theme$']; - trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; - groupedSettings: GroupedSettings; - categoryCounts: Record; - categories: string[]; - visibleSettings: Record; - noResults: boolean; - clearQuery: () => void; - queryText: string; - callOutTitle: string; - callOutSubtitle: string; -} - -type GroupedSettings = Record; - -export class AdvancedSettings extends Component { - constructor(props: AdvancedSettingsProps) { - super(props); - } - - saveConfig = async (changes: SettingsChanges) => { - const arr = Object.entries(changes).map(([key, value]) => - this.props.uiSettingsClient.set(key, value) - ); - return Promise.all(arr); - }; - - render() { - return ( -
- - -

{this.props.callOutSubtitle}

-
- - - - - - -
- ); - } -} diff --git a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/__snapshots__/advanced_settings_voice_announcement.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/__snapshots__/advanced_settings_voice_announcement.test.tsx.snap deleted file mode 100644 index 82c8dcd7f7ea1..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/__snapshots__/advanced_settings_voice_announcement.test.tsx.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Advanced Settings: Voice Announcement should render announcement 1`] = ` - -
- - - -
-
-`; - -exports[`Advanced Settings: Voice Announcement should render nothing 1`] = ` - -
- - - -
-
-`; diff --git a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.test.tsx b/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.test.tsx deleted file mode 100644 index cb206b0f6dc55..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.test.tsx +++ /dev/null @@ -1,73 +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 { shallow } from 'enzyme'; -import { UiSettingsType } from '@kbn/core/public'; - -import { AdvancedSettingsVoiceAnnouncement } from './advanced_settings_voice_announcement'; - -const settingPartial = { - name: 'name', - isOverridden: false, - type: 'string' as UiSettingsType, - value: 'value', - defVal: 'defVal', - optionLabels: { label: 'label' }, - description: 'description', - displayName: 'displayName', - isCustom: false, - requiresPageReload: false, - options: [], - validation: { regex: /a/, message: 'message' }, - category: ['category'], - readOnly: false, -}; - -const testProps = { - nothing: { - query: '', - filteredSettings: [ - { - ariaName: 'General', - ...settingPartial, - }, - ], - }, - searchResult: { - query: 'dark theme', - filteredSettings: [ - { - ariaName: 'General', - ...settingPartial, - }, - ], - }, -}; - -describe('Advanced Settings: Voice Announcement', () => { - it('should render nothing', async () => { - const { query, filteredSettings } = testProps.nothing; - - const component = shallow( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render announcement', async () => { - const { query, filteredSettings } = testProps.searchResult; - - const component = shallow( - - ); - - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx b/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx deleted file mode 100644 index 4f8165f527f9c..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/advanced_settings_voice_announcement.tsx +++ /dev/null @@ -1,92 +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. - */ - -/* - This component aims to insert assertive live region on the page, - to make sure that a screen reader announces layout changes. - - Due to the fact that it has a specific way of detecting what-and-when announce - as well as delay of announcement (which depends on what a user is doing at the moment) - I place a 500ms delay of re-render the text of anouncement. - That time period is best fits the time of screen reader reaction. - That anouncement depends on what user is typying into search box as well as - the speed of ordinary screen reader pronouns what user is typing before start reading this anouncement. - - The order of triggering functions: - 1: React trigs the component to be updated - 2: It places a timer and block render - 3: The time is over - 4: Component renders - - 5: If there is another component call, the timer is dropped (cleared). - */ - -import React, { Component } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiScreenReaderOnly, EuiDelayRender } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FieldSetting } from '../../types'; - -interface Props { - queryText: string; - settings: Record; -} - -export class AdvancedSettingsVoiceAnnouncement extends Component { - shouldComponentUpdate = (nextProps: Props) => { - /* - If a user typed smth new, we should clear the previous timer - and start another one + block component rendering. - - When it is reset and delaying is over as well as no new string came, - it's ready to be rendered. - */ - return nextProps.queryText !== this.props.queryText; - }; - - render() { - const filteredSections = Object.values(this.props.settings).map((setting) => - setting.map((option) => option.ariaName) - ); - const filteredOptions = [...filteredSections]; - return ( - -
- - {this.props.queryText ? ( - - ) : ( - - )} - -
-
- ); - } -} diff --git a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/index.ts b/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/index.ts deleted file mode 100644 index c5cefbc271188..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/advanced_settings_voice_announcement/index.ts +++ /dev/null @@ -1,9 +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. - */ - -export { AdvancedSettingsVoiceAnnouncement } from './advanced_settings_voice_announcement'; diff --git a/src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap deleted file mode 100644 index 7da96ad98f1bf..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CallOuts should render normally 1`] = ` -
- - } - > -

- -

-
-
-`; diff --git a/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx b/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx deleted file mode 100644 index dabf44e2ba948..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx +++ /dev/null @@ -1,41 +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 { EuiCallOut } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; - -export const CallOuts = () => { - return ( -
- - } - color="warning" - iconType="bolt" - > -

- -

-
-
- ); -}; diff --git a/src/plugins/advanced_settings/public/management_app/components/call_outs/index.ts b/src/plugins/advanced_settings/public/management_app/components/call_outs/index.ts deleted file mode 100644 index 7e04d5d370530..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/call_outs/index.ts +++ /dev/null @@ -1,9 +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. - */ - -export { CallOuts } from './call_outs'; diff --git a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap deleted file mode 100644 index dc5c969c42749..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap +++ /dev/null @@ -1,4480 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Field for array setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="array:test:setting-group" - title={ -

- - Array test setting - - - -

- } -> - - - - -`; - -exports[`Field for array setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - default_value - , - } - } - /> - - - - - } - fullWidth={true} - id="array:test:setting-group" - title={ -

- - Array test setting - - - -

- } -> - - - - } - label="array:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for array setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="array:test:setting-group" - title={ -

- - Array test setting - - - } - type="asterisk" - /> - -

- } -> - - - - -`; - -exports[`Field for array setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="array:test:setting-group" - title={ -

- - Array test setting - - - -

- } -> - - - - -`; - -exports[`Field for array setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="array:test:setting-group" - title={ -

- - Array test setting - - - } - type="asterisk" - /> - -

- } -> - - - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for array setting should render user value if there is user value is set 1`] = ` - -
- - - - - - default_value - , - } - } - /> - - - - - } - fullWidth={true} - id="array:test:setting-group" - title={ -

- - Array test setting - - - -

- } -> - - - - - -     - - - } - label="array:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for boolean setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="boolean:test:setting-group" - title={ -

- - Boolean test setting - - - -

- } -> - - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for boolean setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - true - , - } - } - /> - - - - - } - fullWidth={true} - id="boolean:test:setting-group" - title={ -

- - Boolean test setting - - - -

- } -> - - - - } - label="boolean:test:setting" - labelType="label" - > - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for boolean setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="boolean:test:setting-group" - title={ -

- - Boolean test setting - - - } - type="asterisk" - /> - -

- } -> - - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for boolean setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="boolean:test:setting-group" - title={ -

- - Boolean test setting - - - -

- } -> - - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for boolean setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="boolean:test:setting-group" - title={ -

- - Boolean test setting - - - } - type="asterisk" - /> - -

- } -> - - - } - onChange={[Function]} - /> - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for boolean setting should render user value if there is user value is set 1`] = ` - -
- - - - - - true - , - } - } - /> - - - - - } - fullWidth={true} - id="boolean:test:setting-group" - title={ -

- - Boolean test setting - - - -

- } -> - - - - - -     - - - } - label="boolean:test:setting" - labelType="label" - > - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for color setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="color:test:setting-group" - title={ -

- - Color test setting - - - -

- } -> - - - - -`; - -exports[`Field for color setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="color:test:setting-group" - title={ -

- - Color test setting - - - -

- } -> - - - - } - label="color:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for color setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="color:test:setting-group" - title={ -

- - Color test setting - - - } - type="asterisk" - /> - -

- } -> - - - - -`; - -exports[`Field for color setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="color:test:setting-group" - title={ -

- - Color test setting - - - -

- } -> - - - - -`; - -exports[`Field for color setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="color:test:setting-group" - title={ -

- - Color test setting - - - } - type="asterisk" - /> - -

- } -> - - - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for color setting should render user value if there is user value is set 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="color:test:setting-group" - title={ -

- - Color test setting - - - -

- } -> - - - - - -     - - - } - label="color:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for image setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="image:test:setting-group" - title={ -

- - Image test setting - - - -

- } -> - - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for image setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="image:test:setting-group" - title={ -

- - Image test setting - - - -

- } -> - - - - } - label="image:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for image setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="image:test:setting-group" - title={ -

- - Image test setting - - - } - type="asterisk" - /> - -

- } -> - - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for image setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="image:test:setting-group" - title={ -

- - Image test setting - - - -

- } -> - - - } - onChange={[Function]} - /> - - -`; - -exports[`Field for image setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="image:test:setting-group" - title={ -

- - Image test setting - - - } - type="asterisk" - /> - -

- } -> - - - } - onChange={[Function]} - /> - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for image setting should render user value if there is user value is set 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="image:test:setting-group" - title={ -

- - Image test setting - - - -

- } -> - - - - - -     - - - - - - - - } - label="image:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for json setting should render as read only if saving is disabled 1`] = ` - -
- - - - - - {} - , - } - } - /> - - - - - } - fullWidth={true} - id="json:test:setting-group" - title={ -

- - Json test setting - - - -

- } -> - -
- -
-
- -`; - -exports[`Field for json setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - {} - , - } - } - /> - - - - - } - fullWidth={true} - id="json:test:setting-group" - title={ -

- - Json test setting - - - -

- } -> - - - - } - label="json:test:setting" - labelType="label" - > -
- -
-
- -`; - -exports[`Field for json setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="json:test:setting-group" - title={ -

- - Json test setting - - - } - type="asterisk" - /> - -

- } -> - -
- -
-
- -`; - -exports[`Field for json setting should render default value if there is no user value set 1`] = ` - -
- - - - - - {} - , - } - } - /> - - - - - } - fullWidth={true} - id="json:test:setting-group" - title={ -

- - Json test setting - - - -

- } -> - - - - - -     - - - } - label="json:test:setting" - labelType="label" - > -
- -
-
- -`; - -exports[`Field for json setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="json:test:setting-group" - title={ -

- - Json test setting - - - } - type="asterisk" - /> - -

- } -> - -
- -
- -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for json setting should render user value if there is user value is set 1`] = ` - -
- - - - - - {} - , - } - } - /> - - - - - } - fullWidth={true} - id="json:test:setting-group" - title={ -

- - Json test setting - - - -

- } -> - - - - - -     - - - } - label="json:test:setting" - labelType="label" - > -
- -
-
- -`; - -exports[`Field for markdown setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="markdown:test:setting-group" - title={ -

- - Markdown test setting - - - -

- } -> - -
- -
-
- -`; - -exports[`Field for markdown setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="markdown:test:setting-group" - title={ -

- - Markdown test setting - - - -

- } -> - - - - } - label="markdown:test:setting" - labelType="label" - > -
- -
-
- -`; - -exports[`Field for markdown setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="markdown:test:setting-group" - title={ -

- - Markdown test setting - - - } - type="asterisk" - /> - -

- } -> - -
- -
-
- -`; - -exports[`Field for markdown setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="markdown:test:setting-group" - title={ -

- - Markdown test setting - - - -

- } -> - -
- -
-
- -`; - -exports[`Field for markdown setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="markdown:test:setting-group" - title={ -

- - Markdown test setting - - - } - type="asterisk" - /> - -

- } -> - -
- -
- -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for markdown setting should render user value if there is user value is set 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="markdown:test:setting-group" - title={ -

- - Markdown test setting - - - -

- } -> - - - - - -     - - - } - label="markdown:test:setting" - labelType="label" - > -
- -
-
- -`; - -exports[`Field for number setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="number:test:setting-group" - title={ -

- - Number test setting - - - -

- } -> - - - - -`; - -exports[`Field for number setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - 5 - , - } - } - /> - - - - - } - fullWidth={true} - id="number:test:setting-group" - title={ -

- - Number test setting - - - -

- } -> - - - - } - label="number:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for number setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="number:test:setting-group" - title={ -

- - Number test setting - - - } - type="asterisk" - /> - -

- } -> - - - - -`; - -exports[`Field for number setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="number:test:setting-group" - title={ -

- - Number test setting - - - -

- } -> - - - - -`; - -exports[`Field for number setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="number:test:setting-group" - title={ -

- - Number test setting - - - } - type="asterisk" - /> - -

- } -> - - - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for number setting should render user value if there is user value is set 1`] = ` - -
- - - - - - 5 - , - } - } - /> - - - - - } - fullWidth={true} - id="number:test:setting-group" - title={ -

- - Number test setting - - - -

- } -> - - - - - -     - - - } - label="number:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for select setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="select:test:setting-group" - title={ -

- - Select test setting - - - -

- } -> - - - - -`; - -exports[`Field for select setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - Orange - , - } - } - /> - - - - - } - fullWidth={true} - id="select:test:setting-group" - title={ -

- - Select test setting - - - -

- } -> - - - - } - label="select:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for select setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="select:test:setting-group" - title={ -

- - Select test setting - - - } - type="asterisk" - /> - -

- } -> - - - - -`; - -exports[`Field for select setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="select:test:setting-group" - title={ -

- - Select test setting - - - -

- } -> - - - - -`; - -exports[`Field for select setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="select:test:setting-group" - title={ -

- - Select test setting - - - } - type="asterisk" - /> - -

- } -> - - - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for select setting should render user value if there is user value is set 1`] = ` - -
- - - - - - Orange - , - } - } - /> - - - - - } - fullWidth={true} - id="select:test:setting-group" - title={ -

- - Select test setting - - - -

- } -> - - - - - -     - - - } - label="select:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for string setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="string:test:setting-group" - title={ -

- - String test setting - - - -

- } -> - - - - -`; - -exports[`Field for string setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="string:test:setting-group" - title={ -

- - String test setting - - - -

- } -> - - - - } - label="string:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for string setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="string:test:setting-group" - title={ -

- - String test setting - - - } - type="asterisk" - /> - -

- } -> - - - - -`; - -exports[`Field for string setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="string:test:setting-group" - title={ -

- - String test setting - - - -

- } -> - - - - -`; - -exports[`Field for string setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="string:test:setting-group" - title={ -

- - String test setting - - - } - type="asterisk" - /> - -

- } -> - - - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for string setting should render user value if there is user value is set 1`] = ` - -
- - - - - - null - , - } - } - /> - - - - - } - fullWidth={true} - id="string:test:setting-group" - title={ -

- - String test setting - - - -

- } -> - - - - - -     - - - } - label="string:test:setting" - labelType="label" - > - - - -`; - -exports[`Field for stringWithValidation setting should render as read only if saving is disabled 1`] = ` - -
- - } - fullWidth={true} - id="string:test-validation:setting-group" - title={ -

- - String test validation setting - - - -

- } -> - - - - -`; - -exports[`Field for stringWithValidation setting should render as read only with help text if overridden 1`] = ` - -
- - - - - - foo-default - , - } - } - /> - - - - - } - fullWidth={true} - id="string:test-validation:setting-group" - title={ -

- - String test validation setting - - - -

- } -> - - - - } - label="string:test-validation:setting" - labelType="label" - > - - - -`; - -exports[`Field for stringWithValidation setting should render custom setting icon if it is custom 1`] = ` - -
- - } - fullWidth={true} - id="string:test-validation:setting-group" - title={ -

- - String test validation setting - - - } - type="asterisk" - /> - -

- } -> - - - - -`; - -exports[`Field for stringWithValidation setting should render default value if there is no user value set 1`] = ` - -
- - } - fullWidth={true} - id="string:test-validation:setting-group" - title={ -

- - String test validation setting - - - -

- } -> - - - - -`; - -exports[`Field for stringWithValidation setting should render unsaved value if there are unsaved changes 1`] = ` - -
- - } - fullWidth={true} - id="string:test-validation:setting-group" - title={ -

- - String test validation setting - - - } - type="asterisk" - /> - -

- } -> - - - -

- Setting is currently not saved. -

-
-
- -`; - -exports[`Field for stringWithValidation setting should render user value if there is user value is set 1`] = ` - -
- - - - - - foo-default - , - } - } - /> - - - - - } - fullWidth={true} - id="string:test-validation:setting-group" - title={ -

- - String test validation setting - - - -

- } -> - - - - - -     - - - } - label="string:test-validation:setting" - labelType="label" - > - - - -`; diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx deleted file mode 100644 index 2d8d010aad2f3..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx +++ /dev/null @@ -1,494 +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 { I18nProvider } from '@kbn/i18n-react'; -import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; -import '@kbn/code-editor-mock/jest_helper'; -import { mount, ReactWrapper } from 'enzyme'; -import { FieldSetting } from '../../types'; -import { UiSettingsType } from '@kbn/core/public'; -import { notificationServiceMock, docLinksServiceMock } from '@kbn/core/public/mocks'; - -import { findTestSubject } from '@elastic/eui/lib/test'; -import { Field, getEditableValue } from './field'; - -jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({ - useUiSetting: jest.fn(), -})); - -const defaults = { - requiresPageReload: false, - readOnly: false, - category: ['category'], -}; - -const exampleValues = { - array: ['example_value'], - boolean: false, - color: '#FF00CC', - image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=', - json: { foo: 'bar2' }, - markdown: 'Hello World', - number: 1, - select: 'banana', - string: 'hello world', - stringWithValidation: 'foo', -}; - -const settings: Record = { - array: { - name: 'array:test:setting', - ariaName: 'array test setting', - displayName: 'Array test setting', - description: 'Description for Array test setting', - type: 'array', - value: undefined, - defVal: ['default_value'], - isCustom: false, - isOverridden: false, - ...defaults, - }, - boolean: { - name: 'boolean:test:setting', - ariaName: 'boolean test setting', - displayName: 'Boolean test setting', - description: 'Description for Boolean test setting', - type: 'boolean', - value: undefined, - defVal: true, - isCustom: false, - isOverridden: false, - ...defaults, - }, - image: { - name: 'image:test:setting', - ariaName: 'image test setting', - displayName: 'Image test setting', - description: 'Description for Image test setting', - type: 'image', - value: undefined, - defVal: null, - isCustom: false, - isOverridden: false, - ...defaults, - }, - json: { - name: 'json:test:setting', - ariaName: 'json test setting', - displayName: 'Json test setting', - description: 'Description for Json test setting', - type: 'json', - value: '{"foo": "bar"}', - defVal: '{}', - isCustom: false, - isOverridden: false, - ...defaults, - }, - markdown: { - name: 'markdown:test:setting', - ariaName: 'markdown test setting', - displayName: 'Markdown test setting', - description: 'Description for Markdown test setting', - type: 'markdown', - value: undefined, - defVal: '', - isCustom: false, - isOverridden: false, - ...defaults, - }, - number: { - name: 'number:test:setting', - ariaName: 'number test setting', - displayName: 'Number test setting', - description: 'Description for Number test setting', - type: 'number', - value: undefined, - defVal: 5, - isCustom: false, - isOverridden: false, - ...defaults, - }, - select: { - name: 'select:test:setting', - ariaName: 'select test setting', - displayName: 'Select test setting', - description: 'Description for Select test setting', - type: 'select', - value: undefined, - defVal: 'orange', - isCustom: false, - isOverridden: false, - options: ['apple', 'orange', 'banana'], - optionLabels: { - apple: 'Apple', - orange: 'Orange', - // Deliberately left out `banana` to test if it also works with missing labels - }, - ...defaults, - }, - string: { - name: 'string:test:setting', - ariaName: 'string test setting', - displayName: 'String test setting', - description: 'Description for String test setting', - type: 'string', - value: undefined, - defVal: null, - isCustom: false, - isOverridden: false, - ...defaults, - }, - stringWithValidation: { - name: 'string:test-validation:setting', - ariaName: 'string test validation setting', - displayName: 'String test validation setting', - description: 'Description for String test validation setting', - type: 'string', - value: undefined, - defVal: 'foo-default', - isCustom: false, - isOverridden: false, - ...defaults, - }, - color: { - name: 'color:test:setting', - ariaName: 'color test setting', - displayName: 'Color test setting', - description: 'Description for Color test setting', - type: 'color', - value: undefined, - defVal: null, - isCustom: false, - isOverridden: false, - ...defaults, - }, -}; -const userValues = { - array: ['user', 'value'], - boolean: false, - image: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', - json: '{"hello": "world"}', - markdown: '**bold**', - number: 10, - select: 'banana', - string: 'foo', - stringWithValidation: 'fooUserValue', - color: '#FACF0C', -}; - -const handleChange = jest.fn(); -const clearChange = jest.fn(); - -const getFieldSettingValue = (wrapper: ReactWrapper, name: string, type: string) => { - const field = findTestSubject(wrapper, `advancedSetting-editField-${name}`); - if (type === 'boolean') { - return field.props()['aria-checked']; - } else if (type === 'color') { - return field.props().color; - } else { - return field.props().value; - } -}; - -describe('Field', () => { - Object.keys(settings).forEach((type) => { - const setting = settings[type]; - - describe(`for ${type} setting`, () => { - it('should render default value if there is no user value set', async () => { - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render as read only with help text if overridden', async () => { - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render as read only if saving is disabled', async () => { - const component = shallowWithI18nProvider( - - ); - expect(component).toMatchSnapshot(); - }); - - it('should render user value if there is user value is set', async () => { - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render custom setting icon if it is custom', async () => { - const component = shallowWithI18nProvider( - - ); - expect(component).toMatchSnapshot(); - }); - it('should render unsaved value if there are unsaved changes', async () => { - const component = shallowWithI18nProvider( - - ); - expect(component).toMatchSnapshot(); - }); - }); - - if (type === 'select') { - it('should use options for rendering values and optionsLabels for rendering labels', () => { - const component = mountWithI18nProvider( - - ); - const select = findTestSubject(component, `advancedSetting-editField-${setting.name}`); - // @ts-ignore - const values = select.find('option').map((option) => option.prop('value')); - expect(values).toEqual(['apple', 'orange', 'banana']); - // @ts-ignore - const labels = select.find('option').map((option) => option.text()); - expect(labels).toEqual(['Apple', 'Orange', 'banana']); - }); - } - - const setup = () => { - const Wrapper = (props: Record) => ( - - - - ); - const wrapper = mount(); - const component = wrapper.find(I18nProvider).find(Field); - - return { - wrapper, - component, - }; - }; - - if (type === 'image') { - describe(`for changing ${type} setting`, () => { - const { wrapper, component } = setup(); - const userValue = userValues[type]; - (component.instance() as Field).getImageAsBase64 = ({}: Blob) => Promise.resolve(''); - - it('should be able to change value and cancel', async () => { - (component.instance() as Field).onImageChange([userValue] as unknown as FileList); - expect(handleChange).toBeCalled(); - await wrapper.setProps({ - unsavedChanges: { - value: userValue, - changeImage: true, - }, - setting: { - ...(component.instance() as Field).props.setting, - value: userValue, - }, - }); - await (component.instance() as Field).cancelChangeImage(); - expect(clearChange).toBeCalledWith(setting.name); - wrapper.update(); - }); - - it('should be able to change value from existing value', async () => { - await wrapper.setProps({ - unsavedChanges: {}, - }); - const updated = wrapper.update(); - findTestSubject(updated, `advancedSetting-changeImage-${setting.name}`).simulate('click'); - const newUserValue = `${userValue}=`; - await (component.instance() as Field).onImageChange([ - newUserValue, - ] as unknown as FileList); - expect(handleChange).toBeCalled(); - }); - - it('should be able to reset to default value', async () => { - const updated = wrapper.update(); - findTestSubject(updated, `advancedSetting-resetField-${setting.name}`).simulate('click'); - expect(handleChange).toBeCalledWith(setting.name, { - value: getEditableValue(setting.type, setting.defVal, setting.defVal), - changeImage: true, - }); - }); - }); - } else if (type === 'markdown' || type === 'json') { - describe(`for changing ${type} setting`, () => { - const { wrapper, component } = setup(); - const userValue = userValues[type]; - - it('should be able to change value', async () => { - (component.instance() as Field).onCodeEditorChange(userValue as UiSettingsType); - expect(handleChange).toBeCalledWith(setting.name, { value: userValue }); - await wrapper.setProps({ - setting: { - ...(component.instance() as Field).props.setting, - value: userValue, - }, - }); - wrapper.update(); - }); - - it('should be able to reset to default value', async () => { - const updated = wrapper.update(); - findTestSubject(updated, `advancedSetting-resetField-${setting.name}`).simulate('click'); - expect(handleChange).toBeCalledWith(setting.name, { - value: getEditableValue(setting.type, setting.defVal), - }); - }); - - if (type === 'json') { - it('should be able to clear value and have empty object populate', async () => { - await (component.instance() as Field).onCodeEditorChange('' as UiSettingsType); - wrapper.update(); - expect(handleChange).toBeCalledWith(setting.name, { value: setting.defVal }); - }); - } - }); - } else if (type === 'color') { - describe(`for changing ${type} setting`, () => { - const { wrapper, component } = setup(); - const userValue = userValues[type]; - - it('should be able to change value', async () => { - await (component.instance() as Field).onFieldChange(userValue); - const updated = wrapper.update(); - expect(handleChange).toBeCalledWith(setting.name, { value: userValue }); - updated.setProps({ unsavedChanges: { value: userValue } }); - const currentValue = wrapper.find('EuiColorPicker').prop('color'); - expect(currentValue).toEqual(userValue); - }); - - it('should be able to reset to default value', async () => { - await wrapper.setProps({ - unsavedChanges: {}, - setting: { ...setting, value: userValue }, - }); - const updated = wrapper.update(); - findTestSubject(updated, `advancedSetting-resetField-${setting.name}`).simulate('click'); - const expectedEditableValue = getEditableValue(setting.type, setting.defVal); - expect(handleChange).toBeCalledWith(setting.name, { - value: expectedEditableValue, - }); - updated.setProps({ unsavedChanges: { value: expectedEditableValue } }); - const currentValue = wrapper.find('EuiColorPicker').prop('color'); - expect(currentValue).toEqual(expectedEditableValue); - }); - }); - } else { - describe(`for changing ${type} setting`, () => { - const { wrapper, component } = setup(); - // @ts-ignore - const userValue = userValues[type]; - const fieldUserValue = type === 'array' ? userValue.join(', ') : userValue; - - it('should be able to change value', async () => { - await (component.instance() as Field).onFieldChange(fieldUserValue); - const updated = wrapper.update(); - expect(handleChange).toBeCalledWith(setting.name, { value: fieldUserValue }); - updated.setProps({ unsavedChanges: { value: fieldUserValue } }); - const currentValue = getFieldSettingValue(updated, setting.name, type); - expect(currentValue).toEqual(fieldUserValue); - }); - - it('should be able to reset to default value', async () => { - await wrapper.setProps({ - unsavedChanges: {}, - setting: { ...setting, value: fieldUserValue }, - }); - const updated = wrapper.update(); - findTestSubject(updated, `advancedSetting-resetField-${setting.name}`).simulate('click'); - const expectedEditableValue = getEditableValue(setting.type, setting.defVal); - expect(handleChange).toBeCalledWith(setting.name, { - value: expectedEditableValue, - }); - updated.setProps({ unsavedChanges: { value: expectedEditableValue } }); - const currentValue = getFieldSettingValue(updated, setting.name, type); - expect(currentValue).toEqual(expectedEditableValue); - }); - }); - } - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.tsx deleted file mode 100644 index 21d31b53600a0..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx +++ /dev/null @@ -1,667 +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, { PureComponent, Fragment } from 'react'; -import classNames from 'classnames'; - -import { - EuiBadge, - EuiCode, - EuiCodeBlock, - EuiColorPicker, - EuiScreenReaderOnly, - EuiDescribedFormGroup, - EuiFieldNumber, - EuiFieldText, - EuiFilePicker, - EuiFormRow, - EuiIconTip, - EuiImage, - EuiLink, - EuiSpacer, - EuiText, - EuiSelect, - EuiSwitch, - EuiSwitchEvent, - EuiToolTip, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { UiSettingsType, DocLinksStart, ToastsStart } from '@kbn/core/public'; -import { FieldCodeEditor } from './field_code_editor'; -import { FieldSetting, FieldState } from '../../types'; -import { isDefaultValue } from '../../lib'; - -interface FieldProps { - setting: FieldSetting; - handleChange: (name: string, value: FieldState) => void; - enableSaving: boolean; - docLinks: DocLinksStart['links']; - toasts: ToastsStart; - clearChange?: (name: string) => void; - unsavedChanges?: FieldState; - loading?: boolean; -} - -export const getEditableValue = ( - type: UiSettingsType, - value: FieldSetting['value'], - defVal?: FieldSetting['defVal'] -) => { - const val = value === null || value === undefined ? defVal : value; - switch (type) { - case 'array': - return (val as string[]).join(', '); - case 'boolean': - return !!val; - case 'number': - return Number(val); - case 'image': - return val; - default: - return val || ''; - } -}; - -export class Field extends PureComponent { - private changeImageForm = React.createRef(); - - getDisplayedDefaultValue( - type: UiSettingsType, - defVal: FieldSetting['defVal'], - optionLabels: Record = {} - ) { - if (defVal === undefined || defVal === null || defVal === '') { - return 'null'; - } - switch (type) { - case 'array': - return (defVal as string[]).join(', '); - case 'select': - return optionLabels.hasOwnProperty(String(defVal)) - ? optionLabels[String(defVal)] - : String(defVal); - default: - return String(defVal); - } - } - - handleChange = (unsavedChanges: FieldState) => { - this.props.handleChange(this.props.setting.name, unsavedChanges); - }; - - resetField = () => { - const { type, defVal } = this.props.setting; - if (type === 'image') { - this.cancelChangeImage(); - return this.handleChange({ - value: getEditableValue(type, defVal, defVal), - changeImage: true, - }); - } - return this.handleChange({ value: getEditableValue(type, defVal) }); - }; - - componentDidUpdate(prevProps: FieldProps) { - if ( - prevProps.setting.type === 'image' && - prevProps.unsavedChanges?.value && - !this.props.unsavedChanges?.value - ) { - this.cancelChangeImage(); - } - } - - onCodeEditorChange = (value: string) => { - const { defVal, type } = this.props.setting; - - let newUnsavedValue; - let errorParams = {}; - - switch (type) { - case 'json': - const isJsonArray = Array.isArray(JSON.parse((defVal as string) || '{}')); - newUnsavedValue = value || (isJsonArray ? '[]' : '{}'); - try { - JSON.parse(newUnsavedValue); - } catch (e) { - errorParams = { - error: i18n.translate('advancedSettings.field.codeEditorSyntaxErrorMessage', { - defaultMessage: 'Invalid JSON syntax', - }), - isInvalid: true, - }; - } - break; - default: - newUnsavedValue = value; - } - - this.handleChange({ - value: newUnsavedValue, - ...errorParams, - }); - }; - - onFieldChangeSwitch = (e: EuiSwitchEvent) => { - return this.onFieldChange(e.target.checked); - }; - - onFieldChangeEvent = (e: React.ChangeEvent) => - this.onFieldChange(e.target.value); - - onFieldChange = (targetValue: any) => { - const { type, value, defVal, options } = this.props.setting; - let newUnsavedValue; - - switch (type) { - case 'boolean': - const { unsavedChanges } = this.props; - const currentValue = unsavedChanges - ? unsavedChanges.value - : getEditableValue(type, value, defVal); - newUnsavedValue = !currentValue; - break; - case 'number': - newUnsavedValue = Number(targetValue); - break; - case 'select': - if (typeof options?.[0] === 'number') { - newUnsavedValue = Number(targetValue); - } else { - newUnsavedValue = targetValue; - } - break; - default: - newUnsavedValue = targetValue; - } - - this.handleChange({ - value: newUnsavedValue, - }); - }; - - onImageChange = async (files: FileList | null) => { - if (files == null) return; - - if (!files.length) { - this.setState({ - unsavedValue: null, - }); - return; - } - - const file = files[0]; - try { - let base64Image = ''; - if (file instanceof File) { - base64Image = (await this.getImageAsBase64(file)) as string; - } - - this.handleChange({ - changeImage: true, - value: base64Image, - }); - } catch (err) { - this.props.toasts.addDanger( - i18n.translate('advancedSettings.field.imageChangeErrorMessage', { - defaultMessage: 'Image could not be saved', - }) - ); - this.cancelChangeImage(); - } - }; - - async getImageAsBase64(file: Blob): Promise { - const reader = new FileReader(); - reader.readAsDataURL(file); - - return new Promise((resolve, reject) => { - reader.onload = () => { - resolve(reader.result!); - }; - reader.onerror = (err) => { - reject(err); - }; - }); - } - - changeImage = () => { - this.handleChange({ - value: null, - changeImage: true, - }); - }; - - cancelChangeImage = () => { - if (this.changeImageForm.current?.fileInput) { - this.changeImageForm.current.fileInput.value = ''; - this.changeImageForm.current.handleChange(); - } - if (this.props.clearChange) { - this.props.clearChange(this.props.setting.name); - } - }; - - renderField(setting: FieldSetting, ariaDescribedBy?: string) { - const { enableSaving, unsavedChanges, loading } = this.props; - const { - name, - value, - type, - options, - optionLabels = {}, - isOverridden, - defVal, - ariaName, - } = setting; - const a11yProps: { [key: string]: string } = ariaDescribedBy - ? { - 'aria-label': ariaName, - 'aria-describedby': ariaDescribedBy, - } - : { - 'aria-label': ariaName, - }; - const currentValue = unsavedChanges - ? unsavedChanges.value - : getEditableValue(type, value, defVal); - - switch (type) { - case 'boolean': - return ( - - ) : ( - - ) - } - checked={!!currentValue} - onChange={this.onFieldChangeSwitch} - disabled={loading || isOverridden || !enableSaving} - data-test-subj={`advancedSetting-editField-${name}`} - {...a11yProps} - /> - ); - case 'markdown': - case 'json': - return ( -
- -
- ); - case 'image': - const changeImage = unsavedChanges?.changeImage; - if (!isDefaultValue(setting) && !changeImage) { - return ; - } else { - return ( - - ); - } - case 'select': - return ( - { - return { - text: optionLabels.hasOwnProperty(option) ? optionLabels[option] : option, - value: option, - }; - })} - onChange={this.onFieldChangeEvent} - isLoading={loading} - disabled={loading || isOverridden || !enableSaving} - fullWidth - data-test-subj={`advancedSetting-editField-${name}`} - /> - ); - case 'number': - return ( - - ); - case 'color': - return ( - - ); - default: - return ( - - ); - } - } - - renderLabel(setting: FieldSetting) { - return setting.name; - } - - renderHelpText(setting: FieldSetting) { - if (setting.isOverridden) { - return ( - - - - ); - } - - const canUpdateSetting = this.props.enableSaving; - const defaultLink = this.renderResetToDefaultLink(setting); - const imageLink = this.renderChangeImageLink(setting); - - if (canUpdateSetting && (defaultLink || imageLink)) { - return ( - - {defaultLink} - {imageLink} - - ); - } - - return null; - } - - renderTitle(setting: FieldSetting) { - const { unsavedChanges } = this.props; - const isInvalid = unsavedChanges?.isInvalid; - - const unsavedIconLabel = unsavedChanges - ? isInvalid - ? i18n.translate('advancedSettings.field.invalidIconLabel', { - defaultMessage: 'Invalid', - }) - : i18n.translate('advancedSettings.field.unsavedIconLabel', { - defaultMessage: 'Unsaved', - }) - : undefined; - - return ( -

- - {setting.displayName || setting.name} - - {setting.isCustom ? ( - - } - /> - ) : ( - '' - )} - - {unsavedChanges ? ( - - ) : ( - '' - )} -

- ); - } - - renderDescription(setting: FieldSetting) { - let description; - let deprecation; - - if (setting.deprecation) { - const links = this.props.docLinks; - - deprecation = ( - <> - - { - window.open(links.management[setting.deprecation!.docLinksKey], '_blank'); - }} - onClickAriaLabel={i18n.translate('advancedSettings.field.deprecationClickAreaLabel', { - defaultMessage: 'Click to view deprecation documentation for {settingName}.', - values: { - settingName: setting.name, - }, - })} - > - Deprecated - - - - - ); - } - - if (React.isValidElement(setting.description)) { - description = setting.description; - } else { - description = ( -
- ); - } - - return ( - - {deprecation} - {description} - {this.renderDefaultValue(setting)} - - ); - } - - renderDefaultValue(setting: FieldSetting) { - const { type, defVal, optionLabels } = setting; - if (isDefaultValue(setting)) { - return; - } - return ( - - - - {type === 'json' ? ( - - = 500 ? 300 : undefined} - > - {this.getDisplayedDefaultValue(type, defVal)} - - ), - }} - /> - - ) : ( - - {this.getDisplayedDefaultValue(type, defVal, optionLabels)} - ), - }} - /> - - )} - - - ); - } - - renderResetToDefaultLink(setting: FieldSetting) { - const { defVal, ariaName, name } = setting; - if ( - defVal === this.props.unsavedChanges?.value || - isDefaultValue(setting) || - this.props.loading - ) { - return; - } - return ( - - - - -     - - ); - } - - renderChangeImageLink(setting: FieldSetting) { - const changeImage = this.props.unsavedChanges?.changeImage; - const { type, value, ariaName, name } = setting; - if (type !== 'image' || !value || changeImage) { - return; - } - return ( - - - - - - ); - } - - render() { - const { setting, unsavedChanges } = this.props; - const error = unsavedChanges?.error; - const isInvalid = unsavedChanges?.isInvalid; - - const className = classNames('mgtAdvancedSettings__field', { - 'mgtAdvancedSettings__field--unsaved': unsavedChanges, - 'mgtAdvancedSettings__field--invalid': isInvalid, - }); - const groupId = `${setting.name}-group`; - const unsavedId = `${setting.name}-unsaved`; - - return ( - - - <> - {this.renderField(setting, unsavedChanges ? `${groupId} ${unsavedId}` : undefined)} - {unsavedChanges && ( - -

- {unsavedChanges.error - ? unsavedChanges.error - : i18n.translate('advancedSettings.field.settingIsUnsaved', { - defaultMessage: 'Setting is currently not saved.', - })} -

-
- )} - -
-
- ); - } -} diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field_code_editor.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field_code_editor.tsx deleted file mode 100644 index 83a6b0c90bfd6..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/field/field_code_editor.tsx +++ /dev/null @@ -1,107 +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, { useCallback } from 'react'; -import { monaco, XJsonLang } from '@kbn/monaco'; - -import { CodeEditor, MarkdownLang } from '@kbn/code-editor'; - -interface FieldCodeEditorProps { - value: string; - onChange: (value: string) => void; - type: 'markdown' | 'json'; - isReadOnly: boolean; - a11yProps: Record; - name: string; -} - -const MIN_DEFAULT_LINES_COUNT = 6; -const MAX_DEFAULT_LINES_COUNT = 30; - -export const FieldCodeEditor = ({ - value, - onChange, - type, - isReadOnly, - a11yProps, - name, -}: FieldCodeEditorProps) => { - // setting editor height based on lines height and count to stretch and fit its content - const setEditorCalculatedHeight = useCallback( - (editor: monaco.editor.IStandaloneCodeEditor) => { - const editorElement = editor.getDomNode(); - - if (!editorElement) { - return; - } - - const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight); - let lineCount = editor.getModel()?.getLineCount() || MIN_DEFAULT_LINES_COUNT; - if (lineCount < MIN_DEFAULT_LINES_COUNT) { - lineCount = MIN_DEFAULT_LINES_COUNT; - } else if (lineCount > MAX_DEFAULT_LINES_COUNT) { - lineCount = MAX_DEFAULT_LINES_COUNT; - } - const height = lineHeight * lineCount; - - editorElement.id = name; - editorElement.style.height = `${height}px`; - editor.layout(); - }, - [name] - ); - - const trimEditorBlankLines = useCallback((editor: monaco.editor.IStandaloneCodeEditor) => { - const editorModel = editor.getModel(); - - if (!editorModel) { - return; - } - const trimmedValue = editorModel.getValue().trim(); - editorModel.setValue(trimmedValue); - }, []); - - const editorDidMount = useCallback( - (editor) => { - setEditorCalculatedHeight(editor); - - editor.onDidChangeModelContent(() => { - setEditorCalculatedHeight(editor); - }); - - editor.onDidBlurEditorWidget(() => { - trimEditorBlankLines(editor); - }); - }, - [setEditorCalculatedHeight, trimEditorBlankLines] - ); - - return ( - - ); -}; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/__snapshots__/form.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/form/__snapshots__/form.test.tsx.snap deleted file mode 100644 index 064269f885cca..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/__snapshots__/form.test.tsx.snap +++ /dev/null @@ -1,1077 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Form should not render no settings message when instructed not to 1`] = ` - -
- <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- General -

-
-
- - - - - - - , - "settingsCount": -1, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- Dashboard -

-
-
-
- - <_EuiSplitPanelInner> - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- X-pack -

-
-
- - - - - - - , - "settingsCount": 9, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - -
-
-`; - -exports[`Form should render no settings message when there are no settings 1`] = ` - -
- <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- General -

-
-
- - - - - - - , - "settingsCount": -1, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- Dashboard -

-
-
-
- - <_EuiSplitPanelInner> - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- X-pack -

-
-
- - - - - - - , - "settingsCount": 9, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - -
-
-`; - -exports[`Form should render normally 1`] = ` - -
- <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- General -

-
-
- - - - - - - , - "settingsCount": -1, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- Dashboard -

-
-
-
- - <_EuiSplitPanelInner> - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- X-pack -

-
-
- - - - - - - , - "settingsCount": 9, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - -
-
-`; - -exports[`Form should render read-only when saving is disabled 1`] = ` - -
- <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- General -

-
-
- - - - - - - , - "settingsCount": -1, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- Dashboard -

-
-
-
- - <_EuiSplitPanelInner> - - - - - <_EuiSplitPanelOuter - hasBorder={true} - > - <_EuiSplitPanelInner - color="subdued" - > - - - -

- X-pack -

-
-
- - - - - - - , - "settingsCount": 9, - } - } - /> - - -
- - <_EuiSplitPanelInner> - - - - -
-
-`; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx deleted file mode 100644 index 4deb1b9c24d1d..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx +++ /dev/null @@ -1,321 +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 { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; - -import { UiSettingsType } from '@kbn/core/public'; -import { themeServiceMock, notificationServiceMock } from '@kbn/core/public/mocks'; -import { SettingsChanges } from '../../types'; -import { Form } from './form'; - -jest.mock('../field', () => ({ - Field: () => { - return 'field'; - }, -})); - -beforeAll(() => { - const localStorage: Record = { - 'core.chrome.isLocked': true, - }; - - Object.defineProperty(window, 'localStorage', { - value: { - getItem: (key: string) => { - return localStorage[key] || null; - }, - }, - writable: true, - }); -}); - -afterAll(() => { - delete (window as any).localStorage; -}); - -const defaults = { - requiresPageReload: false, - readOnly: false, - value: 'value', - description: 'description', - isOverridden: false, - type: 'string' as UiSettingsType, - isCustom: false, - defVal: 'defVal', -}; - -const settings = { - dashboard: [ - { - ...defaults, - name: 'dashboard:test:setting', - ariaName: 'dashboard test setting', - displayName: 'Dashboard test setting', - category: ['dashboard'], - requiresPageReload: true, - }, - ], - general: [ - { - ...defaults, - name: 'general:test:date', - ariaName: 'general test date', - displayName: 'Test date', - description: 'bar', - category: ['general'], - }, - { - ...defaults, - name: 'setting:test', - ariaName: 'setting test', - displayName: 'Test setting', - description: 'foo', - category: ['general'], - }, - { - ...defaults, - name: 'general:test:array', - ariaName: 'array test', - displayName: 'Test array setting', - description: 'array foo', - type: 'array' as UiSettingsType, - category: ['general'], - defVal: ['test'], - }, - ], - 'x-pack': [ - { - ...defaults, - name: 'xpack:test:setting', - ariaName: 'xpack test setting', - displayName: 'X-Pack test setting', - category: ['x-pack'], - description: 'bar', - }, - ], -}; - -const categories = ['general', 'dashboard', 'hiddenCategory', 'x-pack']; -const categoryCounts = { - general: 2, - dashboard: 1, - 'x-pack': 10, -}; -const save = jest.fn((changes: SettingsChanges) => Promise.resolve([true])); - -const clearQuery = () => {}; - -describe('Form', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render read-only when saving is disabled', async () => { - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render no settings message when there are no settings', async () => { - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should not render no settings message when instructed not to', async () => { - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should hide bottom bar when clicking on the cancel changes button', async () => { - const wrapper = mountWithI18nProvider( - - ); - (wrapper.instance() as Form).setState({ - unsavedChanges: { - 'dashboard:test:setting': { - value: 'changedValue', - }, - }, - }); - const updated = wrapper.update(); - expect(updated.exists('[data-test-subj="advancedSetting-bottomBar"]')).toEqual(true); - await findTestSubject(updated, `advancedSetting-cancelButton`).simulate('click'); - updated.update(); - expect(updated.exists('[data-test-subj="advancedSetting-bottomBar"]')).toEqual(false); - }); - - it('should show a reload toast when saving setting requiring a page reload', async () => { - const toasts = notificationServiceMock.createStartContract().toasts; - const wrapper = mountWithI18nProvider( - - ); - (wrapper.instance() as Form).setState({ - unsavedChanges: { - 'dashboard:test:setting': { - value: 'changedValue', - }, - }, - }); - const updated = wrapper.update(); - - findTestSubject(updated, `advancedSetting-saveButton`).simulate('click'); - expect(save).toHaveBeenCalled(); - await save({ 'dashboard:test:setting': 'changedValue' }); - expect(toasts.add).toHaveBeenCalledWith( - expect.objectContaining({ - title: expect.stringContaining( - 'One or more settings require you to reload the page to take effect.' - ), - }) - ); - }); - - it('should save an array typed field when user provides an empty string correctly', async () => { - const wrapper = mountWithI18nProvider( - - ); - - (wrapper.instance() as Form).setState({ - unsavedChanges: { - 'general:test:array': { - value: '', - }, - }, - }); - - findTestSubject(wrapper.update(), `advancedSetting-saveButton`).simulate('click'); - expect(save).toHaveBeenCalledWith({ 'general:test:array': [] }); - }); - - it('should save an array typed field when user provides a comma separated string correctly', async () => { - const wrapper = mountWithI18nProvider( - - ); - - (wrapper.instance() as Form).setState({ - unsavedChanges: { - 'general:test:array': { - value: 'test1, test2', - }, - }, - }); - - findTestSubject(wrapper.update(), `advancedSetting-saveButton`).simulate('click'); - expect(save).toHaveBeenCalledWith({ 'general:test:array': ['test1', 'test2'] }); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx deleted file mode 100644 index e45af3c98cce6..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx +++ /dev/null @@ -1,432 +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, { PureComponent, Fragment } from 'react'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiSplitPanel, - EuiLink, - EuiCallOut, - EuiSpacer, - EuiText, - EuiBottomBar, - EuiButton, - EuiToolTip, - EuiButtonEmpty, - EuiTitle, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { isEmpty } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { UiCounterMetricType } from '@kbn/analytics'; -import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { DocLinksStart, ThemeServiceStart, ToastsStart } from '@kbn/core/public'; - -import { getCategoryName } from '../../lib'; -import { Field, getEditableValue } from '../field'; -import { FieldSetting, SettingsChanges, FieldState } from '../../types'; - -type Category = string; - -interface FormProps { - settings: Record; - visibleSettings: Record; - categories: Category[]; - categoryCounts: Record; - clearQuery: () => void; - save: (changes: SettingsChanges) => Promise; - showNoResultsMessage: boolean; - enableSaving: boolean; - docLinks: DocLinksStart['links']; - toasts: ToastsStart; - theme: ThemeServiceStart['theme$']; - trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; - queryText?: string; -} - -interface FormState { - unsavedChanges: { - [key: string]: FieldState; - }; - loading: boolean; -} - -export class Form extends PureComponent { - state: FormState = { - unsavedChanges: {}, - loading: false, - }; - - setLoading(loading: boolean) { - this.setState({ - loading, - }); - } - - getSettingByKey = (key: string): FieldSetting | undefined => { - return Object.values(this.props.settings) - .flat() - .find((el) => el.name === key); - }; - - getCountOfUnsavedChanges = (): number => { - return Object.keys(this.state.unsavedChanges).length; - }; - - getCountOfHiddenUnsavedChanges = (): number => { - const shownSettings = Object.values(this.props.visibleSettings) - .flat() - .map((setting) => setting.name); - return Object.keys(this.state.unsavedChanges).filter((key) => !shownSettings.includes(key)) - .length; - }; - - areChangesInvalid = (): boolean => { - const { unsavedChanges } = this.state; - return Object.values(unsavedChanges).some(({ isInvalid }) => isInvalid); - }; - - handleChange = (key: string, change: FieldState) => { - const setting = this.getSettingByKey(key); - if (!setting) { - return; - } - const { type, defVal, value } = setting; - const savedValue = getEditableValue(type, value, defVal); - if (change.value === savedValue) { - return this.clearChange(key); - } - this.setState({ - unsavedChanges: { - ...this.state.unsavedChanges, - [key]: change, - }, - }); - }; - - clearChange = (key: string) => { - if (!this.state.unsavedChanges[key]) { - return; - } - const unsavedChanges = { ...this.state.unsavedChanges }; - delete unsavedChanges[key]; - - this.setState({ - unsavedChanges, - }); - }; - - clearAllUnsaved = () => { - this.setState({ unsavedChanges: {} }); - }; - - saveAll = async () => { - this.setLoading(true); - const { unsavedChanges } = this.state; - - if (isEmpty(unsavedChanges)) { - return; - } - const configToSave: SettingsChanges = {}; - let requiresReload = false; - - Object.entries(unsavedChanges).forEach(([name, { value }]) => { - const setting = this.getSettingByKey(name); - if (!setting) { - return; - } - const { defVal, type, requiresPageReload, metric } = setting; - let valueToSave = value; - let equalsToDefault = false; - switch (type) { - case 'array': - valueToSave = valueToSave.trim(); - valueToSave = - valueToSave === '' ? [] : valueToSave.split(',').map((val: string) => val.trim()); - equalsToDefault = valueToSave.join(',') === (defVal as string[]).join(','); - break; - case 'json': - const isArray = Array.isArray(JSON.parse((defVal as string) || '{}')); - valueToSave = valueToSave.trim(); - valueToSave = valueToSave || (isArray ? '[]' : '{}'); - case 'boolean': - if (metric && this.props.trackUiMetric) { - const metricName = valueToSave ? `${metric.name}_on` : `${metric.name}_off`; - this.props.trackUiMetric(metric.type, metricName); - } - default: - equalsToDefault = valueToSave === defVal; - } - if (requiresPageReload) { - requiresReload = true; - } - configToSave[name] = equalsToDefault ? null : valueToSave; - }); - - try { - await this.props.save(configToSave); - this.clearAllUnsaved(); - if (requiresReload) { - this.renderPageReloadToast(); - } - } catch (e) { - this.props.toasts.addDanger( - i18n.translate('advancedSettings.form.saveErrorMessage', { - defaultMessage: 'Unable to save', - }) - ); - } - this.setLoading(false); - }; - - renderPageReloadToast = () => { - this.props.toasts.add({ - title: i18n.translate('advancedSettings.form.requiresPageReloadToastDescription', { - defaultMessage: 'One or more settings require you to reload the page to take effect.', - }), - toastLifeTimeMs: 15000, - text: toMountPoint( - - - - window.location.reload()} - data-test-subj="windowReloadButton" - > - {i18n.translate('advancedSettings.form.requiresPageReloadToastButtonLabel', { - defaultMessage: 'Reload page', - })} - - - - - ), - color: 'success', - }); - }; - - renderClearQueryLink(totalSettings: number, currentSettings: number) { - const { clearQuery } = this.props; - - if (totalSettings !== currentSettings) { - return ( - - - - - - - - ), - }} - /> - - - ); - } - - return null; - } - - renderCategory(category: Category, settings: FieldSetting[], totalSettings: number) { - return ( - - - - - - -

{getCategoryName(category)}

-
-
- {this.renderClearQueryLink(totalSettings, settings.length)} -
-
- - {settings.map((setting) => { - return ( - - ); - })} - -
- -
- ); - } - - maybeRenderNoSettings(clearQuery: FormProps['clearQuery']) { - if (this.props.showNoResultsMessage) { - return ( - - - - - ), - queryText: {this.props.queryText}, - }} - /> - - } - /> - ); - } - return null; - } - - renderCountOfUnsaved = () => { - const unsavedCount = this.getCountOfUnsavedChanges(); - const hiddenUnsavedCount = this.getCountOfHiddenUnsavedChanges(); - return ( - - - - ); - }; - - renderBottomBar = () => { - const areChangesInvalid = this.areChangesInvalid(); - return ( - - - -

{this.renderCountOfUnsaved()}

-
- - - - {i18n.translate('advancedSettings.form.cancelButtonLabel', { - defaultMessage: 'Cancel changes', - })} - - - - - - {i18n.translate('advancedSettings.form.saveButtonLabel', { - defaultMessage: 'Save changes', - })} - - - -
-
- ); - }; - - render() { - const { unsavedChanges } = this.state; - const { visibleSettings, categories, categoryCounts, clearQuery } = this.props; - const currentCategories: Category[] = []; - const hasUnsavedChanges = !isEmpty(unsavedChanges); - - if (hasUnsavedChanges) { - document.body.classList.add('kbnBody--mgtAdvancedSettingsHasBottomBar'); - } else { - document.body.classList.remove('kbnBody--mgtAdvancedSettingsHasBottomBar'); - } - - categories.forEach((category) => { - if (visibleSettings[category] && visibleSettings[category].length) { - currentCategories.push(category); - } - }); - - return ( - -
- {currentCategories.length - ? currentCategories.map((category) => { - return this.renderCategory( - category, - visibleSettings[category], - categoryCounts[category] - ); - }) - : this.maybeRenderNoSettings(clearQuery)} -
- {hasUnsavedChanges && this.renderBottomBar()} -
- ); - } -} diff --git a/src/plugins/advanced_settings/public/management_app/components/form/index.ts b/src/plugins/advanced_settings/public/management_app/components/form/index.ts deleted file mode 100644 index 5d218b01b0eaa..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/index.ts +++ /dev/null @@ -1,9 +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. - */ - -export { Form } from './form'; diff --git a/src/plugins/advanced_settings/public/management_app/components/search/__snapshots__/search.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/search/__snapshots__/search.test.tsx.snap deleted file mode 100644 index 24f8729cafee1..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/search/__snapshots__/search.test.tsx.snap +++ /dev/null @@ -1,63 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Search should render normally 1`] = ` - - - -`; diff --git a/src/plugins/advanced_settings/public/management_app/components/search/index.ts b/src/plugins/advanced_settings/public/management_app/components/search/index.ts deleted file mode 100644 index a9abed56dbb27..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/search/index.ts +++ /dev/null @@ -1,9 +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. - */ - -export { Search } from './search'; diff --git a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx b/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx deleted file mode 100644 index cef5978945bc8..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx +++ /dev/null @@ -1,63 +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 { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; - -import { findTestSubject } from '@elastic/eui/lib/test'; - -import { Query } from '@elastic/eui'; -import { Search } from './search'; - -const query = Query.parse(''); -const categories = ['general', 'dashboard', 'hiddenCategory', 'x-pack']; - -describe('Search', () => { - it('should render normally', async () => { - const onQueryChange = () => {}; - const component = shallowWithI18nProvider( - - ); - - expect(component).toMatchSnapshot(); - }); - - it('should call parent function when query is changed', async () => { - // This test is brittle as it knows about implementation details - // (EuiFieldSearch uses onKeyup instead of onChange to handle input) - const onQueryChange = jest.fn(); - const component = mountWithI18nProvider( - - ); - findTestSubject(component, 'settingsSearchBar').simulate('keyup', { - target: { value: 'new filter' }, - }); - expect(onQueryChange).toHaveBeenCalledTimes(1); - }); - - it('should handle query parse error', async () => { - const onQueryChangeMock = jest.fn(); - const component = mountWithI18nProvider( - - ); - - const searchBar = findTestSubject(component, 'settingsSearchBar'); - - // Send invalid query - searchBar.simulate('keyup', { target: { value: '?' } }); - expect(onQueryChangeMock).toHaveBeenCalledTimes(0); - expect(component.state().isSearchTextValid).toBe(false); - - onQueryChangeMock.mockReset(); - - // Send valid query to ensure component can recover from invalid query - searchBar.simulate('keyup', { target: { value: 'dateFormat' } }); - expect(onQueryChangeMock).toHaveBeenCalledTimes(1); - expect(component.state().isSearchTextValid).toBe(true); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/components/search/search.tsx b/src/plugins/advanced_settings/public/management_app/components/search/search.tsx deleted file mode 100644 index 8805c9cc7eed9..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/search/search.tsx +++ /dev/null @@ -1,100 +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, { Fragment, PureComponent } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiSearchBar, EuiFormErrorText, Query } from '@elastic/eui'; - -import { getCategoryName } from '../../lib'; - -export const CATEGORY_FIELD = 'category'; - -interface SearchProps { - categories: string[]; - query: Query; - onQueryChange: ({ query }: { query: Query }) => void; -} - -export const parseErrorMsg = i18n.translate( - 'advancedSettings.searchBar.unableToParseQueryErrorMessage', - { defaultMessage: 'Unable to parse query' } -); - -export class Search extends PureComponent { - private categories: Array<{ value: string; name: string }> = []; - - constructor(props: SearchProps) { - super(props); - const { categories } = props; - this.categories = categories.map((category) => { - return { - value: category, - name: getCategoryName(category), - }; - }); - } - - state = { - isSearchTextValid: true, - parseErrorMessage: null, - }; - - onChange = ({ query, error }: { query: Query | null; error: { message: string } | null }) => { - if (error) { - this.setState({ - isSearchTextValid: false, - parseErrorMessage: error.message, - }); - return; - } - - this.setState({ - isSearchTextValid: true, - parseErrorMessage: null, - }); - this.props.onQueryChange({ query: query! }); - }; - - render() { - const { query } = this.props; - - const box = { - incremental: true, - 'data-test-subj': 'settingsSearchBar', - 'aria-label': i18n.translate('advancedSettings.searchBarAriaLabel', { - defaultMessage: 'Search advanced settings', - }), // hack until EuiSearchBar is fixed - }; - - const filters = [ - { - type: 'field_value_selection' as const, - field: CATEGORY_FIELD, - name: i18n.translate('advancedSettings.categorySearchLabel', { - defaultMessage: 'Category', - }), - multiSelect: 'or' as const, - options: this.categories, - }, - ]; - - let queryParseError; - if (!this.state.isSearchTextValid) { - queryParseError = ( - {`${parseErrorMsg}. ${this.state.parseErrorMessage}`} - ); - } - - return ( - - - {queryParseError} - - ); - } -} diff --git a/src/plugins/advanced_settings/public/management_app/i18n_texts.ts b/src/plugins/advanced_settings/public/management_app/i18n_texts.ts deleted file mode 100644 index 5267e4baa8029..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/i18n_texts.ts +++ /dev/null @@ -1,35 +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 { i18n } from '@kbn/i18n'; - -export const i18nTexts = { - defaultSpaceTabTitle: i18n.translate('advancedSettings.spaceSettingsTabTitle', { - defaultMessage: 'Space Settings', - }), - defaultSpaceCalloutTitle: i18n.translate('advancedSettings.defaultSpaceCalloutTitle', { - defaultMessage: 'Changes will affect the current space.', - }), - defaultSpaceCalloutSubtitle: i18n.translate('advancedSettings.defaultSpaceCalloutSubtitle', { - defaultMessage: - 'Changes will only be applied to the current space. These settings are intended for advanced users, as improper configurations may adversely affect aspects of Kibana.', - }), - globalTabTitle: i18n.translate('advancedSettings.globalSettingsTabTitle', { - defaultMessage: 'Global Settings', - }), - globalCalloutTitle: i18n.translate('advancedSettings.globalCalloutTitle', { - defaultMessage: 'Changes will affect all user settings across all spaces', - }), - globalCalloutSubtitle: i18n.translate('advancedSettings.globalCalloutSubtitle', { - defaultMessage: - 'Changes will be applied to all users across all spaces. This includes both native Kibana users and single-sign on users.', - }), - advancedSettingsTitle: i18n.translate('advancedSettings.advancedSettingsLabel', { - defaultMessage: 'Advanced Settings', - }), -}; diff --git a/src/plugins/advanced_settings/public/management_app/index.scss b/src/plugins/advanced_settings/public/management_app/index.scss deleted file mode 100644 index 5f473c519dda6..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './advanced_settings'; diff --git a/src/plugins/advanced_settings/public/management_app/lib/default_category.ts b/src/plugins/advanced_settings/public/management_app/lib/default_category.ts deleted file mode 100644 index c6fb7839662da..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/default_category.ts +++ /dev/null @@ -1,9 +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. - */ - -export const DEFAULT_CATEGORY = 'general'; diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts deleted file mode 100644 index 739485959b316..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts +++ /dev/null @@ -1,36 +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 expect from '@kbn/expect'; -import { getAriaName } from './get_aria_name'; - -describe('Settings', function () { - describe('Advanced', function () { - describe('getAriaName(name)', function () { - it('should return a space delimited lower-case string with no special characters', function () { - expect(getAriaName('xPack:defaultAdminEmail')).to.be('x pack default admin email'); - expect(getAriaName('doc_table:highlight')).to.be('doc table highlight'); - expect(getAriaName('foo')).to.be('foo'); - }); - - it('should return an empty string if passed undefined or null', function () { - expect(getAriaName()).to.be(''); - expect(getAriaName(undefined)).to.be(''); - }); - - it('should preserve category string', function () { - expect(getAriaName('xPack:fooBar:foo_bar_baz category:(general)')).to.be( - 'x pack foo bar foo bar baz category:(general)' - ); - expect(getAriaName('xPack:fooBar:foo_bar_baz category:(general or discover)')).to.be( - 'x pack foo bar foo bar baz category:(general or discover)' - ); - }); - }); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts deleted file mode 100644 index 5e3ff394dd255..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts +++ /dev/null @@ -1,53 +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 { words } from 'lodash'; - -import { Query } from '@elastic/eui'; - -import { CATEGORY_FIELD } from '../components/search/search'; - -const mapWords = (name?: string): string => - words(name ?? '') - .map((word) => word.toLowerCase()) - .join(' '); - -/** - * @name {string} the name of the configuration object - * @returns {string} a space delimited, lowercase string with - * special characters removed. - * - * Examples: - * - `xPack:fooBar:foo_bar_baz` -> `x pack foo bar foo bar baz` - * - `xPack:fooBar:foo_bar_baz category:(general)` -> `x pack foo bar foo bar baz category:(general)` - */ -export function getAriaName(name?: string) { - if (!name) { - return ''; - } - - const query = Query.parse(name); - - if (query.hasOrFieldClause(CATEGORY_FIELD)) { - const categories = query.getOrFieldClause(CATEGORY_FIELD); - const termValue = mapWords(query.removeOrFieldClauses(CATEGORY_FIELD).text); - - if (!categories || !Array.isArray(categories.value)) { - return termValue; - } - - let categoriesQuery = Query.parse(''); - categories.value.forEach((v) => { - categoriesQuery = categoriesQuery.addOrFieldValue(CATEGORY_FIELD, v); - }); - - return `${termValue} ${categoriesQuery.text}`; - } - - return mapWords(name); -} diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts deleted file mode 100644 index 22fe8f7a378a5..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.test.ts +++ /dev/null @@ -1,25 +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 expect from '@kbn/expect'; -import { getCategoryName } from './get_category_name'; - -describe('Settings', function () { - describe('Advanced', function () { - describe('getCategoryName(category)', function () { - it('should capitalize unknown category', function () { - expect(getCategoryName('elasticsearch')).to.be('Elasticsearch'); - }); - - it('should return empty string for no category', function () { - expect(getCategoryName()).to.be(''); - expect(getCategoryName('')).to.be(''); - }); - }); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts deleted file mode 100644 index 7341104bf2abb..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts +++ /dev/null @@ -1,54 +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 { i18n } from '@kbn/i18n'; - -const upperFirst = (str = '') => str.replace(/^./, (strng) => strng.toUpperCase()); - -const names: Record = { - general: i18n.translate('advancedSettings.categoryNames.generalLabel', { - defaultMessage: 'General', - }), - machineLearning: i18n.translate('advancedSettings.categoryNames.machineLearningLabel', { - defaultMessage: 'Machine Learning', - }), - observability: i18n.translate('advancedSettings.categoryNames.observabilityLabel', { - defaultMessage: 'Observability', - }), - timelion: i18n.translate('advancedSettings.categoryNames.timelionLabel', { - defaultMessage: 'Timelion', - }), - notifications: i18n.translate('advancedSettings.categoryNames.notificationsLabel', { - defaultMessage: 'Notifications', - }), - visualizations: i18n.translate('advancedSettings.categoryNames.visualizationsLabel', { - defaultMessage: 'Visualizations', - }), - discover: i18n.translate('advancedSettings.categoryNames.discoverLabel', { - defaultMessage: 'Discover', - }), - dashboard: i18n.translate('advancedSettings.categoryNames.dashboardLabel', { - defaultMessage: 'Dashboard', - }), - reporting: i18n.translate('advancedSettings.categoryNames.reportingLabel', { - defaultMessage: 'Reporting', - }), - search: i18n.translate('advancedSettings.categoryNames.searchLabel', { - defaultMessage: 'Search', - }), - securitySolution: i18n.translate('advancedSettings.categoryNames.securitySolutionLabel', { - defaultMessage: 'Security Solution', - }), - enterpriseSearch: i18n.translate('advancedSettings.categoryNames.enterpriseSearchLabel', { - defaultMessage: 'Enterprise Search', - }), -}; - -export function getCategoryName(category?: string) { - return category ? names[category] || upperFirst(category) : ''; -} diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts b/src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts deleted file mode 100644 index 308f471620e56..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/get_val_type.test.ts +++ /dev/null @@ -1,39 +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 expect from '@kbn/expect'; -import { getValType } from './get_val_type'; - -describe('Settings', function () { - describe('Advanced', function () { - describe('getValType(def, val)', function () { - it('should return the explicitly defined type of a setting', function () { - expect(getValType({ type: 'string' })).to.be('string'); - expect(getValType({ type: 'json' })).to.be('json'); - expect(getValType({ type: 'string', value: 5 })).to.be('string'); - }); - - it('should return array if the value is an Array and there is no defined type', function () { - expect(getValType({ type: 'string' }, [1, 2, 3])).to.be('string'); - expect(getValType({ type: 'json', value: [1, 2, 3] })).to.be('json'); - - expect(getValType({ value: 'someString' }, [1, 2, 3])).to.be('array'); - expect(getValType({ value: [1, 2, 3] }, 'someString')).to.be('array'); - }); - - it('should return the type of the default value if there is no type and it is not an array', function () { - expect(getValType({ value: 'someString' })).to.be('string'); - expect(getValType({ value: 'someString' }, 42)).to.be('string'); - }); - - it('should return the type of the value if the default value is null', function () { - expect(getValType({ value: null }, 'someString')).to.be('string'); - }); - }); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_val_type.ts b/src/plugins/advanced_settings/public/management_app/lib/get_val_type.ts deleted file mode 100644 index eeee280f826ae..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/get_val_type.ts +++ /dev/null @@ -1,40 +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. - */ - -/** - * @param {object} advanced setting definition object - * @param {?} current value of the setting - * @returns {string} the type to use for determining the display and editor - */ - -import { UiSettingsType } from '@kbn/core/public'; -import { FieldSetting } from '../types'; - -export function getValType(def: Partial, value?: any): UiSettingsType { - if (def.type) { - return def.type; - } - - if (Array.isArray(value) || Array.isArray(def.value)) { - return 'array'; - } - - const typeofVal = def.value != null ? typeof def.value : typeof value; - - if (typeofVal === 'bigint') { - return 'number'; - } - - if (typeofVal === 'symbol' || typeofVal === 'object' || typeofVal === 'function') { - throw new Error( - `incompatible UiSettingsType: '${def.name}' type ${typeofVal} | ${JSON.stringify(def)}` - ); - } - - return typeofVal; -} diff --git a/src/plugins/advanced_settings/public/management_app/lib/index.ts b/src/plugins/advanced_settings/public/management_app/lib/index.ts deleted file mode 100644 index 7b812f031a8b7..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/index.ts +++ /dev/null @@ -1,14 +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. - */ - -export { isDefaultValue } from './is_default_value'; -export { toEditableConfig } from './to_editable_config'; -export { getCategoryName } from './get_category_name'; -export { DEFAULT_CATEGORY } from './default_category'; -export { getAriaName } from './get_aria_name'; -export { fieldSorter } from './sort_fields'; diff --git a/src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts b/src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts deleted file mode 100644 index 4828ea957718d..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/is_default_value.test.ts +++ /dev/null @@ -1,76 +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 expect from '@kbn/expect'; -import { isDefaultValue } from './is_default_value'; -import { UiSettingsType } from '@kbn/core/public'; - -describe('Settings', function () { - describe('Advanced', function () { - describe('getCategoryName(category)', function () { - describe('when given a setting definition object', function () { - const setting = { - isCustom: false, - value: 'value', - defVal: 'defaultValue', - displayName: 'displayName', - name: 'name', - ariaName: 'ariaName', - description: 'description', - requiresPageReload: false, - type: 'string' as UiSettingsType, - isOverridden: false, - readOnly: false, - options: [], - optionLabels: { option: 'label' }, - category: ['category'], - validation: { regex: /regexString/, message: 'validation description' }, - }; - - describe('that is custom', function () { - it('should return true', function () { - expect(isDefaultValue({ ...setting, isCustom: true })).to.be(true); - }); - }); - - describe('without a value', function () { - it('should return false for empty string but true for undefined', function () { - expect(isDefaultValue({ ...setting, value: undefined })).to.be(true); - expect(isDefaultValue({ ...setting, value: '' })).to.be(false); - }); - }); - - describe('with a value that is the same as the default value', function () { - it('should return true', function () { - expect(isDefaultValue({ ...setting, value: 'defaultValue' })).to.be(true); - expect(isDefaultValue({ ...setting, value: [], defVal: [] })).to.be(true); - expect( - isDefaultValue({ ...setting, value: '{"foo":"bar"}', defVal: '{"foo":"bar"}' }) - ).to.be(true); - expect(isDefaultValue({ ...setting, value: 123, defVal: 123 })).to.be(true); - expect(isDefaultValue({ ...setting, value: 456, defVal: '456' })).to.be(true); - expect(isDefaultValue({ ...setting, value: false, defVal: false })).to.be(true); - }); - }); - - describe('with a value that is different than the default value', function () { - it('should return false', function () { - expect(isDefaultValue({ ...setting })).to.be(false); - expect(isDefaultValue({ ...setting, value: [1], defVal: [2] })).to.be(false); - expect( - isDefaultValue({ ...setting, value: '{"foo":"bar"}', defVal: '{"foo2":"bar2"}' }) - ).to.be(false); - expect(isDefaultValue({ ...setting, value: 123, defVal: 1234 })).to.be(false); - expect(isDefaultValue({ ...setting, value: 456, defVal: '4567' })).to.be(false); - expect(isDefaultValue({ ...setting, value: true, defVal: false })).to.be(false); - }); - }); - }); - }); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/lib/sort_fields.test.ts b/src/plugins/advanced_settings/public/management_app/lib/sort_fields.test.ts deleted file mode 100644 index ae2d6c609ac60..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/sort_fields.test.ts +++ /dev/null @@ -1,56 +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 { FieldSetting } from '../types'; -import { fieldSorter } from './sort_fields'; - -const createField = (parts: Partial): FieldSetting => ({ - displayName: 'displayName', - name: 'field', - value: 'value', - requiresPageReload: false, - type: 'string', - category: [], - ariaName: 'ariaName', - isOverridden: false, - defVal: 'defVal', - isCustom: false, - ...parts, -}); - -describe('fieldSorter', () => { - it('sort fields based on their `order` field if present on both', () => { - const fieldA = createField({ order: 3 }); - const fieldB = createField({ order: 1 }); - const fieldC = createField({ order: 2 }); - - expect([fieldA, fieldB, fieldC].sort(fieldSorter)).toEqual([fieldB, fieldC, fieldA]); - }); - it('fields with order defined are ordered first', () => { - const fieldA = createField({ order: 2 }); - const fieldB = createField({ order: undefined }); - const fieldC = createField({ order: 1 }); - - expect([fieldA, fieldB, fieldC].sort(fieldSorter)).toEqual([fieldC, fieldA, fieldB]); - }); - it('sorts by `name` when fields have the same `order`', () => { - const fieldA = createField({ order: 2, name: 'B' }); - const fieldB = createField({ order: 1 }); - const fieldC = createField({ order: 2, name: 'A' }); - - expect([fieldA, fieldB, fieldC].sort(fieldSorter)).toEqual([fieldB, fieldC, fieldA]); - }); - - it('sorts by `name` when fields have no `order`', () => { - const fieldA = createField({ order: undefined, name: 'B' }); - const fieldB = createField({ order: undefined, name: 'A' }); - const fieldC = createField({ order: 1 }); - - expect([fieldA, fieldB, fieldC].sort(fieldSorter)).toEqual([fieldC, fieldB, fieldA]); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/lib/sort_fields.ts b/src/plugins/advanced_settings/public/management_app/lib/sort_fields.ts deleted file mode 100644 index 90bfa18d2198e..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/sort_fields.ts +++ /dev/null @@ -1,31 +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 { Comparators } from '@elastic/eui'; -import { FieldSetting } from '../types'; - -const cmp = Comparators.default('asc'); - -export const fieldSorter = (a: FieldSetting, b: FieldSetting): number => { - const aOrder = a.order !== undefined; - const bOrder = b.order !== undefined; - - if (aOrder && bOrder) { - if (a.order === b.order) { - return cmp(a.name, b.name); - } - return cmp(a.order, b.order); - } - if (aOrder) { - return -1; - } - if (bOrder) { - return 1; - } - return cmp(a.name, b.name); -}; diff --git a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts deleted file mode 100644 index 7de174d409b55..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts +++ /dev/null @@ -1,106 +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 { PublicUiSettingsParams } from '@kbn/core/public'; -import expect from '@kbn/expect'; -import { toEditableConfig } from './to_editable_config'; - -const defDefault = { - isOverridden: true, -}; - -function invoke({ - def = defDefault, - name = 'woah', - value = 'forreal', -}: { - def?: PublicUiSettingsParams & { isOverridden?: boolean }; - name?: string; - value?: any; -}) { - return toEditableConfig({ def, name, value, isCustom: def === defDefault, isOverridden: true }); -} - -describe('Settings', function () { - describe('Advanced', function () { - describe('toEditableConfig(def, name, value)', function () { - it('sets name', function () { - expect(invoke({ name: 'who' }).name).to.equal('who'); - }); - - it('sets value', function () { - expect(invoke({ value: 'what' }).value).to.equal('what'); - }); - - it('sets type', function () { - expect(invoke({ value: 'what' }).type).to.be('string'); - expect(invoke({ value: 0 }).type).to.be('number'); - expect(invoke({ value: [] }).type).to.be('array'); - }); - - describe('when given a setting definition object', function () { - let def: PublicUiSettingsParams & { isOverridden?: boolean }; - beforeEach(function () { - def = { - value: 'the original', - description: 'the one and only', - options: ['all the options'], - }; - }); - - it('is not marked as custom', function () { - expect(invoke({ def }).isCustom).to.be(false); - }); - - it('sets a default value', function () { - expect(invoke({ def }).defVal).to.equal(def.value); - }); - - it('sets a description', function () { - expect(invoke({ def }).description).to.equal(def.description); - }); - - it('sets options', function () { - expect(invoke({ def }).options).to.equal(def.options); - }); - - describe('that contains a type', function () { - it('sets that type', function () { - def.type = 'string'; - expect(invoke({ def }).type).to.equal(def.type); - }); - }); - - describe('that contains a value of type array', function () { - it('sets type to array', function () { - def.value = []; - expect(invoke({ def }).type).to.equal('array'); - }); - }); - }); - - describe('when not given a setting definition object', function () { - it('is marked as custom', function () { - expect(invoke({}).isCustom).to.be(true); - }); - - it('sets defVal to undefined', function () { - expect(invoke({}).defVal).to.be(undefined); - }); - - it('sets description to undefined', function () { - expect(invoke({}).description).to.be(undefined); - }); - - it('sets options to undefined', function () { - expect(invoke({}).options).to.be(undefined); - }); - }); - }); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts deleted file mode 100644 index b7a08888bb76a..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts +++ /dev/null @@ -1,59 +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 { PublicUiSettingsParams, UserProvidedValues, SavedObjectAttribute } from '@kbn/core/public'; -import { FieldSetting } from '../types'; -import { getValType } from './get_val_type'; -import { getAriaName } from './get_aria_name'; -import { DEFAULT_CATEGORY } from './default_category'; - -/** - * @param {object} advanced setting definition object - * @param {object} name of setting - * @param {object} current value of setting - * @returns {object} the editable config object - */ -export function toEditableConfig({ - def, - name, - value, - isCustom, - isOverridden, -}: { - def: PublicUiSettingsParams & UserProvidedValues; - name: string; - value: SavedObjectAttribute; - isCustom: boolean; - isOverridden: boolean; -}) { - if (!def) { - def = {}; - } - - const conf: FieldSetting = { - name, - displayName: def.name || name, - ariaName: def.name || getAriaName(name), - value, - category: def.category && def.category.length ? def.category : [DEFAULT_CATEGORY], - isCustom, - isOverridden, - readOnly: !!def.readonly, - defVal: def.value, - type: getValType(def, value), - description: def.description, - deprecation: def.deprecation, - options: def.options, - optionLabels: def.optionLabels, - order: def.order, - requiresPageReload: !!def.requiresPageReload, - metric: def.metric, - }; - - return conf; -} diff --git a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx deleted file mode 100644 index d41f1f1cacfe4..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx +++ /dev/null @@ -1,109 +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 ReactDOM from 'react-dom'; -import { Redirect, RouteChildrenProps } from 'react-router-dom'; -import { Router, Routes, Route } from '@kbn/shared-ux-router'; - -import { i18n } from '@kbn/i18n'; - -import { LocationDescriptor } from 'history'; -import { url } from '@kbn/kibana-utils-plugin/public'; -import { ManagementAppMountParams } from '@kbn/management-plugin/public'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import { StartServicesAccessor } from '@kbn/core/public'; -import type { SectionRegistryStart } from '@kbn/management-settings-section-registry'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; - -import { QUERY } from './advanced_settings'; -import { Settings } from './settings'; - -import './index.scss'; - -const title = i18n.translate('advancedSettings.advancedSettingsLabel', { - defaultMessage: 'Advanced Settings', -}); -const crumb = [{ text: title }]; - -const readOnlyBadge = { - text: i18n.translate('advancedSettings.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('advancedSettings.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save advanced settings', - }), - iconType: 'glasses', -}; - -type RedirectUrlProps = RouteChildrenProps<{ [QUERY]: string }>; - -const redirectUrl = ({ match, location }: RedirectUrlProps): LocationDescriptor => { - const search = url.addQueryParam(location.search, QUERY, match?.params[QUERY]); - - return { - pathname: '/', - search, - }; -}; - -export async function mountManagementSection( - getStartServices: StartServicesAccessor, - params: ManagementAppMountParams, - sectionRegistry: SectionRegistryStart, - usageCollection?: UsageCollectionSetup -) { - params.setBreadcrumbs(crumb); - const [{ settings, notifications, docLinks, application, chrome, i18n: i18nStart, theme }] = - await getStartServices(); - - const { advancedSettings, globalSettings } = application.capabilities; - const canSaveAdvancedSettings = advancedSettings.save as boolean; - const canSaveGlobalSettings = globalSettings.save as boolean; - const canShowGlobalSettings = globalSettings.show as boolean; - const trackUiMetric = usageCollection?.reportUiCounter.bind(usageCollection, 'advanced_settings'); - if (!canSaveAdvancedSettings || (!canSaveGlobalSettings && canShowGlobalSettings)) { - chrome.setBadge(readOnlyBadge); - } - - chrome.docTitle.change(title); - - ReactDOM.render( - - - - {/* TODO: remove route param (`query`) in 7.13 */} - - {(props: RedirectUrlProps) => } - - - - - - - , - params.element - ); - return () => { - chrome.docTitle.reset(); - ReactDOM.unmountComponentAtNode(params.element); - }; -} diff --git a/src/plugins/advanced_settings/public/management_app/settings.test.tsx b/src/plugins/advanced_settings/public/management_app/settings.test.tsx deleted file mode 100644 index a98b29922af1a..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/settings.test.tsx +++ /dev/null @@ -1,376 +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 { Observable } from 'rxjs'; -import { ReactWrapper } from 'enzyme'; -import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; -import dedent from 'dedent'; -import { PublicUiSettingsParams, UserProvidedValues, UiSettingsType } from '@kbn/core/public'; -import { settingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; - -import { FieldSetting } from './types'; -import { Settings } from './settings'; -import { - notificationServiceMock, - docLinksServiceMock, - themeServiceMock, -} from '@kbn/core/public/mocks'; -import { SectionRegistry } from '@kbn/management-settings-section-registry'; -import { Search } from './components/search'; -import { EuiTab } from '@elastic/eui'; - -jest.mock('./components/field', () => ({ - Field: () => { - return 'field'; - }, -})); - -jest.mock('./components/call_outs', () => ({ - CallOuts: () => { - return 'callOuts'; - }, -})); - -jest.mock('./components/search', () => ({ - Search: () => { - return 'search'; - }, -})); - -function mockConfig() { - const defaultConfig: Partial = { - displayName: 'defaultName', - requiresPageReload: false, - isOverridden: false, - ariaName: 'ariaName', - readOnly: false, - isCustom: false, - defVal: 'defVal', - type: 'string' as UiSettingsType, - category: ['category'], - }; - - const config = { - set: (key: string, value: any) => Promise.resolve(true), - remove: (key: string) => Promise.resolve(true), - isCustom: (key: string) => false, - isOverridden: (key: string) => Boolean(config.getAll()[key].isOverridden), - getRegistered: () => ({} as Readonly>), - getUpdate$: () => - new Observable<{ - key: string; - newValue: any; - oldValue: any; - }>(), - isDeclared: (key: string) => true, - isDefault: (key: string) => true, - - getSaved$: () => - new Observable<{ - key: string; - newValue: any; - oldValue: any; - }>(), - - getUpdateErrors$: () => new Observable(), - get: (key: string, defaultOverride?: any): any => config.getAll()[key] || defaultOverride, - get$: (key: string) => new Observable(config.get(key)), - getAll: (): Readonly> => { - return { - 'test:array:setting': { - ...defaultConfig, - value: ['default_value'], - name: 'Test array setting', - description: 'Description for Test array setting', - category: ['elasticsearch'], - }, - 'test:boolean:setting': { - ...defaultConfig, - value: true, - name: 'Test boolean setting', - description: 'Description for Test boolean setting', - category: ['elasticsearch'], - }, - 'test:image:setting': { - ...defaultConfig, - value: null, - name: 'Test image setting', - description: 'Description for Test image setting', - type: 'image', - }, - 'test:json:setting': { - ...defaultConfig, - value: '{"foo": "bar"}', - name: 'Test json setting', - description: 'Description for Test json setting', - type: 'json', - }, - 'test:markdown:setting': { - ...defaultConfig, - value: '', - name: 'Test markdown setting', - description: 'Description for Test markdown setting', - type: 'markdown', - }, - 'test:number:setting': { - ...defaultConfig, - value: 5, - name: 'Test number setting', - description: 'Description for Test number setting', - }, - 'test:select:setting': { - ...defaultConfig, - value: 'orange', - name: 'Test select setting', - description: 'Description for Test select setting', - type: 'select', - options: ['apple', 'orange', 'banana'], - }, - 'test:string:setting': { - ...defaultConfig, - ...{ - value: null, - name: 'Test string setting', - description: 'Description for Test string setting', - type: 'string', - isCustom: true, - }, - }, - 'test:readonlystring:setting': { - ...defaultConfig, - ...{ - value: null, - name: 'Test readonly string setting', - description: 'Description for Test readonly string setting', - type: 'string', - readOnly: true, - }, - }, - 'test:customstring:setting': { - ...defaultConfig, - ...{ - value: null, - name: 'Test custom string setting', - description: 'Description for Test custom string setting', - type: 'string', - isCustom: true, - }, - }, - 'test:isOverridden:string': { - ...defaultConfig, - isOverridden: true, - value: 'foo', - name: 'An overridden string', - description: 'Description for overridden string', - type: 'string', - }, - 'test:isOverridden:number': { - ...defaultConfig, - isOverridden: true, - value: 1234, - name: 'An overridden number', - description: 'Description for overridden number', - type: 'number', - }, - 'test:isOverridden:json': { - ...defaultConfig, - isOverridden: true, - value: dedent` - { - "foo": "bar" - } - `, - name: 'An overridden json', - description: 'Description for overridden json', - type: 'json', - }, - 'test:isOverridden:select': { - ...defaultConfig, - isOverridden: true, - value: 'orange', - name: 'Test overridden select setting', - description: 'Description for overridden select setting', - type: 'select', - options: ['apple', 'orange', 'banana'], - }, - }; - }, - validateValue: (key: string, value: any) => - Promise.resolve({ successfulValidation: true, valid: true }), - }; - return { - core: { - settings: { - client: config, - globalClient: settingsServiceMock.createStartContract().globalClient, - }, - }, - plugins: { - advancedSettings: { - componentRegistry: { - get: () => { - const foo: React.ComponentType = () =>
Hello
; - foo.displayName = 'foo_component'; - return foo; - }, - componentType: { - PAGE_TITLE_COMPONENT: 'page_title_component', - PAGE_SUBTITLE_COMPONENT: 'page_subtitle_component', - }, - }, - }, - }, - }; -} - -describe('Settings', () => { - const defaultQuery = 'test:string:setting'; - const mockHistory = { - listen: jest.fn(), - } as any; - const locationSpy = jest.spyOn(window, 'location', 'get'); - - afterAll(() => { - locationSpy.mockRestore(); - }); - - const mockQuery = (query = defaultQuery) => { - locationSpy.mockImplementation( - () => - ({ - search: `?query=${query}`, - } as any) - ); - }; - - it('should render specific setting if given setting key', async () => { - mockQuery(); - const component = mountWithI18nProvider( - - ); - - expect( - component - .find('Field') - .filterWhere( - (n: ReactWrapper) => (n.prop('setting') as Record).name === defaultQuery - ) - ).toHaveLength(1); - }); - - it('should not render a custom setting', async () => { - // The manual mock for the uiSettings client returns false for isConfig, override that - const uiSettings = mockConfig().core.settings.client; - uiSettings.isCustom = (key) => true; - - const customSettingQuery = 'test:customstring:setting'; - mockQuery(customSettingQuery); - const component = mountWithI18nProvider( - - ); - - expect(component.find('Field')).not.toBeNull(); - expect( - component - .find('Field') - .filterWhere( - (n: ReactWrapper) => - (n.prop('setting') as Record).name === customSettingQuery - ) - ).toEqual({}); - }); - - it('should render read-only when saving is disabled', async () => { - mockQuery(); - const component = mountWithI18nProvider( - - ); - - expect(component.find('Field')).not.toBeNull(); - expect( - component - .find('Field') - .filterWhere( - (n: ReactWrapper) => (n.prop('setting') as Record).name === defaultQuery - ) - .prop('enableSaving') - ).toBe(false); - }); - - it('should render unfiltered with query parsing error', async () => { - const badQuery = 'category:(accessibility))'; - mockQuery(badQuery); - const { toasts } = notificationServiceMock.createStartContract(); - - const component = mountWithI18nProvider( - - ); - - expect(toasts.addWarning).toHaveBeenCalledTimes(1); - expect(component.find(Search).prop('query').text).toEqual(''); - }); - - it('does not render global settings if show is set to false', async () => { - const badQuery = 'category:(accessibility))'; - mockQuery(badQuery); - const { toasts } = notificationServiceMock.createStartContract(); - - const component = mountWithI18nProvider( - - ); - - expect(component.find(EuiTab).length).toEqual(1); - expect(component.find(EuiTab).at(0).text()).toEqual('Space Settings'); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/settings.tsx b/src/plugins/advanced_settings/public/management_app/settings.tsx deleted file mode 100644 index ae5837a97cbec..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/settings.tsx +++ /dev/null @@ -1,369 +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, { useState, useCallback, useMemo } from 'react'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { - EuiSpacer, - Query, - EuiNotificationBadge, - EuiTab, - EuiTabs, - EuiFlexGroup, - EuiFlexItem, - EuiText, -} from '@elastic/eui'; -import { ScopedHistory } from '@kbn/core-application-browser'; -import { SettingsStart } from '@kbn/core-ui-settings-browser'; -import { DocLinksStart } from '@kbn/core-doc-links-browser'; -import { ToastsStart } from '@kbn/core-notifications-browser'; -import { ThemeServiceStart } from '@kbn/core-theme-browser'; -import { UiCounterMetricType } from '@kbn/analytics'; -import { url } from '@kbn/kibana-utils-plugin/common'; -import { parse } from 'query-string'; -import { UiSettingsScope } from '@kbn/core-ui-settings-common'; -import type { SectionRegistryStart } from '@kbn/management-settings-section-registry'; -import type { RegistryEntry } from '@kbn/management-settings-section-registry'; -import { mapConfig, mapSettings, initCategoryCounts, initCategories } from './settings_helper'; -import { parseErrorMsg } from './components/search/search'; -import { AdvancedSettings, QUERY } from './advanced_settings'; -import { Search } from './components/search'; -import { FieldSetting } from './types'; -import { i18nTexts } from './i18n_texts'; -import { getAriaName } from './lib'; - -interface AdvancedSettingsState { - query: Query; - filteredSettings: Record>; - filteredSections: { - global: RegistryEntry[]; - space: RegistryEntry[]; - }; -} - -export type GroupedSettings = Record; - -interface Props { - history: ScopedHistory; - enableSaving: Record; - enableShowing: Record; - settingsService: SettingsStart; - docLinks: DocLinksStart['links']; - toasts: ToastsStart; - theme: ThemeServiceStart['theme$']; - sectionRegistry: SectionRegistryStart; - trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; -} - -const SPACE_SETTINGS_ID = 'space-settings'; -const GLOBAL_SETTINGS_ID = 'global-settings'; - -export const Settings = (props: Props) => { - const { sectionRegistry, history, settingsService, enableSaving, enableShowing, ...rest } = props; - const uiSettings = settingsService.client; - const globalUiSettings = settingsService.globalClient; - - const [settings, setSettings] = useState(mapConfig(uiSettings)); - const [globalSettings, setGlobalSettings] = useState(mapConfig(globalUiSettings)); - - const [groupedSettings, setGroupedSettings] = useState>({ - namespace: mapSettings(settings), - global: mapSettings(globalSettings), - }); - - const [categoryCounts, setCategoryCounts] = useState< - Record> - >({ - namespace: initCategoryCounts(groupedSettings.namespace), - global: initCategoryCounts(groupedSettings.global), - }); - - const [categories, setCategories] = useState>({ - namespace: initCategories(groupedSettings.namespace), - global: initCategories(groupedSettings.global), - }); - - const [queryState, setQueryState] = useState({ - filteredSettings: { - global: {}, - namespace: {}, - }, - filteredSections: { - global: sectionRegistry.getGlobalSections(), - space: sectionRegistry.getSpacesSections(), - }, - query: Query.parse(''), - }); - - const setTimeoutCallback = () => { - const { hash } = window.location; - const id = hash.replace('#', ''); - const element = document.getElementById(id); - - let globalNavOffset = 0; - - const globalNavBars = document - .getElementById('globalHeaderBars') - ?.getElementsByClassName('euiHeader'); - - if (globalNavBars) { - Array.from(globalNavBars).forEach((navBar) => { - globalNavOffset += (navBar as HTMLDivElement).offsetHeight; - }); - } - - if (element) { - element.scrollIntoView(); - window.scrollBy(0, -globalNavOffset); // offsets scroll by height of the global nav - } - }; - - useEffectOnce(() => { - setQueryState(getQueryState(undefined, true)); - - const subscription = (mappedSettings: FieldSetting[], scope: UiSettingsScope) => { - const grouped = { ...groupedSettings }; - grouped[scope] = mapSettings(mappedSettings); - setGroupedSettings(grouped); - - const updatedCategories = { ...categories }; - updatedCategories[scope] = initCategories(groupedSettings[scope]); - setCategories(updatedCategories); - - const updatedCategoryCounts = { ...categoryCounts }; - updatedCategoryCounts[scope] = initCategoryCounts(groupedSettings[scope]); - setCategoryCounts(updatedCategoryCounts); - const updatedQueryState = { ...getQueryState(undefined, true) }; - updatedQueryState.filteredSettings[scope] = mapSettings( - Query.execute(updatedQueryState.query, mappedSettings) - ); - setQueryState(updatedQueryState); - }; - - const uiSettingsSubscription = uiSettings.getUpdate$().subscribe(() => { - const updatedSettings = mapConfig(uiSettings); - setSettings(updatedSettings); - subscription(updatedSettings, 'namespace'); - }); - const globalUiSettingsSubscription = globalUiSettings.getUpdate$().subscribe(() => { - const mappedSettings = mapConfig(globalUiSettings); - setGlobalSettings(mappedSettings); - subscription(mappedSettings, 'global'); - }); - if (window.location.hash !== '') { - setTimeout(() => setTimeoutCallback(), 0); - } - const unregister = history.listen(({ search }) => { - setQueryState(getQueryState(search)); - }); - return () => { - unregister(); - uiSettingsSubscription.unsubscribe(); - globalUiSettingsSubscription.unsubscribe(); - }; - }); - - const setUrlQuery = useCallback( - (query: string = '') => { - const search = url.addQueryParam(window.location.search, QUERY, query); - - history.push({ - pathname: '', // remove any route query param - search, - }); - }, - [history] - ); - - const searchCategories = useMemo(() => { - return categories.global.concat(categories.namespace); - }, [categories.global, categories.namespace]); - - const callOutTitle = (scope: UiSettingsScope) => { - if (scope === 'namespace') { - return i18nTexts.defaultSpaceCalloutTitle; - } - return i18nTexts.globalCalloutTitle; - }; - - const callOutSubtitle = (scope: UiSettingsScope) => { - if (scope === 'namespace') { - return i18nTexts.defaultSpaceCalloutSubtitle; - } - return i18nTexts.globalCalloutSubtitle; - }; - - const getClientForScope = (scope: UiSettingsScope) => { - if (scope === 'namespace') { - return uiSettings; - } - return globalUiSettings; - }; - - const renderAdvancedSettings = (scope: UiSettingsScope) => { - return ( - setUrlQuery('')} - noResults={ - queryState.filteredSections.global.length + queryState.filteredSections.space.length === 0 - } - queryText={queryState.query.text} - callOutTitle={callOutTitle(scope)} - callOutSubtitle={callOutSubtitle(scope)} - settingsService={settingsService} - uiSettingsClient={getClientForScope(scope)} - enableSaving={enableSaving[scope]} - {...rest} - /> - ); - }; - - const tabs = [ - { - id: SPACE_SETTINGS_ID, - name: i18nTexts.defaultSpaceTabTitle, - append: - queryState.query.text !== '' ? ( - - {Object.keys(queryState.filteredSettings.namespace).length + - queryState.filteredSections.space.length} - - ) : null, - content: renderAdvancedSettings('namespace'), - }, - ]; - if (enableShowing.global) { - tabs.push({ - id: GLOBAL_SETTINGS_ID, - name: i18nTexts.globalTabTitle, - append: - queryState.query.text !== '' ? ( - - {Object.keys(queryState.filteredSettings.global).length + - queryState.filteredSections.global.length} - - ) : null, - content: renderAdvancedSettings('global'), - }); - } - - const [selectedTabId, setSelectedTabId] = useState(SPACE_SETTINGS_ID); - - const selectedTabContent = tabs.find((obj) => obj.id === selectedTabId)?.content; - - const onSelectedTabChanged = (id: string) => { - setSelectedTabId(id); - }; - - const renderTabs = () => { - return tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - append={tab.append} - > - {tab.name} - - )); - }; - - const getQuery = (queryString: string, initialQuery = false): Query => { - try { - const query = initialQuery ? getAriaName(queryString) : queryString ?? ''; - return Query.parse(query); - } catch ({ message }) { - props.toasts.addWarning({ - title: parseErrorMsg, - text: message, - }); - return Query.parse(''); - } - }; - - const getQueryText = (search?: string): string => { - const queryParams = parse(search ?? window.location.search) ?? {}; - return (queryParams[QUERY] as string) ?? ''; - }; - - const getQueryState = (search?: string, initialQuery = false): AdvancedSettingsState => { - const queryString = getQueryText(search); - const query = getQuery(queryString, initialQuery); - const filteredSettings = { - namespace: mapSettings(Query.execute(query, settings)), - global: mapSettings(Query.execute(query, globalSettings)), - }; - - return { - query, - filteredSettings, - filteredSections: { - global: sectionRegistry - .getGlobalSections() - .filter(({ queryMatch }) => queryMatch(query.text)), - space: sectionRegistry - .getSpacesSections() - .filter(({ queryMatch }) => queryMatch(query.text)), - }, - }; - }; - - const onQueryChange = useCallback( - ({ query }: { query: Query }) => { - setUrlQuery(query.text); - }, - [setUrlQuery] - ); - - const PageTitle = ( - -

{i18nTexts.advancedSettingsTitle}

-
- ); - - const mapSections = (entries: RegistryEntry[]) => - entries.map(({ Component, queryMatch }, index) => { - if (queryMatch(queryState.query.text)) { - return ( - - ); - } - return null; - }); - - return ( -
- - {PageTitle} - - - - - - {renderTabs()} - {selectedTabContent} - {selectedTabId === SPACE_SETTINGS_ID ? ( - <>{mapSections(queryState.filteredSections.space)} - ) : ( - <>{mapSections(queryState.filteredSections.global)} - )} -
- ); -}; diff --git a/src/plugins/advanced_settings/public/management_app/settings_helper.test.ts b/src/plugins/advanced_settings/public/management_app/settings_helper.test.ts deleted file mode 100644 index e4aaab4bb3054..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/settings_helper.test.ts +++ /dev/null @@ -1,155 +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 { UiSettingsType, UserProvidedValues } from '@kbn/core-ui-settings-common'; -import { PublicUiSettingsParams } from '@kbn/core/public'; -import { Observable } from 'rxjs'; -import { FieldSetting } from './types'; -import { mapConfig, mapSettings, initCategoryCounts, initCategories } from './settings_helper'; - -describe('Settings Helper', () => { - const defaultConfig: Partial = { - displayName: 'defaultName', - requiresPageReload: false, - isOverridden: false, - ariaName: 'ariaName', - readOnly: false, - isCustom: false, - defVal: 'defVal', - type: 'string' as UiSettingsType, - category: ['category'], - }; - - const arraySetting = { - 'test:array:setting': { - ...defaultConfig, - value: ['default_value'], - name: 'Test array setting', - description: 'Description for Test array setting', - category: ['elasticsearch'], - }, - }; - - const booleanSetting = { - 'test:boolean:setting': { - ...defaultConfig, - value: true, - name: 'Test boolean setting', - description: 'Description for Test boolean setting', - category: ['elasticsearch'], - }, - }; - - const imageSetting = { - 'test:image:setting': { - ...defaultConfig, - value: null, - name: 'Test image setting', - description: 'Description for Test image setting', - type: 'image' as UiSettingsType, - }, - }; - - const arrayFieldSetting = { - ariaName: 'Test array setting', - category: ['elasticsearch'], - defVal: ['default_value'], - description: 'Description for Test array setting', - displayName: 'Test array setting', - isCustom: false, - isOverridden: false, - name: 'test:array:setting', - readOnly: false, - requiresPageReload: false, - type: 'string' as UiSettingsType, - }; - - const booleanFieldSetting = { - ariaName: 'Test boolean setting', - category: ['elasticsearch'], - defVal: true, - description: 'Description for Test boolean setting', - displayName: 'Test boolean setting', - isCustom: false, - isOverridden: false, - name: 'test:boolean:setting', - readOnly: false, - requiresPageReload: false, - type: 'string' as UiSettingsType, - }; - - const imageFieldSetting = { - ariaName: 'Test image setting', - category: ['category'], - defVal: null, - description: 'Description for Test image setting', - displayName: 'Test image setting', - isCustom: false, - isOverridden: false, - name: 'test:image:setting', - readOnly: false, - requiresPageReload: false, - type: 'image' as UiSettingsType, - }; - - const config = { - set: (key: string, value: any) => Promise.resolve(true), - remove: (key: string) => Promise.resolve(true), - isCustom: (key: string) => false, - isOverridden: (key: string) => Boolean(config.getAll()[key].isOverridden), - getRegistered: () => ({} as Readonly>), - getUpdate$: () => - new Observable<{ - key: string; - newValue: any; - oldValue: any; - }>(), - isDeclared: (key: string) => true, - isDefault: (key: string) => true, - - getSaved$: () => - new Observable<{ - key: string; - newValue: any; - oldValue: any; - }>(), - - getUpdateErrors$: () => new Observable(), - get: (key: string, defaultOverride?: any): any => config.getAll()[key] || defaultOverride, - get$: (key: string) => new Observable(config.get(key)), - getAll: (): Readonly> => { - return { - ...arraySetting, - ...booleanSetting, - ...imageSetting, - }; - }, - validateValue: (key: string, value: any) => - Promise.resolve({ successfulValidation: true, valid: true }), - }; - - it('mapConfig', () => { - expect(mapConfig(config)).toEqual([arrayFieldSetting, booleanFieldSetting, imageFieldSetting]); - }); - - it('mapSettings, initCategoryCounts and initCategories', () => { - const fieldSetting1: FieldSetting = { ...arrayFieldSetting, value: ['a', 'b', 'c'] }; - const fieldSetting2: FieldSetting = { ...booleanFieldSetting, value: false }; - const fieldSetting3: FieldSetting = { ...imageFieldSetting, value: 'imageSrc' }; - const mapped = mapSettings([fieldSetting1, fieldSetting2, fieldSetting3]); - expect(Object.keys(mapped).sort()).toEqual(['category', 'elasticsearch'].sort()); - expect(mapped.category.length).toEqual(1); - expect(mapped.elasticsearch.length).toEqual(2); - - const categoryCounts = initCategoryCounts(mapped); - expect(categoryCounts).toEqual({ category: 1, elasticsearch: 2 }); - - const categories = initCategories(mapped); - expect(categories).toEqual(['category', 'elasticsearch']); - }); -}); diff --git a/src/plugins/advanced_settings/public/management_app/settings_helper.ts b/src/plugins/advanced_settings/public/management_app/settings_helper.ts deleted file mode 100644 index 15921f2dd77c4..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/settings_helper.ts +++ /dev/null @@ -1,56 +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 { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import { DEFAULT_CATEGORY, fieldSorter, toEditableConfig } from './lib'; -import { FieldSetting } from './types'; -import { GroupedSettings } from './settings'; - -export const mapConfig = (config: IUiSettingsClient) => { - const all = config.getAll(); - return Object.entries(all) - .map(([settingId, settingDef]) => { - return toEditableConfig({ - def: settingDef, - name: settingId, - value: settingDef.userValue, - isCustom: config.isCustom(settingId), - isOverridden: config.isOverridden(settingId), - }); - }) - .filter((c) => !c.readOnly) - .filter((c) => !c.isCustom) // hide any settings that aren't explicitly registered by enabled plugins. - .sort(fieldSorter); -}; - -export const mapSettings = (fieldSettings: FieldSetting[]) => { - // Group settings by category - return fieldSettings.reduce((grouped: GroupedSettings, setting) => { - // We will want to change this logic when we put each category on its - // own page aka allowing a setting to be included in multiple categories. - const category = setting.category[0]; - (grouped[category] = grouped[category] || []).push(setting); - return grouped; - }, {}); -}; - -export const initCategoryCounts = (grouped: GroupedSettings) => { - return Object.keys(grouped).reduce((counts: Record, category: string) => { - counts[category] = grouped[category].length; - return counts; - }, {}); -}; - -export const initCategories = (grouped: GroupedSettings) => { - return Object.keys(grouped).sort((a, b) => { - if (a === DEFAULT_CATEGORY) return -1; - if (b === DEFAULT_CATEGORY) return 1; - if (a > b) return 1; - return a === b ? 0 : -1; - }); -}; diff --git a/src/plugins/advanced_settings/public/management_app/types.ts b/src/plugins/advanced_settings/public/management_app/types.ts deleted file mode 100644 index 9ee0d7811cd30..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/types.ts +++ /dev/null @@ -1,51 +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 type { ReactElement } from 'react'; -import { UiCounterMetricType } from '@kbn/analytics'; -import { UiSettingsType } from '@kbn/core/public'; - -export interface FieldSetting { - displayName: string; - name: string; - value: unknown; - description?: string | ReactElement; - options?: string[] | number[]; - optionLabels?: Record; - requiresPageReload: boolean; - type: UiSettingsType; - category: string[]; - ariaName: string; - isOverridden: boolean; - defVal: unknown; - isCustom: boolean; - readOnly?: boolean; - order?: number; - deprecation?: { - message: string; - docLinksKey: string; - }; - metric?: { - type: UiCounterMetricType; - name: string; - }; -} - -// until eui searchbar and query are typed - -export interface SettingsChanges { - [key: string]: any; -} - -export interface FieldState { - value?: any; - changeImage?: boolean; - loading?: boolean; - isInvalid?: boolean; - error?: string | null; -} diff --git a/src/plugins/advanced_settings/public/mocks.ts b/src/plugins/advanced_settings/public/mocks.ts deleted file mode 100644 index 00e50b6672e07..0000000000000 --- a/src/plugins/advanced_settings/public/mocks.ts +++ /dev/null @@ -1,26 +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 type { - SectionRegistrySetup, - SectionRegistryStart, -} from '@kbn/management-settings-section-registry'; - -const addGlobalSection = jest.fn(); -const addSpaceSection = jest.fn(); -const getGlobalSections = jest.fn(); -const getSpacesSections = jest.fn(); - -export const advancedSettingsMock = { - createSetupContract(): SectionRegistrySetup { - return { addGlobalSection, addSpaceSection }; - }, - createStartContract(): SectionRegistryStart { - return { getGlobalSections, getSpacesSections }; - }, -}; diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.tsx similarity index 65% rename from src/plugins/advanced_settings/public/plugin.ts rename to src/plugins/advanced_settings/public/plugin.tsx index f47993c1bb452..aca337c70ceac 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.tsx @@ -9,10 +9,20 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin } from '@kbn/core/public'; import { SectionRegistry } from '@kbn/management-settings-section-registry'; +import ReactDOM from 'react-dom'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import React from 'react'; +import { withSuspense } from '@kbn/shared-ux-utility'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; const { setup: sectionRegistrySetup, start: sectionRegistryStart } = new SectionRegistry(); +const LazyKibanaSettingsApplication = React.lazy(async () => ({ + default: (await import('@kbn/management-settings-application')).KibanaSettingsApplication, +})); + +const KibanaSettingsApplication = withSuspense(LazyKibanaSettingsApplication); + const title = i18n.translate('advancedSettings.advancedSettingsLabel', { defaultMessage: 'Advanced Settings', }); @@ -30,16 +40,21 @@ export class AdvancedSettingsPlugin id: 'settings', title, order: 3, - async mount(params) { - const { mountManagementSection } = await import( - './management_app/mount_management_section' - ); - return mountManagementSection( - core.getStartServices, - params, - sectionRegistryStart, - usageCollection + async mount({ element, setBreadcrumbs, history }) { + const [coreStart] = await core.getStartServices(); + setBreadcrumbs([{ text: title }]); + + ReactDOM.render( + + + , + element ); + return () => { + ReactDOM.unmountComponentAtNode(element); + }; }, }); diff --git a/src/plugins/advanced_settings/server/capabilities_provider.ts b/src/plugins/advanced_settings/server/capabilities_provider.ts index 760d6f418201b..06f86cce3239a 100644 --- a/src/plugins/advanced_settings/server/capabilities_provider.ts +++ b/src/plugins/advanced_settings/server/capabilities_provider.ts @@ -6,13 +6,15 @@ * Side Public License, v 1. */ -export const capabilitiesProvider = () => ({ +import { AdvancedSettingsConfig } from './config'; + +export const capabilitiesProvider = (config: AdvancedSettingsConfig) => ({ globalSettings: { - show: true, + show: config.globalSettingsEnabled, save: true, }, advancedSettings: { - show: true, + show: config.advancedSettingsEnabled, save: true, }, }); diff --git a/src/plugins/advanced_settings/server/config.ts b/src/plugins/advanced_settings/server/config.ts index ea4950d3f11cc..dadb03cb7d6b4 100644 --- a/src/plugins/advanced_settings/server/config.ts +++ b/src/plugins/advanced_settings/server/config.ts @@ -10,7 +10,8 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginConfigDescriptor } from '@kbn/core-plugins-server'; const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), + globalSettingsEnabled: schema.boolean({ defaultValue: true }), + advancedSettingsEnabled: schema.boolean({ defaultValue: true }), }); export type AdvancedSettingsConfig = TypeOf; diff --git a/src/plugins/advanced_settings/server/plugin.ts b/src/plugins/advanced_settings/server/plugin.ts index 8d100ee2e26b2..aa46c6aba6ebe 100644 --- a/src/plugins/advanced_settings/server/plugin.ts +++ b/src/plugins/advanced_settings/server/plugin.ts @@ -7,19 +7,23 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; + import { capabilitiesProvider } from './capabilities_provider'; +import { AdvancedSettingsConfig } from './config'; export class AdvancedSettingsServerPlugin implements Plugin { private readonly logger: Logger; + private readonly config: AdvancedSettingsConfig; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); + this.config = initializerContext.config.get(); } public setup(core: CoreSetup) { this.logger.debug('advancedSettings: Setup'); - core.capabilities.registerProvider(capabilitiesProvider); + core.capabilities.registerProvider(() => capabilitiesProvider(this.config)); return {}; } diff --git a/src/plugins/advanced_settings/tsconfig.json b/src/plugins/advanced_settings/tsconfig.json index 9db3f75e007f6..547e82d64c73c 100644 --- a/src/plugins/advanced_settings/tsconfig.json +++ b/src/plugins/advanced_settings/tsconfig.json @@ -12,28 +12,13 @@ "@kbn/management-plugin", "@kbn/home-plugin", "@kbn/usage-collection-plugin", - "@kbn/kibana-react-plugin", "@kbn/i18n", - "@kbn/test-jest-helpers", - "@kbn/analytics", - "@kbn/kibana-utils-plugin", - "@kbn/i18n-react", - "@kbn/expect", - "@kbn/monaco", - "@kbn/shared-ux-router", - "@kbn/core-ui-settings-browser-mocks", - "@kbn/core-application-browser", - "@kbn/core-ui-settings-browser", - "@kbn/core-doc-links-browser", - "@kbn/core-notifications-browser", - "@kbn/core-theme-browser", - "@kbn/core-ui-settings-common", "@kbn/config-schema", "@kbn/core-plugins-server", "@kbn/management-settings-section-registry", "@kbn/react-kibana-context-render", - "@kbn/code-editor", - "@kbn/code-editor-mock", + "@kbn/shared-ux-utility", + "@kbn/management-settings-application", ], "exclude": [ "target/**/*", diff --git a/src/plugins/management/public/plugin.tsx b/src/plugins/management/public/plugin.tsx index ee0668d0b9c7a..f3df5201cae7e 100644 --- a/src/plugins/management/public/plugin.tsx +++ b/src/plugins/management/public/plugin.tsx @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -import React from 'react'; -import ReactDOM from 'react-dom'; import { i18n as kbnI18n } from '@kbn/i18n'; import { BehaviorSubject } from 'rxjs'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; @@ -25,8 +23,6 @@ import { AppNavLinkStatus, AppDeepLink, } from '@kbn/core/public'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { withSuspense } from '@kbn/shared-ux-utility'; import { ConfigSchema, ManagementSetup, ManagementStart, NavigationCardsSubject } from './types'; import { MANAGEMENT_APP_ID } from '../common/contants'; @@ -47,12 +43,6 @@ interface ManagementStartDependencies { serverless?: ServerlessPluginStart; } -const LazyKibanaSettingsApplication = React.lazy(async () => ({ - default: (await import('@kbn/management-settings-application')).KibanaSettingsApplication, -})); - -const KibanaSettingsApplication = withSuspense(LazyKibanaSettingsApplication); - export class ManagementPlugin implements Plugin< @@ -176,33 +166,6 @@ export class ManagementPlugin }); } - // Register the Settings app only if in serverless, until we integrate the SettingsApplication into the Advanced settings plugin - // Otherwise, it will be double registered from the Advanced settings plugin - if (plugins.serverless) { - const title = kbnI18n.translate('management.settings.settingsLabel', { - defaultMessage: 'Advanced Settings', - }); - - this.managementSections.definedSections.kibana.registerApp({ - id: 'settings', - title, - order: 3, - async mount({ element, setBreadcrumbs, history }) { - setBreadcrumbs([{ text: title }]); - - ReactDOM.render( - - - , - element - ); - return () => { - ReactDOM.unmountComponentAtNode(element); - }; - }, - }); - } - return { setIsSidebarEnabled: (isSidebarEnabled: boolean) => this.isSidebarEnabled$.next(isSidebarEnabled), diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json index acbf33fc46abc..df0a876b4b3c4 100644 --- a/src/plugins/management/tsconfig.json +++ b/src/plugins/management/tsconfig.json @@ -25,10 +25,7 @@ "@kbn/test-jest-helpers", "@kbn/config-schema", "@kbn/serverless", - "@kbn/management-settings-application", - "@kbn/react-kibana-context-render", - "@kbn/shared-ux-utility", - "@kbn/shared-ux-error-boundary" + "@kbn/shared-ux-error-boundary", ], "exclude": [ "target/**/*" diff --git a/test/functional/apps/management/data_views/_cache.ts b/test/functional/apps/management/data_views/_cache.ts index 447e44052bb8d..764dd99d20252 100644 --- a/test/functional/apps/management/data_views/_cache.ts +++ b/test/functional/apps/management/data_views/_cache.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['settings', 'common', 'header']); - const find = getService('find'); + const testSubjects = getService('testSubjects'); describe('Data view field caps cache advanced setting', async function () { before(async () => { @@ -19,8 +19,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.clickKibanaSettings(); }); it('should have cache setting', async () => { - const cacheSetting = await find.byCssSelector('#data_views\\:cache_max_age-group'); - expect(cacheSetting).to.not.be(undefined); + expect( + await testSubjects.exists('management-settings-editField-data_views:cache_max_age') + ).to.be(true); }); }); } diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 2d85b6f662665..5e8fddb626efd 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -39,7 +39,7 @@ export class SettingsPageObject extends FtrService { async clickKibanaGlobalSettings() { await this.testSubjects.click('settings'); await this.header.waitUntilLoadingHasFinished(); - await this.testSubjects.click('advancedSettingsTab-global-settings'); + await this.testSubjects.click('settings-tab-global-settings'); } async clickKibanaSavedObjects() { @@ -73,21 +73,24 @@ export class SettingsPageObject extends FtrService { async getAdvancedSettings(propertyName: string) { this.log.debug('in getAdvancedSettings'); return await this.testSubjects.getAttribute( - `advancedSetting-editField-${propertyName}`, + `management-settings-editField-${propertyName}`, 'value' ); } async expectDisabledAdvancedSetting(propertyName: string) { expect( - await this.testSubjects.getAttribute(`advancedSetting-editField-${propertyName}`, 'disabled') + await this.testSubjects.getAttribute( + `management-settings-editField-${propertyName}`, + 'disabled' + ) ).to.eql('true'); } async getAdvancedSettingCheckbox(propertyName: string) { this.log.debug('in getAdvancedSettingCheckbox'); return await this.testSubjects.getAttribute( - `advancedSetting-editField-${propertyName}`, + `management-settings-editField-${propertyName}`, 'checked' ); } @@ -95,37 +98,37 @@ export class SettingsPageObject extends FtrService { async getAdvancedSettingAriaCheckbox(propertyName: string) { this.log.debug('in getAdvancedSettingAriaCheckbox'); return await this.testSubjects.getAttribute( - `advancedSetting-editField-${propertyName}`, + `management-settings-editField-${propertyName}`, 'aria-checked' ); } async clearAdvancedSettings(propertyName: string) { - await this.testSubjects.click(`advancedSetting-resetField-${propertyName}`); + await this.testSubjects.click(`management-settings-resetField-${propertyName}`); await this.header.waitUntilLoadingHasFinished(); - await this.testSubjects.click(`advancedSetting-saveButton`); + await this.testSubjects.click(`settings-save-button`); await this.header.waitUntilLoadingHasFinished(); } async setAdvancedSettingsSelect(propertyName: string, propertyValue: string) { await this.find.clickByCssSelector( - `[data-test-subj="advancedSetting-editField-${propertyName}"] option[value="${propertyValue}"]` + `[data-test-subj="management-settings-editField-${propertyName}"] option[value="${propertyValue}"]` ); await this.header.waitUntilLoadingHasFinished(); - await this.testSubjects.click(`advancedSetting-saveButton`); + await this.testSubjects.click(`settings-save-button`); await this.header.waitUntilLoadingHasFinished(); } async setAdvancedSettingsInput(propertyName: string, propertyValue: string) { - const input = await this.testSubjects.find(`advancedSetting-editField-${propertyName}`); + const input = await this.testSubjects.find(`management-settings-editField-${propertyName}`); await input.clearValue(); await input.type(propertyValue); - await this.testSubjects.click(`advancedSetting-saveButton`); + await this.testSubjects.click(`settings-save-button`); await this.header.waitUntilLoadingHasFinished(); } async setAdvancedSettingsTextArea(propertyName: string, propertyValue: string) { - const wrapper = await this.testSubjects.find(`advancedSetting-editField-${propertyName}`); + const wrapper = await this.testSubjects.find(`management-settings-editField-${propertyName}`); const textarea = await wrapper.findByTagName('textarea'); await textarea.focus(); // only way to properly replace the value of the ace editor is via the JS api @@ -133,17 +136,17 @@ export class SettingsPageObject extends FtrService { (editor: string, value: string) => { return (window as any).ace.edit(editor).setValue(value); }, - `advancedSetting-editField-${propertyName}-editor`, + `management-settings-editField-${propertyName}-editor`, propertyValue ); - await this.testSubjects.click(`advancedSetting-saveButton`); + await this.testSubjects.click(`settings-save-button`); await this.header.waitUntilLoadingHasFinished(); } async setAdvancedSettingsImage(propertyName: string, path: string) { - const input = await this.testSubjects.find(`advancedSetting-editField-${propertyName}`); + const input = await this.testSubjects.find(`management-settings-editField-${propertyName}`); await input.type(path); - await this.testSubjects.click(`advancedSetting-saveButton`); + await this.testSubjects.click(`settings-save-button`); await this.header.waitUntilLoadingHasFinished(); } @@ -155,9 +158,9 @@ export class SettingsPageObject extends FtrService { if (curValue === (value ? 'true' : 'false')) return; } - await this.testSubjects.click(`advancedSetting-editField-${propertyName}`); + await this.testSubjects.click(`management-settings-editField-${propertyName}`); await this.header.waitUntilLoadingHasFinished(); - await this.testSubjects.click(`advancedSetting-saveButton`); + await this.testSubjects.click(`settings-save-button`); await this.header.waitUntilLoadingHasFinished(); } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 0155d9d1de5f2..63b76a5cb04ac 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -76,64 +76,6 @@ } }, "messages": { - "advancedSettings.field.changeImageLinkAriaLabel": "Modifier {ariaName}", - "advancedSettings.field.defaultValueText": "Par défaut : {value}", - "advancedSettings.field.defaultValueTypeJsonText": "Par défaut : {value}", - "advancedSettings.field.deprecationClickAreaLabel": "Cliquez ici pour afficher la documentation de déclassement pour {settingName}.", - "advancedSettings.field.resetToDefaultLinkAriaLabel": "Réinitialiser {ariaName} à la valeur par défaut", - "advancedSettings.form.countOfSettingsChanged": "{unsavedCount} {unsavedCount, plural, one {paramètre} many {les paramètres d''index suivants déclassés ?} other {paramètres}} non enregistré(s){hiddenCount, plural, =0 {} one {, # masqué} many {, # masqués} other {, # masqués}}", - "advancedSettings.form.noSearchResultText": "Aucun paramètre trouvé pour {queryText} {clearSearch}", - "advancedSettings.form.searchResultText": "Les termes de la recherche masquent {settingsCount} paramètres {clearSearch}", - "advancedSettings.voiceAnnouncement.noSearchResultScreenReaderMessage": "Il {optionLenght, plural, one {y a # option} many {y a # options} other {y a # options}} dans {sectionLenght, plural, one {# section} many {# sections} other {# sections}}", - "advancedSettings.voiceAnnouncement.searchResultScreenReaderMessage": "Vous avez recherché {query}. Il {optionLenght, plural, one {y a # option} many {y a # options} other {y a # options}} dans {sectionLenght, plural, one {# section} many {# sections} other {# sections}}", - "advancedSettings.advancedSettingsLabel": "Paramètres avancés", - "advancedSettings.badge.readOnly.text": "Lecture seule", - "advancedSettings.badge.readOnly.tooltip": "Impossible d’enregistrer les paramètres avancés", - "advancedSettings.callOutCautionDescription": "Soyez prudent, ces paramètres sont destinés aux utilisateurs très avancés uniquement. Toute modification est susceptible d’entraîner des dommages importants à Kibana. Certains de ces paramètres peuvent être non documentés, non compatibles ou en version d'évaluation technique. Lorsqu’un champ dispose d’une valeur par défaut, le laisser vide entraîne l’application de cette valeur par défaut, ce qui peut ne pas être acceptable compte tenu d’autres directives de configuration. Toute suppression d'un paramètre personnalisé de la configuration de Kibana est définitive.", - "advancedSettings.callOutCautionTitle": "Attention : toute action est susceptible de provoquer des dommages.", - "advancedSettings.categoryNames.dashboardLabel": "Tableau de bord", - "advancedSettings.categoryNames.discoverLabel": "Découverte", - "advancedSettings.categoryNames.enterpriseSearchLabel": "Enterprise Search", - "advancedSettings.categoryNames.generalLabel": "Général", - "advancedSettings.categoryNames.machineLearningLabel": "Machine Learning", - "advancedSettings.categoryNames.notificationsLabel": "Notifications", - "advancedSettings.categoryNames.observabilityLabel": "Observabilité", - "advancedSettings.categoryNames.reportingLabel": "Reporting", - "advancedSettings.categoryNames.searchLabel": "Recherche", - "advancedSettings.categoryNames.securitySolutionLabel": "Solution de sécurité", - "advancedSettings.categoryNames.timelionLabel": "Timelion", - "advancedSettings.categoryNames.visualizationsLabel": "Visualisations", - "advancedSettings.categorySearchLabel": "Catégorie", - "advancedSettings.defaultSpaceCalloutSubtitle": "Les modifications seront uniquement appliquées à l'espace actuel. Ces paramètres sont destinés aux utilisateurs avancés, car des configurations incorrectes peuvent avoir une incidence négative sur des aspects de Kibana.", - "advancedSettings.defaultSpaceCalloutTitle": "Les modifications affecteront l'espace actuel.", - "advancedSettings.featureCatalogueTitle": "Personnalisez votre expérience Kibana : modifiez le format de date, activez le mode sombre, et bien plus encore.", - "advancedSettings.field.changeImageLinkText": "Modifier l'image", - "advancedSettings.field.codeEditorSyntaxErrorMessage": "Syntaxe JSON non valide", - "advancedSettings.field.customSettingAriaLabel": "Paramètre personnalisé", - "advancedSettings.field.customSettingTooltip": "Paramètre personnalisé", - "advancedSettings.field.helpText": "Ce paramètre est défini par le serveur Kibana et ne peut pas être modifié.", - "advancedSettings.field.imageChangeErrorMessage": "Impossible d’enregistrer l'image", - "advancedSettings.field.invalidIconLabel": "Non valide", - "advancedSettings.field.offLabel": "Désactivé", - "advancedSettings.field.onLabel": "Activé", - "advancedSettings.field.resetToDefaultLinkText": "Réinitialiser à la valeur par défaut", - "advancedSettings.field.settingIsUnsaved": "Le paramètre n'est actuellement pas enregistré.", - "advancedSettings.field.unsavedIconLabel": "Non enregistré", - "advancedSettings.form.cancelButtonLabel": "Annuler les modifications", - "advancedSettings.form.clearNoSearchResultText": "(effacer la recherche)", - "advancedSettings.form.clearSearchResultText": "(effacer la recherche)", - "advancedSettings.form.requiresPageReloadToastButtonLabel": "Actualiser la page", - "advancedSettings.form.requiresPageReloadToastDescription": "Un ou plusieurs paramètres nécessitent d’actualiser la page pour pouvoir prendre effet.", - "advancedSettings.form.saveButtonLabel": "Enregistrer les modifications", - "advancedSettings.form.saveButtonTooltipWithInvalidChanges": "Corrigez les paramètres non valides avant d'enregistrer.", - "advancedSettings.form.saveErrorMessage": "Enregistrement impossible", - "advancedSettings.globalCalloutSubtitle": "Les modifications seront appliquées à tous les utilisateurs dans l'ensemble des espaces. Cela inclut les utilisateurs Kibana natifs et les utilisateurs qui se connectent via l'authentification unique.", - "advancedSettings.globalCalloutTitle": "Les modifications auront une incidence sur tous les paramètres utilisateur dans l'ensemble des espaces", - "advancedSettings.globalSettingsTabTitle": "Paramètres généraux", - "advancedSettings.searchBar.unableToParseQueryErrorMessage": "Impossible d'analyser la requête", - "advancedSettings.searchBarAriaLabel": "Rechercher dans les paramètres avancés", - "advancedSettings.spaceSettingsTabTitle": "Paramètres de l'espace", - "advancedSettings.voiceAnnouncement.ariaLabel": "Informations de résultat des paramètres avancés", "autocomplete.conflictIndicesWarning.index.description": "{name} ({count} index)", "autocomplete.customOptionText": "Ajouter {searchValuePlaceholder} comme champ personnalisé", "autocomplete.conflictIndicesWarning.description": "Ce champ est défini avec différents types dans les index suivants ou il n'est pas mappé, ce qui peut entraîner des résultats inattendus lors des requêtes.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6ec1330d0b14e..5ac0b945b1173 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -76,64 +76,6 @@ } }, "messages": { - "advancedSettings.field.changeImageLinkAriaLabel": "{ariaName}の変更", - "advancedSettings.field.defaultValueText": "デフォルト:{value}", - "advancedSettings.field.defaultValueTypeJsonText": "デフォルト:{value}", - "advancedSettings.field.deprecationClickAreaLabel": "クリックすると{settingName}のサポート終了に関するドキュメントが表示されます。", - "advancedSettings.field.resetToDefaultLinkAriaLabel": "{ariaName}をデフォルトにリセット", - "advancedSettings.form.countOfSettingsChanged": "{unsavedCount}個の保存されていない{unsavedCount, plural, other {設定}}{hiddenCount, plural, =0 {} other {、#個が非表示です}}", - "advancedSettings.form.noSearchResultText": "{queryText}{clearSearch}の設定が見つかりません", - "advancedSettings.form.searchResultText": "検索用語により、{settingsCount}件の設定{clearSearch}が非表示になっています", - "advancedSettings.voiceAnnouncement.noSearchResultScreenReaderMessage": "{sectionLenght, plural, other {#個のセクション}}に{optionLenght, plural, other {#個のオプションがあります}}", - "advancedSettings.voiceAnnouncement.searchResultScreenReaderMessage": "{query}を検索しました。{sectionLenght, plural, other {#個のセクション}}に{optionLenght, plural, other {#個のオプションがあります}}", - "advancedSettings.advancedSettingsLabel": "高度な設定", - "advancedSettings.badge.readOnly.text": "読み取り専用", - "advancedSettings.badge.readOnly.tooltip": "高度な設定を保存できません", - "advancedSettings.callOutCautionDescription": "これらの設定は特に上級のユーザー向けなのでご注意ください。ここでの変更は Kibana の重要な部分に不具合を生じさせる可能性があります。これらの設定は非公開、サポート対象外、またはテクニカルプレビュー中の場合があります。フィールドにデフォルト値がある場合、そのフィールドを未入力のままにするとデフォルトに戻り、他の構成で許容されないことがあります。カスタム設定を削除すると、Kibana の構成から永久に削除されます。", - "advancedSettings.callOutCautionTitle": "注意:不具合につながる可能性があります", - "advancedSettings.categoryNames.dashboardLabel": "ダッシュボード", - "advancedSettings.categoryNames.discoverLabel": "Discover", - "advancedSettings.categoryNames.enterpriseSearchLabel": "エンタープライズ サーチ", - "advancedSettings.categoryNames.generalLabel": "一般", - "advancedSettings.categoryNames.machineLearningLabel": "機械学習", - "advancedSettings.categoryNames.notificationsLabel": "通知", - "advancedSettings.categoryNames.observabilityLabel": "Observability", - "advancedSettings.categoryNames.reportingLabel": "レポート", - "advancedSettings.categoryNames.searchLabel": "検索", - "advancedSettings.categoryNames.securitySolutionLabel": "セキュリティソリューション", - "advancedSettings.categoryNames.timelionLabel": "Timelion", - "advancedSettings.categoryNames.visualizationsLabel": "ビジュアライゼーション", - "advancedSettings.categorySearchLabel": "カテゴリー", - "advancedSettings.defaultSpaceCalloutSubtitle": "変更は現在のスペースにのみ適用されます。これらの設定は上級ユーザー向けです。構成が正しくない場合は、Kibanaの動作に悪影響を及ぼすおそれがあります。", - "advancedSettings.defaultSpaceCalloutTitle": "変更は現在のスペースに影響します。", - "advancedSettings.featureCatalogueTitle": "日付形式の変更、ダークモードの有効化など、Kibanaエクスペリエンスをカスタマイズします。", - "advancedSettings.field.changeImageLinkText": "画像を変更", - "advancedSettings.field.codeEditorSyntaxErrorMessage": "無効な JSON 構文", - "advancedSettings.field.customSettingAriaLabel": "カスタム設定", - "advancedSettings.field.customSettingTooltip": "カスタム設定", - "advancedSettings.field.helpText": "この設定は Kibana サーバーにより上書きされ、変更することはできません。", - "advancedSettings.field.imageChangeErrorMessage": "画像を保存できませんでした", - "advancedSettings.field.invalidIconLabel": "無効", - "advancedSettings.field.offLabel": "オフ", - "advancedSettings.field.onLabel": "オン", - "advancedSettings.field.resetToDefaultLinkText": "デフォルトにリセット", - "advancedSettings.field.settingIsUnsaved": "設定は現在保存されていません。", - "advancedSettings.field.unsavedIconLabel": "未保存", - "advancedSettings.form.cancelButtonLabel": "変更をキャンセル", - "advancedSettings.form.clearNoSearchResultText": "(検索結果を消去)", - "advancedSettings.form.clearSearchResultText": "(検索結果を消去)", - "advancedSettings.form.requiresPageReloadToastButtonLabel": "ページを再読み込み", - "advancedSettings.form.requiresPageReloadToastDescription": "設定を有効にするためにページの再読み込みが必要です。", - "advancedSettings.form.saveButtonLabel": "変更を保存", - "advancedSettings.form.saveButtonTooltipWithInvalidChanges": "保存前に無効な設定を修正してください。", - "advancedSettings.form.saveErrorMessage": "を保存できませんでした", - "advancedSettings.globalCalloutSubtitle": "変更はすべてのスペースのすべてのユーザーに適用されます。これにはネイティブKibanaユーザーとシングルサインオンユーザーの両方が含まれます。", - "advancedSettings.globalCalloutTitle": "変更はすべてのスペースのすべてのユーザー設定に影響します", - "advancedSettings.globalSettingsTabTitle": "グローバル設定", - "advancedSettings.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません", - "advancedSettings.searchBarAriaLabel": "高度な設定を検索", - "advancedSettings.spaceSettingsTabTitle": "スペース設定", - "advancedSettings.voiceAnnouncement.ariaLabel": "詳細設定結果情報", "autocomplete.conflictIndicesWarning.index.description": "{name}({count}個のインデックス)", "autocomplete.customOptionText": "{searchValuePlaceholder}をカスタムフィールドとして追加", "autocomplete.conflictIndicesWarning.description": "このフィールドは、次のインデックスで別の型として定義されているか、マッピングされていません。これにより、予期しないクエリ結果になる場合があります。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ba48546d7999e..eeb98e4c894a1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -76,64 +76,6 @@ } }, "messages": { - "advancedSettings.field.changeImageLinkAriaLabel": "更改 {ariaName}", - "advancedSettings.field.defaultValueText": "默认值:{value}", - "advancedSettings.field.defaultValueTypeJsonText": "默认值:{value}", - "advancedSettings.field.deprecationClickAreaLabel": "单击以查看 {settingName} 的过时文档。", - "advancedSettings.field.resetToDefaultLinkAriaLabel": "将 {ariaName} 重置为默认值", - "advancedSettings.form.countOfSettingsChanged": "{unsavedCount} 个未保存{unsavedCount, plural, other {设置}}{hiddenCount, plural, =0 {} other {,# 个已隐藏}}", - "advancedSettings.form.noSearchResultText": "未找到 {queryText} 的设置{clearSearch}", - "advancedSettings.form.searchResultText": "搜索词隐藏了 {settingsCount} 个设置{clearSearch}", - "advancedSettings.voiceAnnouncement.noSearchResultScreenReaderMessage": "{sectionLenght, plural, other {# 个部分}}中有 {optionLenght, plural, other {# 个选项}}", - "advancedSettings.voiceAnnouncement.searchResultScreenReaderMessage": "您已搜索 {query}。{sectionLenght, plural, other {# 个部分}}中有 {optionLenght, plural, other {# 个选项}}", - "advancedSettings.advancedSettingsLabel": "高级设置", - "advancedSettings.badge.readOnly.text": "只读", - "advancedSettings.badge.readOnly.tooltip": "无法保存高级设置", - "advancedSettings.callOutCautionDescription": "此处请谨慎操作,这些设置仅供高级用户使用。您在这里所做的更改可能使 Kibana 的大部分功能出现问题。这些设置有一部分可能未在文档中说明、不受支持或处于技术预览状态。如果字段有默认值,将字段留空会将其设置为默认值,其他配置指令可能不接受其默认值。删除定制设置会将其从 Kibana 的配置中永久删除。", - "advancedSettings.callOutCautionTitle": "注意:在这里您可能会使问题出现", - "advancedSettings.categoryNames.dashboardLabel": "仪表板", - "advancedSettings.categoryNames.discoverLabel": "Discover", - "advancedSettings.categoryNames.enterpriseSearchLabel": "Enterprise Search", - "advancedSettings.categoryNames.generalLabel": "常规", - "advancedSettings.categoryNames.machineLearningLabel": "Machine Learning", - "advancedSettings.categoryNames.notificationsLabel": "通知", - "advancedSettings.categoryNames.observabilityLabel": "Observability", - "advancedSettings.categoryNames.reportingLabel": "报告", - "advancedSettings.categoryNames.searchLabel": "搜索", - "advancedSettings.categoryNames.securitySolutionLabel": "安全解决方案", - "advancedSettings.categoryNames.timelionLabel": "Timelion", - "advancedSettings.categoryNames.visualizationsLabel": "可视化", - "advancedSettings.categorySearchLabel": "类别", - "advancedSettings.defaultSpaceCalloutSubtitle": "将仅对当前工作区应用更改。这些设置适用于高级用户,因为配置错误可能会对 Kibana 的某些方面造成负面影响。", - "advancedSettings.defaultSpaceCalloutTitle": "更改将影响当前工作区。", - "advancedSettings.featureCatalogueTitle": "定制您的 Kibana 体验 — 更改日期格式、打开深色模式,等等。", - "advancedSettings.field.changeImageLinkText": "更改图片", - "advancedSettings.field.codeEditorSyntaxErrorMessage": "JSON 语法无效", - "advancedSettings.field.customSettingAriaLabel": "定制设置", - "advancedSettings.field.customSettingTooltip": "定制设置", - "advancedSettings.field.helpText": "此设置已由 Kibana 服务器覆盖,无法更改。", - "advancedSettings.field.imageChangeErrorMessage": "图片无法保存", - "advancedSettings.field.invalidIconLabel": "无效", - "advancedSettings.field.offLabel": "关闭", - "advancedSettings.field.onLabel": "开启", - "advancedSettings.field.resetToDefaultLinkText": "重置为默认值", - "advancedSettings.field.settingIsUnsaved": "设备当前未保存。", - "advancedSettings.field.unsavedIconLabel": "未保存", - "advancedSettings.form.cancelButtonLabel": "取消更改", - "advancedSettings.form.clearNoSearchResultText": "(清除搜索)", - "advancedSettings.form.clearSearchResultText": "(清除搜索)", - "advancedSettings.form.requiresPageReloadToastButtonLabel": "重新加载页面", - "advancedSettings.form.requiresPageReloadToastDescription": "一个或多个设置需要您重新加载页面才能生效。", - "advancedSettings.form.saveButtonLabel": "保存更改", - "advancedSettings.form.saveButtonTooltipWithInvalidChanges": "保存前请修复无效的设置。", - "advancedSettings.form.saveErrorMessage": "无法保存", - "advancedSettings.globalCalloutSubtitle": "将对所有工作区的所有用户应用更改。这包括本机 Kibana 用户和单点登录用户。", - "advancedSettings.globalCalloutTitle": "更改将影响所有工作区的所有用户设置", - "advancedSettings.globalSettingsTabTitle": "常规设置", - "advancedSettings.searchBar.unableToParseQueryErrorMessage": "无法解析查询", - "advancedSettings.searchBarAriaLabel": "搜索高级设置", - "advancedSettings.spaceSettingsTabTitle": "工作区设置", - "advancedSettings.voiceAnnouncement.ariaLabel": "“高级设置”的结果信息", "autocomplete.conflictIndicesWarning.index.description": "{name}({count} 个索引)", "autocomplete.customOptionText": "将 {searchValuePlaceholder} 添加为字段", "autocomplete.conflictIndicesWarning.description": "此字段在以下索引中定义为不同类型或未映射。这可能导致意外的查询结果。", diff --git a/x-pack/test/accessibility/apps/group1/advanced_settings.ts b/x-pack/test/accessibility/apps/group1/advanced_settings.ts index 44899932302ba..4515105d7557c 100644 --- a/x-pack/test/accessibility/apps/group1/advanced_settings.ts +++ b/x-pack/test/accessibility/apps/group1/advanced_settings.ts @@ -40,21 +40,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // clicking on the toggle button it('adv settings - toggle ', async () => { - await testSubjects.click('advancedSetting-editField-csv:quoteValues'); + await testSubjects.click('management-settings-editField-csv:quoteValues'); await toasts.dismissAllToasts(); await a11y.testAppSnapshot(); }); // clicking on editor panel it('adv settings - edit ', async () => { - await testSubjects.click('advancedSetting-editField-csv:separator'); + await testSubjects.click('management-settings-editField-csv:separator'); await toasts.dismissAllToasts(); await a11y.testAppSnapshot(); }); // clicking on save button it('adv settings - save', async () => { - await testSubjects.click('advancedSetting-saveButton'); + await testSubjects.click('settings-save-button'); await toasts.dismissAllToasts(); await a11y.testAppSnapshot(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/sourcerer.ts b/x-pack/test/security_solution_cypress/cypress/screens/sourcerer.ts index 591df4117d0a6..450d1aa484b5d 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/sourcerer.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/sourcerer.ts @@ -15,7 +15,7 @@ export const SOURCERER = { badgeAlerts: '[data-test-subj="sourcerer-alerts-badge"]', badgeAlertsOption: '[data-test-subj="security-alerts-option-badge"]', siemDefaultIndexInput: - '[data-test-subj="advancedSetting-editField-securitySolution:defaultIndex"]', + '[data-test-subj="management-settings-editField-securitySolution:defaultIndex"]', popoverTitle: '[data-test-subj="sourcerer-title"]', resetButton: 'button[data-test-subj="sourcerer-reset"]', saveButton: 'button[data-test-subj="sourcerer-save"]', diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts b/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts index a534fb3e6d3d8..2bf56c1bd7c66 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts @@ -96,7 +96,7 @@ export const resetSourcerer = () => { export const clickAlertCheckbox = () => cy.get(SOURCERER.alertCheckbox).check({ force: true }); export const addIndexToDefault = (index: string) => { - visitWithTimeRange(`/app/management/kibana/settings?query=category:(securitySolution)`); + visitWithTimeRange(`/app/management/kibana/settings?query=categories:(securitySolution)`); cy.get(SOURCERER.siemDefaultIndexInput) .invoke('val') .then((patterns) => { @@ -107,8 +107,8 @@ export const addIndexToDefault = (index: string) => { } }); - cy.get('button[data-test-subj="advancedSetting-saveButton"]').click(); - cy.get('button[data-test-subj="windowReloadButton"]').click(); + cy.get('button[data-test-subj="settings-save-button"]').click(); + cy.get('button[data-test-subj="pageReloadButton"]').click(); visitWithTimeRange(hostsUrl('allHosts')); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/common/management/data_views/_cache.ts b/x-pack/test_serverless/functional/test_suites/common/management/data_views/_cache.ts index 3050ef2456fbf..f7ae8fbe2cd6e 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/data_views/_cache.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/data_views/_cache.ts @@ -18,7 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should not have cache setting', async () => { await testSubjects.missingOrFail( - 'advancedSetting-editField-data_views\\:cache_max_age-group' + 'management-settings-editField-data_views\\:cache_max_age-group' ); }); });