diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy
new file mode 100644
index 0000000000000..5cf6efe324ac3
--- /dev/null
+++ b/.ci/end2end.groovy
@@ -0,0 +1,125 @@
+#!/usr/bin/env groovy
+
+library identifier: 'apm@current',
+retriever: modernSCM(
+ [$class: 'GitSCMSource',
+ credentialsId: 'f94e9298-83ae-417e-ba91-85c279771570',
+ id: '37cf2c00-2cc7-482e-8c62-7bbffef475e2',
+ remote: 'git@github.com:elastic/apm-pipeline-library.git'])
+
+pipeline {
+ agent { label 'linux && immutable' }
+ environment {
+ BASE_DIR = 'src/github.com/elastic/kibana'
+ HOME = "${env.WORKSPACE}"
+ APM_ITS = 'apm-integration-testing'
+ CYPRESS_DIR = 'x-pack/legacy/plugins/apm/cypress'
+ PIPELINE_LOG_LEVEL = 'DEBUG'
+ }
+ options {
+ timeout(time: 1, unit: 'HOURS')
+ buildDiscarder(logRotator(numToKeepStr: '40', artifactNumToKeepStr: '20', daysToKeepStr: '30'))
+ timestamps()
+ ansiColor('xterm')
+ disableResume()
+ durabilityHint('PERFORMANCE_OPTIMIZED')
+ }
+ triggers {
+ issueCommentTrigger('(?i).*jenkins\\W+run\\W+(?:the\\W+)?e2e(?:\\W+please)?.*')
+ }
+ parameters {
+ booleanParam(name: 'FORCE', defaultValue: false, description: 'Whether to force the run.')
+ }
+ stages {
+ stage('Checkout') {
+ options { skipDefaultCheckout() }
+ steps {
+ deleteDir()
+ gitCheckout(basedir: "${BASE_DIR}", githubNotifyFirstTimeContributor: false,
+ shallow: false, reference: "/var/lib/jenkins/.git-references/kibana.git")
+ script {
+ dir("${BASE_DIR}"){
+ def regexps =[ "^x-pack/legacy/plugins/apm/.*" ]
+ env.APM_UPDATED = isGitRegionMatch(patterns: regexps)
+ }
+ }
+ dir("${APM_ITS}"){
+ git changelog: false,
+ credentialsId: 'f6c7695a-671e-4f4f-a331-acdce44ff9ba',
+ poll: false,
+ url: "git@github.com:elastic/${APM_ITS}.git"
+ }
+ }
+ }
+ stage('Start services') {
+ options { skipDefaultCheckout() }
+ when {
+ anyOf {
+ expression { return params.FORCE }
+ expression { return env.APM_UPDATED != "false" }
+ }
+ }
+ steps {
+ dir("${APM_ITS}"){
+ sh './scripts/compose.py start master --no-kibana --no-xpack-secure'
+ }
+ }
+ }
+ stage('Prepare Kibana') {
+ options { skipDefaultCheckout() }
+ when {
+ anyOf {
+ expression { return params.FORCE }
+ expression { return env.APM_UPDATED != "false" }
+ }
+ }
+ environment {
+ JENKINS_NODE_COOKIE = 'dontKillMe'
+ }
+ steps {
+ dir("${BASE_DIR}"){
+ sh script: "${CYPRESS_DIR}/ci/prepare-kibana.sh"
+ }
+ }
+ }
+ stage('Smoke Tests'){
+ options { skipDefaultCheckout() }
+ when {
+ anyOf {
+ expression { return params.FORCE }
+ expression { return env.APM_UPDATED != "false" }
+ }
+ }
+ steps{
+ dir("${BASE_DIR}"){
+ sh '''
+ jobs -l
+ docker build --tag cypress ${CYPRESS_DIR}/ci
+ docker run --rm -t --user "$(id -u):$(id -g)" \
+ -v `pwd`:/app --network="host" \
+ --name cypress cypress'''
+ }
+ }
+ post {
+ always {
+ dir("${BASE_DIR}"){
+ archiveArtifacts(allowEmptyArchive: false, artifacts: "${CYPRESS_DIR}/screenshots/**,${CYPRESS_DIR}/videos/**,${CYPRESS_DIR}/*e2e-tests.xml")
+ junit(allowEmptyResults: true, testResults: "${CYPRESS_DIR}/*e2e-tests.xml")
+ }
+ dir("${APM_ITS}"){
+ sh 'docker-compose logs > apm-its.log || true'
+ sh 'docker-compose down -v || true'
+ archiveArtifacts(allowEmptyArchive: false, artifacts: 'apm-its.log')
+ }
+ }
+ }
+ }
+ }
+ post {
+ always {
+ dir("${BASE_DIR}"){
+ archiveArtifacts(allowEmptyArchive: true, artifacts: "${CYPRESS_DIR}/ingest-data.log,kibana.log")
+ }
+ }
+ }
+}
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 610681e83798e..c5e6768c17d46 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -5,6 +5,7 @@
# App
/x-pack/legacy/plugins/lens/ @elastic/kibana-app
/x-pack/legacy/plugins/graph/ @elastic/kibana-app
+/src/legacy/server/sample_data/ @elastic/kibana-app
# App Architecture
/src/plugins/data/ @elastic/kibana-app-arch
@@ -66,14 +67,25 @@
/packages/kbn-es/ @elastic/kibana-operations
/packages/kbn-pm/ @elastic/kibana-operations
/packages/kbn-test/ @elastic/kibana-operations
+/src/legacy/server/keystore/ @elastic/kibana-operations
+/src/legacy/server/pid/ @elastic/kibana-operations
+/src/legacy/server/sass/ @elastic/kibana-operations
+/src/legacy/server/utils/ @elastic/kibana-operations
+/src/legacy/server/warnings/ @elastic/kibana-operations
# Platform
/src/core/ @elastic/kibana-platform
-/src/legacy/server/saved_objects/ @elastic/kibana-platform
/config/kibana.yml @elastic/kibana-platform
/x-pack/plugins/features/ @elastic/kibana-platform
/x-pack/plugins/licensing/ @elastic/kibana-platform
/packages/kbn-config-schema/ @elastic/kibana-platform
+/src/legacy/server/config/ @elastic/kibana-platform
+/src/legacy/server/csp/ @elastic/kibana-platform
+/src/legacy/server/http/ @elastic/kibana-platform
+/src/legacy/server/i18n/ @elastic/kibana-platform
+/src/legacy/server/logging/ @elastic/kibana-platform
+/src/legacy/server/saved_objects/ @elastic/kibana-platform
+/src/legacy/server/status/ @elastic/kibana-platform
# Security
/x-pack/legacy/plugins/security/ @elastic/kibana-security
diff --git a/.i18nrc.json b/.i18nrc.json
index e5ba6762da154..fac9b9ce53184 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -19,6 +19,7 @@
"kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types",
"kibana_react": "src/legacy/core_plugins/kibana_react",
"kibana-react": "src/plugins/kibana_react",
+ "kibana_utils": "src/plugins/kibana_utils",
"navigation": "src/legacy/core_plugins/navigation",
"newsfeed": "src/plugins/newsfeed",
"regionMap": "src/legacy/core_plugins/region_map",
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 53e44fbede724..599cf26970030 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -387,6 +387,14 @@ To activate the APM agent, use the [`active`](https://www.elastic.co/guide/en/ap
All config options can be set either via environment variables, or by creating an appropriate config file under `config/apm.dev.js`.
For more information about configuring the APM agent, please refer to [the documentation](https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuring-the-agent.html).
+Example `config/apm.dev.js` file:
+
+```js
+module.exports = {
+ active: true,
+};
+```
+
Once the agent is active, it will trace all incoming HTTP requests to Kibana, monitor for errors, and collect process-level metrics.
The collected data will be sent to the APM Server and is viewable in the APM UI in Kibana.
@@ -405,7 +413,7 @@ The following table outlines possible test file locations and how to invoke them
| Jest | `src/**/*.test.js` `src/**/*.test.ts` | `node scripts/jest -t regexp [test path]` |
| Jest (integration) | `**/integration_tests/**/*.test.js` | `node scripts/jest_integration -t regexp [test path]` |
| Mocha | `src/**/__tests__/**/*.js` `!src/**/public/__tests__/*.js` `packages/kbn-datemath/test/**/*.js` `packages/kbn-dev-utils/src/**/__tests__/**/*.js` `tasks/**/__tests__/**/*.js` | `node scripts/mocha --grep=regexp [test path]` |
-| Functional | `test/*integration/**/config.js` `test/*functional/**/config.js` | `node scripts/functional_tests_server --config test/[directory]/config.js` `node scripts/functional_test_runner --config test/[directory]/config.js --grep=regexp` |
+| Functional | `test/*integration/**/config.js` `test/*functional/**/config.js` `test/accessibility/config.js` | `node scripts/functional_tests_server --config test/[directory]/config.js` `node scripts/functional_test_runner --config test/[directory]/config.js --grep=regexp` |
| Karma | `src/**/public/__tests__/*.js` | `npm run test:dev` |
For X-Pack tests located in `x-pack/` see [X-Pack Testing](x-pack/README.md#testing)
diff --git a/docs/developer/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc
index 6d2c5a72f0532..350a3c2a997cf 100644
--- a/docs/developer/core/development-functional-tests.asciidoc
+++ b/docs/developer/core/development-functional-tests.asciidoc
@@ -98,6 +98,7 @@ When run without any arguments the `FunctionalTestRunner` automatically loads th
* `--config test/functional/config.js` starts Elasticsearch and Kibana servers with the WebDriver tests configured to run in Chrome.
* `--config test/functional/config.firefox.js` starts Elasticsearch and Kibana servers with the WebDriver tests configured to run in Firefox.
* `--config test/api_integration/config.js` starts Elasticsearch and Kibana servers with the api integration tests configuration.
+* `--config test/accessibility/config.ts` starts Elasticsearch and Kibana servers with the WebDriver tests configured to run an accessibility audit using https://www.deque.com/axe/[axe].
There are also command line flags for `--bail` and `--grep`, which behave just like their mocha counterparts. For instance, use `--grep=foo` to run only tests that match a regular expression.
@@ -362,7 +363,7 @@ Full list of services that are used in functional tests can be found here: {blob
** Source: {blob}test/functional/services/remote/remote.ts[test/functional/services/remote/remote.ts]
** Instance of https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html[WebDriver] class
** Responsible for all communication with the browser
-** To perform browser actions, use `remote` service
+** To perform browser actions, use `remote` service
** For searching and manipulating with DOM elements, use `testSubjects` and `find` services
** See the https://seleniumhq.github.io/selenium/docs/api/javascript/[selenium-webdriver docs] for the full API.
diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md
index 960d610b589b8..e01a5e9da50d5 100644
--- a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md
+++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md
@@ -17,6 +17,7 @@ core: {
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
+ savedObjects: SavedObjectsStart;
uiSettings: IUiSettingsClient;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.md
index e12121e0e3ebb..68a1c27b11836 100644
--- a/docs/development/core/public/kibana-plugin-public.appmountcontext.md
+++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.md
@@ -16,5 +16,5 @@ export interface AppMountContext
| Property | Type | Description |
| --- | --- | --- |
-| [core](./kibana-plugin-public.appmountcontext.core.md) | { application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>; chrome: ChromeStart; docLinks: DocLinksStart; http: HttpStart; i18n: I18nStart; notifications: NotificationsStart; overlays: OverlayStart; uiSettings: IUiSettingsClient; injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; }; } | Core service APIs available to mounted applications. |
+| [core](./kibana-plugin-public.appmountcontext.core.md) | { application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>; chrome: ChromeStart; docLinks: DocLinksStart; http: HttpStart; i18n: I18nStart; notifications: NotificationsStart; overlays: OverlayStart; savedObjects: SavedObjectsStart; uiSettings: IUiSettingsClient; injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; }; } | Core service APIs available to mounted applications. |
diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.md b/docs/development/core/public/kibana-plugin-public.chromestart.md
index 153e06d591404..d5d99f3d5be65 100644
--- a/docs/development/core/public/kibana-plugin-public.chromestart.md
+++ b/docs/development/core/public/kibana-plugin-public.chromestart.md
@@ -39,6 +39,7 @@ export interface ChromeStart
| [setBrand(brand)](./kibana-plugin-public.chromestart.setbrand.md) | Set the brand configuration. |
| [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs |
| [setHelpExtension(helpExtension)](./kibana-plugin-public.chromestart.sethelpextension.md) | Override the current set of custom help content |
+| [setHelpSupportUrl(url)](./kibana-plugin-public.chromestart.sethelpsupporturl.md) | Override the default support URL shown in the help menu |
| [setIsCollapsed(isCollapsed)](./kibana-plugin-public.chromestart.setiscollapsed.md) | Set the collapsed state of the chrome navigation. |
| [setIsVisible(isVisible)](./kibana-plugin-public.chromestart.setisvisible.md) | Set the temporary visibility for the chrome. This does nothing if the chrome is hidden by default and should be used to hide the chrome for things like full-screen modes with an exit button. |
diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.sethelpsupporturl.md b/docs/development/core/public/kibana-plugin-public.chromestart.sethelpsupporturl.md
new file mode 100644
index 0000000000000..975283ce59cb7
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-public.chromestart.sethelpsupporturl.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeStart](./kibana-plugin-public.chromestart.md) > [setHelpSupportUrl](./kibana-plugin-public.chromestart.sethelpsupporturl.md)
+
+## ChromeStart.setHelpSupportUrl() method
+
+Override the default support URL shown in the help menu
+
+Signature:
+
+```typescript
+setHelpSupportUrl(url: string): void;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| url | string | |
+
+Returns:
+
+`void`
+
diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md
index cecceb04240e6..1ce18834f5319 100644
--- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md
+++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md
@@ -9,5 +9,5 @@ Search for objects
Signature:
```typescript
-find: (options: Pick) => Promise>;
+find: (options: Pick) => Promise>;
```
diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md
index c4ceb47f66e1b..6033c667c1866 100644
--- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md
+++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md
@@ -20,7 +20,7 @@ export declare class SavedObjectsClient
| [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: { id: string; type: string; }[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id |
| [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object |
| [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object |
-| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects |
+| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects |
| [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object |
## Methods
diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md
index dba0ad8c8560c..25eebf1c06d01 100644
--- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md
+++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md
@@ -23,6 +23,7 @@ export interface HttpServiceSetup
| [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. |
| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. |
| [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. |
+| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. |
| [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. |
## Example
diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md
new file mode 100644
index 0000000000000..9f0eaae8830e1
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md)
+
+## HttpServiceSetup.registerOnPreResponse property
+
+To define custom logic to perform for the server response.
+
+Signature:
+
+```typescript
+registerOnPreResponse: (handler: OnPreResponseHandler) => void;
+```
+
+## Remarks
+
+Doesn't provide the whole response object. Supports extending response with custom headers. See [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md).
+
diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md
index 9144742c9bb73..fceabd1237665 100644
--- a/docs/development/core/server/kibana-plugin-server.md
+++ b/docs/development/core/server/kibana-plugin-server.md
@@ -77,6 +77,9 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. |
| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
+| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. |
+| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. |
+| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [PackageInfo](./kibana-plugin-server.packageinfo.md) | |
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. |
| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. |
@@ -173,6 +176,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation |
| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). |
| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). |
+| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). |
| [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. |
| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. |
| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |
diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md
new file mode 100644
index 0000000000000..8736020daf063
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) > [headers](./kibana-plugin-server.onpreresponseextensions.headers.md)
+
+## OnPreResponseExtensions.headers property
+
+additional headers to attach to the response
+
+Signature:
+
+```typescript
+headers?: ResponseHeaders;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md
new file mode 100644
index 0000000000000..e5aa624c39909
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md)
+
+## OnPreResponseExtensions interface
+
+Additional data to extend a response.
+
+Signature:
+
+```typescript
+export interface OnPreResponseExtensions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [headers](./kibana-plugin-server.onpreresponseextensions.headers.md) | ResponseHeaders | additional headers to attach to the response |
+
diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md
new file mode 100644
index 0000000000000..082de0a9b4aeb
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md)
+
+## OnPreResponseHandler type
+
+See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md).
+
+Signature:
+
+```typescript
+export declare type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md
new file mode 100644
index 0000000000000..736b4298037cf
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md)
+
+## OnPreResponseInfo interface
+
+Response status code.
+
+Signature:
+
+```typescript
+export interface OnPreResponseInfo
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md) | number | |
+
diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md
new file mode 100644
index 0000000000000..4fd4529dc400f
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) > [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md)
+
+## OnPreResponseInfo.statusCode property
+
+Signature:
+
+```typescript
+statusCode: number;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md
new file mode 100644
index 0000000000000..5525f5bf60284
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md)
+
+## OnPreResponseToolkit interface
+
+A tool set defining an outcome of OnPreAuth interceptor for incoming request.
+
+Signature:
+
+```typescript
+export interface OnPreResponseToolkit
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) | (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult | To pass request to the next handler |
+
diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md
new file mode 100644
index 0000000000000..bfb5827b16b2f
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) > [next](./kibana-plugin-server.onpreresponsetoolkit.next.md)
+
+## OnPreResponseToolkit.next property
+
+To pass request to the next handler
+
+Signature:
+
+```typescript
+next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md
index 33a26eef8a7cb..56d064dcb290e 100644
--- a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md
+++ b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md
@@ -8,6 +8,9 @@
```typescript
config: {
+ legacy: {
+ globalConfig$: Observable;
+ };
create: () => Observable;
createIfExists: () => Observable;
};
diff --git a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md
index e7aa32edaa293..c2fadfb779fc9 100644
--- a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md
+++ b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md
@@ -16,7 +16,7 @@ export interface PluginInitializerContext
| Property | Type | Description |
| --- | --- | --- |
-| [config](./kibana-plugin-server.plugininitializercontext.config.md) | { create: <T = ConfigSchema>() => Observable<T>; createIfExists: <T = ConfigSchema>() => Observable<T | undefined>; } | |
+| [config](./kibana-plugin-server.plugininitializercontext.config.md) | { legacy: { globalConfig$: Observable<SharedGlobalConfig>; }; create: <T = ConfigSchema>() => Observable<T>; createIfExists: <T = ConfigSchema>() => Observable<T | undefined>; } | |
| [env](./kibana-plugin-server.plugininitializercontext.env.md) | { mode: EnvironmentMode; packageInfo: Readonly<PackageInfo>; } | |
| [logger](./kibana-plugin-server.plugininitializercontext.logger.md) | LoggerFactory | |
| [opaqueId](./kibana-plugin-server.plugininitializercontext.opaqueid.md) | PluginOpaqueId | |
diff --git a/docs/management/field-formatters/url-formatter.asciidoc b/docs/management/field-formatters/url-formatter.asciidoc
index 2cbade4431202..41d4f75603dc6 100644
--- a/docs/management/field-formatters/url-formatter.asciidoc
+++ b/docs/management/field-formatters/url-formatter.asciidoc
@@ -4,6 +4,8 @@ The `Url` field formatter can take on the following types:
* The *Image* type can be used to specify an image directory where a specified image is located.
* The *Audio* type can be used to specify an audio directory where a specified audio file is located.
+For an *Image* type you can specify width and height attributes. These will be used to set the max width / max height of the image, while keeping the aspect ratio. Image will not be upscaled if it's smaller than the provided size parameters.
+
You can customize either type of URL field formats with templates. A _URL template_ enables you to add specific values
to a partial URL. Use the string `{{value}}` to add the contents of the field to a fixed URL.
diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc
index c983fa2cfe4ba..37fbc6d78c8e4 100644
--- a/docs/maps/connect-to-ems.asciidoc
+++ b/docs/maps/connect-to-ems.asciidoc
@@ -13,11 +13,20 @@ EMS requests are made to the following domains:
* vector.maps.elastic.co
**Elastic Maps** makes requests directly from the browser to EMS.
-To proxy EMS requests through the Kibana server, set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file.
+
+[float]
+=== Connect to Elastic Maps Service from an internal network
+
+To connect to EMS when your Kibana server and browser are in an internal network:
+
+. Set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file to proxy EMS requests through the Kibana server.
+. Update your firewall rules to whitelist connections from your Kibana server to the EMS domains listed above.
+
+NOTE: Coordinate map and region map visualizations do not support `map.proxyElasticMapsServiceInMaps` and will not proxy EMS requests through the Kibana server.
[float]
-=== Disabling Elastic Maps Service
+=== Disable Elastic Maps Service
You might experience EMS connection issues if your Kibana server or browser are on a private network or
behind a firewall. If this happens, you can disable the EMS connection to avoid unnecessary EMS requests.
diff --git a/docs/user/security/images/role-index-privilege.png b/docs/user/security/images/role-index-privilege.png
new file mode 100644
index 0000000000000..1dc1ae640e3ba
Binary files /dev/null and b/docs/user/security/images/role-index-privilege.png differ
diff --git a/docs/user/security/images/role-management.png b/docs/user/security/images/role-management.png
new file mode 100644
index 0000000000000..2a78c69a5e352
Binary files /dev/null and b/docs/user/security/images/role-management.png differ
diff --git a/docs/user/security/images/role-new-user.png b/docs/user/security/images/role-new-user.png
new file mode 100644
index 0000000000000..0e8d75421cca3
Binary files /dev/null and b/docs/user/security/images/role-new-user.png differ
diff --git a/docs/user/security/images/role-space-visualization.png b/docs/user/security/images/role-space-visualization.png
new file mode 100644
index 0000000000000..746af89c66e85
Binary files /dev/null and b/docs/user/security/images/role-space-visualization.png differ
diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc
index f57d1bcd3bc2a..eab3833b3f5ae 100644
--- a/docs/user/security/index.asciidoc
+++ b/docs/user/security/index.asciidoc
@@ -37,4 +37,4 @@ cause Kibana's authorization to behave unexpectedly.
include::authorization/index.asciidoc[]
include::authorization/kibana-privileges.asciidoc[]
include::api-keys/index.asciidoc[]
-
+include::rbac_tutorial.asciidoc[]
diff --git a/docs/user/security/rbac_tutorial.asciidoc b/docs/user/security/rbac_tutorial.asciidoc
new file mode 100644
index 0000000000000..e4dbdc2483f70
--- /dev/null
+++ b/docs/user/security/rbac_tutorial.asciidoc
@@ -0,0 +1,104 @@
+[[space-rbac-tutorial]]
+=== Tutorial: Use role-based access control to customize Kibana spaces
+
+With role-based access control (RBAC), you can provide users access to data, tools,
+and Kibana spaces. In this tutorial, you will learn how to configure roles
+that provide the right users with the right access to the data, tools, and
+Kibana spaces.
+
+[float]
+==== Scenario
+
+Our user is a web developer working on a bank's
+online mortgage service. The web developer has these
+three requirements:
+
+* Have access to the data for that service
+* Build visualizations and dashboards
+* Monitor the performance of the system
+
+You'll provide the web developer with the access and privileges to get the job done.
+
+[float]
+==== Prerequisites
+
+To complete this tutorial, you'll need the following:
+
+* **Administrative privileges**: You must have a role that grants privileges to create a space, role, and user. This is any role which grants the `manage_security` cluster privilege. By default, the `superuser` role provides this access. See the {ref}/built-in-roles.html[built-in] roles.
+* **A space**: In this tutorial, use `Dev Mortgage` as the space
+name. See <> for
+details on creating a space.
+* **Data**: You can use <> or
+live data. In the steps below, Filebeat and Metricbeat data are used.
+
+[float]
+==== Steps
+
+With the requirements in mind, here are the steps that you will work
+through in this tutorial:
+
+* Create a role named `mortgage-developer`
+* Give the role permission to access the data in the relevant indices
+* Give the role permission to create visualizations and dashboards
+* Create the web developer's user account with the proper roles
+
+[float]
+==== Create a role
+
+Go to **Management > Roles**
+for an overview of your roles. This view provides actions
+for you to create, edit, and delete roles.
+
+[role="screenshot"]
+image::security/images/role-management.png["Role management"]
+
+
+You can create as many roles as you like. Click *Create role* and
+provide a name. Use `dev-mortgage` because this role is for a developer
+working on the bank's mortgage application.
+
+
+[float]
+==== Give the role permission to access the data
+
+Access to data in indices is an index-level privilege, so in
+*Index privileges*, add lines for the indices that contain the
+data for this role. Two privileges are required: `read` and
+`view_index_metadata`. All privileges are detailed in the
+https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html[security privileges] documentation.
+
+In the screenshots, Filebeat and Metricbeat data is used, but you
+should use the index patterns for your indices.
+
+[role="screenshot"]
+image::security/images/role-index-privilege.png["Index privilege"]
+
+[float]
+==== Give the role permission to create visualizations and dashboards
+
+By default, roles do not give Kibana privileges. Click **Add space
+privilege** and associate this role with the `Dev Mortgage` space.
+
+To enable users with the `dev-mortgage` role to create visualizations
+and dashboards, click *All* for *Visualize* and *Dashboard*. Also
+assign *All* for *Discover* because it is common for developers
+to create saved searches while designing visualizations.
+
+[role="screenshot"]
+image::security/images/role-space-visualization.png["Associate space"]
+
+[float]
+==== Create the developer's user account with the proper roles
+
+Go to **Management > Users** and click on **Create user** to create a
+user. Give the user the `dev-mortgage` role
+and the `monitoring-user` role, which is required for users of **Stack Monitoring**.
+
+[role="screenshot"]
+image::security/images/role-new-user.png["Developer user"]
+
+Finally, have the developer log in and access the Dev Mortgage space
+and create a new visualization.
+
+NOTE: If the user is assigned to only one space, they will automatically enter that space on login.
+
diff --git a/docs/visualize/most-frequent.asciidoc b/docs/visualize/most-frequent.asciidoc
index 7452f1c4c3d7e..e9085d18185ec 100644
--- a/docs/visualize/most-frequent.asciidoc
+++ b/docs/visualize/most-frequent.asciidoc
@@ -7,20 +7,19 @@ levels of {es} {ref}/search-aggregations-bucket.html[bucket] aggregations.
The most frequently used visualizations include:
-* Line, Area and Bar charts
+* Line, area, and bar charts
* Pie charts
-* Data table
-* Metric visualization
-* Goal and Gauge visualization
+* Data tables
+* Metric, goals, and gauges
* Heat maps
-* Tag cloud
+* Tag clouds
[float]
=== Configure your visualization
-You configure visualizations using the default editor, which is broken into *Metrics* and *Buckets*, and includes a default count
+You configure visualizations using the default editor, which is broken into metrics and buckets, and includes a default count
metric. Each visualization supports different configurations for what the metrics and buckets
-represent. For example, a Bar chart allows you to add an X-axis:
+represent. For example, a bar chart allows you to add an X-axis:
[role="screenshot"]
image::images/add-bucket.png["",height=478]
diff --git a/package.json b/package.json
index 1ff42384f95d5..cf36d4ce884ac 100644
--- a/package.json
+++ b/package.json
@@ -88,7 +88,8 @@
"**/image-diff/gm/debug": "^2.6.9",
"**/react-dom": "^16.12.0",
"**/react-test-renderer": "^16.12.0",
- "**/deepmerge": "^4.2.2"
+ "**/deepmerge": "^4.2.2",
+ "**/serialize-javascript": "^2.1.1"
},
"workspaces": {
"packages": [
@@ -112,7 +113,7 @@
"@elastic/charts": "^14.0.0",
"@elastic/datemath": "5.0.2",
"@elastic/ems-client": "1.0.5",
- "@elastic/eui": "16.0.0",
+ "@elastic/eui": "17.0.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.3.3",
@@ -165,6 +166,7 @@
"encode-uri-query": "1.0.1",
"execa": "^3.2.0",
"expiry-js": "0.1.7",
+ "fast-deep-equal": "^3.1.1",
"file-loader": "4.2.0",
"font-awesome": "4.7.0",
"getos": "^3.1.0",
@@ -228,6 +230,7 @@
"react-resize-detector": "^4.2.0",
"react-router-dom": "^4.3.1",
"react-sizeme": "^2.3.6",
+ "react-use": "^13.10.2",
"reactcss": "1.2.3",
"redux": "4.0.0",
"redux-actions": "2.2.1",
diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json
index 8c5358c82208d..0cc54fa2a64c4 100644
--- a/packages/kbn-test/package.json
+++ b/packages/kbn-test/package.json
@@ -15,7 +15,8 @@
"@kbn/dev-utils": "1.0.0",
"@types/parse-link-header": "^1.0.0",
"@types/strip-ansi": "^5.2.1",
- "@types/xml2js": "^0.4.5"
+ "@types/xml2js": "^0.4.5",
+ "diff": "^4.0.1"
},
"dependencies": {
"chalk": "^2.4.2",
diff --git a/packages/kbn-test/src/failed_tests_reporter/README.md b/packages/kbn-test/src/failed_tests_reporter/README.md
index 1af309ba525b6..20592ecd733b6 100644
--- a/packages/kbn-test/src/failed_tests_reporter/README.md
+++ b/packages/kbn-test/src/failed_tests_reporter/README.md
@@ -12,10 +12,10 @@ copy(`wget "${Array.from($$('a[href$=".xml"]')).filter(a => a.innerText === 'Dow
This copies a script to download the reports, which you should execute in the `test/junit` directory.
-Next, run the CLI in `--dry-run` mode so that it doesn't actually communicate with Github.
+Next, run the CLI in `--no-github-update` mode so that it doesn't actually communicate with Github and `--no-report-update` to prevent the script from mutating the reports on disk and instead log the updated report.
```sh
-node scripts/report_failed_tests.js --verbose --dry-run --build-url foo
+node scripts/report_failed_tests.js --verbose --no-github-update --no-report-update
```
-If you specify the `GITHUB_TOKEN` environment variable then `--dry-run` will execute read operations but still won't execute write operations.
\ No newline at end of file
+Unless you specify the `GITHUB_TOKEN` environment variable requests to read existing issues will use anonymous access which is limited to 60 requests per hour.
\ No newline at end of file
diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts
new file mode 100644
index 0000000000000..02b6b5f064218
--- /dev/null
+++ b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+const Fs = jest.requireActual('fs');
+
+export const FTR_REPORT = Fs.readFileSync(require.resolve('./ftr_report.xml'), 'utf8');
+export const JEST_REPORT = Fs.readFileSync(require.resolve('./jest_report.xml'), 'utf8');
+export const KARMA_REPORT = Fs.readFileSync(require.resolve('./karma_report.xml'), 'utf8');
+export const MOCHA_REPORT = Fs.readFileSync(require.resolve('./mocha_report.xml'), 'utf8');
diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts
new file mode 100644
index 0000000000000..9e800e88bc9ba
--- /dev/null
+++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts
@@ -0,0 +1,350 @@
+/*
+ * 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 Path from 'path';
+
+import { ToolingLog } from '@kbn/dev-utils';
+// @ts-ignore
+import { createPatch } from 'diff';
+
+// turns out Jest can't encode xml diffs in their JUnit reports...
+expect.addSnapshotSerializer({
+ test: v => typeof v === 'string' && (v.includes('<') || v.includes('>')),
+ print: v =>
+ v
+ .replace(//g, '›')
+ .replace(/^\s+$/gm, ''),
+});
+
+jest.mock('fs', () => {
+ const realFs = jest.requireActual('fs');
+ return {
+ readFile: realFs.read,
+ writeFile: (...args: any[]) => {
+ setTimeout(args[args.length - 1], 0);
+ },
+ };
+});
+
+import { FTR_REPORT, JEST_REPORT, MOCHA_REPORT, KARMA_REPORT } from './__fixtures__';
+import { parseTestReport } from './test_report';
+import { addMessagesToReport } from './add_messages_to_report';
+
+beforeEach(() => {
+ jest.resetAllMocks();
+});
+
+const log = new ToolingLog();
+
+it('rewrites ftr reports with minimal changes', async () => {
+ const xml = await addMessagesToReport({
+ report: await parseTestReport(FTR_REPORT),
+ messages: [
+ {
+ name: 'maps app maps loaded from sample data ecommerce "before all" hook',
+ classname:
+ 'Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js',
+ message: 'foo bar',
+ },
+ ],
+ log,
+ reportPath: Path.resolve(__dirname, './__fixtures__/ftr_report.xml'),
+ });
+
+ expect(createPatch('ftr.xml', FTR_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(`
+ Index: ftr.xml
+ ===================================================================
+ --- ftr.xml [object Object]
+ +++ ftr.xml
+ @@ -2,52 +2,56 @@
+ ‹testsuites›
+ ‹testsuite timestamp="2019-06-05T23:37:10" time="903.670" tests="129" failures="5" skipped="71"›
+ ‹testcase name="maps app maps loaded from sample data ecommerce "before all" hook" classname="Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js" time="154.378"›
+ ‹system-out›
+ - ‹![CDATA[[00:00:00] │
+ + [00:00:00] │
+ [00:07:04] └-: maps app
+ ...
+ [00:15:02] │
+ -]]›
+ +
+ ‹/system-out›
+ ‹failure›
+ - ‹![CDATA[Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~="layerTocActionsPanelToggleButtonRoad_Map_-_Bright"])
+ + Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~="layerTocActionsPanelToggleButtonRoad_Map_-_Bright"])
+ Wait timed out after 10055ms
+ at /var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:834:17
+ at process._tickCallback (internal/process/next_tick.js:68:7)
+ at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9)
+ - at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13)]]›
+ - ‹/failure›
+ + at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13)
+ +
+ +
+ +Failed Tests Reporter:
+ + - foo bar
+ +‹/failure›
+ ‹/testcase›
+ ‹testcase name="maps app "after all" hook" classname="Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps" time="0.179"›
+ ‹system-out›
+ - ‹![CDATA[[00:00:00] │
+ + [00:00:00] │
+ [00:07:04] └-: maps app
+ ...
+ -]]›
+ +
+ ‹/system-out›
+ ‹failure›
+ - ‹![CDATA[{ NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used.
+ + { NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used.
+ at promise.finally (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:726:38)
+ at Object.thenFinally [as finally] (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/promise.js:124:12)
+ - at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }]]›
+ + at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }
+ ‹/failure›
+ ‹/testcase›
+ ‹testcase name="InfraOps app feature controls infrastructure security global infrastructure all privileges shows infrastructure navlink" classname="Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/infra/feature_controls/infrastructure_security·ts"›
+ ‹system-out›
+ - ‹![CDATA[[00:00:00] │
+ + [00:00:00] │
+ [00:05:13] └-: InfraOps app
+ ...
+ -]]›
+ +
+ ‹/system-out›
+ ‹skipped/›
+ ‹/testcase›
+ ‹testcase name="machine learning anomaly detection saved search with lucene query job creation opens the advanced section" classname="Firefox XPack UI Functional Tests.x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job·ts" time="6.040"›
+ - ‹system-out›‹![CDATA[[00:21:57] └-: machine learning...]]›‹/system-out›
+ - ‹failure›‹![CDATA[{ NoSuchSessionError: Tried to run command without establishing a connection
+ + ‹system-out›[00:21:57] └-: machine learning...‹/system-out›
+ + ‹failure›{ NoSuchSessionError: Tried to run command without establishing a connection
+ at Object.throwDecodedError (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/error.js:550:15)
+ at parseHttpResponse (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:563:13)
+ at Executor.execute (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:489:26)
+ - at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }]]›‹/failure›
+ + at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }‹/failure›
+ ‹/testcase›
+ ‹/testsuite›
+ -‹/testsuites›
+ +‹/testsuites›
+ \\ No newline at end of file
+
+ `);
+});
+
+it('rewrites jest reports with minimal changes', async () => {
+ const xml = await addMessagesToReport({
+ report: await parseTestReport(JEST_REPORT),
+ messages: [
+ {
+ classname: 'X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp',
+ name: 'launcher can reconnect if process died',
+ message: 'foo bar',
+ },
+ ],
+ log,
+ reportPath: Path.resolve(__dirname, './__fixtures__/jest_report.xml'),
+ });
+
+ expect(createPatch('jest.xml', JEST_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(`
+ Index: jest.xml
+ ===================================================================
+ --- jest.xml [object Object]
+ +++ jest.xml
+ @@ -3,13 +3,17 @@
+ ‹testsuite name="x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts" timestamp="2019-06-07T03:42:21" time="14.504" tests="5" failures="1" skipped="0" file="/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts"›
+ ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can start and end a process" time="1.316"/›
+ ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can force kill the process if langServer can not exit" time="3.182"/›
+ ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can reconnect if process died" time="7.060"›
+ - ‹failure›
+ - ‹![CDATA[TypeError: Cannot read property '0' of undefined
+ - at Object.‹anonymous›.test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10)]]›
+ - ‹/failure›
+ + ‹failure›‹![CDATA[
+ + TypeError: Cannot read property '0' of undefined
+ + at Object.‹anonymous›.test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10)
+ +
+ +
+ +Failed Tests Reporter:
+ + - foo bar
+ +]]›‹/failure›
+ ‹/testcase›
+ ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="passive launcher can start and end a process" time="0.435"/›
+ ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="passive launcher should restart a process if a process died before connected" time="1.502"/›
+ ‹/testsuite›
+ -‹/testsuites›
+ +‹/testsuites›
+ \\ No newline at end of file
+
+ `);
+});
+
+it('rewrites mocha reports with minimal changes', async () => {
+ const xml = await addMessagesToReport({
+ report: await parseTestReport(MOCHA_REPORT),
+ messages: [
+ {
+ name: 'code in multiple nodes "before all" hook',
+ classname: 'X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts',
+ message: 'foo bar',
+ },
+ ],
+ log,
+ reportPath: Path.resolve(__dirname, './__fixtures__/mocha_report.xml'),
+ });
+
+ expect(createPatch('mocha.xml', MOCHA_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(`
+ Index: mocha.xml
+ ===================================================================
+ --- mocha.xml [object Object]
+ +++ mocha.xml
+ @@ -2,12 +2,12 @@
+ ‹testsuites›
+ ‹testsuite timestamp="2019-06-13T23:29:36" time="30.739" tests="1444" failures="2" skipped="3"›
+ ‹testcase name="code in multiple nodes "before all" hook" classname="X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts" time="0.121"›
+ ‹system-out›
+ - ‹![CDATA[]]›
+ +
+ ‹/system-out›
+ - ‹failure›
+ - ‹![CDATA[Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable
+ + ‹failure›‹![CDATA[
+ + Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable
+ ‹html›
+ ‹head›‹title›503 Service Temporarily Unavailable‹/title›‹/head›
+ ‹body bgcolor="white"›
+ ‹center›‹h1›503 Service Temporarily Unavailable‹/h1›‹/center›
+ @@ -15,24 +15,28 @@
+ ‹/body›
+ ‹/html›
+
+ at Function.getSnapshot (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/packages/kbn-es/src/artifact.js:95:13)
+ - at process._tickCallback (internal/process/next_tick.js:68:7)]]›
+ - ‹/failure›
+ + at process._tickCallback (internal/process/next_tick.js:68:7)
+ +
+ +
+ +Failed Tests Reporter:
+ + - foo bar
+ +]]›‹/failure›
+ ‹/testcase›
+ ‹testcase name="code in multiple nodes "after all" hook" classname="X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts" time="0.003"›
+ ‹system-out›
+ - ‹![CDATA[]]›
+ +
+ ‹/system-out›
+ ‹failure›
+ - ‹![CDATA[TypeError: Cannot read property 'shutdown' of undefined
+ + TypeError: Cannot read property 'shutdown' of undefined
+ at Context.shutdown (plugins/code/server/__tests__/multi_node.ts:125:23)
+ - at process.topLevelDomainCallback (domain.js:120:23)]]›
+ + at process.topLevelDomainCallback (domain.js:120:23)
+ ‹/failure›
+ ‹/testcase›
+ ‹testcase name="repository service test can not clone a repo by ssh without a key" classname="X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/repository_service·ts" time="0.005"›
+ ‹system-out›
+ - ‹![CDATA[]]›
+ +
+ ‹/system-out›
+ ‹/testcase›
+ ‹/testsuite›
+ -‹/testsuites›
+ +‹/testsuites›
+ \\ No newline at end of file
+
+ `);
+});
+
+it('rewrites karma reports with minimal changes', async () => {
+ const xml = await addMessagesToReport({
+ report: await parseTestReport(KARMA_REPORT),
+ messages: [
+ {
+ name:
+ 'CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK',
+ classname: 'Browser Unit Tests.CoordinateMapsVisualizationTest',
+ message: 'foo bar',
+ },
+ ],
+ log,
+ reportPath: Path.resolve(__dirname, './__fixtures__/karma_report.xml'),
+ });
+
+ expect(createPatch('karma.xml', KARMA_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(`
+ Index: karma.xml
+ ===================================================================
+ --- karma.xml [object Object]
+ +++ karma.xml
+ @@ -1,5 +1,5 @@
+ -‹?xml version="1.0"?›
+ +‹?xml version="1.0" encoding="utf-8"?›
+ ‹testsuite name="Chrome 75.0.3770 (Mac OS X 10.14.5)" package="" timestamp="2019-07-02T19:53:21" id="0" hostname="spalger.lan" tests="648" errors="0" failures="4" time="1.759"›
+ ‹properties›
+ ‹property name="browser.fullName" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"/›
+ ‹/properties›
+ @@ -7,27 +7,31 @@
+ ‹testcase name="Vis-Editor-Agg-Params plugin directive should hide custom label parameter" time="0" classname="Browser Unit Tests.Vis-Editor-Agg-Params plugin directive"›
+ ‹skipped/›
+ ‹/testcase›
+ ‹testcase name="CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK" time="0.265" classname="Browser Unit Tests.CoordinateMapsVisualizationTest"›
+ - ‹failure type=""›Error: expected 7069 to be below 64
+ - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11)
+ - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8)
+ - at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15)
+ - at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60)
+ + ‹failure type=""›‹![CDATA[Error: expected 7069 to be below 64
+ + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11)
+ + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8)
+ + at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15)
+ + at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60)
+ at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40)
+ at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22)
+ - at Generator.prototype.<computed> [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21)
+ - at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103)
+ - at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194)
+ -‹/failure›
+ + at Generator.prototype.‹computed› [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21)
+ + at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103)
+ + at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194)
+ +
+ +
+ +Failed Tests Reporter:
+ + - foo bar
+ +]]›‹/failure›
+ ‹/testcase›
+ ‹testcase name="CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should toggle to Heatmap OK" time="0.055" classname="Browser Unit Tests.CoordinateMapsVisualizationTest"/›
+ ‹testcase name="VegaParser._parseSchema should warn on vega-lite version too new to be supported" time="0.001" classname="Browser Unit Tests.VegaParser·_parseSchema"/›
+ ‹system-out›
+ - ‹![CDATA[Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'ready to load tests for shard 1 of 4'
+ + Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'ready to load tests for shard 1 of 4'
+ ,Chrome 75.0.3770 (Mac OS X 10.14.5) WARN: 'Unmatched GET to http://localhost:9876/api/interpreter/fns'
+ ...
+
+ -]]›
+ +
+ ‹/system-out›
+ ‹system-err/›
+ -‹/testsuite›
+ +‹/testsuite›
+ \\ No newline at end of file
+
+ `);
+});
diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts
new file mode 100644
index 0000000000000..f82e1ef1fc19a
--- /dev/null
+++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts
@@ -0,0 +1,90 @@
+/*
+ * 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 Fs from 'fs';
+import { promisify } from 'util';
+
+import { ToolingLog } from '@kbn/dev-utils';
+import xml2js from 'xml2js';
+
+import { TestReport, makeFailedTestCaseIter } from './test_report';
+
+const writeAsync = promisify(Fs.writeFile);
+
+export interface Message {
+ classname: string;
+ name: string;
+ message: string;
+}
+
+/**
+ * Mutate the report to include mentions of Github issues related to test failures,
+ * then write the updated report to disk
+ */
+export async function addMessagesToReport(options: {
+ log: ToolingLog;
+ report: TestReport;
+ messages: Message[];
+ reportPath: string;
+ dryRun?: boolean;
+}) {
+ const { log, report, messages, reportPath, dryRun } = options;
+
+ for (const testCase of makeFailedTestCaseIter(report)) {
+ const { classname, name } = testCase.$;
+ const messageList = messages
+ .filter(u => u.classname === classname && u.name === name)
+ .reduce((acc, u) => `${acc}\n - ${u.message}`, '');
+
+ if (!messageList) {
+ continue;
+ }
+
+ log.info(`${classname} - ${name}:${messageList}`);
+ const append = `\n\nFailed Tests Reporter:${messageList}\n`;
+
+ if (
+ testCase.failure[0] &&
+ typeof testCase.failure[0] === 'object' &&
+ typeof testCase.failure[0]._ === 'string'
+ ) {
+ testCase.failure[0]._ += append;
+ } else {
+ testCase.failure[0] = String(testCase.failure[0]) + append;
+ }
+ }
+
+ const builder = new xml2js.Builder({
+ cdata: true,
+ xmldec: { version: '1.0', encoding: 'utf-8' },
+ });
+
+ const xml = builder
+ .buildObject(report)
+ .split('\n')
+ .map(line => (line.trim() === '' ? '' : line))
+ .join('\n');
+
+ if (dryRun) {
+ log.info(`updated ${reportPath}\n${xml}`);
+ } else {
+ await writeAsync(reportPath, xml, 'utf8');
+ }
+ return xml;
+}
diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts
index 1e0514a9b1cb0..fe6e0bbc796ee 100644
--- a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts
@@ -17,14 +17,12 @@
* under the License.
*/
-import { ToolingLog } from '@kbn/dev-utils';
-
import { getFailures } from './get_failures';
-
-const log = new ToolingLog();
+import { parseTestReport } from './test_report';
+import { FTR_REPORT, JEST_REPORT, KARMA_REPORT, MOCHA_REPORT } from './__fixtures__';
it('discovers failures in ftr report', async () => {
- const failures = await getFailures(log, require.resolve('./__fixtures__/ftr_report.xml'));
+ const failures = getFailures(await parseTestReport(FTR_REPORT));
expect(failures).toMatchInlineSnapshot(`
Array [
Object {
@@ -37,15 +35,39 @@ it('discovers failures in ftr report', async () => {
at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9)
at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13)
",
+ "likelyIrrelevant": false,
"name": "maps app maps loaded from sample data ecommerce \\"before all\\" hook",
"time": "154.378",
},
+ Object {
+ "classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps",
+ "failure": "
+ { NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used.
+ at promise.finally (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:726:38)
+ at Object.thenFinally [as finally] (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/promise.js:124:12)
+ at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }
+ ",
+ "likelyIrrelevant": true,
+ "name": "maps app \\"after all\\" hook",
+ "time": "0.179",
+ },
+ Object {
+ "classname": "Firefox XPack UI Functional Tests.x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job·ts",
+ "failure": "{ NoSuchSessionError: Tried to run command without establishing a connection
+ at Object.throwDecodedError (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/error.js:550:15)
+ at parseHttpResponse (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:563:13)
+ at Executor.execute (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:489:26)
+ at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }",
+ "likelyIrrelevant": true,
+ "name": "machine learning anomaly detection saved search with lucene query job creation opens the advanced section",
+ "time": "6.040",
+ },
]
`);
});
it('discovers failures in jest report', async () => {
- const failures = await getFailures(log, require.resolve('./__fixtures__/jest_report.xml'));
+ const failures = getFailures(await parseTestReport(JEST_REPORT));
expect(failures).toMatchInlineSnapshot(`
Array [
Object {
@@ -54,6 +76,7 @@ it('discovers failures in jest report', async () => {
TypeError: Cannot read property '0' of undefined
at Object..test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10)
",
+ "likelyIrrelevant": false,
"name": "launcher can reconnect if process died",
"time": "7.060",
},
@@ -62,7 +85,7 @@ it('discovers failures in jest report', async () => {
});
it('discovers failures in karma report', async () => {
- const failures = await getFailures(log, require.resolve('./__fixtures__/karma_report.xml'));
+ const failures = getFailures(await parseTestReport(KARMA_REPORT));
expect(failures).toMatchInlineSnapshot(`
Array [
Object {
@@ -78,6 +101,7 @@ it('discovers failures in karma report', async () => {
at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103)
at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194)
",
+ "likelyIrrelevant": false,
"name": "CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK",
"time": "0.265",
},
@@ -86,6 +110,39 @@ it('discovers failures in karma report', async () => {
});
it('discovers failures in mocha report', async () => {
- const failures = await getFailures(log, require.resolve('./__fixtures__/mocha_report.xml'));
- expect(failures).toMatchInlineSnapshot(`Array []`);
+ const failures = getFailures(await parseTestReport(MOCHA_REPORT));
+ expect(failures).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "classname": "X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts",
+ "failure": "
+ Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable
+
+ 503 Service Temporarily Unavailable
+
+
503 Service Temporarily Unavailable
+
nginx/1.13.7
+
+
+
+ at Function.getSnapshot (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/packages/kbn-es/src/artifact.js:95:13)
+ at process._tickCallback (internal/process/next_tick.js:68:7)
+ ",
+ "likelyIrrelevant": true,
+ "name": "code in multiple nodes \\"before all\\" hook",
+ "time": "0.121",
+ },
+ Object {
+ "classname": "X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts",
+ "failure": "
+ TypeError: Cannot read property 'shutdown' of undefined
+ at Context.shutdown (plugins/code/server/__tests__/multi_node.ts:125:23)
+ at process.topLevelDomainCallback (domain.js:120:23)
+ ",
+ "likelyIrrelevant": true,
+ "name": "code in multiple nodes \\"after all\\" hook",
+ "time": "0.003",
+ },
+ ]
+ `);
});
diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.ts b/packages/kbn-test/src/failed_tests_reporter/get_failures.ts
index 85eff8eb07f5d..be058791f737a 100644
--- a/packages/kbn-test/src/failed_tests_reporter/get_failures.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/get_failures.ts
@@ -17,69 +17,16 @@
* under the License.
*/
-import { promisify } from 'util';
-import Fs from 'fs';
-
-import xml2js from 'xml2js';
import stripAnsi from 'strip-ansi';
-import { ToolingLog } from '@kbn/dev-utils';
-
-type TestReport =
- | {
- testsuites: {
- testsuite: TestSuite[];
- };
- }
- | {
- testsuite: TestSuite;
- };
-
-interface TestSuite {
- $: {
- /* ISO8601 timetamp when test suite ran */
- timestamp: string;
- /* number of second this tests suite took */
- time: string;
- /* number of tests as a string */
- tests: string;
- /* number of failed tests as a string */
- failures: string;
- /* number of skipped tests as a string */
- skipped: string;
- };
- testcase: TestCase[];
-}
-interface TestCase {
- $: {
- /* unique test name */
- name: string;
- /* somewhat human readable combination of test name and file */
- classname: string;
- /* number of seconds this test took */
- time: string;
- };
- /* contents of system-out elements */
- 'system-out'?: string[];
- /* contents of failure elements */
- failure?: Array;
- /* contents of skipped elements */
- skipped?: string[];
-}
+import { FailedTestCase, TestReport, makeFailedTestCaseIter } from './test_report';
-export type TestFailure = TestCase['$'] & {
+export type TestFailure = FailedTestCase['$'] & {
failure: string;
+ likelyIrrelevant: boolean;
};
-const readAsync = promisify(Fs.readFile);
-
-const indent = (text: string) =>
- ` ${text
- .split('\n')
- .map(l => ` ${l}`)
- .join('\n')}`;
-
-const getFailureText = (failure: NonNullable) => {
+const getFailureText = (failure: FailedTestCase['failure']) => {
const [failureNode] = failure;
if (failureNode && typeof failureNode === 'object' && typeof failureNode._ === 'string') {
@@ -89,7 +36,7 @@ const getFailureText = (failure: NonNullable) => {
return stripAnsi(String(failureNode));
};
-const isLikelyIrrelevant = ({ name, failure }: TestFailure) => {
+const isLikelyIrrelevant = (name: string, failure: string) => {
if (
failure.includes('NoSuchSessionError: This driver instance does not have a valid session ID') ||
failure.includes('NoSuchSessionError: Tried to run command without establishing a connection')
@@ -118,47 +65,25 @@ const isLikelyIrrelevant = ({ name, failure }: TestFailure) => {
if (failure.includes('Unable to fetch Kibana status API response from Kibana')) {
return true;
}
-};
-
-export async function getFailures(log: ToolingLog, testReportPath: string) {
- const xml = await readAsync(testReportPath, 'utf8');
-
- // Parses junit XML files
- const report: TestReport = await xml2js.parseStringPromise(xml);
- // Grab the failures. Reporters may report multiple testsuites in a single file.
- const testSuites = 'testsuites' in report ? report.testsuites.testsuite : [report.testsuite];
+ return false;
+};
+export function getFailures(report: TestReport) {
const failures: TestFailure[] = [];
- for (const testSuite of testSuites) {
- for (const testCase of testSuite.testcase) {
- const { failure } = testCase;
- if (!failure) {
- continue;
- }
+ for (const testCase of makeFailedTestCaseIter(report)) {
+ const failure = getFailureText(testCase.failure);
+ const likelyIrrelevant = isLikelyIrrelevant(testCase.$.name, failure);
+ failures.push({
// unwrap xml weirdness
- const failureCase: TestFailure = {
- ...testCase.$,
- // Strip ANSI color characters
- failure: getFailureText(failure),
- };
-
- if (isLikelyIrrelevant(failureCase)) {
- log.warning(
- `Ignoring likely irrelevant failure: ${failureCase.classname} - ${
- failureCase.name
- }\n${indent(failureCase.failure)}`
- );
- continue;
- }
-
- failures.push(failureCase);
- }
+ ...testCase.$,
+ // Strip ANSI color characters
+ failure,
+ likelyIrrelevant,
+ });
}
- log.info(`Found ${failures.length} test failures`);
-
return failures;
}
diff --git a/packages/kbn-test/src/failed_tests_reporter/github_api.ts b/packages/kbn-test/src/failed_tests_reporter/github_api.ts
index 46278044c840d..d8a952bee42e5 100644
--- a/packages/kbn-test/src/failed_tests_reporter/github_api.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/github_api.ts
@@ -19,7 +19,7 @@
import Url from 'url';
-import Axios, { AxiosRequestConfig } from 'axios';
+import Axios, { AxiosRequestConfig, AxiosInstance } from 'axios';
import parseLinkHeader from 'parse-link-header';
import { ToolingLog, isAxiosResponseError, isAxiosRequestError } from '@kbn/dev-utils';
@@ -40,25 +40,34 @@ type RequestOptions = AxiosRequestConfig & {
};
export class GithubApi {
- private readonly x = Axios.create({
- headers: {
- Authorization: `token ${this.token}`,
- 'User-Agent': 'elastic/kibana#failed_test_reporter',
- },
- });
+ private readonly log: ToolingLog;
+ private readonly token: string | undefined;
+ private readonly dryRun: boolean;
+ private readonly x: AxiosInstance;
/**
* Create a GithubApi helper object, if token is undefined requests won't be
* sent, but will instead be logged.
*/
- constructor(
- private readonly log: ToolingLog,
- private readonly token: string | undefined,
- private readonly dryRun: boolean
- ) {
- if (!token && !dryRun) {
+ constructor(options: {
+ log: GithubApi['log'];
+ token: GithubApi['token'];
+ dryRun: GithubApi['dryRun'];
+ }) {
+ this.log = options.log;
+ this.token = options.token;
+ this.dryRun = options.dryRun;
+
+ if (!this.token && !this.dryRun) {
throw new TypeError('token parameter is required');
}
+
+ this.x = Axios.create({
+ headers: {
+ ...(this.token ? { Authorization: `token ${this.token}` } : {}),
+ 'User-Agent': 'elastic/kibana#failed_test_reporter',
+ },
+ });
}
private failedTestIssuesPageCache: {
diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts
index 0e9f8db587cb7..ef6ab3c51ab19 100644
--- a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts
@@ -18,19 +18,14 @@
*/
import dedent from 'dedent';
-import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/dev-utils';
-import { createFailureIssue, updatedFailureIssue } from './report_failure';
+import { createFailureIssue, updateFailureIssue } from './report_failure';
jest.mock('./github_api');
const { GithubApi } = jest.requireMock('./github_api');
describe('createFailureIssue()', () => {
it('creates new github issue with failure text, link to issue, and valid metadata', async () => {
- const log = new ToolingLog();
- const writer = new ToolingLogCollectingWriter();
- log.setWriters([writer]);
-
const api = new GithubApi();
await createFailureIssue(
@@ -40,8 +35,8 @@ describe('createFailureIssue()', () => {
failure: 'this is the failure text',
name: 'test name',
time: '2018-01-01T01:00:00Z',
+ likelyIrrelevant: false,
},
- log,
api
);
@@ -72,23 +67,14 @@ describe('createFailureIssue()', () => {
],
}
`);
- expect(writer.messages).toMatchInlineSnapshot(`
- Array [
- " [34minfo[39m Created issue undefined",
- ]
- `);
});
});
-describe('updatedFailureIssue()', () => {
+describe('updateFailureIssue()', () => {
it('increments failure count and adds new comment to issue', async () => {
- const log = new ToolingLog();
- const writer = new ToolingLogCollectingWriter();
- log.setWriters([writer]);
-
const api = new GithubApi();
- await updatedFailureIssue(
+ await updateFailureIssue(
'https://build-url',
{
html_url: 'https://github.com/issues/1234',
@@ -101,7 +87,6 @@ describe('updatedFailureIssue()', () => {
"
`,
},
- log,
api
);
@@ -139,10 +124,5 @@ describe('updatedFailureIssue()', () => {
],
}
`);
- expect(writer.messages).toMatchInlineSnapshot(`
- Array [
- " [34minfo[39m Updated issue https://github.com/issues/1234, failCount: 11",
- ]
- `);
});
});
diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts
index afb46c429c7fc..97e9d517576fc 100644
--- a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts
@@ -17,18 +17,11 @@
* under the License.
*/
-import { ToolingLog } from '@kbn/dev-utils';
-
import { TestFailure } from './get_failures';
import { GithubIssue, GithubApi } from './github_api';
import { getIssueMetadata, updateIssueMetadata } from './issue_metadata';
-export async function createFailureIssue(
- buildUrl: string,
- failure: TestFailure,
- log: ToolingLog,
- api: GithubApi
-) {
+export async function createFailureIssue(buildUrl: string, failure: TestFailure, api: GithubApi) {
const title = `Failing test: ${failure.classname} - ${failure.name}`;
const body = updateIssueMetadata(
@@ -48,16 +41,10 @@ export async function createFailureIssue(
}
);
- const newIssueUrl = await api.createIssue(title, body, ['failed-test']);
- log.info(`Created issue ${newIssueUrl}`);
+ return await api.createIssue(title, body, ['failed-test']);
}
-export async function updatedFailureIssue(
- buildUrl: string,
- issue: GithubIssue,
- log: ToolingLog,
- api: GithubApi
-) {
+export async function updateFailureIssue(buildUrl: string, issue: GithubIssue, api: GithubApi) {
// Increment failCount
const newCount = getIssueMetadata(issue.body, 'test.failCount', 0) + 1;
const newBody = updateIssueMetadata(issue.body, {
@@ -67,5 +54,5 @@ export async function updatedFailureIssue(
await api.editIssueBodyAndEnsureOpen(issue.number, newBody);
await api.addIssueComment(issue.number, `New failure: [Jenkins Build](${buildUrl})`);
- log.info(`Updated issue ${issue.html_url}, failCount: ${newCount}`);
+ return newCount;
}
diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
index 0eea1d32e5c2e..b3c2a8dc338da 100644
--- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
@@ -22,19 +22,22 @@ import globby from 'globby';
import { getFailures } from './get_failures';
import { GithubApi } from './github_api';
-import { updatedFailureIssue, createFailureIssue } from './report_failure';
+import { updateFailureIssue, createFailureIssue } from './report_failure';
import { getIssueMetadata } from './issue_metadata';
+import { readTestReport } from './test_report';
+import { addMessagesToReport, Message } from './add_messages_to_report';
export function runFailedTestsReporterCli() {
run(
async ({ log, flags }) => {
- const buildUrl = flags['build-url'];
- if (typeof buildUrl !== 'string' || !buildUrl) {
- throw createFlagError('Missing --build-url or process.env.BUILD_URL');
+ let updateGithub = flags['github-update'];
+ if (updateGithub && !process.env.GITHUB_TOKEN) {
+ throw createFailError(
+ 'GITHUB_TOKEN environment variable must be set, otherwise use --no-github-update flag'
+ );
}
- const dryRun = !!flags['dry-run'];
- if (!dryRun) {
+ if (updateGithub) {
// JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others
const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//);
const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH;
@@ -48,26 +51,43 @@ export function runFailedTestsReporterCli() {
const isMasterOrVersion =
branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/);
if (!isMasterOrVersion || isPr) {
- throw createFailError('Failure issues only created on master/version branch jobs', {
- exitCode: 0,
- });
+ log.info('Failure issues only created on master/version branch jobs');
+ updateGithub = false;
}
+ }
- if (!process.env.GITHUB_TOKEN) {
- throw createFailError(
- 'GITHUB_TOKEN environment variable must be set, otherwise use --dry-run flag'
- );
- }
+ const githubApi = new GithubApi({
+ log,
+ token: process.env.GITHUB_TOKEN,
+ dryRun: !updateGithub,
+ });
+
+ const buildUrl = flags['build-url'] || (updateGithub ? '' : 'http://buildUrl');
+ if (typeof buildUrl !== 'string' || !buildUrl) {
+ throw createFlagError('Missing --build-url or process.env.BUILD_URL');
}
- const githubApi = new GithubApi(log, process.env.GITHUB_TOKEN, dryRun);
const reportPaths = await globby(['target/junit/**/*.xml'], {
cwd: REPO_ROOT,
absolute: true,
});
for (const reportPath of reportPaths) {
- for (const failure of await getFailures(log, reportPath)) {
+ const report = await readTestReport(reportPath);
+ const messages: Message[] = [];
+
+ for (const failure of await getFailures(report)) {
+ if (failure.likelyIrrelevant) {
+ messages.push({
+ classname: failure.classname,
+ name: failure.name,
+ message:
+ 'Failure is likely irrelevant' +
+ (updateGithub ? ', so an issue was not created or updated' : ''),
+ });
+ continue;
+ }
+
const existingIssue = await githubApi.findFailedTestIssue(
i =>
getIssueMetadata(i.body, 'test.class') === failure.classname &&
@@ -75,23 +95,57 @@ export function runFailedTestsReporterCli() {
);
if (existingIssue) {
- await updatedFailureIssue(buildUrl, existingIssue, log, githubApi);
- } else {
- await createFailureIssue(buildUrl, failure, log, githubApi);
+ const newFailureCount = await updateFailureIssue(buildUrl, existingIssue, githubApi);
+ const url = existingIssue.html_url;
+ const message =
+ `Test has failed ${newFailureCount - 1} times on tracked branches: ${url}` +
+ (updateGithub
+ ? `. Updated existing issue: ${url} (fail count: ${newFailureCount})`
+ : '');
+
+ messages.push({
+ classname: failure.classname,
+ name: failure.name,
+ message,
+ });
+ continue;
}
+
+ const newIssueUrl = await createFailureIssue(buildUrl, failure, githubApi);
+ const message =
+ `Test has not failed recently on tracked branches` +
+ (updateGithub ? `Created new issue: ${newIssueUrl}` : '');
+
+ messages.push({
+ classname: failure.classname,
+ name: failure.name,
+ message,
+ });
}
+
+ // mutates report to include messages and writes updated report to disk
+ await addMessagesToReport({
+ report,
+ messages,
+ log,
+ reportPath,
+ dryRun: !flags['report-update'],
+ });
}
},
{
description: `a cli that opens issues or updates existing issues based on junit reports`,
flags: {
- boolean: ['dry-run'],
+ boolean: ['github-update', 'report-update'],
string: ['build-url'],
default: {
+ 'github-update': true,
+ 'report-update': true,
'build-url': process.env.BUILD_URL,
},
help: `
- --dry-run Execute the CLI without contacting Github
+ --no-github-update Execute the CLI without writing to Github
+ --no-report-update Execute the CLI without writing to the JUnit reports
--build-url URL of the failed build, defaults to process.env.BUILD_URL
`,
},
diff --git a/packages/kbn-test/src/failed_tests_reporter/test_report.ts b/packages/kbn-test/src/failed_tests_reporter/test_report.ts
new file mode 100644
index 0000000000000..644a4cc9fd5a7
--- /dev/null
+++ b/packages/kbn-test/src/failed_tests_reporter/test_report.ts
@@ -0,0 +1,100 @@
+/*
+ * 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 Fs from 'fs';
+import { promisify } from 'util';
+
+import xml2js from 'xml2js';
+
+const readAsync = promisify(Fs.readFile);
+
+export type TestReport =
+ | {
+ testsuites: {
+ testsuite: TestSuite[];
+ };
+ }
+ | {
+ testsuite: TestSuite;
+ };
+
+export interface TestSuite {
+ $: {
+ /* ISO8601 timetamp when test suite ran */
+ timestamp: string;
+ /* number of second this tests suite took */
+ time: string;
+ /* number of tests as a string */
+ tests: string;
+ /* number of failed tests as a string */
+ failures: string;
+ /* number of skipped tests as a string */
+ skipped: string;
+ };
+ testcase: TestCase[];
+}
+
+export interface TestCase {
+ $: {
+ /* unique test name */
+ name: string;
+ /* somewhat human readable combination of test name and file */
+ classname: string;
+ /* number of seconds this test took */
+ time: string;
+ };
+ /* contents of system-out elements */
+ 'system-out'?: string[];
+ /* contents of failure elements */
+ failure?: Array;
+ /* contents of skipped elements */
+ skipped?: string[];
+}
+
+export interface FailedTestCase extends TestCase {
+ failure: Array;
+}
+
+/**
+ * Parse JUnit XML Files
+ */
+export async function parseTestReport(xml: string): Promise {
+ return await xml2js.parseStringPromise(xml);
+}
+
+export async function readTestReport(testReportPath: string) {
+ return await parseTestReport(await readAsync(testReportPath, 'utf8'));
+}
+
+export function* makeFailedTestCaseIter(report: TestReport) {
+ // Grab the failures. Reporters may report multiple testsuites in a single file.
+ const testSuites = 'testsuites' in report ? report.testsuites.testsuite : [report.testsuite];
+
+ for (const testSuite of testSuites) {
+ for (const testCase of testSuite.testcase) {
+ const { failure } = testCase;
+
+ if (!failure) {
+ continue;
+ }
+
+ yield testCase as FailedTestCase;
+ }
+ }
+}
diff --git a/packages/kbn-ui-framework/dist/kui_dark.css b/packages/kbn-ui-framework/dist/kui_dark.css
index dcbd65fbca520..aa16bcdb34d36 100644
--- a/packages/kbn-ui-framework/dist/kui_dark.css
+++ b/packages/kbn-ui-framework/dist/kui_dark.css
@@ -1,10 +1,24 @@
+/* 1 */
+/* 1 */
+/**
+ * 1. Extend beta badges to at least 40% of the container's width
+ * 2. Fix for IE to ensure badges are visible outside of a