mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[settings] Extract and fix Section Registry (#163502)
## Summary While working to extract various portions of the `advancedSettings` plugin into packages, I found the `ComponentRegistry` in the plugin to have a number of issues that contributed to a fairly bad UX: - the API allows for adding/overriding the title, subtitle and footer of the Advanced Settings page, but only the footer is rendered. - the API is available to all plugins, but only renders a single entry... so depending on the plugin load order, the render is not guaranteed. - filtering the footer in or out of the display is delegated to the component itself, so: - it only takes effect on render. - the count is only updated if you click on the page that contains it, but that logic is currently broken. - the error message is inaccurate.  This PR fixes those issues and more: - extracts the registry into its own package. - changes the API to allow for multiple sections from multiple plugins. - changes the API to filter these sections from the plugin, rather than from each individual component. - fixes state management to show sections, keep counts accurate, etc.  --------- Co-authored-by: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com>
This commit is contained in:
parent
309666acc2
commit
1546490e98
62 changed files with 433 additions and 807 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -9,7 +9,7 @@ x-pack/test/alerting_api_integration/common/plugins/aad @elastic/response-ops
|
|||
packages/kbn-ace @elastic/platform-deployment-management
|
||||
x-pack/plugins/actions @elastic/response-ops
|
||||
x-pack/test/alerting_api_integration/common/plugins/actions_simulators @elastic/response-ops
|
||||
src/plugins/advanced_settings @elastic/appex-sharedux
|
||||
src/plugins/advanced_settings @elastic/appex-sharedux @elastic/platform-deployment-management
|
||||
x-pack/packages/ml/aiops_components @elastic/ml-ui
|
||||
x-pack/plugins/aiops @elastic/ml-ui
|
||||
x-pack/packages/ml/aiops_utils @elastic/ml-ui
|
||||
|
@ -478,6 +478,7 @@ packages/kbn-managed-vscode-config @elastic/kibana-operations
|
|||
packages/kbn-managed-vscode-config-cli @elastic/kibana-operations
|
||||
packages/kbn-management/cards_navigation @elastic/platform-deployment-management
|
||||
src/plugins/management @elastic/platform-deployment-management
|
||||
packages/kbn-management/settings/section_registry @elastic/appex-sharedux @elastic/platform-deployment-management
|
||||
packages/kbn-management/storybook/config @elastic/platform-deployment-management
|
||||
test/plugin_functional/plugins/management_test_plugin @elastic/kibana-app-services
|
||||
packages/kbn-mapbox-gl @elastic/kibana-gis
|
||||
|
|
|
@ -495,6 +495,7 @@
|
|||
"@kbn/logstash-plugin": "link:x-pack/plugins/logstash",
|
||||
"@kbn/management-cards-navigation": "link:packages/kbn-management/cards_navigation",
|
||||
"@kbn/management-plugin": "link:src/plugins/management",
|
||||
"@kbn/management-settings-section-registry": "link:packages/kbn-management/settings/section_registry",
|
||||
"@kbn/management-test-plugin": "link:test/plugin_functional/plugins/management_test_plugin",
|
||||
"@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl",
|
||||
"@kbn/maps-custom-raster-source-plugin": "link:x-pack/examples/third_party_maps_source_example",
|
||||
|
|
56
packages/kbn-management/settings/section_registry/README.mdx
Normal file
56
packages/kbn-management/settings/section_registry/README.mdx
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
id: kbn-management/settings/SectionRegistry
|
||||
slug: /kbn-management/settings/section-registry/
|
||||
title: Section Registry
|
||||
description: A registry which allows a consumer to add sections to Advanced Settings.
|
||||
tags: ['management', 'settings']
|
||||
date: 2023-08-04
|
||||
---
|
||||
|
||||
This registry is fairly straightforward: it allows a consumer to add a section to the Advanced Settings page. This registry would be consumed by a plugin and exposed on the `start` and `setup` contracts:
|
||||
|
||||
```ts
|
||||
|
||||
const registry = new SectionRegistry();
|
||||
|
||||
export class PluginBar
|
||||
implements Plugin<PluginBarSetup, PluginBarStart, PluginBarSetupDeps, PluginBarStartDeps>
|
||||
{
|
||||
public setup(
|
||||
_core: CoreSetup,
|
||||
_setupDeps: PluginFooSetupDeps
|
||||
) {
|
||||
return {
|
||||
sectionRegistry: sectionRegistry.setup,
|
||||
};
|
||||
}
|
||||
|
||||
public start(
|
||||
_core: CoreStart,
|
||||
_startDeps: PluginFooStartDeps
|
||||
) {
|
||||
return {
|
||||
sectionRegistry: sectionRegistry.start,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class PluginFoo
|
||||
implements Plugin<PluginFooSetup, PluginFooStart, PluginFooSetupDeps, PluginFooStartDeps>
|
||||
{
|
||||
public setup(
|
||||
core: CoreSetup,
|
||||
{ pluginBar: { sectionRegistry } }: PluginFooSetupDeps
|
||||
) {
|
||||
const Component = (props: RegistryComponentProps) => <SomeComponent {...props} />;
|
||||
|
||||
const queryMatch = (query: string) => {
|
||||
const searchTerm = query.toLowerCase();
|
||||
return SEARCH_TERMS.some((term) => term.indexOf(searchTerm) >= 0);
|
||||
};
|
||||
|
||||
sectionRegistry.setup.addGlobalSection(Component, queryMatch);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
|
@ -6,4 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { PageSubtitle } from './page_subtitle';
|
||||
export { SectionRegistry } from './section_registry';
|
||||
export type {
|
||||
SectionRegistrySetup,
|
||||
SectionRegistryStart,
|
||||
RegistryComponentProps,
|
||||
RegistryEntry,
|
||||
} from './section_registry';
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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: ['<rootDir>/packages/kbn-management/settings/section_registry'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/packages/kbn-management/settings/section_registry',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/packages/kbn-management/settings/section_registry/**/*.{ts,tsx}',
|
||||
],
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/management-settings-section-registry",
|
||||
"owner": "@elastic/appex-sharedux @elastic/platform-deployment-management"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/management-settings-section-registry",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { SectionRegistry } from './section_registry';
|
||||
|
||||
describe('SectionRegistry', () => {
|
||||
let registry = new SectionRegistry();
|
||||
|
||||
beforeEach(() => {
|
||||
registry = new SectionRegistry();
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should allow a global component to be registered', () => {
|
||||
const Component = () => <div />;
|
||||
const queryMatch = () => true;
|
||||
registry.setup.addGlobalSection(Component, queryMatch);
|
||||
|
||||
const entries = registry.start.getGlobalSections();
|
||||
expect(entries).toHaveLength(1);
|
||||
expect(entries[0].Component).toBe(Component);
|
||||
expect(entries[0].queryMatch).toBe(queryMatch);
|
||||
expect(registry.start.getSpacesSections()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should allow a spaces component to be registered', () => {
|
||||
const Component = () => <div />;
|
||||
const queryMatch = () => true;
|
||||
registry.setup.addSpaceSection(Component, queryMatch);
|
||||
|
||||
const entries = registry.start.getSpacesSections();
|
||||
expect(entries).toHaveLength(1);
|
||||
expect(entries[0].Component).toBe(Component);
|
||||
expect(entries[0].queryMatch).toBe(queryMatch);
|
||||
expect(registry.start.getGlobalSections()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { ComponentType } from 'react';
|
||||
import { ToastsStart } from '@kbn/core-notifications-browser';
|
||||
import { UiSettingsScope } from '@kbn/core-ui-settings-common';
|
||||
|
||||
/**
|
||||
* Props provided to a `RegistryComponent`.
|
||||
*/
|
||||
export interface RegistryComponentProps {
|
||||
toasts: ToastsStart;
|
||||
enableSaving: Record<UiSettingsScope, boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry entry for a section.
|
||||
*/
|
||||
export interface RegistryEntry {
|
||||
Component: RegistryComponent;
|
||||
queryMatch: QueryMatchFn;
|
||||
}
|
||||
|
||||
type RegistryComponent = ComponentType<RegistryComponentProps>;
|
||||
type PageType = 'space' | 'global';
|
||||
type QueryMatchFn = (term: string) => boolean;
|
||||
type Registry = Record<PageType, RegistryEntry[]>;
|
||||
|
||||
/**
|
||||
* A registry of sections to add to pages within Advanced Settings.
|
||||
*/
|
||||
export class SectionRegistry {
|
||||
private registry: Registry = {
|
||||
space: [],
|
||||
global: [],
|
||||
};
|
||||
|
||||
setup = {
|
||||
/**
|
||||
* Registers a section within the "Space" page.
|
||||
*
|
||||
* @param Component - A React component to render.
|
||||
* @param queryMatch - A function that, given a search term, returns true if the section should be rendered.
|
||||
*/
|
||||
addSpaceSection: (Component: RegistryComponent, queryMatch: QueryMatchFn) => {
|
||||
this.registry.space.push({ Component, queryMatch });
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a section within the "Global" page.
|
||||
*
|
||||
* @param Component - A React component to render.
|
||||
* @param queryMatch - A function that, given a search term, returns true if the section should be rendered.
|
||||
*/
|
||||
addGlobalSection: (Component: RegistryComponent, queryMatch: QueryMatchFn) => {
|
||||
this.registry.global.push({ Component, queryMatch });
|
||||
},
|
||||
};
|
||||
|
||||
start = {
|
||||
/**
|
||||
* Retrieve components registered for the "Space" page.
|
||||
*/
|
||||
getGlobalSections: (): RegistryEntry[] => {
|
||||
return this.registry.global;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve components registered for the "Global" page.
|
||||
*/
|
||||
getSpacesSections: (): RegistryEntry[] => {
|
||||
return this.registry.space;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The `setup` contract provided by a `SectionRegistry`.
|
||||
*/
|
||||
export type SectionRegistrySetup = SectionRegistry['setup'];
|
||||
|
||||
/**
|
||||
* The `start` contract provided by a `SectionRegistry`.
|
||||
*/
|
||||
export type SectionRegistryStart = SectionRegistry['start'];
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core-notifications-browser",
|
||||
"@kbn/core-ui-settings-common",
|
||||
]
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/advanced-settings-plugin",
|
||||
"owner": "@elastic/appex-sharedux",
|
||||
"owner": "@elastic/appex-sharedux @elastic/platform-deployment-management",
|
||||
"plugin": {
|
||||
"id": "advancedSettings",
|
||||
"server": true,
|
||||
|
@ -18,4 +18,4 @@
|
|||
"kibanaUtils"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ComponentRegistry register should disallow registering a component with a duplicate id 1`] = `"Component with id advanced_settings_page_title is already registered."`;
|
|
@ -1,79 +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 { ComponentRegistry } from './component_registry';
|
||||
|
||||
describe('ComponentRegistry', () => {
|
||||
describe('register', () => {
|
||||
it('should allow a component to be registered', () => {
|
||||
const component = () => <div />;
|
||||
new ComponentRegistry().setup.register(
|
||||
ComponentRegistry.componentType.PAGE_TITLE_COMPONENT,
|
||||
component
|
||||
);
|
||||
});
|
||||
|
||||
it('should disallow registering a component with a duplicate id', () => {
|
||||
const registry = new ComponentRegistry();
|
||||
const component = () => <div />;
|
||||
registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component);
|
||||
expect(() =>
|
||||
registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, () => (
|
||||
<span />
|
||||
))
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('should allow a component to be overriden', () => {
|
||||
const registry = new ComponentRegistry();
|
||||
const component = () => <div />;
|
||||
registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component);
|
||||
|
||||
const anotherComponent = () => <span />;
|
||||
registry.setup.register(
|
||||
ComponentRegistry.componentType.PAGE_TITLE_COMPONENT,
|
||||
anotherComponent,
|
||||
true
|
||||
);
|
||||
|
||||
expect(registry.start.get(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT)).toBe(
|
||||
anotherComponent
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should allow a component to be retrieved', () => {
|
||||
const registry = new ComponentRegistry();
|
||||
const component = () => <div />;
|
||||
registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component);
|
||||
expect(registry.start.get(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT)).toBe(
|
||||
component
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set a displayName for the component if one does not exist', () => {
|
||||
const component: React.ComponentType = () => <div />;
|
||||
const registry = new ComponentRegistry();
|
||||
registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component);
|
||||
|
||||
expect(component.displayName).toEqual(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT);
|
||||
});
|
||||
|
||||
it('should not set a displayName for the component if one already exists', () => {
|
||||
const component: React.ComponentType = () => <div />;
|
||||
component.displayName = '<AwesomeComponent>';
|
||||
const registry = new ComponentRegistry();
|
||||
|
||||
registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component);
|
||||
|
||||
expect(component.displayName).toEqual('<AwesomeComponent>');
|
||||
});
|
||||
});
|
|
@ -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 { ComponentType } from 'react';
|
||||
import { PageTitle } from './page_title';
|
||||
import { PageSubtitle } from './page_subtitle';
|
||||
import { PageFooter } from './page_footer';
|
||||
|
||||
type Id =
|
||||
| 'advanced_settings_page_title'
|
||||
| 'advanced_settings_page_subtitle'
|
||||
| 'advanced_settings_page_footer';
|
||||
|
||||
const componentType: { [key: string]: Id } = {
|
||||
PAGE_TITLE_COMPONENT: 'advanced_settings_page_title' as Id,
|
||||
PAGE_SUBTITLE_COMPONENT: 'advanced_settings_page_subtitle' as Id,
|
||||
PAGE_FOOTER_COMPONENT: 'advanced_settings_page_footer' as Id,
|
||||
};
|
||||
|
||||
type RegistryComponent = ComponentType<Record<string, any> | undefined>;
|
||||
|
||||
export class ComponentRegistry {
|
||||
static readonly componentType = componentType;
|
||||
static readonly defaultRegistry: Record<Id, RegistryComponent> = {
|
||||
advanced_settings_page_title: PageTitle,
|
||||
advanced_settings_page_subtitle: PageSubtitle,
|
||||
advanced_settings_page_footer: PageFooter,
|
||||
};
|
||||
|
||||
registry: { [key in Id]?: RegistryComponent } = {};
|
||||
|
||||
setup = {
|
||||
componentType: ComponentRegistry.componentType,
|
||||
/**
|
||||
* Attempts to register the provided component, with the ability to optionally allow
|
||||
* the component to override an existing one.
|
||||
*
|
||||
* If the intent is to override, then `allowOverride` must be set to true, otherwise an exception is thrown.
|
||||
*
|
||||
* @param id the id of the component to register
|
||||
* @param component the component
|
||||
* @param allowOverride (default: false) - optional flag to allow this component to override a previously registered component
|
||||
*/
|
||||
register: (id: Id, component: RegistryComponent, allowOverride = false) => {
|
||||
if (!allowOverride && id in this.registry) {
|
||||
throw new Error(`Component with id ${id} is already registered.`);
|
||||
}
|
||||
|
||||
// Setting a display name if one does not already exist.
|
||||
// This enhances the snapshots, as well as the debugging experience.
|
||||
if (!component.displayName) {
|
||||
component.displayName = id;
|
||||
}
|
||||
|
||||
this.registry[id] = component;
|
||||
},
|
||||
};
|
||||
|
||||
start = {
|
||||
componentType: ComponentRegistry.componentType,
|
||||
/**
|
||||
* Retrieve a registered component by its ID.
|
||||
* If the component does not exist, then an exception is thrown.
|
||||
*
|
||||
* @param id the ID of the component to retrieve
|
||||
*/
|
||||
get: (id: Id): RegistryComponent => {
|
||||
return this.registry[id] || ComponentRegistry.defaultRegistry[id];
|
||||
},
|
||||
};
|
||||
}
|
|
@ -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 { ComponentRegistry } from './component_registry';
|
|
@ -1,3 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PageFooter should render normally 1`] = `""`;
|
|
@ -1,18 +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 } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { PageFooter } from './page_footer';
|
||||
|
||||
describe('PageFooter', () => {
|
||||
it('should render normally', () => {
|
||||
expect(shallowWithI18nProvider(<PageFooter />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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 PageFooter = () => null;
|
|
@ -1,3 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PageSubtitle should render normally 1`] = `""`;
|
|
@ -1,18 +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 } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { PageSubtitle } from './page_subtitle';
|
||||
|
||||
describe('PageSubtitle', () => {
|
||||
it('should render normally', () => {
|
||||
expect(shallowWithI18nProvider(<PageSubtitle />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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 PageSubtitle = () => null;
|
|
@ -1,15 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PageTitle should render normally 1`] = `
|
||||
<EuiText>
|
||||
<h1
|
||||
data-test-subj="managementSettingsTitle"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Settings"
|
||||
id="advancedSettings.pageTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h1>
|
||||
</EuiText>
|
||||
`;
|
|
@ -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 { PageTitle } from './page_title';
|
|
@ -1,18 +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 } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { PageTitle } from './page_title';
|
||||
|
||||
describe('PageTitle', () => {
|
||||
it('should render normally', () => {
|
||||
expect(shallowWithI18nProvider(<PageTitle />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,21 +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 { EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export const PageTitle = () => {
|
||||
return (
|
||||
<EuiText>
|
||||
<h1 data-test-subj="managementSettingsTitle">
|
||||
<FormattedMessage id="advancedSettings.pageTitle" defaultMessage="Settings" />
|
||||
</h1>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -10,7 +10,6 @@ import React from 'react';
|
|||
import { PluginInitializerContext } from '@kbn/core/public';
|
||||
import { AdvancedSettingsPlugin } from './plugin';
|
||||
export type { AdvancedSettingsSetup, AdvancedSettingsStart } from './types';
|
||||
export { ComponentRegistry } from './component_registry';
|
||||
|
||||
/**
|
||||
* Exports the field component as a React.lazy component. We're explicitly naming it lazy here
|
||||
|
|
|
@ -13,7 +13,7 @@ export const i18nTexts = {
|
|||
defaultMessage: 'Space Settings',
|
||||
}),
|
||||
defaultSpaceCalloutTitle: i18n.translate('advancedSettings.defaultSpaceCalloutTitle', {
|
||||
defaultMessage: 'Changes will affect the `default` space',
|
||||
defaultMessage: 'Changes will affect the current space.',
|
||||
}),
|
||||
defaultSpaceCalloutSubtitle: i18n.translate('advancedSettings.defaultSpaceCalloutSubtitle', {
|
||||
defaultMessage:
|
||||
|
|
|
@ -12,18 +12,17 @@ import { Redirect, RouteChildrenProps } from 'react-router-dom';
|
|||
import { Router, Routes, Route } from '@kbn/shared-ux-router';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
import { LocationDescriptor } from 'history';
|
||||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
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 { ComponentRegistry } from '../types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
@ -56,11 +55,12 @@ const redirectUrl = ({ match, location }: RedirectUrlProps): LocationDescriptor
|
|||
export async function mountManagementSection(
|
||||
getStartServices: StartServicesAccessor,
|
||||
params: ManagementAppMountParams,
|
||||
componentRegistry: ComponentRegistry['start'],
|
||||
sectionRegistry: SectionRegistryStart,
|
||||
usageCollection?: UsageCollectionSetup
|
||||
) {
|
||||
params.setBreadcrumbs(crumb);
|
||||
const [{ settings, notifications, docLinks, application, chrome }] = await getStartServices();
|
||||
const [{ settings, notifications, docLinks, application, chrome, i18n: i18nStart, theme }] =
|
||||
await getStartServices();
|
||||
|
||||
const { advancedSettings, globalSettings } = application.capabilities;
|
||||
const canSaveAdvancedSettings = advancedSettings.save as boolean;
|
||||
|
@ -74,34 +74,32 @@ export async function mountManagementSection(
|
|||
chrome.docTitle.change(title);
|
||||
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={params.theme$}>
|
||||
<I18nProvider>
|
||||
<Router history={params.history}>
|
||||
<Routes>
|
||||
{/* TODO: remove route param (`query`) in 7.13 */}
|
||||
<Route path={`/:${QUERY}`}>
|
||||
{(props: RedirectUrlProps) => <Redirect to={redirectUrl(props)} />}
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Settings
|
||||
history={params.history}
|
||||
enableSaving={{
|
||||
namespace: canSaveAdvancedSettings,
|
||||
global: canSaveGlobalSettings,
|
||||
}}
|
||||
enableShowing={{ namespace: true, global: canShowGlobalSettings }}
|
||||
toasts={notifications.toasts}
|
||||
docLinks={docLinks.links}
|
||||
settingsService={settings}
|
||||
theme={params.theme$}
|
||||
componentRegistry={componentRegistry}
|
||||
trackUiMetric={trackUiMetric}
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</I18nProvider>
|
||||
</KibanaThemeProvider>,
|
||||
<KibanaRenderContextProvider {...{ i18n: i18nStart, theme }}>
|
||||
<Router history={params.history}>
|
||||
<Routes>
|
||||
{/* TODO: remove route param (`query`) in 7.13 */}
|
||||
<Route path={`/:${QUERY}`}>
|
||||
{(props: RedirectUrlProps) => <Redirect to={redirectUrl(props)} />}
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Settings
|
||||
history={params.history}
|
||||
enableSaving={{
|
||||
namespace: canSaveAdvancedSettings,
|
||||
global: canSaveGlobalSettings,
|
||||
}}
|
||||
enableShowing={{ namespace: true, global: canShowGlobalSettings }}
|
||||
toasts={notifications.toasts}
|
||||
docLinks={docLinks.links}
|
||||
settingsService={settings}
|
||||
theme={params.theme$}
|
||||
sectionRegistry={sectionRegistry}
|
||||
trackUiMetric={trackUiMetric}
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</KibanaRenderContextProvider>,
|
||||
params.element
|
||||
);
|
||||
return () => {
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
docLinksServiceMock,
|
||||
themeServiceMock,
|
||||
} from '@kbn/core/public/mocks';
|
||||
import { ComponentRegistry } from '../component_registry';
|
||||
import { SectionRegistry } from '@kbn/management-settings-section-registry';
|
||||
import { Search } from './components/search';
|
||||
import { EuiTab } from '@elastic/eui';
|
||||
|
||||
|
@ -257,7 +257,7 @@ describe('Settings', () => {
|
|||
toasts={notificationServiceMock.createStartContract().toasts}
|
||||
docLinks={docLinksServiceMock.createStartContract().links}
|
||||
settingsService={mockConfig().core.settings}
|
||||
componentRegistry={new ComponentRegistry().start}
|
||||
sectionRegistry={new SectionRegistry().start}
|
||||
theme={themeServiceMock.createStartContract().theme$}
|
||||
/>
|
||||
);
|
||||
|
@ -286,7 +286,7 @@ describe('Settings', () => {
|
|||
toasts={notificationServiceMock.createStartContract().toasts}
|
||||
docLinks={docLinksServiceMock.createStartContract().links}
|
||||
settingsService={mockConfig().core.settings}
|
||||
componentRegistry={new ComponentRegistry().start}
|
||||
sectionRegistry={new SectionRegistry().start}
|
||||
theme={themeServiceMock.createStartContract().theme$}
|
||||
/>
|
||||
);
|
||||
|
@ -312,7 +312,7 @@ describe('Settings', () => {
|
|||
toasts={notificationServiceMock.createStartContract().toasts}
|
||||
docLinks={docLinksServiceMock.createStartContract().links}
|
||||
settingsService={mockConfig().core.settings}
|
||||
componentRegistry={new ComponentRegistry().start}
|
||||
sectionRegistry={new SectionRegistry().start}
|
||||
theme={themeServiceMock.createStartContract().theme$}
|
||||
/>
|
||||
);
|
||||
|
@ -341,7 +341,7 @@ describe('Settings', () => {
|
|||
toasts={toasts}
|
||||
docLinks={docLinksServiceMock.createStartContract().links}
|
||||
settingsService={mockConfig().core.settings}
|
||||
componentRegistry={new ComponentRegistry().start}
|
||||
sectionRegistry={new SectionRegistry().start}
|
||||
theme={themeServiceMock.createStartContract().theme$}
|
||||
/>
|
||||
);
|
||||
|
@ -363,7 +363,7 @@ describe('Settings', () => {
|
|||
toasts={toasts}
|
||||
docLinks={docLinksServiceMock.createStartContract().links}
|
||||
settingsService={mockConfig().core.settings}
|
||||
componentRegistry={new ComponentRegistry().start}
|
||||
sectionRegistry={new SectionRegistry().start}
|
||||
theme={themeServiceMock.createStartContract().theme$}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -26,19 +26,23 @@ 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 { ComponentRegistry } from '..';
|
||||
import { Search } from './components/search';
|
||||
import { FieldSetting } from './types';
|
||||
import { i18nTexts } from './i18n_texts';
|
||||
import { getAriaName } from './lib';
|
||||
|
||||
interface AdvancedSettingsState {
|
||||
footerQueryMatched: boolean;
|
||||
query: Query;
|
||||
filteredSettings: Record<UiSettingsScope, Record<string, FieldSetting[]>>;
|
||||
filteredSections: {
|
||||
global: RegistryEntry[];
|
||||
space: RegistryEntry[];
|
||||
};
|
||||
}
|
||||
|
||||
export type GroupedSettings = Record<string, FieldSetting[]>;
|
||||
|
@ -51,7 +55,7 @@ interface Props {
|
|||
docLinks: DocLinksStart['links'];
|
||||
toasts: ToastsStart;
|
||||
theme: ThemeServiceStart['theme$'];
|
||||
componentRegistry: ComponentRegistry['start'];
|
||||
sectionRegistry: SectionRegistryStart;
|
||||
trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void;
|
||||
}
|
||||
|
||||
|
@ -59,8 +63,7 @@ const SPACE_SETTINGS_ID = 'space-settings';
|
|||
const GLOBAL_SETTINGS_ID = 'global-settings';
|
||||
|
||||
export const Settings = (props: Props) => {
|
||||
const { componentRegistry, history, settingsService, enableSaving, enableShowing, ...rest } =
|
||||
props;
|
||||
const { sectionRegistry, history, settingsService, enableSaving, enableShowing, ...rest } = props;
|
||||
const uiSettings = settingsService.client;
|
||||
const globalUiSettings = settingsService.globalClient;
|
||||
|
||||
|
@ -89,7 +92,10 @@ export const Settings = (props: Props) => {
|
|||
global: {},
|
||||
namespace: {},
|
||||
},
|
||||
footerQueryMatched: false,
|
||||
filteredSections: {
|
||||
global: sectionRegistry.getGlobalSections(),
|
||||
space: sectionRegistry.getSpacesSections(),
|
||||
},
|
||||
query: Query.parse(''),
|
||||
});
|
||||
|
||||
|
@ -206,7 +212,9 @@ export const Settings = (props: Props) => {
|
|||
categories={categories[scope]}
|
||||
visibleSettings={queryState.filteredSettings[scope]}
|
||||
clearQuery={() => setUrlQuery('')}
|
||||
noResults={!queryState.footerQueryMatched}
|
||||
noResults={
|
||||
queryState.filteredSections.global.length + queryState.filteredSections.space.length === 0
|
||||
}
|
||||
queryText={queryState.query.text}
|
||||
callOutTitle={callOutTitle(scope)}
|
||||
callOutSubtitle={callOutSubtitle(scope)}
|
||||
|
@ -225,7 +233,8 @@ export const Settings = (props: Props) => {
|
|||
append:
|
||||
queryState.query.text !== '' ? (
|
||||
<EuiNotificationBadge className="eui-alignCenter" size="m" key="spaceSettings-badge">
|
||||
{Object.keys(queryState.filteredSettings.namespace).length}
|
||||
{Object.keys(queryState.filteredSettings.namespace).length +
|
||||
queryState.filteredSections.space.length}
|
||||
</EuiNotificationBadge>
|
||||
) : null,
|
||||
content: renderAdvancedSettings('namespace'),
|
||||
|
@ -239,7 +248,7 @@ export const Settings = (props: Props) => {
|
|||
queryState.query.text !== '' ? (
|
||||
<EuiNotificationBadge className="eui-alignCenter" size="m" key="spaceSettings-badge">
|
||||
{Object.keys(queryState.filteredSettings.global).length +
|
||||
Number(queryState.footerQueryMatched)}
|
||||
queryState.filteredSections.global.length}
|
||||
</EuiNotificationBadge>
|
||||
) : null,
|
||||
content: renderAdvancedSettings('global'),
|
||||
|
@ -297,7 +306,14 @@ export const Settings = (props: Props) => {
|
|||
return {
|
||||
query,
|
||||
filteredSettings,
|
||||
footerQueryMatched: initialQuery ? false : queryState.footerQueryMatched,
|
||||
filteredSections: {
|
||||
global: sectionRegistry
|
||||
.getGlobalSections()
|
||||
.filter(({ queryMatch }) => queryMatch(query.text)),
|
||||
space: sectionRegistry
|
||||
.getSpacesSections()
|
||||
.filter(({ queryMatch }) => queryMatch(query.text)),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -308,19 +324,25 @@ export const Settings = (props: Props) => {
|
|||
[setUrlQuery]
|
||||
);
|
||||
|
||||
const onFooterQueryMatchChange = useCallback(
|
||||
(matched: boolean) => {
|
||||
setQueryState({ ...queryState, footerQueryMatched: matched });
|
||||
},
|
||||
[queryState]
|
||||
);
|
||||
|
||||
const PageTitle = (
|
||||
<EuiText>
|
||||
<h1 data-test-subj="managementSettingsTitle">{i18nTexts.advancedSettingsTitle}</h1>
|
||||
</EuiText>
|
||||
);
|
||||
const PageFooter = componentRegistry.get(componentRegistry.componentType.PAGE_FOOTER_COMPONENT);
|
||||
|
||||
const mapSections = (entries: RegistryEntry[]) =>
|
||||
entries.map(({ Component, queryMatch }, index) => {
|
||||
if (queryMatch(queryState.query.text)) {
|
||||
return (
|
||||
<Component
|
||||
key={`component-${index}`}
|
||||
toasts={props.toasts}
|
||||
enableSaving={props.enableSaving}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -337,14 +359,11 @@ export const Settings = (props: Props) => {
|
|||
<EuiSpacer size="m" />
|
||||
<EuiTabs>{renderTabs()}</EuiTabs>
|
||||
{selectedTabContent}
|
||||
{selectedTabId === GLOBAL_SETTINGS_ID ? (
|
||||
<PageFooter
|
||||
toasts={props.toasts}
|
||||
query={queryState.query}
|
||||
onQueryMatchChange={onFooterQueryMatchChange}
|
||||
enableSaving={props.enableSaving}
|
||||
/>
|
||||
) : null}
|
||||
{selectedTabId === SPACE_SETTINGS_ID ? (
|
||||
<>{mapSections(queryState.filteredSections.space)}</>
|
||||
) : (
|
||||
<>{mapSections(queryState.filteredSections.global)}</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,17 +6,21 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ComponentRegistry } from './component_registry';
|
||||
import type {
|
||||
SectionRegistrySetup,
|
||||
SectionRegistryStart,
|
||||
} from '@kbn/management-settings-section-registry';
|
||||
|
||||
const register = jest.fn();
|
||||
const get = jest.fn();
|
||||
const componentType = ComponentRegistry.componentType;
|
||||
const addGlobalSection = jest.fn();
|
||||
const addSpaceSection = jest.fn();
|
||||
const getGlobalSections = jest.fn();
|
||||
const getSpacesSections = jest.fn();
|
||||
|
||||
export const advancedSettingsMock = {
|
||||
createSetupContract() {
|
||||
return { component: { register, componentType } };
|
||||
createSetupContract(): SectionRegistrySetup {
|
||||
return { addGlobalSection, addSpaceSection };
|
||||
},
|
||||
createStartContract() {
|
||||
return { component: { get, componentType } };
|
||||
createStartContract(): SectionRegistryStart {
|
||||
return { getGlobalSections, getSpacesSections };
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, Plugin } from '@kbn/core/public';
|
||||
import { ComponentRegistry } from './component_registry';
|
||||
import { SectionRegistry } from '@kbn/management-settings-section-registry';
|
||||
import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types';
|
||||
|
||||
const component = new ComponentRegistry();
|
||||
const { setup: sectionRegistrySetup, start: sectionRegistryStart } = new SectionRegistry();
|
||||
|
||||
const title = i18n.translate('advancedSettings.advancedSettingsLabel', {
|
||||
defaultMessage: 'Advanced Settings',
|
||||
|
@ -37,7 +37,7 @@ export class AdvancedSettingsPlugin
|
|||
return mountManagementSection(
|
||||
core.getStartServices,
|
||||
params,
|
||||
component.start,
|
||||
sectionRegistryStart,
|
||||
usageCollection
|
||||
);
|
||||
},
|
||||
|
@ -59,13 +59,13 @@ export class AdvancedSettingsPlugin
|
|||
}
|
||||
|
||||
return {
|
||||
component: component.setup,
|
||||
...sectionRegistrySetup,
|
||||
};
|
||||
}
|
||||
|
||||
public start() {
|
||||
return {
|
||||
component: component.start,
|
||||
...sectionRegistryStart,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,19 +10,16 @@ import { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
|||
|
||||
import { ManagementSetup } from '@kbn/management-plugin/public';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { ComponentRegistry } from './component_registry';
|
||||
import type {
|
||||
SectionRegistrySetup,
|
||||
SectionRegistryStart,
|
||||
} from '@kbn/management-settings-section-registry';
|
||||
|
||||
export interface AdvancedSettingsSetup {
|
||||
component: ComponentRegistry['setup'];
|
||||
}
|
||||
export interface AdvancedSettingsStart {
|
||||
component: ComponentRegistry['start'];
|
||||
}
|
||||
export type AdvancedSettingsSetup = SectionRegistrySetup;
|
||||
export type AdvancedSettingsStart = SectionRegistryStart;
|
||||
|
||||
export interface AdvancedSettingsPluginSetup {
|
||||
management: ManagementSetup;
|
||||
home?: HomePublicPluginSetup;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
}
|
||||
|
||||
export { ComponentRegistry };
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
"@kbn/core-ui-settings-common",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/core-plugins-server",
|
||||
"@kbn/management-settings-section-registry",
|
||||
"@kbn/react-kibana-context-render",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
29
src/plugins/telemetry_management_section/common/constants.ts
Normal file
29
src/plugins/telemetry_management_section/common/constants.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* These are the terms provided to Advanced Settings that map to this section. When searching,
|
||||
* Advanced Settings will match against these terms to show or hide the section.
|
||||
*/
|
||||
export const SEARCH_TERMS: string[] = [
|
||||
'telemetry',
|
||||
'usage data', // Keeping this term for BWC
|
||||
'usage collection',
|
||||
i18n.translate('telemetry.telemetryConstant', {
|
||||
defaultMessage: 'telemetry',
|
||||
}),
|
||||
i18n.translate('telemetry.usageCollectionConstant', {
|
||||
defaultMessage: 'usage collection',
|
||||
}),
|
||||
].flatMap((term) => {
|
||||
// Automatically lower-case and split by space the terms from above
|
||||
const lowerCased = term.toLowerCase();
|
||||
return [lowerCased, ...lowerCased.split(' ')];
|
||||
});
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { PageFooter } from './page_footer';
|
||||
export { SEARCH_TERMS } from './constants';
|
|
@ -7,8 +7,8 @@
|
|||
"server": false,
|
||||
"browser": true,
|
||||
"requiredPlugins": [
|
||||
"advancedSettings",
|
||||
"telemetry"
|
||||
"telemetry",
|
||||
"advancedSettings"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"usageCollection"
|
||||
|
@ -17,4 +17,4 @@
|
|||
"usageCollection"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -253,7 +253,6 @@ exports[`TelemetryManagementSectionComponent renders null because allowChangingO
|
|||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
onQueryMatchChange={[MockFunction]}
|
||||
showAppliesSettingMessage={true}
|
||||
telemetryService={
|
||||
TelemetryService {
|
||||
|
|
|
@ -22,7 +22,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
const coreSetup = coreMock.createSetup();
|
||||
|
||||
it('renders as expected', () => {
|
||||
const onQueryMatchChange = jest.fn();
|
||||
const telemetryService = new TelemetryService({
|
||||
config: {
|
||||
appendServerlessChannelsSuffix: false,
|
||||
|
@ -44,7 +43,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
shallowWithIntl(
|
||||
<TelemetryManagementSection
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
showAppliesSettingMessage={true}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
|
@ -55,7 +53,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
});
|
||||
|
||||
it('renders null because query does not match the SEARCH_TERMS', () => {
|
||||
const onQueryMatchChange = jest.fn();
|
||||
const telemetryService = new TelemetryService({
|
||||
config: {
|
||||
appendServerlessChannelsSuffix: false,
|
||||
|
@ -77,7 +74,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
<React.Suspense fallback={<span>Fallback</span>}>
|
||||
<TelemetryManagementSection
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
showAppliesSettingMessage={false}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
|
@ -90,9 +86,7 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
component.rerender(
|
||||
<React.Suspense fallback={<span>Fallback</span>}>
|
||||
<TelemetryManagementSection
|
||||
query={{ text: 'asdasdasd' }}
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
showAppliesSettingMessage={false}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
|
@ -100,15 +94,12 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
/>
|
||||
</React.Suspense>
|
||||
);
|
||||
expect(onQueryMatchChange).toHaveBeenCalledWith(false);
|
||||
expect(onQueryMatchChange).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
component.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('renders because query matches the SEARCH_TERMS', () => {
|
||||
const onQueryMatchChange = jest.fn();
|
||||
const telemetryService = new TelemetryService({
|
||||
config: {
|
||||
appendServerlessChannelsSuffix: false,
|
||||
|
@ -129,7 +120,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
const component = mountWithIntl(
|
||||
<TelemetryManagementSection
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
showAppliesSettingMessage={false}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
|
@ -145,17 +135,12 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
|
||||
// It should also render if there is no query at all.
|
||||
expect(component.setProps({ ...component.props(), query: {} }).html()).not.toBe('');
|
||||
expect(onQueryMatchChange).toHaveBeenCalledWith(true);
|
||||
|
||||
// Should only be called once because the second time does not change the result
|
||||
expect(onQueryMatchChange).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
component.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('renders null because allowChangingOptInStatus is false', () => {
|
||||
const onQueryMatchChange = jest.fn();
|
||||
const telemetryService = new TelemetryService({
|
||||
config: {
|
||||
appendServerlessChannelsSuffix: false,
|
||||
|
@ -176,7 +161,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
const component = mountWithIntl(
|
||||
<TelemetryManagementSection
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
showAppliesSettingMessage={true}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
|
@ -186,14 +170,12 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
try {
|
||||
expect(component).toMatchSnapshot();
|
||||
component.setProps({ ...component.props(), query: { text: 'TeLEMetry' } });
|
||||
expect(onQueryMatchChange).toHaveBeenCalledWith(false);
|
||||
} finally {
|
||||
component.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('shows the OptInExampleFlyout', () => {
|
||||
const onQueryMatchChange = jest.fn();
|
||||
const telemetryService = new TelemetryService({
|
||||
config: {
|
||||
appendServerlessChannelsSuffix: false,
|
||||
|
@ -214,7 +196,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
const component = mountWithIntl(
|
||||
<TelemetryManagementSection
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
showAppliesSettingMessage={false}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
|
@ -235,7 +216,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
});
|
||||
|
||||
it('toggles the OptIn button', async () => {
|
||||
const onQueryMatchChange = jest.fn();
|
||||
const telemetryService = new TelemetryService({
|
||||
config: {
|
||||
appendServerlessChannelsSuffix: false,
|
||||
|
@ -256,7 +236,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
const component = mountWithIntl(
|
||||
<TelemetryManagementSection
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
showAppliesSettingMessage={false}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
|
@ -284,7 +263,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
});
|
||||
|
||||
it('test the wrapper (for coverage purposes)', () => {
|
||||
const onQueryMatchChange = jest.fn();
|
||||
const telemetryService = new TelemetryService({
|
||||
config: {
|
||||
appendServerlessChannelsSuffix: false,
|
||||
|
@ -307,7 +285,6 @@ describe('TelemetryManagementSectionComponent', () => {
|
|||
<TelemetryManagementSection
|
||||
showAppliesSettingMessage={true}
|
||||
telemetryService={telemetryService}
|
||||
onQueryMatchChange={onQueryMatchChange}
|
||||
enableSaving={true}
|
||||
toasts={coreStart.notifications.toasts}
|
||||
docLinks={docLinks}
|
||||
|
|
|
@ -27,28 +27,10 @@ import { OptInExampleFlyout } from './opt_in_example_flyout';
|
|||
|
||||
type TelemetryService = TelemetryPluginSetup['telemetryService'];
|
||||
|
||||
const SEARCH_TERMS: string[] = [
|
||||
'telemetry',
|
||||
'usage data', // Keeping this term for BWC
|
||||
'usage collection',
|
||||
i18n.translate('telemetry.telemetryConstant', {
|
||||
defaultMessage: 'telemetry',
|
||||
}),
|
||||
i18n.translate('telemetry.usageCollectionConstant', {
|
||||
defaultMessage: 'usage collection',
|
||||
}),
|
||||
].flatMap((term) => {
|
||||
// Automatically lower-case and split by space the terms from above
|
||||
const lowerCased = term.toLowerCase();
|
||||
return [lowerCased, ...lowerCased.split(' ')];
|
||||
});
|
||||
|
||||
interface Props {
|
||||
telemetryService: TelemetryService;
|
||||
onQueryMatchChange: (searchTermMatches: boolean) => void;
|
||||
showAppliesSettingMessage: boolean;
|
||||
enableSaving: boolean;
|
||||
query?: { text: string };
|
||||
toasts: ToastsStart;
|
||||
docLinks: DocLinksStart['links'];
|
||||
}
|
||||
|
@ -57,7 +39,6 @@ interface State {
|
|||
processing: boolean;
|
||||
showExample: boolean;
|
||||
showSecurityExample: boolean;
|
||||
queryMatches: boolean | null;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
|
@ -69,47 +50,18 @@ export class TelemetryManagementSection extends Component<Props, State> {
|
|||
processing: false,
|
||||
showExample: false,
|
||||
showSecurityExample: false,
|
||||
queryMatches: props.query ? this.checkQueryMatch(props.query) : null,
|
||||
enabled: this.props.telemetryService.getIsOptedIn() || false,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
const { query } = nextProps;
|
||||
const queryMatches = this.checkQueryMatch(query);
|
||||
|
||||
if (queryMatches !== this.state.queryMatches) {
|
||||
this.setState(
|
||||
{
|
||||
queryMatches,
|
||||
},
|
||||
() => {
|
||||
this.props.onQueryMatchChange(queryMatches);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
checkQueryMatch(query?: { text: string }): boolean {
|
||||
const searchTerm = (query?.text ?? '').toLowerCase();
|
||||
return (
|
||||
this.props.telemetryService.getCanChangeOptInStatus() &&
|
||||
SEARCH_TERMS.some((term) => term.indexOf(searchTerm) >= 0)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { telemetryService } = this.props;
|
||||
const { showExample, queryMatches, enabled, processing } = this.state;
|
||||
const { showExample, enabled, processing } = this.state;
|
||||
|
||||
if (!telemetryService.getCanChangeOptInStatus()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (queryMatches !== null && !queryMatches) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{showExample && (
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { lazy, Suspense } from 'react';
|
|||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import type { TelemetryPluginSetup } from '@kbn/telemetry-plugin/public';
|
||||
import { DocLinksStart } from '@kbn/core/public';
|
||||
import { RegistryComponentProps } from '@kbn/management-settings-section-registry';
|
||||
import type TelemetryManagementSection from './telemetry_management_section';
|
||||
|
||||
export type TelemetryManagementSectionWrapperProps = Omit<
|
||||
|
@ -23,12 +24,16 @@ export function telemetryManagementSectionWrapper(
|
|||
telemetryService: TelemetryPluginSetup['telemetryService'],
|
||||
docLinks: DocLinksStart['links']
|
||||
) {
|
||||
const TelemetryManagementSectionWrapper = (props: TelemetryManagementSectionWrapperProps) => (
|
||||
const TelemetryManagementSectionWrapper = ({
|
||||
enableSaving,
|
||||
...props
|
||||
}: RegistryComponentProps) => (
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<TelemetryManagementSectionComponent
|
||||
showAppliesSettingMessage={true}
|
||||
telemetryService={telemetryService}
|
||||
docLinks={docLinks}
|
||||
enableSaving={enableSaving.global === true}
|
||||
{...props}
|
||||
/>
|
||||
</Suspense>
|
||||
|
|
|
@ -12,10 +12,8 @@ import type { TelemetryPluginSetup } from '@kbn/telemetry-plugin/public';
|
|||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import type { CoreStart, CoreSetup, DocLinksStart } from '@kbn/core/public';
|
||||
|
||||
import {
|
||||
telemetryManagementSectionWrapper,
|
||||
TelemetryManagementSectionWrapperProps,
|
||||
} from './components/telemetry_management_section_wrapper';
|
||||
import { telemetryManagementSectionWrapper } from './components/telemetry_management_section_wrapper';
|
||||
import { SEARCH_TERMS } from '../common';
|
||||
|
||||
export interface TelemetryManagementSectionPluginDepsSetup {
|
||||
telemetry: TelemetryPluginSetup;
|
||||
|
@ -40,20 +38,22 @@ export class TelemetryManagementSectionPlugin {
|
|||
|
||||
const ApplicationUsageTrackingProvider =
|
||||
usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
|
||||
advancedSettings.component.register(
|
||||
advancedSettings.component.componentType.PAGE_FOOTER_COMPONENT,
|
||||
(props) => {
|
||||
return (
|
||||
<ApplicationUsageTrackingProvider>
|
||||
{telemetryManagementSectionWrapper(
|
||||
telemetryService,
|
||||
docLinksLinks
|
||||
)(props as TelemetryManagementSectionWrapperProps)}
|
||||
</ApplicationUsageTrackingProvider>
|
||||
);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const queryMatch = (query: string) => {
|
||||
const searchTerm = query.toLowerCase();
|
||||
return (
|
||||
telemetryService.getCanChangeOptInStatus() &&
|
||||
SEARCH_TERMS.some((term) => term.indexOf(searchTerm) >= 0)
|
||||
);
|
||||
};
|
||||
|
||||
advancedSettings.addGlobalSection((props) => {
|
||||
return (
|
||||
<ApplicationUsageTrackingProvider>
|
||||
{telemetryManagementSectionWrapper(telemetryService, docLinksLinks)(props)}
|
||||
</ApplicationUsageTrackingProvider>
|
||||
);
|
||||
}, queryMatch);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"common/**/*",
|
||||
"public/**/*",
|
||||
"../../../typings/**/*"
|
||||
],
|
||||
|
@ -16,6 +17,7 @@
|
|||
"@kbn/test-jest-helpers",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/i18n",
|
||||
"@kbn/management-settings-section-registry",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -950,6 +950,8 @@
|
|||
"@kbn/management-cards-navigation/*": ["packages/kbn-management/cards_navigation/*"],
|
||||
"@kbn/management-plugin": ["src/plugins/management"],
|
||||
"@kbn/management-plugin/*": ["src/plugins/management/*"],
|
||||
"@kbn/management-settings-section-registry": ["packages/kbn-management/settings/section_registry"],
|
||||
"@kbn/management-settings-section-registry/*": ["packages/kbn-management/settings/section_registry/*"],
|
||||
"@kbn/management-storybook-config": ["packages/kbn-management/storybook/config"],
|
||||
"@kbn/management-storybook-config/*": ["packages/kbn-management/storybook/config/*"],
|
||||
"@kbn/management-test-plugin": ["test/plugin_functional/plugins/management_test_plugin"],
|
||||
|
@ -1562,7 +1564,9 @@
|
|||
"@kbn/yarn-lock-validator/*": ["packages/kbn-yarn-lock-validator/*"],
|
||||
// END AUTOMATED PACKAGE LISTING
|
||||
// Allows for importing from `kibana` package for the exported types.
|
||||
"@emotion/core": ["typings/@emotion"],
|
||||
"@emotion/core": [
|
||||
"typings/@emotion"
|
||||
],
|
||||
},
|
||||
// Support .tsx files and transform JSX into calls to React.createElement
|
||||
"jsx": "react",
|
||||
|
@ -1635,4 +1639,4 @@
|
|||
"@kbn/ambient-storybook-types"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@
|
|||
"licensing"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"advancedSettings",
|
||||
"home",
|
||||
"management",
|
||||
"usageCollection"
|
||||
|
@ -29,4 +28,4 @@
|
|||
"common"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { advancedSettingsMock } from '@kbn/advanced-settings-plugin/public/mocks';
|
||||
|
||||
import { AdvancedSettingsService } from './advanced_settings_service';
|
||||
|
||||
const componentRegistryMock = advancedSettingsMock.createSetupContract();
|
||||
|
||||
describe('Advanced Settings Service', () => {
|
||||
describe('#setup', () => {
|
||||
it('registers space-aware components to augment the advanced settings screen', () => {
|
||||
const deps = {
|
||||
getActiveSpace: jest.fn().mockResolvedValue({ id: 'foo', name: 'foo-space' }),
|
||||
componentRegistry: componentRegistryMock.component,
|
||||
};
|
||||
|
||||
const advancedSettingsService = new AdvancedSettingsService();
|
||||
advancedSettingsService.setup(deps);
|
||||
|
||||
expect(deps.componentRegistry.register).toHaveBeenCalledTimes(2);
|
||||
expect(deps.componentRegistry.register).toHaveBeenCalledWith(
|
||||
componentRegistryMock.component.componentType.PAGE_TITLE_COMPONENT,
|
||||
expect.any(Function),
|
||||
true
|
||||
);
|
||||
|
||||
expect(deps.componentRegistry.register).toHaveBeenCalledWith(
|
||||
componentRegistryMock.component.componentType.PAGE_SUBTITLE_COMPONENT,
|
||||
expect.any(Function),
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import type { AdvancedSettingsSetup } from '@kbn/advanced-settings-plugin/public';
|
||||
|
||||
import { AdvancedSettingsSubtitle, AdvancedSettingsTitle } from './components';
|
||||
import type { Space } from '../../common';
|
||||
|
||||
interface SetupDeps {
|
||||
getActiveSpace: () => Promise<Space>;
|
||||
componentRegistry: AdvancedSettingsSetup['component'];
|
||||
}
|
||||
|
||||
export class AdvancedSettingsService {
|
||||
public setup({ getActiveSpace, componentRegistry }: SetupDeps) {
|
||||
const PageTitle = () => <AdvancedSettingsTitle getActiveSpace={getActiveSpace} />;
|
||||
const SubTitle = () => <AdvancedSettingsSubtitle getActiveSpace={getActiveSpace} />;
|
||||
|
||||
componentRegistry.register(
|
||||
componentRegistry.componentType.PAGE_TITLE_COMPONENT,
|
||||
PageTitle,
|
||||
true
|
||||
);
|
||||
componentRegistry.register(
|
||||
componentRegistry.componentType.PAGE_SUBTITLE_COMPONENT,
|
||||
SubTitle,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { act } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { AdvancedSettingsSubtitle } from './advanced_settings_subtitle';
|
||||
|
||||
describe('AdvancedSettingsSubtitle', () => {
|
||||
it('renders as expected', async () => {
|
||||
const space = {
|
||||
id: 'my-space',
|
||||
name: 'My Space',
|
||||
disabledFeatures: [],
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<AdvancedSettingsSubtitle getActiveSpace={() => Promise.resolve(space)} />
|
||||
);
|
||||
|
||||
// Wait for active space to resolve before requesting the component to update
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(wrapper.find(EuiCallOut)).toHaveLength(1);
|
||||
});
|
||||
});
|
|
@ -1,46 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { Space } from '../../../../common';
|
||||
|
||||
interface Props {
|
||||
getActiveSpace: () => Promise<Space>;
|
||||
}
|
||||
|
||||
export const AdvancedSettingsSubtitle = (props: Props) => {
|
||||
const [activeSpace, setActiveSpace] = useState<Space | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
props.getActiveSpace().then((space) => setActiveSpace(space));
|
||||
}, [props]);
|
||||
|
||||
if (!activeSpace) return null;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiCallOut
|
||||
color="primary"
|
||||
iconType="spacesApp"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription"
|
||||
defaultMessage="The settings on this page apply to the {spaceName} space, unless otherwise specified."
|
||||
values={{
|
||||
spaceName: <strong>{activeSpace.name}</strong>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
|
@ -1,8 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AdvancedSettingsSubtitle } from './advanced_settings_subtitle';
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { act } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { AdvancedSettingsTitle } from './advanced_settings_title';
|
||||
import { SpaceAvatarInternal } from '../../../space_avatar/space_avatar_internal';
|
||||
|
||||
describe('AdvancedSettingsTitle', () => {
|
||||
it('renders without crashing', async () => {
|
||||
const space = {
|
||||
id: 'my-space',
|
||||
name: 'My Space',
|
||||
disabledFeatures: [],
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<AdvancedSettingsTitle getActiveSpace={() => Promise.resolve(space)} />
|
||||
);
|
||||
|
||||
await act(async () => {});
|
||||
|
||||
// wait for SpaceAvatar to lazy-load
|
||||
await act(async () => {});
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find(SpaceAvatarInternal)).toHaveLength(1);
|
||||
});
|
||||
});
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiTitle } from '@elastic/eui';
|
||||
import React, { lazy, Suspense, useEffect, useState } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { Space } from '../../../../common';
|
||||
import { getSpaceAvatarComponent } from '../../../space_avatar';
|
||||
|
||||
// No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana.
|
||||
const LazySpaceAvatar = lazy(() =>
|
||||
getSpaceAvatarComponent().then((component) => ({ default: component }))
|
||||
);
|
||||
|
||||
interface Props {
|
||||
getActiveSpace: () => Promise<Space>;
|
||||
}
|
||||
|
||||
export const AdvancedSettingsTitle = (props: Props) => {
|
||||
const [activeSpace, setActiveSpace] = useState<Space | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
props.getActiveSpace().then((space) => setActiveSpace(space));
|
||||
}, [props]);
|
||||
|
||||
if (!activeSpace) return null;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" responsive={false} alignItems={'center'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<LazySpaceAvatar space={activeSpace} />
|
||||
</Suspense>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ marginLeft: '10px' }}>
|
||||
<EuiTitle size="m">
|
||||
<h1 data-test-subj="managementSettingsTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.advancedSettingsTitle.settingsTitle"
|
||||
defaultMessage="Settings"
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -1,8 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AdvancedSettingsTitle } from './advanced_settings_title';
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AdvancedSettingsSubtitle } from './advanced_settings_subtitle';
|
||||
export { AdvancedSettingsTitle } from './advanced_settings_title';
|
|
@ -1,8 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AdvancedSettingsService } from './advanced_settings_service';
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { advancedSettingsMock } from '@kbn/advanced-settings-plugin/public/mocks';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { homePluginMock } from '@kbn/home-plugin/public/mocks';
|
||||
import {
|
||||
|
@ -63,29 +62,6 @@ describe('Spaces plugin', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should register the advanced settings components if the advanced_settings plugin is available', () => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const advancedSettings = advancedSettingsMock.createSetupContract();
|
||||
|
||||
const plugin = new SpacesPlugin(coreMock.createPluginInitializerContext());
|
||||
plugin.setup(coreSetup, { advancedSettings });
|
||||
|
||||
expect(advancedSettings.component.register.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"advanced_settings_page_title",
|
||||
[Function],
|
||||
true,
|
||||
],
|
||||
Array [
|
||||
"advanced_settings_page_subtitle",
|
||||
[Function],
|
||||
true,
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#start', () => {
|
||||
|
|
|
@ -5,13 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AdvancedSettingsSetup } from '@kbn/advanced-settings-plugin/public';
|
||||
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
|
||||
import type { FeaturesPluginStart } from '@kbn/features-plugin/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
|
||||
|
||||
import { AdvancedSettingsService } from './advanced_settings';
|
||||
import type { ConfigType } from './config';
|
||||
import { createSpacesFeatureCatalogueEntry } from './create_feature_catalogue_entry';
|
||||
import { ManagementService } from './management';
|
||||
|
@ -22,7 +20,6 @@ import type { SpacesApi } from './types';
|
|||
import { getUiApi } from './ui_api';
|
||||
|
||||
export interface PluginsSetup {
|
||||
advancedSettings?: AdvancedSettingsSetup;
|
||||
home?: HomePublicPluginSetup;
|
||||
management?: ManagementSetup;
|
||||
}
|
||||
|
@ -78,14 +75,6 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart
|
|||
});
|
||||
}
|
||||
|
||||
if (plugins.advancedSettings) {
|
||||
const advancedSettingsService = new AdvancedSettingsService();
|
||||
advancedSettingsService.setup({
|
||||
getActiveSpace: () => this.spacesManager.getActiveSpace(),
|
||||
componentRegistry: plugins.advancedSettings.component,
|
||||
});
|
||||
}
|
||||
|
||||
spaceSelectorApp.create({
|
||||
getStartServices: core.getStartServices,
|
||||
application: core.application,
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"@kbn/features-plugin",
|
||||
"@kbn/licensing-plugin",
|
||||
"@kbn/es-ui-shared-plugin",
|
||||
"@kbn/advanced-settings-plugin",
|
||||
"@kbn/home-plugin",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/management-plugin",
|
||||
|
|
|
@ -130,7 +130,6 @@
|
|||
"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.pageTitle": "Paramètres",
|
||||
"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",
|
||||
|
@ -35641,7 +35640,6 @@
|
|||
"xpack.spaces.legacyUrlConflict.calloutBodyText": "Assurez-vous qu'il s'agit du {objectNoun} que vous recherchez. Sinon, consultez l'autre. {documentationLink}",
|
||||
"xpack.spaces.legacyUrlConflict.linkButton": "Accéder à un autre {objectNoun}",
|
||||
"xpack.spaces.legacyURLConflict.toolTipText": "Ce {objectNoun} possède l'ID [id={currentObjectId}]. L'autre {objectNoun} possède l'ID [id={otherObjectId}].",
|
||||
"xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription": "Les paramètres de cette page s'appliquent à l'espace {spaceName}, sauf indication contraire.",
|
||||
"xpack.spaces.management.confirmDeleteModal.confirmButton": "{isLoading, select, true {Suppression de l'espace et de tous les contenus…} other {Supprimer l'espace et tous les contenus}}",
|
||||
"xpack.spaces.management.confirmDeleteModal.description": "Cet espace et {allContents} seront définitivement supprimés.",
|
||||
"xpack.spaces.management.copyToSpace.copyStatusSummary.conflictsMessage": "Conflits détectés dans l'espace {space}. Développez cette section pour les résoudre.",
|
||||
|
@ -35685,7 +35683,6 @@
|
|||
"xpack.spaces.legacyUrlConflict.calloutTitle": "2 objets enregistrés utilisent cette URL",
|
||||
"xpack.spaces.legacyUrlConflict.dismissButton": "Rejeter",
|
||||
"xpack.spaces.legacyUrlConflict.documentationLinkText": "En savoir plus",
|
||||
"xpack.spaces.management.advancedSettingsTitle.settingsTitle": "Paramètres",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton": "Annuler",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.reloadWarningMessage": "Vous avez mis à jour les fonctionnalités visibles dans cet espace. Votre page sera rechargée après l'enregistrement.",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.title": "Confirmer la mise à jour de l'espace",
|
||||
|
|
|
@ -130,7 +130,6 @@
|
|||
"advancedSettings.globalCalloutSubtitle": "変更はすべてのスペースのすべてのユーザーに適用されます。これにはネイティブKibanaユーザーとシングルサインオンユーザーの両方が含まれます。",
|
||||
"advancedSettings.globalCalloutTitle": "変更はすべてのスペースのすべてのユーザー設定に影響します",
|
||||
"advancedSettings.globalSettingsTabTitle": "グローバル設定",
|
||||
"advancedSettings.pageTitle": "設定",
|
||||
"advancedSettings.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません",
|
||||
"advancedSettings.searchBarAriaLabel": "高度な設定を検索",
|
||||
"advancedSettings.spaceSettingsTabTitle": "スペース設定",
|
||||
|
@ -35640,7 +35639,6 @@
|
|||
"xpack.spaces.legacyUrlConflict.calloutBodyText": "これが検索している{objectNoun}であることを確認してください。そうでない場合は、他の項目に移動します。{documentationLink}",
|
||||
"xpack.spaces.legacyUrlConflict.linkButton": "他の{objectNoun}に移動",
|
||||
"xpack.spaces.legacyURLConflict.toolTipText": "この{objectNoun}は[id={currentObjectId}]があります。他のは{objectNoun}[id={otherObjectId}]があります。",
|
||||
"xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription": "このページの設定は、別途指定されていない限り{spaceName}スペースに適用されます。",
|
||||
"xpack.spaces.management.confirmDeleteModal.confirmButton": "{isLoading, select, true {スペースとすべてのコンテンツを削除中...} other {スペースとすべてのコンテンツを削除}}",
|
||||
"xpack.spaces.management.confirmDeleteModal.description": "このスペースと{allContents}は完全に削除されます。",
|
||||
"xpack.spaces.management.copyToSpace.copyStatusSummary.conflictsMessage": "{space}スペースで競合が検出されました。解決するにはこのセクションを拡張してください。",
|
||||
|
@ -35684,7 +35682,6 @@
|
|||
"xpack.spaces.legacyUrlConflict.calloutTitle": "2つの保存されたオブジェクトがこのURLを使用します",
|
||||
"xpack.spaces.legacyUrlConflict.dismissButton": "閉じる",
|
||||
"xpack.spaces.legacyUrlConflict.documentationLinkText": "詳細",
|
||||
"xpack.spaces.management.advancedSettingsTitle.settingsTitle": "設定",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton": "キャンセル",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.reloadWarningMessage": "このスペースで表示される機能を更新しました。保存後にページが更新されます。",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.title": "スペースの更新の確認",
|
||||
|
|
|
@ -130,7 +130,6 @@
|
|||
"advancedSettings.globalCalloutSubtitle": "将对所有工作区的所有用户应用更改。这包括本机 Kibana 用户和单点登录用户。",
|
||||
"advancedSettings.globalCalloutTitle": "更改将影响所有工作区的所有用户设置",
|
||||
"advancedSettings.globalSettingsTabTitle": "常规设置",
|
||||
"advancedSettings.pageTitle": "设置",
|
||||
"advancedSettings.searchBar.unableToParseQueryErrorMessage": "无法解析查询",
|
||||
"advancedSettings.searchBarAriaLabel": "搜索高级设置",
|
||||
"advancedSettings.spaceSettingsTabTitle": "工作区设置",
|
||||
|
@ -35634,7 +35633,6 @@
|
|||
"xpack.spaces.legacyUrlConflict.calloutBodyText": "检查这是否是您正寻找的 {objectNoun}。否则,请前往另一个。{documentationLink}",
|
||||
"xpack.spaces.legacyUrlConflict.linkButton": "前往其他 {objectNoun}",
|
||||
"xpack.spaces.legacyURLConflict.toolTipText": "此 {objectNoun} 具有 [id={currentObjectId}]。其他 {objectNoun} 具有 [id={otherObjectId}]。",
|
||||
"xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription": "除非已指定,否则此页面上的设置适用于 {spaceName} 空间。",
|
||||
"xpack.spaces.management.confirmDeleteModal.confirmButton": "{isLoading, select, true {正在删除工作区和所有内容……} other {删除工作区和所有内容}}",
|
||||
"xpack.spaces.management.confirmDeleteModal.description": "此工作区和{allContents}将被永久删除。",
|
||||
"xpack.spaces.management.copyToSpace.copyStatusSummary.conflictsMessage": "在 {space} 工作区中检测到冲突。展开此部分可进行解决。",
|
||||
|
@ -35678,7 +35676,6 @@
|
|||
"xpack.spaces.legacyUrlConflict.calloutTitle": "2 个已保存对象使用此 URL",
|
||||
"xpack.spaces.legacyUrlConflict.dismissButton": "关闭",
|
||||
"xpack.spaces.legacyUrlConflict.documentationLinkText": "了解详情",
|
||||
"xpack.spaces.management.advancedSettingsTitle.settingsTitle": "设置",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton": "取消",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.reloadWarningMessage": "您已更新此工作区中的可见功能。保存后,您的页面将重新加载。",
|
||||
"xpack.spaces.management.confirmAlterActiveSpaceModal.title": "确认更新工作区",
|
||||
|
|
|
@ -4784,6 +4784,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/management-settings-section-registry@link:packages/kbn-management/settings/section_registry":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/management-storybook-config@link:packages/kbn-management/storybook/config":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue