diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index e9b1b8a2e683..9670fc82f670 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -211,6 +211,32 @@ from *{stack-monitor-app}*. Turns off all unnecessary animations in the {kib} UI. Refresh the page to apply the changes. +[float] +[[kibana-banners-settings]] +==== Banners + +[NOTE] +==== +Banners are a https://www.elastic.co/subscriptions[subscription feature]. +==== + +[horizontal] +[[banners-placement]]`banners:placement`:: +Set to `Top` to display a banner above the Elastic header for this space. Defaults to the value of +the `xpack.banners.placement` configuration property. + +[[banners-textcontent]]`banners:textContent`:: +The text to display inside the banner for this space, either plain text or Markdown. +Defaults to the value of the `xpack.banners.textContent` configuration property. + +[[banners-textcolor]]`banners:textColor`:: +The color for the banner text for this space. Defaults to the value of +the `xpack.banners.textColor` configuration property. + +[[banners-backgroundcolor]]`banners:backgroundColor`:: +The color of the banner background for this space. Defaults to the value of +the `xpack.banners.backgroundColor` configuration property. + [float] [[kibana-dashboard-settings]] ==== Dashboard diff --git a/docs/settings/banners-settings.asciidoc b/docs/settings/banners-settings.asciidoc index 2a68cbe82f9f..ce56d4dbe7a4 100644 --- a/docs/settings/banners-settings.asciidoc +++ b/docs/settings/banners-settings.asciidoc @@ -9,6 +9,11 @@ Banners are disabled by default. You need to manually configure them in order to You can configure the `xpack.banners` settings in your `kibana.yml` file. +[NOTE] +==== +Banners are a https://www.elastic.co/subscriptions[subscription feature]. +==== + [[general-banners-settings-kb]] ==== General banner settings @@ -16,7 +21,7 @@ You can configure the `xpack.banners` settings in your `kibana.yml` file. |=== | `xpack.banners.placement` -| Set to `header` to enable the header banner. Defaults to `disabled`. +| Set to `top` to display a banner above the Elastic header. Defaults to `disabled`. | `xpack.banners.textContent` | The text to display inside the banner, either plain text or Markdown. @@ -27,9 +32,7 @@ You can configure the `xpack.banners` settings in your `kibana.yml` file. | `xpack.banners.backgroundColor` | The color of the banner background. Defaults to `#FFF9E8`. -|=== +| `xpack.banners.disableSpaceBanners` +| If true, per-space banner overrides will be disabled. Defaults to `false`. -[NOTE] -==== -The `banners` plugin is a https://www.elastic.co/subscriptions[subscription feature] -==== \ No newline at end of file +|=== diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index cd0ce7005cc4..6f1b9dc5bf82 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -52,6 +52,8 @@ export { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_serv export { i18nServiceMock } from './i18n/i18n_service.mock'; export { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; +type MockedPluginInitializerConfig = jest.Mocked['config']>; + export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { kibana: { @@ -70,7 +72,7 @@ export function pluginInitializerContextConfigMock(config: T) { }, }; - const mock: jest.Mocked['config']> = { + const mock: MockedPluginInitializerConfig = { legacy: { globalConfig$: of(globalConfig), get: () => globalConfig, @@ -82,8 +84,12 @@ export function pluginInitializerContextConfigMock(config: T) { return mock; } +type PluginInitializerContextMock = Omit, 'config'> & { + config: MockedPluginInitializerConfig; +}; + function pluginInitializerContextMock(config: T = {} as T) { - const mock: PluginInitializerContext = { + const mock: PluginInitializerContextMock = { opaqueId: Symbol(), logger: loggingSystemMock.create(), env: { diff --git a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap index 517a6238c251..be5163e89367 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap +++ b/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap @@ -1791,6 +1791,7 @@ exports[`Field for json setting should render as read only if saving is disabled maxLines={30} minLines={6} mode="json" + name="advancedSetting-editField-json:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -1897,6 +1898,7 @@ exports[`Field for json setting should render as read only with help text if ove maxLines={30} minLines={6} mode="json" + name="advancedSetting-editField-json:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -1979,6 +1981,7 @@ exports[`Field for json setting should render custom setting icon if it is custo maxLines={30} minLines={6} mode="json" + name="advancedSetting-editField-json:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2092,6 +2095,7 @@ exports[`Field for json setting should render default value if there is no user maxLines={30} minLines={6} mode="json" + name="advancedSetting-editField-json:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2181,6 +2185,7 @@ exports[`Field for json setting should render unsaved value if there are unsaved maxLines={30} minLines={6} mode="json" + name="advancedSetting-editField-json:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2305,6 +2310,7 @@ exports[`Field for json setting should render user value if there is user value maxLines={30} minLines={6} mode="json" + name="advancedSetting-editField-json:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2376,6 +2382,7 @@ exports[`Field for markdown setting should render as read only if saving is disa maxLines={30} minLines={6} mode="markdown" + name="advancedSetting-editField-markdown:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2479,6 +2486,7 @@ exports[`Field for markdown setting should render as read only with help text if maxLines={30} minLines={6} mode="markdown" + name="advancedSetting-editField-markdown:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2561,6 +2569,7 @@ exports[`Field for markdown setting should render custom setting icon if it is c maxLines={30} minLines={6} mode="markdown" + name="advancedSetting-editField-markdown:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2632,6 +2641,7 @@ exports[`Field for markdown setting should render default value if there is no u maxLines={30} minLines={6} mode="markdown" + name="advancedSetting-editField-markdown:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2721,6 +2731,7 @@ exports[`Field for markdown setting should render unsaved value if there are uns maxLines={30} minLines={6} mode="markdown" + name="advancedSetting-editField-markdown:test:setting-editor" onChange={[Function]} setOptions={ Object { @@ -2838,6 +2849,7 @@ exports[`Field for markdown setting should render user value if there is user va maxLines={30} minLines={6} mode="markdown" + name="advancedSetting-editField-markdown:test:setting-editor" onChange={[Function]} setOptions={ Object { diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.tsx index f5db5c3e371b..d4a5020bbbb8 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/field/field.tsx @@ -326,6 +326,7 @@ export class Field extends PureComponent {
= { type: 'keyword', _meta: { description: 'Default value of the setting was changed.' }, }, + 'banners:textContent': { + type: 'keyword', + _meta: { description: 'Default value of the setting was changed.' }, + }, // non-sensitive 'visualize:enableLabs': { type: 'boolean', @@ -412,6 +416,18 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'banners:placement': { + type: 'keyword', + _meta: { description: 'Non-default value of setting.' }, + }, + 'banners:textColor': { + type: 'text', + _meta: { description: 'Non-default value of setting.' }, + }, + 'banners:backgroundColor': { + type: 'text', + _meta: { description: 'Non-default value of setting.' }, + }, 'observability:enableAlertingExperience': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 2ae3882ac3bd..a77e4ce87417 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -19,6 +19,7 @@ export interface UsageStats { 'xpackDashboardMode:roles': string; 'securitySolution:ipReputationLinks': string; 'xPack:defaultAdminEmail': string; + 'banners:textContent': string; /** * non-sensitive settings */ @@ -115,4 +116,7 @@ export interface UsageStats { 'csv:quoteValues': boolean; 'dateFormat:dow': string; dateFormat: string; + 'banners:placement': string; + 'banners:textColor': string; + 'banners:backgroundColor': string; } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index d7d1d096c31d..f31e86719415 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -7484,6 +7484,12 @@ "description": "Default value of the setting was changed." } }, + "banners:textContent": { + "type": "keyword", + "_meta": { + "description": "Default value of the setting was changed." + } + }, "visualize:enableLabs": { "type": "boolean", "_meta": { @@ -8033,6 +8039,24 @@ "description": "Non-default value of setting." } }, + "banners:placement": { + "type": "keyword", + "_meta": { + "description": "Non-default value of setting." + } + }, + "banners:textColor": { + "type": "text", + "_meta": { + "description": "Non-default value of setting." + } + }, + "banners:backgroundColor": { + "type": "text", + "_meta": { + "description": "Non-default value of setting." + } + }, "observability:enableAlertingExperience": { "type": "boolean", "_meta": { diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 14bd002ec948..699165a51ca8 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -28,6 +28,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider async clickLinkText(text: string) { await find.clickByDisplayedLinkText(text); } + async clickKibanaSettings() { await testSubjects.click('settings'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -89,6 +90,22 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider await PageObjects.header.waitUntilLoadingHasFinished(); } + async setAdvancedSettingsTextArea(propertyName: string, propertyValue: string) { + const wrapper = await testSubjects.find(`advancedSetting-editField-${propertyName}`); + const textarea = await wrapper.findByTagName('textarea'); + await textarea.focus(); + // only way to properly replace the value of the ace editor is via the JS api + await browser.execute( + (editor: string, value: string) => { + return (window as any).ace.edit(editor).setValue(value); + }, + `advancedSetting-editField-${propertyName}-editor`, + propertyValue + ); + await testSubjects.click(`advancedSetting-saveButton`); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + async toggleAdvancedSettingCheckbox(propertyName: string) { await testSubjects.click(`advancedSetting-editField-${propertyName}`); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -162,6 +179,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider async sortBy(columnName: string) { const chartTypes = await find.allByCssSelector('table.euiTable thead tr th button'); + async function getChartType(chart: Record) { const chartString = await chart.getVisibleText(); if (chartString === columnName) { @@ -169,6 +187,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider await PageObjects.header.waitUntilLoadingHasFinished(); } } + const getChartTypesPromises = chartTypes.map(getChartType); return Promise.all(getChartTypesPromises); } diff --git a/x-pack/plugins/banners/README.md b/x-pack/plugins/banners/README.md index 890c194e1bcb..6ef2d9196f37 100644 --- a/x-pack/plugins/banners/README.md +++ b/x-pack/plugins/banners/README.md @@ -12,7 +12,7 @@ The options are The placement of the banner. The allowed values are: - `disabled` - The banner will be disabled - - `header` - The banner will be displayed in the header + - `top` - The banner will be displayed in the header - `textContent` @@ -31,7 +31,7 @@ The color for the banner's background. Must be a valid hex color `kibana.yml` ```yaml xpack.banners: - placement: 'header' + placement: 'top' textContent: 'Production environment - Proceed with **special levels** of caution' textColor: '#FF0000' backgroundColor: '#CC2211' diff --git a/x-pack/plugins/banners/common/types.ts b/x-pack/plugins/banners/common/types.ts index 0c785f516ddb..6c16b4e8055b 100644 --- a/x-pack/plugins/banners/common/types.ts +++ b/x-pack/plugins/banners/common/types.ts @@ -10,7 +10,7 @@ export interface BannerInfoResponse { banner: BannerConfiguration; } -export type BannerPlacement = 'disabled' | 'header'; +export type BannerPlacement = 'disabled' | 'top'; export interface BannerConfiguration { placement: BannerPlacement; diff --git a/x-pack/plugins/banners/public/components/banner.tsx b/x-pack/plugins/banners/public/components/banner.tsx index ea30e46881d0..ae2898629765 100644 --- a/x-pack/plugins/banners/public/components/banner.tsx +++ b/x-pack/plugins/banners/public/components/banner.tsx @@ -26,7 +26,7 @@ export const Banner: FC = ({ bannerConfig }) => { }} >
- +
); diff --git a/x-pack/plugins/banners/public/plugin.test.tsx b/x-pack/plugins/banners/public/plugin.test.tsx index 036ad17e2598..8722d9516b9d 100644 --- a/x-pack/plugins/banners/public/plugin.test.tsx +++ b/x-pack/plugins/banners/public/plugin.test.tsx @@ -7,11 +7,19 @@ import { getBannerInfoMock } from './plugin.test.mocks'; import { coreMock } from '../../../../src/core/public/mocks'; +import { BannerConfiguration } from '../common/types'; import { BannersPlugin } from './plugin'; -import { BannerClientConfig } from './types'; const nextTick = async () => await new Promise((resolve) => resolve()); +const createBannerConfig = (parts: Partial = {}): BannerConfiguration => ({ + placement: 'disabled', + textContent: 'foo', + textColor: '#FFFFFF', + backgroundColor: '#000000', + ...parts, +}); + describe('BannersPlugin', () => { let plugin: BannersPlugin; let pluginInitContext: ReturnType; @@ -25,11 +33,12 @@ describe('BannersPlugin', () => { getBannerInfoMock.mockResolvedValue({ allowed: false, + banner: createBannerConfig(), }); }); - const startPlugin = async (config: BannerClientConfig) => { - pluginInitContext = coreMock.createPluginInitializerContext(config); + const startPlugin = async () => { + pluginInitContext = coreMock.createPluginInitializerContext(); plugin = new BannersPlugin(pluginInitContext); plugin.setup(coreSetup); plugin.start(coreStart); @@ -41,46 +50,62 @@ describe('BannersPlugin', () => { getBannerInfoMock.mockReset(); }); - it('calls `getBannerInfo` if `config.placement !== disabled`', async () => { - await startPlugin({ - placement: 'header', + describe('when banner is allowed', () => { + it('registers the header banner if `banner.placement` is `top`', async () => { + getBannerInfoMock.mockResolvedValue({ + allowed: true, + banner: createBannerConfig({ + placement: 'top', + }), + }); + + await startPlugin(); + + expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledTimes(1); + expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledWith({ + content: expect.any(Function), + }); }); - expect(getBannerInfoMock).toHaveBeenCalledTimes(1); - }); + it('does not register the header banner if `banner.placement` is `disabled`', async () => { + getBannerInfoMock.mockResolvedValue({ + allowed: true, + banner: createBannerConfig({ + placement: 'disabled', + }), + }); - it('does not call `getBannerInfo` if `config.placement === disabled`', async () => { - await startPlugin({ - placement: 'disabled', - }); + await startPlugin(); - expect(getBannerInfoMock).not.toHaveBeenCalled(); - }); - - it('registers the header banner if `getBannerInfo` return `allowed=true`', async () => { - getBannerInfoMock.mockResolvedValue({ - allowed: true, - }); - - await startPlugin({ - placement: 'header', - }); - - expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledTimes(1); - expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledWith({ - content: expect.any(Function), + expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledTimes(0); }); }); - it('does not register the header banner if `getBannerInfo` return `allowed=false`', async () => { - getBannerInfoMock.mockResolvedValue({ - allowed: false, + describe('when banner is not allowed', () => { + it('does not register the header banner if `banner.placement` is `top`', async () => { + getBannerInfoMock.mockResolvedValue({ + allowed: false, + banner: createBannerConfig({ + placement: 'top', + }), + }); + + await startPlugin(); + + expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledTimes(0); }); - await startPlugin({ - placement: 'header', - }); + it('does not register the header banner if `banner.placement` is `disabled`', async () => { + getBannerInfoMock.mockResolvedValue({ + allowed: false, + banner: createBannerConfig({ + placement: 'disabled', + }), + }); - expect(coreStart.chrome.setHeaderBanner).not.toHaveBeenCalled(); + await startPlugin(); + + expect(coreStart.chrome.setHeaderBanner).toHaveBeenCalledTimes(0); + }); }); }); diff --git a/x-pack/plugins/banners/public/plugin.tsx b/x-pack/plugins/banners/public/plugin.tsx index dca99a816a25..014d2de58b9e 100644 --- a/x-pack/plugins/banners/public/plugin.tsx +++ b/x-pack/plugins/banners/public/plugin.tsx @@ -9,35 +9,28 @@ import React from 'react'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; import { Banner } from './components'; -import { BannerClientConfig } from './types'; import { getBannerInfo } from './get_banner_info'; export class BannersPlugin implements Plugin<{}, {}, {}, {}> { - private readonly config: BannerClientConfig; - - constructor(context: PluginInitializerContext) { - this.config = context.config.get(); - } + constructor(context: PluginInitializerContext) {} setup({}: CoreSetup<{}, {}>) { return {}; } start({ chrome, uiSettings, http }: CoreStart) { - if (this.config.placement !== 'disabled') { - getBannerInfo(http).then( - ({ allowed, banner }) => { - if (allowed) { - chrome.setHeaderBanner({ - content: toMountPoint(), - }); - } - }, - () => { - chrome.setHeaderBanner(undefined); + getBannerInfo(http).then( + ({ allowed, banner }) => { + if (allowed && banner.placement === 'top') { + chrome.setHeaderBanner({ + content: toMountPoint(), + }); } - ); - } + }, + () => { + chrome.setHeaderBanner(undefined); + } + ); return {}; } diff --git a/x-pack/plugins/banners/server/config.ts b/x-pack/plugins/banners/server/config.ts index 9a8cc9680c29..ec1c7006a84c 100644 --- a/x-pack/plugins/banners/server/config.ts +++ b/x-pack/plugins/banners/server/config.ts @@ -5,12 +5,13 @@ * 2.0. */ +import { get } from 'lodash'; import { schema, TypeOf } from '@kbn/config-schema'; import { PluginConfigDescriptor } from 'kibana/server'; import { isHexColor } from './utils'; const configSchema = schema.object({ - placement: schema.oneOf([schema.literal('disabled'), schema.literal('header')], { + placement: schema.oneOf([schema.literal('disabled'), schema.literal('top')], { defaultValue: 'disabled', }), textContent: schema.string({ defaultValue: '' }), @@ -30,13 +31,25 @@ const configSchema = schema.object({ }, defaultValue: '#FFF9E8', }), + disableSpaceBanners: schema.boolean({ defaultValue: false }), }); export type BannersConfigType = TypeOf; export const config: PluginConfigDescriptor = { schema: configSchema, - exposeToBrowser: { - placement: true, - }, + exposeToBrowser: {}, + deprecations: () => [ + (rootConfig, fromPath, addDeprecation) => { + const pluginConfig = get(rootConfig, fromPath); + if (pluginConfig?.placement === 'header') { + addDeprecation({ + message: 'The `header` value for xpack.banners.placement has been replaced by `top`', + }); + pluginConfig.placement = 'top'; + } + + return rootConfig; + }, + ], }; diff --git a/x-pack/plugins/banners/public/types.ts b/x-pack/plugins/banners/server/plugin.test.mocks.ts similarity index 50% rename from x-pack/plugins/banners/public/types.ts rename to x-pack/plugins/banners/server/plugin.test.mocks.ts index 1f0ce524a785..316699c2c202 100644 --- a/x-pack/plugins/banners/public/types.ts +++ b/x-pack/plugins/banners/server/plugin.test.mocks.ts @@ -5,8 +5,12 @@ * 2.0. */ -import { BannerPlacement } from '../common'; +export const registerRoutesMock = jest.fn(); +jest.doMock('./routes', () => ({ + registerRoutes: registerRoutesMock, +})); -export interface BannerClientConfig { - placement: BannerPlacement; -} +export const registerSettingsMock = jest.fn(); +jest.doMock('./ui_settings', () => ({ + registerSettings: registerSettingsMock, +})); diff --git a/x-pack/plugins/banners/server/plugin.test.ts b/x-pack/plugins/banners/server/plugin.test.ts new file mode 100644 index 000000000000..b3f8c7be696c --- /dev/null +++ b/x-pack/plugins/banners/server/plugin.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { registerRoutesMock, registerSettingsMock } from './plugin.test.mocks'; + +import { coreMock } from '../../../../src/core/server/mocks'; +import { BannersPlugin } from './plugin'; +import { BannersConfigType } from './config'; + +describe('BannersPlugin', () => { + let plugin: BannersPlugin; + let pluginInitContext: ReturnType; + let coreSetup: ReturnType; + let bannerConfig: BannersConfigType; + + beforeEach(() => { + bannerConfig = { + placement: 'top', + textContent: 'foo', + backgroundColor: '#000000', + textColor: '#FFFFFF', + disableSpaceBanners: false, + }; + pluginInitContext = coreMock.createPluginInitializerContext(); + pluginInitContext.config.get.mockReturnValue(bannerConfig); + coreSetup = coreMock.createSetup(); + + plugin = new BannersPlugin(pluginInitContext); + }); + + afterEach(() => { + registerRoutesMock.mockReset(); + registerSettingsMock.mockReset(); + }); + + describe('#setup', () => { + it('calls `registerRoutes` with the correct parameters', () => { + plugin.setup(coreSetup); + + expect(registerRoutesMock).toHaveBeenCalledTimes(1); + expect(registerRoutesMock).toHaveBeenCalledWith(expect.any(Object), bannerConfig); + }); + it('calls `registerSettings` with the correct parameters', () => { + plugin.setup(coreSetup); + + expect(registerSettingsMock).toHaveBeenCalledTimes(1); + expect(registerSettingsMock).toHaveBeenCalledWith(coreSetup.uiSettings, bannerConfig); + }); + }); +}); diff --git a/x-pack/plugins/banners/server/plugin.ts b/x-pack/plugins/banners/server/plugin.ts index 66cd08318997..852ba135c478 100644 --- a/x-pack/plugins/banners/server/plugin.ts +++ b/x-pack/plugins/banners/server/plugin.ts @@ -6,21 +6,22 @@ */ import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/server'; -import { BannerConfiguration } from '../common'; import { BannersConfigType } from './config'; import { BannersRequestHandlerContext } from './types'; import { registerRoutes } from './routes'; +import { registerSettings } from './ui_settings'; export class BannersPlugin implements Plugin<{}, {}, {}, {}> { - private readonly config: BannerConfiguration; + private readonly config: BannersConfigType; constructor(context: PluginInitializerContext) { - this.config = convertConfig(context.config.get()); + this.config = context.config.get(); } setup({ uiSettings, getStartServices, http }: CoreSetup<{}, {}>) { const router = http.createRouter(); registerRoutes(router, this.config); + registerSettings(uiSettings, this.config); return {}; } @@ -29,5 +30,3 @@ export class BannersPlugin implements Plugin<{}, {}, {}, {}> { return {}; } } - -const convertConfig = (raw: BannersConfigType): BannerConfiguration => raw; diff --git a/x-pack/plugins/banners/server/routes/index.ts b/x-pack/plugins/banners/server/routes/index.ts index a4eedc3234c8..347236b4df4e 100644 --- a/x-pack/plugins/banners/server/routes/index.ts +++ b/x-pack/plugins/banners/server/routes/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { BannerConfiguration } from '../../common'; +import { BannersConfigType } from '../config'; import { BannersRouter } from '../types'; import { registerInfoRoute } from './info'; -export const registerRoutes = (router: BannersRouter, config: BannerConfiguration) => { +export const registerRoutes = (router: BannersRouter, config: BannersConfigType) => { registerInfoRoute(router, config); }; diff --git a/x-pack/plugins/banners/server/routes/info.ts b/x-pack/plugins/banners/server/routes/info.ts index e0db842028c3..806c4a7ae6b3 100644 --- a/x-pack/plugins/banners/server/routes/info.ts +++ b/x-pack/plugins/banners/server/routes/info.ts @@ -5,26 +5,33 @@ * 2.0. */ +import { IUiSettingsClient } from 'kibana/server'; import { ILicense } from '../../../licensing/server'; -import { BannerInfoResponse, BannerConfiguration } from '../../common'; +import { BannersConfigType } from '../config'; +import { BannerInfoResponse, BannerConfiguration, BannerPlacement } from '../../common'; import { BannersRouter } from '../types'; -export const registerInfoRoute = (router: BannersRouter, config: BannerConfiguration) => { +export const registerInfoRoute = (router: BannersRouter, config: BannersConfigType) => { router.get( { path: '/api/banners/info', validate: false, options: { - authRequired: false, + authRequired: 'optional', }, }, - (ctx, req, res) => { + async (ctx, req, res) => { const allowed = isValidLicense(ctx.licensing.license); + const bannerConfig = + req.auth.isAuthenticated && config.disableSpaceBanners === false + ? await getBannerConfig(ctx.core.uiSettings.client) + : config; + return res.ok({ body: { allowed, - banner: config, + banner: bannerConfig, } as BannerInfoResponse, }); } @@ -34,3 +41,19 @@ export const registerInfoRoute = (router: BannersRouter, config: BannerConfigura const isValidLicense = (license: ILicense): boolean => { return license.hasAtLeast('gold'); }; + +const getBannerConfig = async (client: IUiSettingsClient): Promise => { + const [placement, textContent, textColor, backgroundColor] = await Promise.all([ + client.get('banners:placement'), + client.get('banners:textContent'), + client.get('banners:textColor'), + client.get('banners:backgroundColor'), + ]); + + return { + placement, + textContent, + textColor, + backgroundColor, + }; +}; diff --git a/x-pack/plugins/banners/server/ui_settings.test.ts b/x-pack/plugins/banners/server/ui_settings.test.ts new file mode 100644 index 000000000000..9fae01977433 --- /dev/null +++ b/x-pack/plugins/banners/server/ui_settings.test.ts @@ -0,0 +1,72 @@ +/* + * 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 { uiSettingsServiceMock } from '../../../../src/core/server/mocks'; +import { BannersConfigType } from './config'; +import { registerSettings } from './ui_settings'; + +const createConfig = (parts: Partial = {}): BannersConfigType => ({ + placement: 'disabled', + backgroundColor: '#0000', + textColor: '#FFFFFF', + textContent: 'Hello from the banner', + disableSpaceBanners: false, + ...parts, +}); + +describe('registerSettings', () => { + let uiSettings: ReturnType; + + beforeEach(() => { + uiSettings = uiSettingsServiceMock.createSetupContract(); + }); + + it('registers the settings', () => { + registerSettings(uiSettings, createConfig()); + + expect(uiSettings.register).toHaveBeenCalledTimes(1); + expect(uiSettings.register).toHaveBeenCalledWith({ + 'banners:placement': expect.any(Object), + 'banners:textContent': expect.any(Object), + 'banners:textColor': expect.any(Object), + 'banners:backgroundColor': expect.any(Object), + }); + }); + + it('does not register the settings if `config.disableSpaceBanners` is `true`', () => { + registerSettings(uiSettings, createConfig({ disableSpaceBanners: true })); + + expect(uiSettings.register).not.toHaveBeenCalled(); + }); + + it('uses the configuration values as defaults', () => { + const config = createConfig({ + placement: 'top', + backgroundColor: '#FF00CC', + textColor: '#AAFFEE', + textContent: 'Some text', + }); + + registerSettings(uiSettings, config); + + expect(uiSettings.register).toHaveBeenCalledTimes(1); + expect(uiSettings.register).toHaveBeenCalledWith({ + 'banners:placement': expect.objectContaining({ + value: config.placement, + }), + 'banners:textContent': expect.objectContaining({ + value: config.textContent, + }), + 'banners:textColor': expect.objectContaining({ + value: config.textColor, + }), + 'banners:backgroundColor': expect.objectContaining({ + value: config.backgroundColor, + }), + }); + }); +}); diff --git a/x-pack/plugins/banners/server/ui_settings.ts b/x-pack/plugins/banners/server/ui_settings.ts new file mode 100644 index 000000000000..d35ab76c41a5 --- /dev/null +++ b/x-pack/plugins/banners/server/ui_settings.ts @@ -0,0 +1,120 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { UiSettingsServiceSetup } from 'src/core/server'; +import { BannersConfigType } from './config'; +import { isHexColor } from './utils'; + +export const registerSettings = (uiSettings: UiSettingsServiceSetup, config: BannersConfigType) => { + if (config.disableSpaceBanners) { + return; + } + + const subscriptionLink = ` + + ${i18n.translate('xpack.banners.settings.subscriptionRequiredLink.text', { + defaultMessage: 'Subscription required.', + })} + + `; + + uiSettings.register({ + 'banners:placement': { + name: i18n.translate('xpack.banners.settings.placement.title', { + defaultMessage: 'Banner placement', + }), + description: i18n.translate('xpack.banners.settings.placement.description', { + defaultMessage: + 'Display a top banner for this space, above the Elastic header. {subscriptionLink}', + values: { + subscriptionLink, + }, + }), + category: ['banner'], + order: 1, + type: 'select', + value: config.placement, + options: ['disabled', 'top'], + optionLabels: { + disabled: i18n.translate('xpack.banners.settings.placement.disabled', { + defaultMessage: 'Disabled', + }), + top: i18n.translate('xpack.banners.settings.placement.top', { + defaultMessage: 'Top', + }), + }, + requiresPageReload: true, + schema: schema.oneOf([schema.literal('disabled'), schema.literal('top')]), + }, + 'banners:textContent': { + name: i18n.translate('xpack.banners.settings.textContent.title', { + defaultMessage: 'Banner text', + }), + description: i18n.translate('xpack.banners.settings.text.description', { + defaultMessage: 'Add Markdown-formatted text to the banner. {subscriptionLink}', + values: { + subscriptionLink, + }, + }), + sensitive: true, + category: ['banner'], + order: 2, + type: 'markdown', + value: config.textContent, + requiresPageReload: true, + schema: schema.string(), + }, + 'banners:textColor': { + name: i18n.translate('xpack.banners.settings.textColor.title', { + defaultMessage: 'Banner text color', + }), + description: i18n.translate('xpack.banners.settings.textColor.description', { + defaultMessage: 'Set the color of the banner text. {subscriptionLink}', + values: { + subscriptionLink, + }, + }), + category: ['banner'], + order: 3, + type: 'color', + value: config.textColor, + requiresPageReload: true, + schema: schema.string({ + validate: (color) => { + if (!isHexColor(color)) { + return `'banners:textColor' must be an hex color`; + } + }, + }), + }, + 'banners:backgroundColor': { + name: i18n.translate('xpack.banners.settings.backgroundColor.title', { + defaultMessage: 'Banner background color', + }), + description: i18n.translate('xpack.banners.settings.backgroundColor.description', { + defaultMessage: 'Set the background color for the banner. {subscriptionLink}', + values: { + subscriptionLink, + }, + }), + category: ['banner'], + order: 4, + type: 'color', + value: config.backgroundColor, + requiresPageReload: true, + schema: schema.string({ + validate: (color) => { + if (!isHexColor(color)) { + return `'banners:backgroundColor' must be an hex color`; + } + }, + }), + }, + }); +}; diff --git a/x-pack/test/banners_functional/config.ts b/x-pack/test/banners_functional/config.ts new file mode 100644 index 000000000000..21cce31ca5d8 --- /dev/null +++ b/x-pack/test/banners_functional/config.ts @@ -0,0 +1,45 @@ +/* + * 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 path from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services, pageObjects } from './ftr_provider_context'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + return { + testFiles: [require.resolve('./tests')], + servers: { + ...kibanaFunctionalConfig.get('servers'), + }, + services, + pageObjects, + + junit: { + reportName: 'X-Pack Banners Functional Tests', + }, + + esTestCluster: kibanaFunctionalConfig.get('esTestCluster'), + apps: { + ...kibanaFunctionalConfig.get('apps'), + }, + + esArchiver: { + directory: path.resolve(__dirname, '..', 'functional', 'es_archives'), + }, + + kbnTestServer: { + ...kibanaFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'), + '--xpack.banners.placement=header', + '--xpack.banners.textContent="global banner text"', + ], + }, + }; +} diff --git a/x-pack/test/banners_functional/ftr_provider_context.ts b/x-pack/test/banners_functional/ftr_provider_context.ts new file mode 100644 index 000000000000..faac2954b00f --- /dev/null +++ b/x-pack/test/banners_functional/ftr_provider_context.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { services } from '../functional/services'; +import { pageObjects } from '../functional/page_objects'; + +export type FtrProviderContext = GenericFtrProviderContext; +export { services, pageObjects }; diff --git a/x-pack/test/banners_functional/tests/global.ts b/x-pack/test/banners_functional/tests/global.ts new file mode 100644 index 000000000000..cef404d7ed13 --- /dev/null +++ b/x-pack/test/banners_functional/tests/global.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'security', 'banners']); + + describe('global pages', () => { + it('displays the global banner on the login page', async () => { + await PageObjects.common.navigateToApp('login'); + + expect(await PageObjects.banners.isTopBannerVisible()).to.eql(true); + expect(await PageObjects.banners.getTopBannerText()).to.eql('global banner text'); + }); + }); +} diff --git a/x-pack/test/banners_functional/tests/index.ts b/x-pack/test/banners_functional/tests/index.ts new file mode 100644 index 000000000000..301c872c746e --- /dev/null +++ b/x-pack/test/banners_functional/tests/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('banners - functional tests', function () { + this.tags('ciGroup2'); + + loadTestFile(require.resolve('./global')); + loadTestFile(require.resolve('./spaces')); + }); +} diff --git a/x-pack/test/banners_functional/tests/spaces.ts b/x-pack/test/banners_functional/tests/spaces.ts new file mode 100644 index 000000000000..f8c412c0df0e --- /dev/null +++ b/x-pack/test/banners_functional/tests/spaces.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects([ + 'common', + 'security', + 'banners', + 'settings', + 'spaceSelector', + ]); + + describe('per-spaces banners', () => { + before(async () => { + await esArchiver.load('banners/multispace'); + }); + + after(async () => { + await esArchiver.unload('banners/multispace'); + }); + + before(async () => { + await PageObjects.security.login(undefined, undefined, { + expectSpaceSelector: true, + }); + await PageObjects.spaceSelector.clickSpaceCard('default'); + + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSettings(); + + await PageObjects.settings.setAdvancedSettingsTextArea( + 'banners:textContent', + 'default space banner text' + ); + }); + + it('displays the space-specific banner within the space', async () => { + await PageObjects.common.navigateToApp('home'); + + expect(await PageObjects.banners.isTopBannerVisible()).to.eql(true); + expect(await PageObjects.banners.getTopBannerText()).to.eql('default space banner text'); + }); + + it('displays the global banner within another space', async () => { + await PageObjects.common.navigateToApp('home', { basePath: '/s/another-space' }); + + expect(await PageObjects.banners.isTopBannerVisible()).to.eql(true); + expect(await PageObjects.banners.getTopBannerText()).to.eql('global banner text'); + }); + + it('displays the global banner on the login page', async () => { + await PageObjects.security.forceLogout(); + await PageObjects.common.navigateToApp('login'); + + expect(await PageObjects.banners.isTopBannerVisible()).to.eql(true); + expect(await PageObjects.banners.getTopBannerText()).to.eql('global banner text'); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/banners/multispace/data.json b/x-pack/test/functional/es_archives/banners/multispace/data.json new file mode 100644 index 000000000000..fc0e0dc7b7ee --- /dev/null +++ b/x-pack/test/functional/es_archives/banners/multispace/data.json @@ -0,0 +1,62 @@ +{ + "type": "doc", + "value": { + "id": "config:6.0.0", + "index": ".kibana", + "source": { + "config": { + "buildNum": 8467, + "dateFormat:tz": "UTC", + "defaultRoute": "http://example.com/evil" + }, + "type": "config" + } + } +} + +{ + "type": "doc", + "value": { + "id": "another-space:config:6.0.0", + "index": ".kibana", + "source": { + "namespace": "another-space", + "config": { + "buildNum": 8467, + "dateFormat:tz": "UTC", + "defaultRoute": "/app/canvas" + }, + "type": "config" + } + } +} + +{ + "type": "doc", + "value": { + "id": "space:default", + "index": ".kibana", + "source": { + "space": { + "description": "This is the default space!", + "name": "Default" + }, + "type": "space" + } + } +} + +{ + "type": "doc", + "value": { + "id": "space:another-space", + "index": ".kibana", + "source": { + "space": { + "description": "This is another space", + "name": "Another Space" + }, + "type": "space" + } + } +} diff --git a/x-pack/test/functional/es_archives/banners/multispace/mappings.json b/x-pack/test/functional/es_archives/banners/multispace/mappings.json new file mode 100644 index 000000000000..f3793c7ca678 --- /dev/null +++ b/x-pack/test/functional/es_archives/banners/multispace/mappings.json @@ -0,0 +1,287 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "mappings": { + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "defaultRoute": { + "type": "keyword" + } + } + }, + "dashboard": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "strict", + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "dynamic": "strict", + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "spaceId": { + "type": "keyword" + }, + "timelion-sheet": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "url": { + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/page_objects/banners_page.ts b/x-pack/test/functional/page_objects/banners_page.ts new file mode 100644 index 000000000000..d2e4e43cec11 --- /dev/null +++ b/x-pack/test/functional/page_objects/banners_page.ts @@ -0,0 +1,30 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export function BannersPageProvider({ getService }: FtrProviderContext) { + const find = getService('find'); + + class BannersPage { + isTopBannerVisible() { + return find.existsByCssSelector('.header__topBanner .kbnUserBanner__container'); + } + + async getTopBannerText() { + if (!(await this.isTopBannerVisible())) { + return ''; + } + const bannerContainer = await find.byCssSelector( + '.header__topBanner .kbnUserBanner__container' + ); + return bannerContainer.getVisibleText(); + } + } + + return new BannersPage(); +} diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index 66ad6542e518..0e66615371a4 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -42,6 +42,7 @@ import { TagManagementPageProvider } from './tag_management_page'; import { NavigationalSearchProvider } from './navigational_search'; import { SearchSessionsPageProvider } from './search_sessions_management_page'; import { DetectionsPageProvider } from '../../security_solution_ftr/page_objects/detections'; +import { BannersPageProvider } from './banners_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones @@ -80,5 +81,6 @@ export const pageObjects = { endpoint: EndpointPageProvider, ingestPipelines: IngestPipelinesPageProvider, navigationalSearch: NavigationalSearchProvider, + banners: BannersPageProvider, detections: DetectionsPageProvider, }; diff --git a/x-pack/test/plugin_functional/test_suites/global_search/global_search_bar.ts b/x-pack/test/plugin_functional/test_suites/global_search/global_search_bar.ts index 077044d29f7d..a44ded43a0bf 100644 --- a/x-pack/test/plugin_functional/test_suites/global_search/global_search_bar.ts +++ b/x-pack/test/plugin_functional/test_suites/global_search/global_search_bar.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - describe('TOTO GlobalSearchBar', function () { + describe('GlobalSearchBar', function () { const { common, navigationalSearch } = getPageObjects(['common', 'navigationalSearch']); const esArchiver = getService('esArchiver'); const browser = getService('browser');