mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
add per space configuration to custom header banner (#94449)
* restore the banners ui settings * fix banner init logic * fix unit tests * update telemetry schema * add basic server-side plugin tests * add FTR tests for banners plugin * use keyword for sensitive setting * update snapshots * setting name consistency with configuration properties * fix setting names in telemetry files * open banner links in new tab * add config.disableSpaceBanners property * fix types * add descriptions to banner settings * change label and value header->top * finishing header->top replacement * doc nits * add banners section to advanced options doc * feedback on advanced options doc * adapt deprecation to new format
This commit is contained in:
parent
ae1014bdd8
commit
ddac0e9501
32 changed files with 1045 additions and 86 deletions
|
@ -209,6 +209,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
|
||||
|
|
|
@ -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]
|
||||
====
|
||||
|===
|
||||
|
|
|
@ -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<T> = jest.Mocked<PluginInitializerContext<T>['config']>;
|
||||
|
||||
export function pluginInitializerContextConfigMock<T>(config: T) {
|
||||
const globalConfig: SharedGlobalConfig = {
|
||||
kibana: {
|
||||
|
@ -70,7 +72,7 @@ export function pluginInitializerContextConfigMock<T>(config: T) {
|
|||
},
|
||||
};
|
||||
|
||||
const mock: jest.Mocked<PluginInitializerContext<T>['config']> = {
|
||||
const mock: MockedPluginInitializerConfig<T> = {
|
||||
legacy: {
|
||||
globalConfig$: of(globalConfig),
|
||||
get: () => globalConfig,
|
||||
|
@ -82,8 +84,12 @@ export function pluginInitializerContextConfigMock<T>(config: T) {
|
|||
return mock;
|
||||
}
|
||||
|
||||
type PluginInitializerContextMock<T> = Omit<PluginInitializerContext<T>, 'config'> & {
|
||||
config: MockedPluginInitializerConfig<T>;
|
||||
};
|
||||
|
||||
function pluginInitializerContextMock<T>(config: T = {} as T) {
|
||||
const mock: PluginInitializerContext<T> = {
|
||||
const mock: PluginInitializerContextMock<T> = {
|
||||
opaqueId: Symbol(),
|
||||
logger: loggingSystemMock.create(),
|
||||
env: {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -326,6 +326,7 @@ export class Field extends PureComponent<FieldProps> {
|
|||
<div data-test-subj={`advancedSetting-editField-${name}`}>
|
||||
<EuiCodeEditor
|
||||
{...a11yProps}
|
||||
name={`advancedSetting-editField-${name}-editor`}
|
||||
mode={type}
|
||||
theme="textmate"
|
||||
value={currentValue}
|
||||
|
|
|
@ -43,6 +43,10 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
|
|||
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',
|
||||
|
@ -408,6 +412,18 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
|
|||
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.' },
|
||||
|
|
|
@ -18,6 +18,7 @@ export interface UsageStats {
|
|||
'timelion:graphite.url': string;
|
||||
'xpackDashboardMode:roles': string;
|
||||
'securitySolution:ipReputationLinks': string;
|
||||
'banners:textContent': string;
|
||||
/**
|
||||
* non-sensitive settings
|
||||
*/
|
||||
|
@ -114,4 +115,7 @@ export interface UsageStats {
|
|||
'csv:quoteValues': boolean;
|
||||
'dateFormat:dow': string;
|
||||
dateFormat: string;
|
||||
'banners:placement': string;
|
||||
'banners:textColor': string;
|
||||
'banners:backgroundColor': string;
|
||||
}
|
||||
|
|
|
@ -7478,6 +7478,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": {
|
||||
|
@ -8027,6 +8033,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": {
|
||||
|
|
|
@ -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<string, any>) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -10,7 +10,7 @@ export interface BannerInfoResponse {
|
|||
banner: BannerConfiguration;
|
||||
}
|
||||
|
||||
export type BannerPlacement = 'disabled' | 'header';
|
||||
export type BannerPlacement = 'disabled' | 'top';
|
||||
|
||||
export interface BannerConfiguration {
|
||||
placement: BannerPlacement;
|
||||
|
|
|
@ -26,7 +26,7 @@ export const Banner: FC<BannerProps> = ({ bannerConfig }) => {
|
|||
}}
|
||||
>
|
||||
<div data-test-subj="bannerInnerWrapper">
|
||||
<Markdown markdown={textContent} />
|
||||
<Markdown markdown={textContent} openLinksInNewTab={true} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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<void>((resolve) => resolve());
|
||||
|
||||
const createBannerConfig = (parts: Partial<BannerConfiguration> = {}): BannerConfiguration => ({
|
||||
placement: 'disabled',
|
||||
textContent: 'foo',
|
||||
textColor: '#FFFFFF',
|
||||
backgroundColor: '#000000',
|
||||
...parts,
|
||||
});
|
||||
|
||||
describe('BannersPlugin', () => {
|
||||
let plugin: BannersPlugin;
|
||||
let pluginInitContext: ReturnType<typeof coreMock.createPluginInitializerContext>;
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<BannerClientConfig>();
|
||||
}
|
||||
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(<Banner bannerConfig={banner} />),
|
||||
});
|
||||
}
|
||||
},
|
||||
() => {
|
||||
chrome.setHeaderBanner(undefined);
|
||||
getBannerInfo(http).then(
|
||||
({ allowed, banner }) => {
|
||||
if (allowed && banner.placement === 'top') {
|
||||
chrome.setHeaderBanner({
|
||||
content: toMountPoint(<Banner bannerConfig={banner} />),
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
chrome.setHeaderBanner(undefined);
|
||||
}
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -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<typeof configSchema>;
|
||||
|
||||
export const config: PluginConfigDescriptor<BannersConfigType> = {
|
||||
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;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}));
|
54
x-pack/plugins/banners/server/plugin.test.ts
Normal file
54
x-pack/plugins/banners/server/plugin.test.ts
Normal file
|
@ -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<typeof coreMock.createPluginInitializerContext>;
|
||||
let coreSetup: ReturnType<typeof coreMock.createSetup>;
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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<BannersConfigType>());
|
||||
this.config = context.config.get<BannersConfigType>();
|
||||
}
|
||||
|
||||
setup({ uiSettings, getStartServices, http }: CoreSetup<{}, {}>) {
|
||||
const router = http.createRouter<BannersRequestHandlerContext>();
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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<BannerConfiguration> => {
|
||||
const [placement, textContent, textColor, backgroundColor] = await Promise.all([
|
||||
client.get<BannerPlacement>('banners:placement'),
|
||||
client.get<string>('banners:textContent'),
|
||||
client.get<string>('banners:textColor'),
|
||||
client.get<string>('banners:backgroundColor'),
|
||||
]);
|
||||
|
||||
return {
|
||||
placement,
|
||||
textContent,
|
||||
textColor,
|
||||
backgroundColor,
|
||||
};
|
||||
};
|
||||
|
|
72
x-pack/plugins/banners/server/ui_settings.test.ts
Normal file
72
x-pack/plugins/banners/server/ui_settings.test.ts
Normal file
|
@ -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> = {}): BannersConfigType => ({
|
||||
placement: 'disabled',
|
||||
backgroundColor: '#0000',
|
||||
textColor: '#FFFFFF',
|
||||
textContent: 'Hello from the banner',
|
||||
disableSpaceBanners: false,
|
||||
...parts,
|
||||
});
|
||||
|
||||
describe('registerSettings', () => {
|
||||
let uiSettings: ReturnType<typeof uiSettingsServiceMock.createSetupContract>;
|
||||
|
||||
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,
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
120
x-pack/plugins/banners/server/ui_settings.ts
Normal file
120
x-pack/plugins/banners/server/ui_settings.ts
Normal file
|
@ -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 = `
|
||||
<a href="https://www.elastic.co/subscriptions" target="_blank" rel="noopener noreferrer">
|
||||
${i18n.translate('xpack.banners.settings.subscriptionRequiredLink.text', {
|
||||
defaultMessage: 'Subscription required.',
|
||||
})}
|
||||
</a>
|
||||
`;
|
||||
|
||||
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`;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
45
x-pack/test/banners_functional/config.ts
Normal file
45
x-pack/test/banners_functional/config.ts
Normal file
|
@ -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"',
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
13
x-pack/test/banners_functional/ftr_provider_context.ts
Normal file
13
x-pack/test/banners_functional/ftr_provider_context.ts
Normal file
|
@ -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<typeof services, typeof pageObjects>;
|
||||
export { services, pageObjects };
|
22
x-pack/test/banners_functional/tests/global.ts
Normal file
22
x-pack/test/banners_functional/tests/global.ts
Normal file
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
17
x-pack/test/banners_functional/tests/index.ts
Normal file
17
x-pack/test/banners_functional/tests/index.ts
Normal file
|
@ -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'));
|
||||
});
|
||||
}
|
67
x-pack/test/banners_functional/tests/spaces.ts
Normal file
67
x-pack/test/banners_functional/tests/spaces.ts
Normal file
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
x-pack/test/functional/page_objects/banners_page.ts
Normal file
30
x-pack/test/functional/page_objects/banners_page.ts
Normal file
|
@ -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();
|
||||
}
|
|
@ -41,6 +41,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
|
||||
|
@ -78,5 +79,6 @@ export const pageObjects = {
|
|||
roleMappings: RoleMappingsPageProvider,
|
||||
ingestPipelines: IngestPipelinesPageProvider,
|
||||
navigationalSearch: NavigationalSearchProvider,
|
||||
banners: BannersPageProvider,
|
||||
detections: DetectionsPageProvider,
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue