diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 5c54fb6ef1ce4..000943bf62d8e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -5,6 +5,10 @@
# The #CC# prefix delineates Code Coverage,
# used for the 'team' designator within Kibana Stats
+# Tech leads
+/dev_docs @elastic/kibana-tech-leads
+/packages/kbn-docs-utils/ @elastic/kibana-tech-leads @elastic/kibana-operations
+
# App
/x-pack/plugins/discover_enhanced/ @elastic/kibana-app
/x-pack/plugins/lens/ @elastic/kibana-app
@@ -201,6 +205,7 @@
/test/functional/services/remote @elastic/kibana-qa
# Core
+/examples/hello_world/ @elastic/kibana-core
/src/core/ @elastic/kibana-core
/src/plugins/saved_objects_tagging_oss @elastic/kibana-core
/config/kibana.yml @elastic/kibana-core
diff --git a/dev_docs/dev_welcome.mdx b/dev_docs/dev_welcome.mdx
deleted file mode 100644
index cc185e689fa43..0000000000000
--- a/dev_docs/dev_welcome.mdx
+++ /dev/null
@@ -1,17 +0,0 @@
----
-id: kibDevDocsWelcome
-slug: /kibana-dev-docs/welcome
-title: Welcome
-summary: Build custom solutions and applications on top of Kibana.
-date: 2021-01-02
-tags: ['kibana','dev', 'contributor']
----
-
-Welcome to Kibana's plugin developer documentation!
-
-Did you know that the vast majority of functionality built inside of Kibana is a plugin? A handful of core services hold the system together,
-but it's our vast system of plugin developers that provide the amazing, out of the box, functionality you can use when building your own set of
-custom utilities and applications.
-
-Browse the `Services` section to view all the plugins that offer functionality you can take advantage of, or check out the
-`API documentation` to dig into the nitty gritty details of every public plugin API.
diff --git a/dev_docs/getting_started/dev_welcome.mdx b/dev_docs/getting_started/dev_welcome.mdx
new file mode 100644
index 0000000000000..3d645b4e54d66
--- /dev/null
+++ b/dev_docs/getting_started/dev_welcome.mdx
@@ -0,0 +1,20 @@
+---
+id: kibDevDocsWelcome
+slug: /kibana-dev-docs/welcome
+title: Welcome
+summary: Build custom solutions and applications on top of Kibana.
+date: 2021-01-02
+tags: ['kibana', 'dev', 'contributor']
+---
+
+[Kibana](https://www.elastic.co/what-is/kibana) is a pluggable platform that allows users to search, visualize and analyze data in Elasticsearch.
+
+Kibana ships with many out-of-the-box capabilities that can be extended and enhanced by custom javascript plugins. Developers can also write their own custom applications.
+
+Recommended next reading:
+
+1.
+2. Create a simple .
+
+Check out our to dig into the nitty gritty details of
+every public plugin API.
diff --git a/dev_docs/getting_started/hello_world_generated.png b/dev_docs/getting_started/hello_world_generated.png
new file mode 100644
index 0000000000000..57f389389b794
Binary files /dev/null and b/dev_docs/getting_started/hello_world_generated.png differ
diff --git a/dev_docs/getting_started/hello_world_manual.png b/dev_docs/getting_started/hello_world_manual.png
new file mode 100644
index 0000000000000..bb36aab32f392
Binary files /dev/null and b/dev_docs/getting_started/hello_world_manual.png differ
diff --git a/dev_docs/getting_started/hello_world_plugin.mdx b/dev_docs/getting_started/hello_world_plugin.mdx
new file mode 100644
index 0000000000000..d3b30b240dedc
--- /dev/null
+++ b/dev_docs/getting_started/hello_world_plugin.mdx
@@ -0,0 +1,157 @@
+---
+id: kibHelloWorldApp
+slug: /kibana-dev-docs/hello-world-app
+title: Hello World
+summary: Build a very basic plugin that registers an application that says "Hello World!".
+date: 2021-08-03
+tags: ['kibana', 'dev', 'contributor', 'tutorials']
+---
+
+This tutorial walks you through two ways to create a plugin that registers an application that says "Hello World!".
+
+You can view the tested example plugin at [examples/hello_world](https://github.com/elastic/kibana/tree/master/examples/hello_world).
+
+## 1. Set up your development environment
+
+Read through to get your development environment set up.
+
+## 2. Option 1 - Write it manually
+
+This is a good option if you want to understand the bare minimum needed to register a "Hello world" application. The example plugin is based off of this option.
+
+1. Create your plugin folder. Start off in the `kibana` folder.
+
+```
+$ cd examples
+$ mkdir hello_world
+$ cd hello_world
+```
+
+2. Create the .
+
+```
+$ touch kibana.json
+```
+
+and add the following:
+
+```
+{
+ "id": "helloWorld",
+ "version": "1.0.0",
+ "kibanaVersion": "kibana",
+ "ui": true
+}
+```
+
+3. Create a `tsconfig.json` file.
+
+```
+$ touch tsconfig.json
+```
+
+And add the following to it:
+
+```
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true
+ },
+ "include": [
+ "index.ts",
+ "common/**/*.ts",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "server/**/*.ts",
+ "../../typings/**/*"
+ ],
+ "exclude": []
+}
+```
+
+4. Create a .
+
+```
+$ mkdir public
+$ touch plugin.tsx
+```
+
+And add the following to it:
+
+```ts
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '../../../src/core/public';
+
+export class HelloWorldPlugin implements Plugin {
+ public setup(core: CoreSetup) {
+ // Register an application into the side navigation menu
+ core.application.register({
+ id: 'helloWorld',
+ title: 'Hello World',
+ async mount({ element }: AppMountParameters) {
+ ReactDOM.render(
Hello World!
, element);
+ return () => ReactDOM.unmountComponentAtNode(element);
+ },
+ });
+ }
+ public start(core: CoreStart) {
+ return {};
+ }
+ public stop() {}
+}
+```
+
+5. Create a .
+
+```
+$ touch index.ts
+```
+
+```ts
+import { HelloWorldPlugin } from './plugin';
+
+export function plugin() {
+ return new HelloWorldPlugin();
+}
+```
+
+## 2. Option 2 - Use the automatic plugin generator
+
+This is an easy option to get up and running ASAP and includes additional code.
+
+Use the Automatic plugin generator to get a basic structure for a new plugin. Plugins that are not part of the Kibana repo should be developed inside the plugins folder. If you are building a new plugin to check in to the Kibana repo, you will choose between a few locations:
+
+`x-pack/plugins` for plugins related to subscription features
+`src/plugins` for plugins related to free features
+`examples` for developer example plugins (these will not be included in the distributables)
+
+```
+% node scripts/generate_plugin hello_world
+? Plugin name (use camelCase) helloWorld
+? Will this plugin be part of the Kibana repository? Yes
+? What type of internal plugin would you like to create Kibana Example
+? Should an UI plugin be generated? Yes
+? Should a server plugin be generated? No
+ succ 🎉
+
+ Your plugin has been created in examples/hello_world
+```
+
+## 3. Build your new application
+
+Run `yarn kbn bootstrap`
+
+## 3. Start Kibana with examples and navigate to your new application
+
+In one terminal window, run `yarn es snapshot --license trial` to boot up Elasticsearch.
+
+In another terminal window, run `yarn start --run-examples` to boot up Kibana and include the example plugins. Your example plugin should show up in the navigation at the very bottom.
+
+If you build it manually it will look something like this:
+
+
+If you built it with the generator, it will look something like this:
+
diff --git a/dev_docs/tutorials/setting_up_a_development_env.mdx b/dev_docs/getting_started/setting_up_a_development_env.mdx
similarity index 87%
rename from dev_docs/tutorials/setting_up_a_development_env.mdx
rename to dev_docs/getting_started/setting_up_a_development_env.mdx
index 449e8b886a44d..04e0511e255b1 100644
--- a/dev_docs/tutorials/setting_up_a_development_env.mdx
+++ b/dev_docs/getting_started/setting_up_a_development_env.mdx
@@ -1,8 +1,8 @@
---
id: kibDevTutorialSetupDevEnv
slug: /kibana-dev-docs/tutorial/setup-dev-env
-title: Setting up a Development Environment
-summary: Learn how to setup a development environemnt for contributing to the Kibana repository
+title: Set up a Development Environment
+summary: Learn how to setup a development environment for contributing to the Kibana repository
date: 2021-04-26
tags: ['kibana', 'onboarding', 'dev', 'architecture', 'setup']
---
@@ -12,11 +12,11 @@ Setting up a development environment is pretty easy.
In order to support Windows development we currently require you to use one of the following:
- - [Git Bash](https://git-scm.com/download/win)
- - [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about)
+- [Git Bash](https://git-scm.com/download/win)
+- [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about)
+Before running the steps below, please make sure you have installed [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) and that you are running all commands in either Git Bash or WSL.
- Before running the steps below, please make sure you have installed [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) and that you are running all commands in either Git Bash or WSL.
## Get the code
diff --git a/dev_docs/tutorials/building_a_plugin.mdx b/dev_docs/key_concepts/anatomy_of_a_plugin.mdx
similarity index 67%
rename from dev_docs/tutorials/building_a_plugin.mdx
rename to dev_docs/key_concepts/anatomy_of_a_plugin.mdx
index e751ce7d01b16..4ff5e403ff851 100644
--- a/dev_docs/tutorials/building_a_plugin.mdx
+++ b/dev_docs/key_concepts/anatomy_of_a_plugin.mdx
@@ -1,13 +1,13 @@
---
id: kibDevTutorialBuildAPlugin
-slug: /kibana-dev-docs/tutorials/build-a-plugin
-title: Kibana plugin tutorial
-summary: Anatomy of a Kibana plugin and how to build one
-date: 2021-02-05
-tags: ['kibana', 'onboarding', 'dev', 'tutorials']
+slug: /kibana-dev-docs/tutorials/anatomy-of-a-plugin
+title: Anatomy of a plugin
+summary: Anatomy of a Kibana plugin.
+date: 2021-08-03
+tags: ['kibana', 'onboarding', 'dev']
---
-Prereading material:
+Pre-reading material:
-
@@ -39,27 +39,50 @@ plugins/
```
{
- "id": "demo",
- "version": "kibana",
+ "id": "examplePluginId",
+ "version": "1.0.0",
+ "kibanaVersion": "7.14.0",
"server": true,
"ui": true,
- "owner": { [1]
+ "configPath": "path/to/config",
+ "type": "standard",
+ "owner": {
"name": "App Services",
"githubTeam": "kibana-app-services"
},
- "description": "This plugin extends Kibana by doing xyz, and allows other plugins to extend Kibana by offering abc functionality. It also exposes some helper utilities that do efg", [2]
- "requiredPlugins": ["data"], [3]
- "optionalPlugins": ["alerting"] [4]
- "requiredBundles": ["anotherPlugin"] [5]
+ "description": "A description about this plugin!",
+ "requiredPlugins": ["data"],
+ "optionalPlugins": ["alerting"]
+ "requiredBundles": ["anotherPlugin"]
}
```
-[1], [2]: Every internal plugin should fill in the owner and description properties.
+`id` - [Required] The id of your plugin can be anything, though it should be fairly unique, as every plugin in an installation needs to be unique. It must be snakeCase.
+
+`version` - [Required] Note the version of your plugin. For internal plugins that don't specify a `kibanaVersion`, this will have to match the version of Kibana or ci will fail. Because teams won't want to be bumping this number for every release, internal plugins should set `kibanaVersion` to "kibana", and set this to anything.
+
+`kibanaVersion` - [Optional] If your plugin is only compatible with a specific version of Kibana, put it here. Internal, first-party plugins should set this to "kibana", otherwise they will need to bump this value, or the one in `version`, every time the Kibana version is bumped. When [#61087](https://github.com/elastic/kibana/issues/61087) is fixed, we will stop requiring this field for internal plugins.
+
+`server` - [Optional] If your plugin contains server-side code, this must be true.
+
+`ui` - [Optional] If your plugin contains client-side code, this must be true.
+
+`configPath` - [Optional] Every plugin might allow Kibana users to adjust configuration via kibana.yml file. If your plugin needs to read config from `kibana.yml , you should declare what property name it should have access to.
+
+`type` - [Optional] Should be either `preboot` or `standard` which specifies the type of plugin. Default value, if unspecified, is `standard`. There are two types of plugins:
+
+- preboot plugins are bootstrapped to prepare the environment before Kibana starts.
+- standard plugins define Kibana functionality while Kibana is running.
+
+`owner` - [Required] Help users of your plugin know who manages this plugin and how to get in touch. This is required for internal plugins. `Owner.name` should be the name of the team that manages this plugin. This should match the team that owns this code in the [CODEOWNERS](https://github.com/elastic/kibana/blob/master/.github/CODEOWNERS) file (however, this is not currently enforced). Internal teams should also use a [GitHub team alias](https://github.com/orgs/elastic/teams) for `owner.githubTeam`. While many teams can contribute to a plugin, only a single team should be the primary owner.
+
+`description` - [Required] Give your plugin a description to help other developers understand what it does. This is required for internal plugins.
-[3], [4]: Any plugin that you have a dependency on should be listed in `requiredPlugins` or `optionalPlugins`. Doing this will ensure that you have access to that plugin's start and setup contract inside your own plugin's start and setup lifecycle methods. If a plugin you optionally depend on is not installed or disabled, it will be undefined if you try to access it. If a plugin you require is not installed or disabled, kibana will fail to build.
+`requiredPlugins` - [Optional] If your plugin requires any other plugins to work, you must list them here by id. If any of the required plugins are disabled or not installed, then your plugin will be disabled.
-[5]: Don't worry too much about getting 5 right. The build optimizer will complain if any of these values are incorrect.
+`optionalPlugins` - [Optional] If your plugin has an optional dependency on other plugins, you must list them here by id. If any of the optional plugins are disabled or not installed, your plugin will still load, however that plugin's API contract will be undefined in the second parameter of the setup and start functions.
+`requiredBundles` - [Required in certain situations] Don't worry about getting this right. The build optimizer will complain if any of these values are incorrect.
You don't need to declare a dependency on a plugin if you only wish to access its types.
@@ -233,7 +256,7 @@ With that specified in the plugin manifest, the appropriate interfaces are then
import type { CoreSetup, CoreStart } from 'kibana/server';
import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server';
-interface DemoSetupPlugins { [1]
+interface DemoSetupPlugins { [1];
foobar: FoobarPluginSetup;
}
@@ -242,13 +265,13 @@ interface DemoStartPlugins {
}
export class DemoPlugin {
- public setup(core: CoreSetup, plugins: DemoSetupPlugins) { [2]
+ public setup(core: CoreSetup, plugins: DemoSetupPlugins) { [2];
const { foobar } = plugins;
foobar.getFoo(); // 'foo'
foobar.getBar(); // throws because getBar does not exist
}
- public start(core: CoreStart, plugins: DemoStartPlugins) { [3]
+ public start(core: CoreStart, plugins: DemoStartPlugins) { [3];
const { foobar } = plugins;
foobar.getFoo(); // throws because getFoo does not exist
foobar.getBar(); // 'bar'
diff --git a/dev_docs/tutorials/testing_plugins.mdx b/dev_docs/tutorials/testing_plugins.mdx
new file mode 100644
index 0000000000000..96e13555a36a2
--- /dev/null
+++ b/dev_docs/tutorials/testing_plugins.mdx
@@ -0,0 +1,1367 @@
+---
+id: kibDevTutorialTestingPlugins
+slug: /kibana-dev-docs/tutorial/testing-plugins
+title: Testing Kibana Plugins
+summary: Learn how to test different aspects of Kibana plugins
+date: 2021-07-05
+tags: ['kibana', 'onboarding', 'dev', 'architecture', 'testing']
+---
+
+This document outlines best practices and patterns for testing Kibana Plugins.
+
+## Strategy
+
+In general, we recommend three tiers of tests:
+- Unit tests: small, fast, exhaustive, make heavy use of mocks for external dependencies
+- Integration tests: higher-level tests that verify interactions between systems (eg. HTTP APIs, Elasticsearch API calls, calling other plugin contracts).
+- End-to-end tests (e2e): tests that verify user-facing behavior through the browser
+
+These tiers should roughly follow the traditional ["testing pyramid"](https://martinfowler.com/articles/practical-test-pyramid.html), where there are more exhaustive testing at the unit level, fewer at the integration level, and very few at the functional level.
+
+## Core Integrations
+
+### Core Mocks
+
+When testing a plugin's integration points with Core APIs, it is heavily recommended to utilize the mocks provided in `src/core/server/mocks` and `src/core/public/mocks`. The majority of these mocks are dumb `jest` mocks that mimic the interface of their respective Core APIs, however they do not return realistic return values.
+
+If the unit under test expects a particular response from a Core API, the test will need to set this return value explicitly. The return values are type checked to match the Core API where possible to ensure that mocks are updated when Core APIs changed.
+
+#### Example
+
+```typescript
+import { elasticsearchServiceMock } from 'src/core/server/mocks';
+
+test('my test', async () => {
+ // Setup mock and faked response
+ const esClient = elasticsearchServiceMock.createScopedClusterClient();
+ esClient.callAsCurrentUser.mockResolvedValue(/** insert ES response here */);
+
+ // Call unit under test with mocked client
+ const result = await myFunction(esClient);
+
+ // Assert that client was called with expected arguments
+ expect(esClient.callAsCurrentUser).toHaveBeenCalledWith(/** expected args */);
+ // Expect that unit under test returns expected value based on client's response
+ expect(result).toEqual(/** expected return value */)
+});
+```
+
+## Strategies for specific Core APIs
+
+### HTTP Routes
+The HTTP API interface is another public contract of Kibana, although not every Kibana endpoint is for external use. When evaluating the required level of test coverage for an HTTP resource, make your judgment based on whether an endpoint is considered to be public or private. Public API is expected to have a higher level of test coverage.
+Public API tests should cover the **observable behavior** of the system, therefore they should be close to the real user interactions as much as possible, ideally by using HTTP requests to communicate with the Kibana server as a real user would do.
+
+##### Preconditions
+We are going to add tests for `myPlugin` plugin that allows to format user-provided text, store and retrieve it later.
+The plugin has *thin* route controllers isolating all the network layer dependencies and delegating all the logic to the plugin model.
+
+```typescript
+class TextFormatter {
+ public static async format(text: string, sanitizer: Deps['sanitizer']) {
+ // sanitizer.sanitize throws MisformedTextError when passed text contains HTML markup
+ const sanitizedText = await sanitizer.sanitize(text);
+ return sanitizedText;
+ }
+
+ public static async save(text: string, savedObjectsClient: SavedObjectsClient) {
+ const { id } = await savedObjectsClient.update('myPlugin-type', 'myPlugin', {
+ userText: text
+ });
+ return { id };
+ }
+
+ public static async getById(id: string, savedObjectsClient: SavedObjectsClient) {
+ const { attributes } = await savedObjectsClient.get('myPlugin-type', id);
+ return { text: attributes.userText };
+ }
+}
+router.get(
+ {
+ path: '/myPlugin/formatter',
+ validate: {
+ query: schema.object({
+ text: schema.string({ maxLength: 100 }),
+ }),
+ },
+ },
+ async (context, request, response) => {
+ try {
+ const formattedText = await TextFormatter.format(request.query.text, deps.sanitizer);
+ return response.ok({ body: formattedText });
+ } catch(error) {
+ if (error instanceof MisformedTextError) {
+ return response.badRequest({ body: error.message })
+ }
+
+ throw e;
+ }
+ }
+);
+router.post(
+ {
+ path: '/myPlugin/formatter/text',
+ validate: {
+ body: schema.object({
+ text: schema.string({ maxLength: 100 }),
+ }),
+ },
+ },
+ async (context, request, response) => {
+ try {
+ const { id } = await TextFormatter.save(request.query.text, context.core.savedObjects.client);
+ return response.ok({ body: { id } });
+ } catch(error) {
+ if (SavedObjectsErrorHelpers.isConflictError(error)) {
+ return response.conflict({ body: error.message })
+ }
+ throw e;
+ }
+ }
+);
+
+router.get(
+ {
+ path: '/myPlugin/formatter/text/{id}',
+ validate: {
+ params: schema.object({
+ id: schema.string(),
+ }),
+ },
+ },
+ async (context, request, response) => {
+ try {
+ const { text } = await TextFormatter.getById(request.params.id, context.core.savedObjects.client);
+ return response.ok({
+ body: text
+ });
+ } catch(error) {
+ if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
+ return response.notFound()
+ }
+ throw e;
+ }
+ }
+);
+```
+
+#### Unit testing
+Unit tests provide the simplest and fastest way to test the logic in your route controllers and plugin models.
+Use them whenever adding an integration test is hard and slow due to complex setup or the number of logic permutations.
+Since all external core and plugin dependencies are mocked, you don't have the guarantee that the whole system works as
+expected.
+
+Pros:
+- fast
+- easier to debug
+
+Cons:
+- doesn't test against real dependencies
+- doesn't cover integration with other plugins
+
+###### Example
+You can leverage existing unit-test infrastructure for this. You should add `*.test.ts` file and use dependencies mocks to cover the functionality with a broader test suit that covers:
+- input permutations
+- input edge cases
+- expected exception
+- interaction with dependencies
+```typescript
+// src/plugins/my_plugin/server/formatter.test.ts
+describe('TextFormatter', () => {
+ describe('format()', () => {
+ const sanitizer = sanitizerMock.createSetup();
+ sanitizer.sanitize.mockImplementation((input: string) => `sanitizer result:${input}`);
+
+ it('formats text to a ... format', async () => {
+ expect(await TextFormatter.format('aaa', sanitizer)).toBe('...');
+ });
+
+ it('calls Sanitizer.sanitize with correct arguments', async () => {
+ await TextFormatter.format('aaa', sanitizer);
+ expect(sanitizer.sanitize).toHaveBeenCalledTimes(1);
+ expect(sanitizer.sanitize).toHaveBeenCalledWith('aaa');
+ });
+
+ it('throws MisformedTextError if passed string contains banned symbols', async () => {
+ sanitizer.sanitize.mockRejectedValueOnce(new MisformedTextError());
+ await expect(TextFormatter.format('any', sanitizer)).rejects.toThrow(MisformedTextError);
+ });
+ // ... other tests
+ });
+});
+```
+
+#### Integration tests
+Depending on the number of external dependencies, you can consider implementing several high-level integration tests.
+They would work as a set of [smoke tests](https://en.wikipedia.org/wiki/Smoke_testing_(software)) for the most important functionality.
+
+Main subjects for tests should be:
+- authenticated / unauthenticated access to an endpoint.
+- endpoint validation (params, query, body).
+- main business logic.
+- dependencies on other plugins.
+
+##### Functional Test Runner
+If your plugin relies on the elasticsearch server to store data and supports additional configuration, you can leverage the Functional Test Runner(FTR) to implement integration tests.
+FTR bootstraps an elasticsearch and a Kibana instance and runs the test suite against it.
+
+Pros:
+- runs the whole Elastic stack
+- tests cross-plugin integration
+- emulates a real user interaction with the stack
+- allows adjusting config values
+
+Cons:
+- slow start
+- hard to debug
+- brittle tests
+
+###### Example
+You can reuse existing [api_integration](https://github.com/elastic/kibana/blob/master/test/api_integration/config.js) setup by registering a test file within a
+[test loader](https://github.com/elastic/kibana/blob/master/test/api_integration/apis/index.ts). More about the existing FTR setup in the
+[contribution guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-specific-kibana-tests)
+
+The tests cover:
+- authenticated / non-authenticated user access (when applicable)
+```typescript
+// test/api_integration/apis/my_plugin/something.ts
+export default function({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+ const security = getService('security');
+
+ describe('myPlugin', () => {
+ it('returns limited info when not authenticated', async () => {
+ await security.logout();
+ const response = await supertest
+ .get('/myPlugin/health')
+ .set('content-type', 'application/json')
+ .expect(200);
+
+ expect(response.body).to.have.property('basicInfo');
+ expect(response.body).not.to.have.property('detailedInfo');
+ });
+
+ it('returns detailed info when authenticated', async () => {
+ await security.loginAsSuperUser();
+ const response = await supertest
+ .get('/myPlugin/health')
+ .set('content-type', 'application/json')
+ .expect(200);
+
+ expect(response.body).to.have.property('basicInfo');
+ expect(response.body).to.have.property('detailedInfo');
+ });
+ });
+```
+- request validation
+```typescript
+// test/api_integration/apis/my_plugin/something.ts
+export default function({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+
+ describe('myPlugin', () => {
+ it('validate params before to store text', async () => {
+ const response = await supertest
+ .post('/myPlugin/formatter/text')
+ .set('content-type', 'application/json')
+ .send({ text: 'aaa'.repeat(100) })
+ .expect(400);
+
+ expect(response.body).to.have.property('message');
+ expect(response.body.message).to.contain('must have a maximum length of [100]');
+ });
+ });
+```
+- the main logic of the plugin
+```typescript
+export default function({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+ describe('myPlugin', () => {
+ it('stores text', async () => {
+ const response = await supertest
+ .post('/myPlugin/formatter/text')
+ .set('content-type', 'application/json')
+ .send({ text: 'aaa' })
+ .expect(200);
+
+ expect(response.body).to.have.property('id');
+ expect(response.body.id).to.be.a('string');
+ });
+
+ it('retrieves text', async () => {
+ const { body } = await supertest
+ .post('/myPlugin/formatter/text')
+ .set('content-type', 'application/json')
+ .send({ text: 'bbb' })
+ .expect(200);
+
+ const response = await supertest.get(`/myPlugin/formatter/text/${body.id}`).expect(200);
+ expect(response.text).be('bbb');
+ });
+
+ it('returns NotFound error when cannot find a text', async () => {
+ await supertest
+ .get('/myPlugin/something/missing')
+ .expect(404, 'Saved object [myPlugin-type/missing] not found');
+ });
+ });
+```
+
+##### TestUtils
+It can be utilized if your plugin doesn't interact with the elasticsearch server or mocks the own methods doing so.
+Runs tests against real Kibana server instance.
+
+Pros:
+- runs the real Kibana instance
+- tests cross-plugin integration
+- emulates a real user interaction with the HTTP resources
+
+Cons:
+- faster than FTR because it doesn't run elasticsearch instance, but still slow
+- hard to debug
+- doesn't cover Kibana CLI logic
+
+###### Example
+To have access to Kibana TestUtils, you should create `integration_tests` folder and import `test_utils` within a test file:
+```typescript
+// src/plugins/my_plugin/server/integration_tests/formatter.test.ts
+import * as kbnTestServer from 'src/core/test_helpers/kbn_server';
+
+describe('myPlugin', () => {
+ describe('GET /myPlugin/formatter', () => {
+ let root: ReturnType;
+ beforeAll(async () => {
+ root = kbnTestServer.createRoot();
+ await root.preboot();
+ await root.setup();
+ await root.start();
+ }, 30000);
+
+ afterAll(async () => await root.shutdown());
+ it('validates given text', async () => {
+ const response = await kbnTestServer.request
+ .get(root, '/myPlugin/formatter')
+ .query({ text: 'input string'.repeat(100) })
+ .expect(400);
+
+ expect(response.body).toHaveProperty('message');
+ });
+
+ it('formats given text', async () => {
+ const response = await kbnTestServer.request
+ .get(root, '/myPlugin/formatter')
+ .query({ text: 'input string' })
+ .expect(200);
+
+ expect(response.text).toBe('...');
+ });
+
+ it('returns BadRequest if passed string contains banned symbols', async () => {
+ await kbnTestServer.request
+ .get(root, '/myPlugin/formatter')
+ .query({ text: '