diff --git a/docs/development/core/server/kibana-plugin-server.logger.get.md b/docs/development/core/server/kibana-plugin-server.logger.get.md
new file mode 100644
index 0000000000000..b4a2d8a124260
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.logger.get.md
@@ -0,0 +1,33 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Logger](./kibana-plugin-server.logger.md) > [get](./kibana-plugin-server.logger.get.md)
+
+## Logger.get() method
+
+Returns a new [Logger](./kibana-plugin-server.logger.md) instance extending the current logger context.
+
+Signature:
+
+```typescript
+get(...childContextPaths: string[]): Logger;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| childContextPaths | string[]
| |
+
+Returns:
+
+`Logger`
+
+## Example
+
+
+```typescript
+const logger = loggerFactory.get('plugin', 'service'); // 'plugin.service' context
+const subLogger = logger.get('feature'); // 'plugin.service.feature' context
+
+```
+
diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md
index ea5ca6502b076..cdd709375aa51 100644
--- a/docs/development/core/server/kibana-plugin-server.md
+++ b/docs/development/core/server/kibana-plugin-server.md
@@ -90,7 +90,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. |
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
-| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request |
+| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request |
| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. |
| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. |
| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route |
diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md
index c9fc80596efa9..d9b781e1e550e 100644
--- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md
+++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md
@@ -6,7 +6,7 @@
Plugin specific context passed to a route handler.
-Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request
+Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request
Signature:
diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md
index 5bb22579d123e..1c78de966c46f 100644
--- a/src/core/MIGRATION.md
+++ b/src/core/MIGRATION.md
@@ -46,6 +46,8 @@
- [How to](#how-to)
- [Configure plugin](#configure-plugin)
- [Handle plugin configuration deprecations](#handle-plugin-config-deprecations)
+ - [Use scoped services](#use-scoped-services)
+ - [Declare a custom scoped service](#declare-a-custom-scoped-service)
- [Mock new platform services in tests](#mock-new-platform-services-in-tests)
- [Writing mocks for your plugin](#writing-mocks-for-your-plugin)
- [Using mocks in your tests](#using-mocks-in-your-tests)
@@ -1190,22 +1192,23 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS
| `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ |
| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) |
| `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | |
-| `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client |
-| `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client |
-| `xpackMainPlugin.info.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | |
+| `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | |
+| `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | |
| `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactory`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | |
| `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | |
| `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | |
| `server.savedObjects.getScopedSavedObjectsClient` | [`core.savedObjects.getScopedClient`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | |
| `request.getSavedObjectsClient` | [`context.core.savedObjects.client`](/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md) | |
+| `request.getUiSettingsService` | [`context.uiSettings.client`](/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md) | |
| `kibana.Plugin.deprecations` | [Handle plugin configuration deprecations](#handle-plugin-config-deprecations) and [`PluginConfigDescriptor.deprecations`](docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md) | Deprecations from New Platform are not applied to legacy configuration |
_See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-server.coresetup.md)_
##### Plugin services
-| Legacy Platform | New Platform | Notes |
-| ------------------------------------------- | ------------------------------------------------------------------------------ | ----- |
-| `server.plugins.xpack_main.registerFeature` | [`plugins.features.registerFeature`](x-pack/plugins/features/server/plugin.ts) | |
+| Legacy Platform | New Platform | Notes |
+| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----- |
+| `server.plugins.xpack_main.registerFeature` | [`plugins.features.registerFeature`](x-pack/plugins/features/server/plugin.ts) | |
+| `server.plugins.xpack_main.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | |
#### UI Exports
@@ -1399,7 +1402,7 @@ export const config: PluginConfigDescriptor = {
deprecations: ({ rename, unused }) => [
rename('oldProperty', 'newProperty'),
unused('someUnusedProperty'),
- ]
+ ]
};
```
@@ -1413,7 +1416,7 @@ export const config: PluginConfigDescriptor = {
deprecations: ({ renameFromRoot, unusedFromRoot }) => [
renameFromRoot('oldplugin.property', 'myplugin.property'),
unusedFromRoot('oldplugin.deprecated'),
- ]
+ ]
};
```
@@ -1421,6 +1424,68 @@ Note that deprecations registered in new platform's plugins are not applied to t
During migration, if you still need the deprecations to be effective in the legacy plugin, you need to declare them in
both plugin definitions.
+### Use scoped services
+Whenever Kibana needs to get access to data saved in elasticsearch, it should perform a check whether an end-user has access to the data.
+In the legacy platform, Kibana requires to bind elasticsearch related API with an incoming request to access elasticsearch service on behalf of a user.
+```js
+ async function handler(req, res) {
+ const dataCluster = server.plugins.elasticsearch.getCluster('data');
+ const data = await dataCluster.callWithRequest(req, 'ping');
+ }
+```
+
+The new platform introduced [a handler interface](/rfcs/text/0003_handler_interface.md) on the server-side to perform that association internally. Core services, that require impersonation with an incoming request, are
+exposed via `context` argument of [the request handler interface.](/docs/development/core/server/kibana-plugin-server.requesthandler.md)
+The above example looks in the new platform as
+```js
+ async function handler(context, req, res) {
+ const data = await context.core.elasticsearch.adminClient.callAsInternalUser('ping')
+ }
+```
+
+The [request handler context](/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md) exposed the next scoped **core** services:
+| Legacy Platform | New Platform |
+| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------|
+| `request.getSavedObjectsClient` | [`context.savedObjects.client`](/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md) |
+| `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) |
+| `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) |
+| `request.getUiSettingsService` | [`context.uiSettings.client`](/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md) |
+
+#### Declare a custom scoped service
+Plugins can extend the handler context with custom API that will be available to the plugin itself and all dependent plugins.
+For example, the plugin creates a custom elasticsearch client and want to use it via the request handler context:
+
+```ts
+import { CoreSetup, IScopedClusterClient } from 'kibana/server';
+
+export interface MyPluginContext {
+ client: IScopedClusterClient;
+}
+
+// extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file
+declare module 'src/core/server' {
+ interface RequestHandlerContext {
+ myPlugin?: MyPluginContext;
+ }
+}
+
+class Plugin {
+ setup(core: CoreSetup) {
+ const client = core.elasticsearch.createClient('myClient');
+ core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => {
+ return { client: client.asScoped(req) };
+ });
+
+ router.get(
+ { path: '/api/my-plugin/', validate },
+ async (context, req, res) => {
+ const data = await context.myPlugin.client.callAsCurrentUser('endpoint');
+ ...
+ }
+ );
+ }
+```
+
### Mock new platform services in tests
#### Writing mocks for your plugin
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index 2aaa8306e871f..ba930d46e0865 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -234,6 +234,8 @@ export { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './legacy';
* data client which uses the credentials of the incoming request
* - {@link ScopedClusterClient | elasticsearch.adminClient} - Elasticsearch
* admin client which uses the credentials of the incoming request
+ * - {@link IUiSettingsClient | uiSettings.client} - uiSettings client
+ * which uses the credentials of the incoming request
*
* @public
*/
diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js
index 172dec9a1d111..87026ce25d9aa 100644
--- a/test/plugin_functional/config.js
+++ b/test/plugin_functional/config.js
@@ -57,11 +57,12 @@ export default async function({ readConfigFile }) {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig.get('kbnTestServer.serverArgs'),
+
+ // Required to load new platform plugins via `--plugin-path` flag.
+ '--env.name=development',
...plugins.map(
pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}`
),
- // Required to load new platform plugins via `--plugin-path` flag.
- '--env.name=development',
],
},
};
diff --git a/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx
index 5c8e1d03d5a4a..bda1557bdaf91 100644
--- a/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx
+++ b/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx
@@ -22,9 +22,6 @@ import { CorePluginAPluginSetup } from '../../core_plugin_a/public/plugin';
declare global {
interface Window {
- corePluginB?: string;
- hasAccessToInjectedMetadata?: boolean;
- receivedStartServices?: boolean;
env?: PluginInitializerContext['env'];
}
}
@@ -39,12 +36,6 @@ export class CorePluginBPlugin
window.env = pluginContext.env;
}
public setup(core: CoreSetup, deps: CorePluginBDeps) {
- window.corePluginB = `Plugin A said: ${deps.core_plugin_a.getGreeting()}`;
- window.hasAccessToInjectedMetadata = 'getInjectedVar' in core.injectedMetadata;
- core.getStartServices().then(([coreStart, plugins]) => {
- window.receivedStartServices = 'overlays' in coreStart;
- });
-
core.application.register({
id: 'bar',
title: 'Bar',
@@ -53,6 +44,12 @@ export class CorePluginBPlugin
return renderApp(context, params);
},
});
+
+ return {
+ sayHi() {
+ return `Plugin A said: ${deps.core_plugin_a.getGreeting()}`;
+ },
+ };
}
public start() {}
diff --git a/test/plugin_functional/plugins/core_provider_plugin/index.ts b/test/plugin_functional/plugins/core_provider_plugin/index.ts
new file mode 100644
index 0000000000000..01f3a67c6b554
--- /dev/null
+++ b/test/plugin_functional/plugins/core_provider_plugin/index.ts
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { resolve } from 'path';
+import { Legacy } from '../../../../kibana';
+
+// eslint-disable-next-line import/no-default-export
+export default function CoreProviderPlugin(kibana: any) {
+ const config: Legacy.PluginSpecOptions = {
+ id: 'core-provider',
+ require: [],
+ publicDir: resolve(__dirname, 'public'),
+ init: (server: Legacy.Server) => ({}),
+ uiExports: {
+ hacks: [resolve(__dirname, 'public/index')],
+ },
+ };
+
+ return new kibana.Plugin(config);
+}
diff --git a/test/plugin_functional/plugins/core_provider_plugin/package.json b/test/plugin_functional/plugins/core_provider_plugin/package.json
new file mode 100644
index 0000000000000..941503b934cbb
--- /dev/null
+++ b/test/plugin_functional/plugins/core_provider_plugin/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "core_provider_plugin",
+ "version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/core_provider_plugin",
+ "kibana": {
+ "version": "kibana",
+ "templateVersion": "1.0.0"
+ },
+ "license": "Apache-2.0",
+ "scripts": {
+ "kbn": "node ../../../../scripts/kbn.js",
+ "build": "rm -rf './target' && tsc"
+ },
+ "devDependencies": {
+ "typescript": "3.5.3"
+ }
+}
diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts b/test/plugin_functional/plugins/core_provider_plugin/public/index.ts
similarity index 78%
rename from test/plugin_functional/plugins/ui_settings_plugin/public/index.ts
rename to test/plugin_functional/plugins/core_provider_plugin/public/index.ts
index 3c5997132d460..c74928203db56 100644
--- a/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts
+++ b/test/plugin_functional/plugins/core_provider_plugin/public/index.ts
@@ -16,6 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { UiSettingsPlugin } from './plugin';
+import { npSetup, npStart } from 'ui/new_platform';
+import '../types';
-export const plugin = () => new UiSettingsPlugin();
+window.__coreProvider = {
+ setup: npSetup,
+ start: npStart,
+ testUtils: {
+ delay: (ms: number) => new Promise(res => setTimeout(res, ms)),
+ },
+};
diff --git a/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json
new file mode 100644
index 0000000000000..c29959197958d
--- /dev/null
+++ b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true
+ },
+ "include": [
+ "index.ts",
+ "types.ts",
+ "public/**/*.ts",
+ "../../../../typings/**/*",
+ ],
+ "exclude": []
+}
diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx b/test/plugin_functional/plugins/core_provider_plugin/types.ts
similarity index 66%
rename from test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx
rename to test/plugin_functional/plugins/core_provider_plugin/types.ts
index 883d203b4c37a..bf19578c37baa 100644
--- a/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx
+++ b/test/plugin_functional/plugins/core_provider_plugin/types.ts
@@ -16,22 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { CoreSetup, Plugin } from 'kibana/public';
+import { LegacyCoreSetup, LegacyCoreStart } from 'kibana/public';
declare global {
interface Window {
- uiSettingsPlugin?: Record;
- uiSettingsPluginValue?: string;
+ __coreProvider: {
+ setup: {
+ core: LegacyCoreSetup;
+ plugins: Record;
+ };
+ start: {
+ core: LegacyCoreStart;
+ plugins: Record;
+ };
+ testUtils: {
+ delay: (ms: number) => Promise;
+ };
+ };
}
}
-
-export class UiSettingsPlugin implements Plugin {
- public setup(core: CoreSetup) {
- window.uiSettingsPlugin = core.uiSettings.getAll().ui_settings_plugin;
- window.uiSettingsPluginValue = core.uiSettings.get('ui_settings_plugin');
- }
-
- public start() {}
- public stop() {}
-}
diff --git a/test/plugin_functional/plugins/ui_settings_plugin/kibana.json b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json
index 05d2dca0af937..35e4c35490e2f 100644
--- a/test/plugin_functional/plugins/ui_settings_plugin/kibana.json
+++ b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json
@@ -4,5 +4,5 @@
"kibanaVersion": "kibana",
"configPath": ["ui_settings_plugin"],
"server": true,
- "ui": true
+ "ui": false
}
diff --git a/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json
index 1ba21f11b7de2..7c170405bbfc7 100644
--- a/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json
+++ b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json
@@ -5,9 +5,6 @@
"skipLibCheck": true
},
"include": [
- "index.ts",
- "public/**/*.ts",
- "public/**/*.tsx",
"server/**/*.ts",
"../../../../typings/**/*",
],
diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts
index ff53583546487..b76463ee76739 100644
--- a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts
+++ b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts
@@ -19,6 +19,7 @@
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
+import '../../../../test/plugin_functional/plugins/core_provider_plugin/types';
// eslint-disable-next-line import/no-default-export
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
@@ -31,22 +32,35 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
await PageObjects.common.navigateToApp('settings');
});
- it('should attach string to window.corePluginB', async () => {
- const corePluginB = await browser.execute('return window.corePluginB');
- expect(corePluginB).to.equal(`Plugin A said: Hello from Plugin A!`);
+ it('should run the new platform plugins', async () => {
+ expect(
+ await browser.execute(() => {
+ return window.__coreProvider.setup.plugins.core_plugin_b.sayHi();
+ })
+ ).to.be('Plugin A said: Hello from Plugin A!');
});
});
- describe('have injectedMetadata service provided', function describeIndexTests() {
+ describe('should have access to the core services', function describeIndexTests() {
before(async () => {
- await PageObjects.common.navigateToApp('bar');
+ await PageObjects.common.navigateToApp('settings');
+ });
+
+ it('to injectedMetadata service', async () => {
+ expect(
+ await browser.execute(() => {
+ return window.__coreProvider.setup.core.injectedMetadata.getKibanaBuildNumber();
+ })
+ ).to.be.a('number');
});
- it('should attach boolean to window.hasAccessToInjectedMetadata', async () => {
- const hasAccessToInjectedMetadata = await browser.execute(
- 'return window.hasAccessToInjectedMetadata'
- );
- expect(hasAccessToInjectedMetadata).to.equal(true);
+ it('to start services via coreSetup.getStartServices', async () => {
+ expect(
+ await browser.executeAsync(async cb => {
+ const [coreStart] = await window.__coreProvider.setup.core.getStartServices();
+ cb(Boolean(coreStart.overlays));
+ })
+ ).to.be(true);
});
});
@@ -61,16 +75,5 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
expect(envData.packageInfo.version).to.be.a('string');
});
});
-
- describe('have access to start services via coreSetup.getStartServices', function describeIndexTests() {
- before(async () => {
- await PageObjects.common.navigateToApp('bar');
- });
-
- it('should attach boolean to window.receivedStartServices', async () => {
- const receivedStartServices = await browser.execute('return window.receivedStartServices');
- expect(receivedStartServices).to.equal(true);
- });
- });
});
}
diff --git a/test/plugin_functional/test_suites/core_plugins/ui_settings.ts b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts
index 2b4227ee798e3..dec79fd15f4dd 100644
--- a/test/plugin_functional/test_suites/core_plugins/ui_settings.ts
+++ b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts
@@ -18,6 +18,7 @@
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
+import '../../plugins/core_provider_plugin/types';
// eslint-disable-next-line import/no-default-export
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
@@ -31,15 +32,30 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
});
it('client plugins have access to registered settings', async () => {
- const settings = await browser.execute('return window.uiSettingsPlugin');
+ const settings = await browser.execute(() => {
+ return window.__coreProvider.setup.core.uiSettings.getAll().ui_settings_plugin;
+ });
+
expect(settings).to.eql({
category: ['any'],
description: 'just for testing',
name: 'from_ui_settings_plugin',
value: '2',
});
- const settingsValue = await browser.execute('return window.uiSettingsPluginValue');
+
+ const settingsValue = await browser.execute(() => {
+ return window.__coreProvider.setup.core.uiSettings.get('ui_settings_plugin');
+ });
+
expect(settingsValue).to.be('2');
+
+ const settingsValueViaObservables = await browser.executeAsync(async (callback: Function) => {
+ window.__coreProvider.setup.core.uiSettings
+ .get$('ui_settings_plugin')
+ .subscribe(v => callback(v));
+ });
+
+ expect(settingsValueViaObservables).to.be('2');
});
it('server plugins have access to registered settings', async () => {
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js
index 2edfbbc27fc45..2e0d608e522d7 100644
--- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js
+++ b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js
@@ -312,6 +312,54 @@ describe('XPackInfo', () => {
});
});
+ it('onLicenseInfoChange() allows to subscribe to license update', async () => {
+ const license$ = new BehaviorSubject(createLicense());
+
+ const xPackInfo = new XPackInfo(mockServer, {
+ licensing: {
+ license$,
+ refresh: () => null,
+ },
+ });
+
+ const watcherFeature = xPackInfo.feature('watcher');
+ watcherFeature.registerLicenseCheckResultsGenerator(info => ({
+ type: info.license.getType(),
+ }));
+
+ const statuses = [];
+ xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults()));
+
+ license$.next(createLicense({ type: 'basic' }));
+ expect(statuses).to.eql([{ type: 'basic' }]);
+
+ license$.next(createLicense({ type: 'trial' }));
+ expect(statuses).to.eql([{ type: 'basic' }, { type: 'trial' }]);
+ });
+
+ it('refreshNow() leads to onLicenseInfoChange()', async () => {
+ const license$ = new BehaviorSubject(createLicense());
+
+ const xPackInfo = new XPackInfo(mockServer, {
+ licensing: {
+ license$,
+ refresh: () => license$.next({ type: 'basic' }),
+ },
+ });
+
+ const watcherFeature = xPackInfo.feature('watcher');
+
+ watcherFeature.registerLicenseCheckResultsGenerator(info => ({
+ type: info.license.getType(),
+ }));
+
+ const statuses = [];
+ xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults()));
+
+ await xPackInfo.refreshNow();
+ expect(statuses).to.eql([{ type: 'basic' }]);
+ });
+
it('getSignature() returns correct signature.', async () => {
const license$ = new BehaviorSubject(createLicense());
const xPackInfo = new XPackInfo(mockServer, {
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts
index fbb8929154c36..9d5a8e64645ec 100644
--- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts
+++ b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts
@@ -101,6 +101,8 @@ export class XPackInfo {
error: license.error,
};
}
+
+ this._licenseInfoChangedListeners.forEach(fn => fn());
});
this._license = new XPackInfoLicense(() => this._cache.license);
diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js
index f3e9db0053ad6..2b92e70fb30af 100644
--- a/x-pack/scripts/functional_tests.js
+++ b/x-pack/scripts/functional_tests.js
@@ -36,4 +36,6 @@ require('@kbn/test').runTestsCli([
require.resolve('../test/ui_capabilities/spaces_only/config'),
require.resolve('../test/upgrade_assistant_integration/config'),
require.resolve('../test/licensing_plugin/config'),
+ require.resolve('../test/licensing_plugin/config.public'),
+ require.resolve('../test/licensing_plugin/config.legacy'),
]);
diff --git a/x-pack/test/licensing_plugin/apis/changes.ts b/x-pack/test/licensing_plugin/apis/changes.ts
deleted file mode 100644
index cf4fecfa32d94..0000000000000
--- a/x-pack/test/licensing_plugin/apis/changes.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import expect from '@kbn/expect';
-import { FtrProviderContext } from '../services';
-import { PublicLicenseJSON } from '../../../plugins/licensing/server';
-
-const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
-
-export default function({ getService, getPageObjects }: FtrProviderContext) {
- const supertest = getService('supertest');
- const esSupertestWithoutAuth = getService('esSupertestWithoutAuth');
- const security = getService('security');
- const PageObjects = getPageObjects(['common', 'security']);
- const testSubjects = getService('testSubjects');
-
- const scenario = {
- async setup() {
- await security.role.create('license_manager-role', {
- elasticsearch: {
- cluster: ['all'],
- },
- kibana: [
- {
- base: ['all'],
- spaces: ['*'],
- },
- ],
- });
-
- await security.user.create('license_manager_user', {
- password: 'license_manager_user-password',
- roles: ['license_manager-role'],
- full_name: 'license_manager user',
- });
-
- // ensure we're logged out so we can login as the appropriate users
- await PageObjects.security.forceLogout();
- await PageObjects.security.login('license_manager_user', 'license_manager_user-password');
- },
-
- async teardown() {
- await security.role.delete('license_manager-role');
- },
-
- async startBasic() {
- const response = await esSupertestWithoutAuth
- .post('/_license/start_basic?acknowledge=true')
- .auth('license_manager_user', 'license_manager_user-password')
- .expect(200);
-
- expect(response.body.basic_was_started).to.be(true);
- },
-
- async startTrial() {
- const response = await esSupertestWithoutAuth
- .post('/_license/start_trial?acknowledge=true')
- .auth('license_manager_user', 'license_manager_user-password')
- .expect(200);
-
- expect(response.body.trial_was_started).to.be(true);
- },
-
- async deleteLicense() {
- const response = await esSupertestWithoutAuth
- .delete('/_license')
- .auth('license_manager_user', 'license_manager_user-password')
- .expect(200);
-
- expect(response.body.acknowledged).to.be(true);
- },
-
- async getLicense(): Promise {
- // > --xpack.licensing.api_polling_frequency set in test config
- // to wait for Kibana server to re-fetch the license from Elasticsearch
- await delay(1000);
-
- const { body } = await supertest.get('/api/licensing/info').expect(200);
- return body;
- },
- };
-
- describe('changes in license types', () => {
- after(async () => {
- await scenario.startBasic();
- });
-
- it('provides changes in license types', async () => {
- await scenario.setup();
- const initialLicense = await scenario.getLicense();
- expect(initialLicense.license?.type).to.be('basic');
- // security enabled explicitly in test config
- expect(initialLicense.features?.security).to.eql({
- isAvailable: true,
- isEnabled: true,
- });
-
- const {
- body: legacyInitialLicense,
- headers: legacyInitialLicenseHeaders,
- } = await supertest.get('/api/xpack/v1/info').expect(200);
-
- expect(legacyInitialLicense.license?.type).to.be('basic');
- expect(legacyInitialLicense.features).to.have.property('security');
- expect(legacyInitialLicenseHeaders['kbn-xpack-sig']).to.be.a('string');
-
- // license hasn't changed
- const refetchedLicense = await scenario.getLicense();
- expect(refetchedLicense.license?.type).to.be('basic');
- expect(refetchedLicense.signature).to.be(initialLicense.signature);
-
- const {
- body: legacyRefetchedLicense,
- headers: legacyRefetchedLicenseHeaders,
- } = await supertest.get('/api/xpack/v1/info').expect(200);
-
- expect(legacyRefetchedLicense.license?.type).to.be('basic');
- expect(legacyRefetchedLicenseHeaders['kbn-xpack-sig']).to.be(
- legacyInitialLicenseHeaders['kbn-xpack-sig']
- );
-
- // server allows to request trial only once.
- // other attempts will throw 403
- await scenario.startTrial();
- const trialLicense = await scenario.getLicense();
- expect(trialLicense.license?.type).to.be('trial');
- expect(trialLicense.signature).to.not.be(initialLicense.signature);
-
- expect(trialLicense.features?.security).to.eql({
- isAvailable: true,
- isEnabled: true,
- });
-
- const { body: legacyTrialLicense, headers: legacyTrialLicenseHeaders } = await supertest
- .get('/api/xpack/v1/info')
- .expect(200);
-
- expect(legacyTrialLicense.license?.type).to.be('trial');
- expect(legacyTrialLicense.features).to.have.property('security');
- expect(legacyTrialLicenseHeaders['kbn-xpack-sig']).to.not.be(
- legacyInitialLicenseHeaders['kbn-xpack-sig']
- );
-
- await scenario.startBasic();
- const basicLicense = await scenario.getLicense();
- expect(basicLicense.license?.type).to.be('basic');
- expect(basicLicense.signature).not.to.be(initialLicense.signature);
-
- expect(basicLicense.features?.security).to.eql({
- isAvailable: true,
- isEnabled: true,
- });
-
- const { body: legacyBasicLicense, headers: legacyBasicLicenseHeaders } = await supertest
- .get('/api/xpack/v1/info')
- .expect(200);
- expect(legacyBasicLicense.license?.type).to.be('basic');
- expect(legacyBasicLicense.features).to.have.property('security');
- expect(legacyBasicLicenseHeaders['kbn-xpack-sig']).to.not.be(
- legacyInitialLicenseHeaders['kbn-xpack-sig']
- );
-
- await scenario.deleteLicense();
- const inactiveLicense = await scenario.getLicense();
- expect(inactiveLicense.signature).to.not.be(initialLicense.signature);
- expect(inactiveLicense).to.not.have.property('license');
- expect(inactiveLicense.features?.security).to.eql({
- isAvailable: false,
- isEnabled: true,
- });
- // banner shown only when license expired not just deleted
- await testSubjects.missingOrFail('licenseExpiredBanner');
- });
- });
-}
diff --git a/x-pack/test/licensing_plugin/config.legacy.ts b/x-pack/test/licensing_plugin/config.legacy.ts
new file mode 100644
index 0000000000000..27dc3df9944ad
--- /dev/null
+++ b/x-pack/test/licensing_plugin/config.legacy.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+
+export default async function({ readConfigFile }: FtrConfigProviderContext) {
+ const commonConfig = await readConfigFile(require.resolve('./config'));
+
+ return {
+ ...commonConfig.getAll(),
+ testFiles: [require.resolve('./legacy')],
+ };
+}
diff --git a/x-pack/test/licensing_plugin/config.public.ts b/x-pack/test/licensing_plugin/config.public.ts
new file mode 100644
index 0000000000000..42209aa49bcb4
--- /dev/null
+++ b/x-pack/test/licensing_plugin/config.public.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import path from 'path';
+import { KIBANA_ROOT } from '@kbn/test';
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+
+export default async function({ readConfigFile }: FtrConfigProviderContext) {
+ const commonConfig = await readConfigFile(require.resolve('./config'));
+
+ return {
+ ...commonConfig.getAll(),
+ testFiles: [require.resolve('./public')],
+ kbnTestServer: {
+ serverArgs: [
+ ...commonConfig.get('kbnTestServer.serverArgs'),
+
+ // Required to load new platform plugin provider via `--plugin-path` flag.
+ '--env.name=development',
+ `--plugin-path=${path.resolve(
+ KIBANA_ROOT,
+ 'test/plugin_functional/plugins/core_provider_plugin'
+ )}`,
+ ],
+ },
+ };
+}
diff --git a/x-pack/test/licensing_plugin/config.ts b/x-pack/test/licensing_plugin/config.ts
index 9a83a6f6b5a0b..60d44cbd4c47f 100644
--- a/x-pack/test/licensing_plugin/config.ts
+++ b/x-pack/test/licensing_plugin/config.ts
@@ -22,7 +22,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
};
return {
- testFiles: [require.resolve('./apis')],
+ testFiles: [require.resolve('./server')],
servers,
services,
pageObjects,
@@ -43,7 +43,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
...functionalTestsConfig.get('kbnTestServer'),
serverArgs: [
...functionalTestsConfig.get('kbnTestServer.serverArgs'),
- '--xpack.licensing.api_polling_frequency=300',
+ '--xpack.licensing.api_polling_frequency=100',
],
},
diff --git a/x-pack/test/licensing_plugin/legacy/index.ts b/x-pack/test/licensing_plugin/legacy/index.ts
new file mode 100644
index 0000000000000..5c45b8f097baf
--- /dev/null
+++ b/x-pack/test/licensing_plugin/legacy/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrProviderContext } from '../services';
+
+// eslint-disable-next-line import/no-default-export
+export default function({ loadTestFile }: FtrProviderContext) {
+ describe('Legacy licensing plugin', function() {
+ this.tags('ciGroup2');
+ // MUST BE LAST! CHANGES LICENSE TYPE!
+ loadTestFile(require.resolve('./updates'));
+ });
+}
diff --git a/x-pack/test/licensing_plugin/legacy/updates.ts b/x-pack/test/licensing_plugin/legacy/updates.ts
new file mode 100644
index 0000000000000..14657368c78ae
--- /dev/null
+++ b/x-pack/test/licensing_plugin/legacy/updates.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../services';
+import { createScenario } from '../scenario';
+import '../../../../test/plugin_functional/plugins/core_provider_plugin/types';
+
+// eslint-disable-next-line import/no-default-export
+export default function(ftrContext: FtrProviderContext) {
+ const { getService } = ftrContext;
+ const supertest = getService('supertest');
+ const testSubjects = getService('testSubjects');
+
+ const scenario = createScenario(ftrContext);
+
+ describe('changes in license types', () => {
+ after(async () => {
+ await scenario.startBasic();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ await scenario.teardown();
+ });
+
+ it('provides changes in license types', async () => {
+ await scenario.setup();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ const {
+ body: legacyInitialLicense,
+ headers: legacyInitialLicenseHeaders,
+ } = await supertest.get('/api/xpack/v1/info').expect(200);
+
+ expect(legacyInitialLicense.license?.type).to.be('basic');
+ expect(legacyInitialLicense.features).to.have.property('security');
+ expect(legacyInitialLicenseHeaders['kbn-xpack-sig']).to.be.a('string');
+
+ await scenario.startTrial();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ const { body: legacyTrialLicense, headers: legacyTrialLicenseHeaders } = await supertest
+ .get('/api/xpack/v1/info')
+ .expect(200);
+
+ expect(legacyTrialLicense.license?.type).to.be('trial');
+ expect(legacyTrialLicense.features).to.have.property('security');
+ expect(legacyTrialLicenseHeaders['kbn-xpack-sig']).to.not.be(
+ legacyInitialLicenseHeaders['kbn-xpack-sig']
+ );
+
+ await scenario.startBasic();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ const { body: legacyBasicLicense, headers: legacyBasicLicenseHeaders } = await supertest
+ .get('/api/xpack/v1/info')
+ .expect(200);
+ expect(legacyBasicLicense.license?.type).to.be('basic');
+ expect(legacyBasicLicense.features).to.have.property('security');
+ expect(legacyBasicLicenseHeaders['kbn-xpack-sig']).to.not.be(
+ legacyInitialLicenseHeaders['kbn-xpack-sig']
+ );
+
+ await scenario.deleteLicense();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ // banner shown only when license expired not just deleted
+ await testSubjects.missingOrFail('licenseExpiredBanner');
+ });
+ });
+}
diff --git a/x-pack/test/licensing_plugin/public/index.ts b/x-pack/test/licensing_plugin/public/index.ts
new file mode 100644
index 0000000000000..3e1445d9a4aab
--- /dev/null
+++ b/x-pack/test/licensing_plugin/public/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrProviderContext } from '../services';
+
+// eslint-disable-next-line import/no-default-export
+export default function({ loadTestFile }: FtrProviderContext) {
+ describe('Licensing plugin public client', function() {
+ this.tags('ciGroup2');
+ // MUST BE LAST! CHANGES LICENSE TYPE!
+ loadTestFile(require.resolve('./updates'));
+ });
+}
diff --git a/x-pack/test/licensing_plugin/public/updates.ts b/x-pack/test/licensing_plugin/public/updates.ts
new file mode 100644
index 0000000000000..80822f6fb2505
--- /dev/null
+++ b/x-pack/test/licensing_plugin/public/updates.ts
@@ -0,0 +1,112 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../services';
+import { LicensingPluginSetup } from '../../../plugins/licensing/public';
+import { createScenario } from '../scenario';
+import '../../../../test/plugin_functional/plugins/core_provider_plugin/types';
+
+// eslint-disable-next-line import/no-default-export
+export default function(ftrContext: FtrProviderContext) {
+ const { getService } = ftrContext;
+ const testSubjects = getService('testSubjects');
+ const browser = getService('browser');
+
+ const scenario = createScenario(ftrContext);
+
+ describe('changes in license types', () => {
+ after(async () => {
+ await scenario.startBasic();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ await scenario.teardown();
+ });
+
+ it('provides changes in license types', async () => {
+ await scenario.setup();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ expect(
+ await browser.executeAsync(async (cb: Function) => {
+ const { setup, testUtils } = window.__coreProvider;
+ // this call enforces signature check to detect license update
+ // and causes license re-fetch
+ await setup.core.http.get('/');
+ await testUtils.delay(100);
+
+ const licensing: LicensingPluginSetup = setup.plugins.licensing;
+ licensing.license$.subscribe(license => cb(license.type));
+ })
+ ).to.be('basic');
+
+ // license hasn't changed
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ expect(
+ await browser.executeAsync(async (cb: Function) => {
+ const { setup, testUtils } = window.__coreProvider;
+ // this call enforces signature check to detect license update
+ // and causes license re-fetch
+ await setup.core.http.get('/');
+ await testUtils.delay(100);
+
+ const licensing: LicensingPluginSetup = setup.plugins.licensing;
+ licensing.license$.subscribe(license => cb(license.type));
+ })
+ ).to.be('basic');
+
+ await scenario.startTrial();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ expect(
+ await browser.executeAsync(async (cb: Function) => {
+ const { setup, testUtils } = window.__coreProvider;
+ // this call enforces signature check to detect license update
+ // and causes license re-fetch
+ await setup.core.http.get('/');
+ await testUtils.delay(100);
+
+ const licensing: LicensingPluginSetup = setup.plugins.licensing;
+ licensing.license$.subscribe(license => cb(license.type));
+ })
+ ).to.be('trial');
+
+ await scenario.startBasic();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ expect(
+ await browser.executeAsync(async (cb: Function) => {
+ const { setup, testUtils } = window.__coreProvider;
+ // this call enforces signature check to detect license update
+ // and causes license re-fetch
+ await setup.core.http.get('/');
+ await testUtils.delay(100);
+
+ const licensing: LicensingPluginSetup = setup.plugins.licensing;
+ licensing.license$.subscribe(license => cb(license.type));
+ })
+ ).to.be('basic');
+
+ await scenario.deleteLicense();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+
+ expect(
+ await browser.executeAsync(async (cb: Function) => {
+ const { setup, testUtils } = window.__coreProvider;
+ // this call enforces signature check to detect license update
+ // and causes license re-fetch
+ await setup.core.http.get('/');
+ await testUtils.delay(100);
+
+ const licensing: LicensingPluginSetup = setup.plugins.licensing;
+ licensing.license$.subscribe(license => cb(license.type));
+ })
+ ).to.be(null);
+
+ // banner shown only when license expired not just deleted
+ await testSubjects.missingOrFail('licenseExpiredBanner');
+ });
+ });
+}
diff --git a/x-pack/test/licensing_plugin/scenario.ts b/x-pack/test/licensing_plugin/scenario.ts
new file mode 100644
index 0000000000000..46837dfc1be91
--- /dev/null
+++ b/x-pack/test/licensing_plugin/scenario.ts
@@ -0,0 +1,91 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+import { FtrProviderContext } from './services';
+import { PublicLicenseJSON } from '../../plugins/licensing/server';
+import '../../../test/plugin_functional/plugins/core_provider_plugin/types';
+
+const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
+
+export function createScenario({ getService, getPageObjects }: FtrProviderContext) {
+ const supertest = getService('supertest');
+ const esSupertestWithoutAuth = getService('esSupertestWithoutAuth');
+ const security = getService('security');
+ const PageObjects = getPageObjects(['common', 'security']);
+
+ const scenario = {
+ async setup() {
+ await security.role.create('license_manager-role', {
+ elasticsearch: {
+ cluster: ['all'],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ spaces: ['*'],
+ },
+ ],
+ });
+
+ await security.user.create('license_manager_user', {
+ password: 'license_manager_user-password',
+ roles: ['license_manager-role'],
+ full_name: 'license_manager user',
+ });
+
+ // ensure we're logged out so we can login as the appropriate users
+ await PageObjects.security.logout();
+ await PageObjects.security.login('license_manager_user', 'license_manager_user-password');
+ },
+
+ // make sure a license is present, otherwise the security is not available anymore.
+ async teardown() {
+ await security.role.delete('license_manager-role');
+ await security.user.delete('license_manager_user');
+ },
+
+ // elasticsearch allows to downgrade a license only once. other attempts will throw 403.
+ async startBasic() {
+ const response = await esSupertestWithoutAuth
+ .post('/_license/start_basic?acknowledge=true')
+ .auth('license_manager_user', 'license_manager_user-password')
+ .expect(200);
+
+ expect(response.body.basic_was_started).to.be(true);
+ },
+
+ // elasticsearch allows to request trial only once. other attempts will throw 403.
+ async startTrial() {
+ const response = await esSupertestWithoutAuth
+ .post('/_license/start_trial?acknowledge=true')
+ .auth('license_manager_user', 'license_manager_user-password')
+ .expect(200);
+
+ expect(response.body.trial_was_started).to.be(true);
+ },
+
+ async deleteLicense() {
+ const response = await esSupertestWithoutAuth
+ .delete('/_license')
+ .auth('license_manager_user', 'license_manager_user-password')
+ .expect(200);
+
+ expect(response.body.acknowledged).to.be(true);
+ },
+
+ async getLicense(): Promise {
+ const { body } = await supertest.get('/api/licensing/info').expect(200);
+ return body;
+ },
+
+ async waitForPluginToDetectLicenseUpdate() {
+ // > --xpack.licensing.api_polling_frequency set in test config
+ // to wait for Kibana server to re-fetch the license from Elasticsearch
+ await delay(500);
+ },
+ };
+ return scenario;
+}
diff --git a/x-pack/test/licensing_plugin/apis/header.ts b/x-pack/test/licensing_plugin/server/header.ts
similarity index 93%
rename from x-pack/test/licensing_plugin/apis/header.ts
rename to x-pack/test/licensing_plugin/server/header.ts
index 8d95054feaaf2..d2073e8773f18 100644
--- a/x-pack/test/licensing_plugin/apis/header.ts
+++ b/x-pack/test/licensing_plugin/server/header.ts
@@ -7,6 +7,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../services';
+// eslint-disable-next-line import/no-default-export
export default function({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
diff --git a/x-pack/test/licensing_plugin/apis/index.ts b/x-pack/test/licensing_plugin/server/index.ts
similarity index 76%
rename from x-pack/test/licensing_plugin/apis/index.ts
rename to x-pack/test/licensing_plugin/server/index.ts
index fbc0449dcd8fc..374bfcc0aa6b4 100644
--- a/x-pack/test/licensing_plugin/apis/index.ts
+++ b/x-pack/test/licensing_plugin/server/index.ts
@@ -6,13 +6,14 @@
import { FtrProviderContext } from '../services';
+// eslint-disable-next-line import/no-default-export
export default function({ loadTestFile }: FtrProviderContext) {
- describe('Licensing plugin', function() {
+ describe('Licensing plugin server client', function() {
this.tags('ciGroup2');
loadTestFile(require.resolve('./info'));
loadTestFile(require.resolve('./header'));
// MUST BE LAST! CHANGES LICENSE TYPE!
- loadTestFile(require.resolve('./changes'));
+ loadTestFile(require.resolve('./updates'));
});
}
diff --git a/x-pack/test/licensing_plugin/apis/info.ts b/x-pack/test/licensing_plugin/server/info.ts
similarity index 95%
rename from x-pack/test/licensing_plugin/apis/info.ts
rename to x-pack/test/licensing_plugin/server/info.ts
index 7ec009d85cd09..cce042c718b7b 100644
--- a/x-pack/test/licensing_plugin/apis/info.ts
+++ b/x-pack/test/licensing_plugin/server/info.ts
@@ -7,6 +7,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../services';
+// eslint-disable-next-line import/no-default-export
export default function({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
diff --git a/x-pack/test/licensing_plugin/server/updates.ts b/x-pack/test/licensing_plugin/server/updates.ts
new file mode 100644
index 0000000000000..ca0fb37069b3f
--- /dev/null
+++ b/x-pack/test/licensing_plugin/server/updates.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../services';
+import { createScenario } from '../scenario';
+import '../../../../test/plugin_functional/plugins/core_provider_plugin/types';
+
+// eslint-disable-next-line import/no-default-export
+export default function(ftrContext: FtrProviderContext) {
+ const { getService } = ftrContext;
+ const testSubjects = getService('testSubjects');
+
+ const scenario = createScenario(ftrContext);
+
+ describe('changes in license types', () => {
+ after(async () => {
+ await scenario.startBasic();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ await scenario.teardown();
+ });
+
+ it('provides changes in license types', async () => {
+ await scenario.setup();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ const initialLicense = await scenario.getLicense();
+ expect(initialLicense.license?.type).to.be('basic');
+ // security enabled explicitly in test config
+ expect(initialLicense.features?.security).to.eql({
+ isAvailable: true,
+ isEnabled: true,
+ });
+
+ // license hasn't changed
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ const refetchedLicense = await scenario.getLicense();
+ expect(refetchedLicense.license?.type).to.be('basic');
+ expect(refetchedLicense.signature).to.be(initialLicense.signature);
+
+ await scenario.startTrial();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ const trialLicense = await scenario.getLicense();
+ expect(trialLicense.license?.type).to.be('trial');
+ expect(trialLicense.signature).to.not.be(initialLicense.signature);
+
+ expect(trialLicense.features?.security).to.eql({
+ isAvailable: true,
+ isEnabled: true,
+ });
+
+ await scenario.startBasic();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ const basicLicense = await scenario.getLicense();
+ expect(basicLicense.license?.type).to.be('basic');
+ expect(basicLicense.signature).not.to.be(initialLicense.signature);
+
+ expect(basicLicense.features?.security).to.eql({
+ isAvailable: true,
+ isEnabled: true,
+ });
+
+ await scenario.deleteLicense();
+ await scenario.waitForPluginToDetectLicenseUpdate();
+ const inactiveLicense = await scenario.getLicense();
+ expect(inactiveLicense.signature).to.not.be(initialLicense.signature);
+ expect(inactiveLicense).to.not.have.property('license');
+ expect(inactiveLicense.features?.security).to.eql({
+ isAvailable: false,
+ isEnabled: true,
+ });
+
+ // banner shown only when license expired not just deleted
+ await testSubjects.missingOrFail('licenseExpiredBanner');
+ });
+ });
+}