Skip to content

Commit

Permalink
[Advanced Settings] Integrate new Settings application into stateful …
Browse files Browse the repository at this point in the history
…Kibana (elastic#175255)

Closes elastic#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.

<img width="1352" alt="Screenshot 2024-01-23 at 16 46 03"
src="https://github.com/elastic/kibana/assets/59341489/1f3b7088-58e2-46e8-a7dd-ae0fc346b4ba">

<br><br>

"Usage collection" section in Global settings:

<img width="1099" alt="Screenshot 2024-01-23 at 16 48 24"
src="https://github.com/elastic/kibana/assets/59341489/ebc54ad5-348b-46dd-a047-b418ddc7ba4f">

### 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.


<!--
### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### Risk Matrix

Delete this section if it is not applicable to this PR.

Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.

When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:

| Risk | Probability | Severity | Mitigation/Notes |

|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces&mdash;unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes&mdash;Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
-->

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
2 people authored and CoenWarmer committed Feb 15, 2024
1 parent 015d710 commit 4eb3578
Show file tree
Hide file tree
Showing 77 changed files with 242 additions and 10,389 deletions.
2 changes: 1 addition & 1 deletion config/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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')}
Expand Down
57 changes: 45 additions & 12 deletions packages/kbn-management/settings/application/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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>(Query.parse(queryParam));
Expand All @@ -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]: {
Expand All @@ -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,
};
}

Expand All @@ -110,7 +125,7 @@ export const SettingsApplication = () => {
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
{globalSettingsEnabled && (
{globalTabEnabled && (
<>
<EuiTabs>
{Object.keys(tabs).map((id) => (
Expand All @@ -130,13 +145,31 @@ export const SettingsApplication = () => {
)}
<EuiSpacer size="xl" />
{selectedTab.fields.length ? (
<Form
fields={selectedTab.fields}
categoryCounts={selectedTab.categoryCounts}
isSavingEnabled={true}
onClearQuery={() => onQueryChange()}
scope={selectedTabId === SPACE_SETTINGS_TAB_ID ? 'namespace' : 'global'}
/>
<>
<Form
fields={selectedTab.fields}
categoryCounts={selectedTab.categoryCounts}
isSavingEnabled={selectedTab.isSavingEnabled}
onClearQuery={() => onQueryChange()}
scope={selectedTabId === SPACE_SETTINGS_TAB_ID ? 'namespace' : 'global'}
/>
<EuiSpacer size="l" />
{selectedTab.sections.length > 0 &&
selectedTab.sections.map(({ Component, queryMatch }, index) => {
if (queryMatch(query.text)) {
return (
<Component
key={`component-${index}`}
toasts={getToastsService()}
enableSaving={{
global: canSaveGlobalSettings,
namespace: canSaveSpaceSettings,
}}
/>
);
}
})}
</>
) : (
<EmptyState {...{ queryText: query?.text, onClearQuery: () => onQueryChange() }} />
)}
Expand Down
15 changes: 14 additions & 1 deletion packages/kbn-management/settings/application/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,22 @@ export const KibanaSettingsApplication = ({
settings,
theme,
history,
sectionRegistry,
application,
chrome,
}: SettingsApplicationKibanaDependencies) => (
<SettingsApplicationKibanaProvider
{...{ settings, theme, i18n, notifications, docLinks, history }}
{...{
settings,
theme,
i18n,
notifications,
docLinks,
history,
sectionRegistry,
application,
chrome,
}}
>
<SettingsApplication />
</SettingsApplicationKibanaProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -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 = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(<CallOuts />);

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',
};
61 changes: 58 additions & 3 deletions packages/kbn-management/settings/application/services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, UiSettingMetadata>;
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;
Expand All @@ -43,6 +50,12 @@ export interface KibanaDependencies {
>;
};
history: ScopedHistory;
sectionRegistry: SectionRegistryStart;
notifications: {
toasts: ToastsStart;
};
application: Pick<ApplicationStart, 'capabilities'>;
chrome: Pick<ChromeStart, 'setBadge'>;
}

export type SettingsApplicationKibanaDependencies = KibanaDependencies & FormKibanaDependencies;
Expand All @@ -65,6 +78,10 @@ export const SettingsApplicationProvider: FC<SettingsApplicationServices> = ({
links,
showDanger,
getAllowlistedSettings,
getSections,
getCapabilities,
setBadge,
getToastsService,
subscribeToUpdates,
isCustomSetting,
isOverriddenSetting,
Expand All @@ -75,6 +92,10 @@ export const SettingsApplicationProvider: FC<SettingsApplicationServices> = ({
<SettingsApplicationContext.Provider
value={{
getAllowlistedSettings,
getSections,
getToastsService,
getCapabilities,
setBadge,
subscribeToUpdates,
isCustomSetting,
isOverriddenSetting,
Expand All @@ -97,7 +118,17 @@ export const SettingsApplicationKibanaProvider: FC<SettingsApplicationKibanaDepe
children,
...dependencies
}) => {
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) => {
Expand All @@ -114,6 +145,26 @@ export const SettingsApplicationKibanaProvider: FC<SettingsApplicationKibanaDepe
return normalizeSettings(rawSettings);
};

const getSections = (scope: UiSettingsScope) => {
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);
Expand All @@ -131,6 +182,10 @@ export const SettingsApplicationKibanaProvider: FC<SettingsApplicationKibanaDepe

const services: Services = {
getAllowlistedSettings,
getSections,
getToastsService: () => notifications.toasts,
getCapabilities,
setBadge: (badge: ChromeBadge) => chrome.setBadge(badge),
isCustomSetting,
isOverriddenSetting,
subscribeToUpdates,
Expand Down
3 changes: 3 additions & 0 deletions packages/kbn-management/settings/application/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions packages/kbn-management/settings/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions packages/kbn-management/settings/types/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { RegistryEntry } from '@kbn/management-settings-section-registry';
import { CategoryCounts } from './category';
import { FieldDefinition } from '.';

Expand All @@ -16,5 +17,7 @@ export interface SettingsTabs {
categoryCounts: CategoryCounts;
callOutTitle: string;
callOutText: string;
sections: RegistryEntry[];
isSavingEnabled: boolean;
};
}
1 change: 1 addition & 0 deletions packages/kbn-management/settings/types/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
"@kbn/analytics",
"@kbn/core",
"@kbn/core-ui-settings-common",
"@kbn/management-settings-section-registry",
]
}
Loading

0 comments on commit 4eb3578

Please sign in to comment.