diff --git a/.ci/.storybook/main.js b/.ci/.storybook/main.js
new file mode 100644
index 0000000000000..e399ec087e168
--- /dev/null
+++ b/.ci/.storybook/main.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+const config = require('@kbn/storybook').defaultConfig;
+const aliases = require('../../src/dev/storybook/aliases.ts').storybookAliases;
+
+config.refs = {};
+
+for (const alias of Object.keys(aliases).filter((a) => a !== 'ci_composite')) {
+ // snake_case -> Title Case
+ const title = alias
+ .replace(/_/g, ' ')
+ .split(' ')
+ .map((n) => n[0].toUpperCase() + n.slice(1))
+ .join(' ');
+
+ config.refs[alias] = {
+ title: title,
+ url: `${process.env.STORYBOOK_BASE_URL}/${alias}`,
+ };
+}
+
+module.exports = config;
diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky
index b9880c410fc68..7eafc66465bc7 100644
--- a/.ci/Jenkinsfile_flaky
+++ b/.ci/Jenkinsfile_flaky
@@ -3,47 +3,39 @@
library 'kibana-pipeline-library'
kibanaLibrary.load()
-def CI_GROUP_PARAM = params.CI_GROUP
+def TASK_PARAM = params.TASK ?: params.CI_GROUP
// Looks like 'oss:ciGroup:1', 'oss:firefoxSmoke'
-def JOB_PARTS = CI_GROUP_PARAM.split(':')
+def JOB_PARTS = TASK_PARAM.split(':')
def IS_XPACK = JOB_PARTS[0] == 'xpack'
-def JOB = JOB_PARTS[1]
+def JOB = JOB_PARTS.size() > 1 ? JOB_PARTS[1] : JOB_PARTS[0]
def CI_GROUP = JOB_PARTS.size() > 2 ? JOB_PARTS[2] : ''
def EXECUTIONS = params.NUMBER_EXECUTIONS.toInteger()
def AGENT_COUNT = getAgentCount(EXECUTIONS)
-
-def worker = getWorkerFromParams(IS_XPACK, JOB, CI_GROUP)
-
-def workerFailures = []
+def NEED_BUILD = JOB != 'jestIntegration' && JOB != 'apiIntegration'
currentBuild.displayName += trunc(" ${params.GITHUB_OWNER}:${params.branch_specifier}", 24)
currentBuild.description = "${params.CI_GROUP}
Agents: ${AGENT_COUNT}
Executions: ${params.NUMBER_EXECUTIONS}"
kibanaPipeline(timeoutMinutes: 180) {
def agents = [:]
+ def workerFailures = []
+
+ def worker = getWorkerFromParams(IS_XPACK, JOB, CI_GROUP)
+
for(def agentNumber = 1; agentNumber <= AGENT_COUNT; agentNumber++) {
- def agentNumberInside = agentNumber
def agentExecutions = floor(EXECUTIONS/AGENT_COUNT) + (agentNumber <= EXECUTIONS%AGENT_COUNT ? 1 : 0)
+
agents["agent-${agentNumber}"] = {
- catchErrors {
- print "Agent ${agentNumberInside} - ${agentExecutions} executions"
-
- withEnv([
- 'IGNORE_SHIP_CI_STATS_ERROR=true',
- ]) {
- workers.functional('flaky-test-runner', {
- if (!IS_XPACK) {
- kibanaPipeline.buildOss()
- if (CI_GROUP == '1') {
- runbld("./test/scripts/jenkins_build_kbn_sample_panel_action.sh", "Build kbn tp sample panel action for ciGroup1")
- }
- } else {
- kibanaPipeline.buildXpack()
- }
- }, getWorkerMap(agentNumberInside, agentExecutions, worker, workerFailures))()
- }
- }
+ agentProcess(
+ agentNumber: agentNumber,
+ agentExecutions: agentExecutions,
+ worker: worker,
+ workerFailures: workerFailures,
+ needBuild: NEED_BUILD,
+ isXpack: IS_XPACK,
+ ciGroup: CI_GROUP
+ )
}
}
@@ -59,14 +51,70 @@ kibanaPipeline(timeoutMinutes: 180) {
}
}
+def agentProcess(Map params = [:]) {
+ def config = [
+ agentNumber: 1,
+ agentExecutions: 0,
+ worker: {},
+ workerFailures: [],
+ needBuild: false,
+ isXpack: false,
+ ciGroup: null,
+ ] + params
+
+ catchErrors {
+ print "Agent ${config.agentNumber} - ${config.agentExecutions} executions"
+
+ withEnv([
+ 'IGNORE_SHIP_CI_STATS_ERROR=true',
+ ]) {
+ kibanaPipeline.withTasks([
+ parallel: 20,
+ ]) {
+ task {
+ if (config.needBuild) {
+ if (!config.isXpack) {
+ kibanaPipeline.buildOss()
+ } else {
+ kibanaPipeline.buildXpack()
+ }
+ }
+
+ for(def i = 0; i < config.agentExecutions; i++) {
+ def taskNumber = i
+ task({
+ withEnv([
+ "REMOVE_KIBANA_INSTALL_DIR=1",
+ ]) {
+ catchErrors {
+ try {
+ config.worker()
+ } catch (ex) {
+ config.workerFailures << "agent-${config.agentNumber}-${taskNumber}"
+ throw ex
+ }
+ }
+ }
+ })
+ }
+ }
+ }
+ }
+ }
+}
+
def getWorkerFromParams(isXpack, job, ciGroup) {
if (!isXpack) {
if (job == 'accessibility') {
return kibanaPipeline.functionalTestProcess('kibana-accessibility', './test/scripts/jenkins_accessibility.sh')
} else if (job == 'firefoxSmoke') {
return kibanaPipeline.functionalTestProcess('firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh')
- } else if(job == 'visualRegression') {
+ } else if (job == 'visualRegression') {
return kibanaPipeline.functionalTestProcess('visualRegression', './test/scripts/jenkins_visual_regression.sh')
+ } else if (job == 'jestIntegration') {
+ return kibanaPipeline.scriptTaskDocker('Jest Integration Tests', 'test/scripts/test/jest_integration.sh')
+ } else if (job == 'apiIntegration') {
+ return kibanaPipeline.scriptTask('API Integration Tests', 'test/scripts/test/api_integration.sh')
} else {
return kibanaPipeline.ossCiGroupProcess(ciGroup)
}
@@ -76,45 +124,16 @@ def getWorkerFromParams(isXpack, job, ciGroup) {
return kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh')
} else if (job == 'firefoxSmoke') {
return kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh')
- } else if(job == 'visualRegression') {
+ } else if (job == 'visualRegression') {
return kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh')
} else {
return kibanaPipeline.xpackCiGroupProcess(ciGroup)
}
}
-def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWorkerProcesses = 12) {
- def workerMap = [:]
- def numberOfWorkers = Math.min(numberOfExecutions, maxWorkerProcesses)
-
- for(def i = 1; i <= numberOfWorkers; i++) {
- def workerExecutions = floor(numberOfExecutions/numberOfWorkers + (i <= numberOfExecutions%numberOfWorkers ? 1 : 0))
-
- workerMap["agent-${agentNumber}-worker-${i}"] = { workerNumber ->
- for(def j = 0; j < workerExecutions; j++) {
- print "Execute agent-${agentNumber} worker-${workerNumber}: ${j}"
- withEnv([
- "REMOVE_KIBANA_INSTALL_DIR=1",
- ]) {
- catchErrors {
- try {
- worker(workerNumber)
- } catch (ex) {
- workerFailures << "agent-${agentNumber} worker-${workerNumber}-${j}"
- throw ex
- }
- }
- }
- }
- }
- }
-
- return workerMap
-}
-
def getAgentCount(executions) {
- // Increase agent count every 24 worker processess, up to 3 agents maximum
- return Math.min(3, 1 + floor(executions/24))
+ // Increase agent count every 20 worker processess, up to 3 agents maximum
+ return Math.min(3, 1 + floor(executions/20))
}
def trunc(str, length) {
diff --git a/.eslintignore b/.eslintignore
index ea8ab55ad7726..4559711bb9dd3 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -15,6 +15,7 @@ node_modules
target
snapshots.js
+!/.ci
!/.eslintrc.js
!.storybook
diff --git a/docs/api/alerts.asciidoc b/docs/api/alerts.asciidoc
new file mode 100644
index 0000000000000..a19c538bcb4d7
--- /dev/null
+++ b/docs/api/alerts.asciidoc
@@ -0,0 +1,42 @@
+[[alerts-api]]
+== Alerts APIs
+
+The following APIs are available for managing {kib} alerts.
+
+* <> to create an alert
+
+* <> to update the attributes for existing alerts
+
+* <> to retrieve a single alert by ID
+
+* <> to permanently remove an alert
+
+* <> to retrieve a paginated set of alerts by condition
+
+* <> to retrieve a list of all alert types
+
+* <> to enable a single alert by ID
+
+* <> to disable a single alert by ID
+
+* <> to mute alert instances for a single alert by ID
+
+* <> to unmute alert instances for a single alert by ID
+
+* <> to unmute all alert instances for a single alert by ID
+
+* <> to retrieve the health of the alerts framework
+
+include::alerts/create.asciidoc[]
+include::alerts/update.asciidoc[]
+include::alerts/get.asciidoc[]
+include::alerts/delete.asciidoc[]
+include::alerts/find.asciidoc[]
+include::alerts/list.asciidoc[]
+include::alerts/enable.asciidoc[]
+include::alerts/disable.asciidoc[]
+include::alerts/mute_all.asciidoc[]
+include::alerts/mute.asciidoc[]
+include::alerts/unmute_all.asciidoc[]
+include::alerts/unmute.asciidoc[]
+include::alerts/health.asciidoc[]
diff --git a/docs/api/alerts/create.asciidoc b/docs/api/alerts/create.asciidoc
new file mode 100644
index 0000000000000..9e188b971c9b5
--- /dev/null
+++ b/docs/api/alerts/create.asciidoc
@@ -0,0 +1,189 @@
+[[alerts-api-create]]
+=== Create alert API
+++++
+Create alert
+++++
+
+Create {kib} alerts.
+
+[[alerts-api-create-request]]
+==== Request
+
+`POST :/api/alerts/alert`
+
+[[alerts-api-create-request-body]]
+==== Request body
+
+`name`::
+ (Required, string) A name to reference and search.
+
+`tags`::
+ (Optional, string array) A list of keywords to reference and search.
+
+`alertTypeId`::
+ (Required, string) The ID of the alert type that you want to call when the alert is scheduled to run.
+
+`schedule`::
+ (Required, object) The schedule specifying when this alert should be run, using one of the available schedule formats specified under
++
+._Schedule Formats_.
+[%collapsible%open]
+=====
+A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule.
+
+We currently support the _Interval format_ which specifies the interval in seconds, minutes, hours or days at which the alert should execute.
+Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`.
+
+There are plans to support multiple other schedule formats in the near future.
+=====
+
+`throttle`::
+ (Optional, string) How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications during this period.
+
+`notifyWhen`::
+ (Required, string) The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`.
+
+`enabled`::
+ (Optional, boolean) Indicates if you want to run the alert on an interval basis after it is created.
+
+`consumer`::
+ (Required, string) The name of the application that owns the alert. This name has to match the Kibana Feature name, as that dictates the required RBAC privileges.
+
+`params`::
+ (Required, object) The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
+
+`actions`::
+ (Optional, object array) An array of the following action objects.
++
+.Properties of the action objects:
+[%collapsible%open]
+=====
+ `group`:::
+ (Required, string) Grouping actions is recommended for escalations for different types of alert instances. If you don't need this, set this value to `default`.
+
+ `id`:::
+ (Required, string) The ID of the action saved object to execute.
+
+ `actionTypeId`:::
+ (Required, string) The ID of the <>.
+
+ `params`:::
+ (Required, object) The map to the `params` that the <> will receive. ` params` are handled as Mustache templates and passed a default set of context.
+=====
+
+
+[[alerts-api-create-request-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+[[alerts-api-create-example]]
+==== Example
+
+[source,sh]
+--------------------------------------------------
+$ curl -X POST api/alerts/alert -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d '
+{
+ "params":{
+ "aggType":"avg",
+ "termSize":6,
+ "thresholdComparator":">",
+ "timeWindowSize":5,
+ "timeWindowUnit":"m",
+ "groupBy":"top",
+ "threshold":[
+ 1000
+ ],
+ "index":[
+ ".test-index"
+ ],
+ "timeField":"@timestamp",
+ "aggField":"sheet.version",
+ "termField":"name.keyword"
+ },
+ "consumer":"alerts",
+ "alertTypeId":".index-threshold",
+ "schedule":{
+ "interval":"1m"
+ },
+ "actions":[
+ {
+ "id":"dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2",
+ "actionTypeId":".server-log",
+ "group":"threshold met",
+ "params":{
+ "level":"info",
+ "message":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
+ }
+ }
+ ],
+ "tags":[
+ "cpu"
+ ],
+ "notifyWhen":"onActionGroupChange",
+ "name":"my alert"
+}'
+--------------------------------------------------
+// KIBANA
+
+The API returns the following:
+
+[source,sh]
+--------------------------------------------------
+{
+ "id": "41893910-6bca-11eb-9e0d-85d233e3ee35",
+ "notifyWhen": "onActionGroupChange",
+ "params": {
+ "aggType": "avg",
+ "termSize": 6,
+ "thresholdComparator": ">",
+ "timeWindowSize": 5,
+ "timeWindowUnit": "m",
+ "groupBy": "top",
+ "threshold": [
+ 1000
+ ],
+ "index": [
+ ".kibana"
+ ],
+ "timeField": "@timestamp",
+ "aggField": "sheet.version",
+ "termField": "name.keyword"
+ },
+ "consumer": "alerts",
+ "alertTypeId": ".index-threshold",
+ "schedule": {
+ "interval": "1m"
+ },
+ "actions": [
+ {
+ "actionTypeId": ".server-log",
+ "group": "threshold met",
+ "params": {
+ "level": "info",
+ "message": "alert {{alertName}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
+ },
+ "id": "dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2"
+ }
+ ],
+ "tags": [
+ "cpu"
+ ],
+ "name": "my alert",
+ "enabled": true,
+ "throttle": null,
+ "apiKeyOwner": "elastic",
+ "createdBy": "elastic",
+ "updatedBy": "elastic",
+ "muteAll": false,
+ "mutedInstanceIds": [],
+ "updatedAt": "2021-02-10T18:03:19.961Z",
+ "createdAt": "2021-02-10T18:03:19.961Z",
+ "scheduledTaskId": "425b0800-6bca-11eb-9e0d-85d233e3ee35",
+ "executionStatus": {
+ "lastExecutionDate": "2021-02-10T18:03:19.966Z",
+ "status": "pending"
+ }
+}
+--------------------------------------------------
diff --git a/docs/api/alerts/delete.asciidoc b/docs/api/alerts/delete.asciidoc
new file mode 100644
index 0000000000000..b51005daae658
--- /dev/null
+++ b/docs/api/alerts/delete.asciidoc
@@ -0,0 +1,36 @@
+[[alerts-api-delete]]
+=== Delete alert API
+++++
+Delete alert
+++++
+
+Permanently remove an alert.
+
+WARNING: Once you delete an alert, you cannot recover it.
+
+[[alerts-api-delete-request]]
+==== Request
+
+`DELETE :/api/alerts/alert/`
+
+[[alerts-api-delete-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert that you want to remove.
+
+[[alerts-api-delete-response-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Example
+
+Delete an alert with ID:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X DELETE api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/disable.asciidoc b/docs/api/alerts/disable.asciidoc
new file mode 100644
index 0000000000000..5f74c33379409
--- /dev/null
+++ b/docs/api/alerts/disable.asciidoc
@@ -0,0 +1,34 @@
+[[alerts-api-disable]]
+=== Disable alert API
+++++
+Disable alert
+++++
+
+Disable an alert.
+
+[[alerts-api-disable-request]]
+==== Request
+
+`POST :/api/alerts/alert//_disable`
+
+[[alerts-api-disable-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert that you want to disable.
+
+[[alerts-api-disable-response-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Example
+
+Disable an alert with ID:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_disable
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/enable.asciidoc b/docs/api/alerts/enable.asciidoc
new file mode 100644
index 0000000000000..a10383f2a440d
--- /dev/null
+++ b/docs/api/alerts/enable.asciidoc
@@ -0,0 +1,34 @@
+[[alerts-api-enable]]
+=== Enable alert API
+++++
+Enable alert
+++++
+
+Enable an alert.
+
+[[alerts-api-enable-request]]
+==== Request
+
+`POST :/api/alerts/alert//_enable`
+
+[[alerts-api-enable-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert that you want to enable.
+
+[[alerts-api-enable-response-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Example
+
+Enable an alert with ID:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_enable
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/find.asciidoc b/docs/api/alerts/find.asciidoc
new file mode 100644
index 0000000000000..97cd9f4c19ba7
--- /dev/null
+++ b/docs/api/alerts/find.asciidoc
@@ -0,0 +1,117 @@
+[[alerts-api-find]]
+=== Find alerts API
+++++
+Find alerts
+++++
+
+Retrieve a paginated set of alerts based on condition.
+
+[[alerts-api-find-request]]
+==== Request
+
+`GET :/api/alerts/_find`
+
+[[alerts-api-find-query-params]]
+==== Query Parameters
+
+`per_page`::
+ (Optional, number) The number of alerts to return per page.
+
+`page`::
+ (Optional, number) The page number.
+
+`search`::
+ (Optional, string) An Elasticsearch {ref}/query-dsl-simple-query-string-query.html[simple_query_string] query that filters the alerts in the response.
+
+`default_search_operator`::
+ (Optional, string) The operator to use for the `simple_query_string`. The default is 'OR'.
+
+`search_fields`::
+ (Optional, array|string) The fields to perform the `simple_query_string` parsed query against.
+
+`fields`::
+ (Optional, array|string) The fields to return in the `attributes` key of the response.
+
+`sort_field`::
+ (Optional, string) Sorts the response. Could be an alert fields returned in the `attributes` key of the response.
+
+`sort_order`::
+ (Optional, string) Sort direction, either `asc` or `desc`.
+
+`has_reference`::
+ (Optional, object) Filters the alerts that have a relations with the reference objects with the specific "type" and "ID".
+
+`filter`::
+ (Optional, string) A <> string that you filter with an attribute from your saved object.
+ It should look like savedObjectType.attributes.title: "myTitle". However, If you used a direct attribute of a saved object, such as `updatedAt`,
+ you will have to define your filter, for example, savedObjectType.updatedAt > 2018-12-22.
+
+NOTE: As alerts change in {kib}, the results on each page of the response also
+change. Use the find API for traditional paginated results, but avoid using it to export large amounts of data.
+
+[[alerts-api-find-request-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Examples
+
+Find alerts with names that start with `my`:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X GET api/alerts/_find?search_fields=name&search=my*
+--------------------------------------------------
+// KIBANA
+
+The API returns the following:
+
+[source,sh]
+--------------------------------------------------
+{
+ "page": 1,
+ "perPage": 10,
+ "total": 1,
+ "data": [
+ {
+ "id": "0a037d60-6b62-11eb-9e0d-85d233e3ee35",
+ "notifyWhen": "onActionGroupChange",
+ "params": {
+ "aggType": "avg",
+ },
+ "consumer": "alerts",
+ "alertTypeId": "test.alert.type",
+ "schedule": {
+ "interval": "1m"
+ },
+ "actions": [],
+ "tags": [],
+ "name": "test alert",
+ "enabled": true,
+ "throttle": null,
+ "apiKeyOwner": "elastic",
+ "createdBy": "elastic",
+ "updatedBy": "elastic",
+ "muteAll": false,
+ "mutedInstanceIds": [],
+ "updatedAt": "2021-02-10T05:37:19.086Z",
+ "createdAt": "2021-02-10T05:37:19.086Z",
+ "scheduledTaskId": "0b092d90-6b62-11eb-9e0d-85d233e3ee35",
+ "executionStatus": {
+ "lastExecutionDate": "2021-02-10T17:55:14.262Z",
+ "status": "ok"
+ }
+ },
+ ]
+}
+--------------------------------------------------
+
+For parameters that accept multiple values (e.g. `fields`), repeat the
+query parameter for each value:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X GET api/alerts/_find?fields=id&fields=name
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/get.asciidoc b/docs/api/alerts/get.asciidoc
new file mode 100644
index 0000000000000..934d7466dec3d
--- /dev/null
+++ b/docs/api/alerts/get.asciidoc
@@ -0,0 +1,70 @@
+[[alerts-api-get]]
+=== Get alert API
+++++
+Get alert
+++++
+
+Retrieve an alert by ID.
+
+[[alerts-api-get-request]]
+==== Request
+
+`GET :/api/alerts/alert/`
+
+[[alerts-api-get-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert to retrieve.
+
+[[alerts-api-get-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+[[alerts-api-get-example]]
+==== Example
+
+Retrieve the alert object with the ID `41893910-6bca-11eb-9e0d-85d233e3ee35`:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X GET api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35
+--------------------------------------------------
+// KIBANA
+
+The API returns the following:
+
+[source,sh]
+--------------------------------------------------
+{
+ "id": "0a037d60-6b62-11eb-9e0d-85d233e3ee35",
+ "notifyWhen": "onActionGroupChange",
+ "params": {
+ "aggType": "avg",
+ },
+ "consumer": "alerts",
+ "alertTypeId": "test.alert.type",
+ "schedule": {
+ "interval": "1m"
+ },
+ "actions": [],
+ "tags": [],
+ "name": "test alert",
+ "enabled": true,
+ "throttle": null,
+ "apiKeyOwner": "elastic",
+ "createdBy": "elastic",
+ "updatedBy": "elastic",
+ "muteAll": false,
+ "mutedInstanceIds": [],
+ "updatedAt": "2021-02-10T05:37:19.086Z",
+ "createdAt": "2021-02-10T05:37:19.086Z",
+ "scheduledTaskId": "0b092d90-6b62-11eb-9e0d-85d233e3ee35",
+ "executionStatus": {
+ "lastExecutionDate": "2021-02-10T17:55:14.262Z",
+ "status": "ok"
+ }
+}
+--------------------------------------------------
diff --git a/docs/api/alerts/health.asciidoc b/docs/api/alerts/health.asciidoc
new file mode 100644
index 0000000000000..3710ccf424945
--- /dev/null
+++ b/docs/api/alerts/health.asciidoc
@@ -0,0 +1,85 @@
+[[alerts-api-health]]
+=== Get Alerting framework health API
+++++
+Get Alerting framework health
+++++
+
+Retrieve the health status of the Alerting framework.
+
+[[alerts-api-health-request]]
+==== Request
+
+`GET :/api/alerts/_health`
+
+[[alerts-api-health-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+[[alerts-api-health-example]]
+==== Example
+
+Retrieve the health status of the Alerting framework:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X GET api/alerts/_health
+--------------------------------------------------
+// KIBANA
+
+The API returns the following:
+
+[source,sh]
+--------------------------------------------------
+{
+ "isSufficientlySecure":true,
+ "hasPermanentEncryptionKey":true,
+ "alertingFrameworkHeath":{
+ "decryptionHealth":{
+ "status":"ok",
+ "timestamp":"2021-02-10T23:35:04.949Z"
+ },
+ "executionHealth":{
+ "status":"ok",
+ "timestamp":"2021-02-10T23:35:04.949Z"
+ },
+ "readHealth":{
+ "status":"ok",
+ "timestamp":"2021-02-10T23:35:04.949Z"
+ }
+ }
+}
+--------------------------------------------------
+
+The health API response contains the following properties:
+
+[cols="2*<"]
+|===
+
+| `isSufficientlySecure`
+| Returns `false` if security is enabled, but TLS is not.
+
+| `hasPermanentEncryptionKey`
+| Return the state `false` if Encrypted Saved Object plugin has not a permanent encryption Key.
+
+| `alertingFrameworkHeath`
+| This state property has three substates that identify the health of the alerting framework API: `decryptionHealth`, `executionHealth`, and `readHealth`.
+
+|===
+
+`alertingFrameworkHeath` consists of the following properties:
+
+[cols="2*<"]
+|===
+
+| `decryptionHealth`
+| Returns the timestamp and status of the alert decryption: `ok`, `warn` or `error` .
+
+| `executionHealth`
+| Returns the timestamp and status of the alert execution: `ok`, `warn` or `error`.
+
+| `readHealth`
+| Returns the timestamp and status of the alert reading events: `ok`, `warn` or `error`.
+
+|===
diff --git a/docs/api/alerts/list.asciidoc b/docs/api/alerts/list.asciidoc
new file mode 100644
index 0000000000000..0bc3e158ec263
--- /dev/null
+++ b/docs/api/alerts/list.asciidoc
@@ -0,0 +1,127 @@
+[[alerts-api-list]]
+=== List alert types API
+++++
+List all alert types API
+++++
+
+Retrieve a list of all alert types.
+
+[[alerts-api-list-request]]
+==== Request
+
+`GET :/api/alerts/list_alert_types`
+
+[[alerts-api-list-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+[[alerts-api-list-example]]
+==== Example
+
+[source,sh]
+--------------------------------------------------
+$ curl -X GET api/alerts/list_alert_types
+--------------------------------------------------
+// KIBANA
+
+The API returns the following:
+
+[source,sh]
+--------------------------------------------------
+[
+ {
+ "id":".index-threshold",
+ "name":"Index threshold",
+ "actionGroups":[
+ {
+ "id":"threshold met",
+ "name":"Threshold met"
+ },
+ {
+ "id":"recovered",
+ "name":"Recovered"
+ }
+ ],
+ "recoveryActionGroup":{
+ "id":"recovered",
+ "name":"Recovered"
+ },
+ "defaultActionGroupId":"threshold met",
+ "actionVariables":{
+ "context":[
+ {
+ "name":"message",
+ "description":"A pre-constructed message for the alert."
+ },
+ ],
+ "state":[],
+ "params":[
+ {
+ "name":"threshold",
+ "description":"An array of values to use as the threshold; 'between' and 'notBetween' require two values, the others require one."
+ },
+ {
+ "name":"index",
+ "description":"index"
+ },
+ ]
+ },
+ "producer":"stackAlerts",
+ "minimumLicenseRequired":"basic",
+ "enabledInLicense":true,
+ "authorizedConsumers":{
+ "alerts":{
+ "read":true,
+ "all":true
+ },
+ "stackAlerts":{
+ "read":true,
+ "all":true
+ },
+ "uptime":{
+ "read":true,
+ "all":true
+ }
+ }
+ }
+]
+--------------------------------------------------
+
+Each alert type contains the following properties:
+
+[cols="2*<"]
+|===
+
+| `name`
+| The descriptive name of the alert type.
+
+| `id`
+| The unique ID of the alert type.
+
+| `minimumLicenseRequired`
+| The license required to use the alert type.
+
+| `enabledInLicense`
+| Whether the alert type is enabled or disabled based on the license.
+
+| `actionGroups`
+| An explicit list of groups for which the alert type can schedule actions, each with the action group's unique ID and human readable name. Alert `actions` validation will use this configuration to ensure that groups are valid. Use `kbn-i18n` to translate the names of the action group when registering the alert type.
+
+| `recoveryActionGroup`
+| An action group to use when an alert instance goes from an active state, to an inactive one. Do not specify this action group under the `actionGroups` property. If `recoveryActionGroup` is not specified, the default `recovered` action group is used.
+
+| `defaultActionGroupId`
+| The default ID for the alert type group.
+
+| `actionVariables`
+| An explicit list of action variables that the alert type makes available via context and state in action parameter templates, and a short human readable description. The Alert UI will use this information to prompt users for these variables in action parameter editors. Use `kbn-i18n` to translate the descriptions.
+
+| `producer`
+| The ID of the application producing this alert type.
+
+| `authorizedConsumers`
+| The list of the plugins IDs that have access to the alert type.
+
+|===
diff --git a/docs/api/alerts/mute.asciidoc b/docs/api/alerts/mute.asciidoc
new file mode 100644
index 0000000000000..9279786deae4c
--- /dev/null
+++ b/docs/api/alerts/mute.asciidoc
@@ -0,0 +1,37 @@
+[[alerts-api-mute]]
+=== Mute alert instance API
+++++
+Mute alert instance
+++++
+
+Mute an alert instance.
+
+[[alerts-api-mute-request]]
+==== Request
+
+`POST :/api/alerts/alert//alert_instance//_mute`
+
+[[alerts-api-mute-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert whose instance you want to mute.
+
+`alert_instance_id`::
+ (Required, string) The ID of the alert instance that you want to mute.
+
+[[alerts-api-mute-response-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Example
+
+Mute alert instance with ID:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/alert_instance/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_mute
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/mute_all.asciidoc b/docs/api/alerts/mute_all.asciidoc
new file mode 100644
index 0000000000000..f8a8c137240c6
--- /dev/null
+++ b/docs/api/alerts/mute_all.asciidoc
@@ -0,0 +1,34 @@
+[[alerts-api-mute-all]]
+=== Mute all alert instances API
+++++
+Mute all alert instances
+++++
+
+Mute all alert instances.
+
+[[alerts-api-mute-all-request]]
+==== Request
+
+`POST :/api/alerts/alert//_mute_all`
+
+[[alerts-api-mute-all-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert whose instances you want to mute.
+
+[[alerts-api-mute-all-response-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Example
+
+Mute all alert instances with ID:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_mute_all
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/unmute.asciidoc b/docs/api/alerts/unmute.asciidoc
new file mode 100644
index 0000000000000..f091ae3f45325
--- /dev/null
+++ b/docs/api/alerts/unmute.asciidoc
@@ -0,0 +1,37 @@
+[[alerts-api-unmute]]
+=== Unmute alert instance API
+++++
+Unmute alert instance
+++++
+
+Unmute an alert instance.
+
+[[alerts-api-unmute-request]]
+==== Request
+
+`POST :/api/alerts/alert//alert_instance//_unmute`
+
+[[alerts-api-unmute-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert whose instance you want to mute..
+
+`alert_instance_id`::
+ (Required, string) The ID of the alert instance that you want to unmute.
+
+[[alerts-api-unmute-response-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Example
+
+Unmute alert instance with ID:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/alert_instance/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_unmute
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/unmute_all.asciidoc b/docs/api/alerts/unmute_all.asciidoc
new file mode 100644
index 0000000000000..2359d120cf260
--- /dev/null
+++ b/docs/api/alerts/unmute_all.asciidoc
@@ -0,0 +1,34 @@
+[[alerts-api-unmute-all]]
+=== Unmute all alert instances API
+++++
+Unmute all alert instances
+++++
+
+Unmute all alert instances.
+
+[[alerts-api-unmute-all-request]]
+==== Request
+
+`POST :/api/alerts/alert//_unmute_all`
+
+[[alerts-api-unmute-all-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert whose instances you want to unmute.
+
+[[alerts-api-unmute-all-response-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+==== Example
+
+Unmute all alert instances with ID:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_unmute_all
+--------------------------------------------------
+// KIBANA
diff --git a/docs/api/alerts/update.asciidoc b/docs/api/alerts/update.asciidoc
new file mode 100644
index 0000000000000..aee2dd049a66f
--- /dev/null
+++ b/docs/api/alerts/update.asciidoc
@@ -0,0 +1,134 @@
+[[alerts-api-update]]
+=== Update alert API
+++++
+Update alert
+++++
+
+Update the attributes for an existing alert.
+
+[[alerts-api-update-request]]
+==== Request
+
+`PUT :/api/alerts/alert/`
+
+[[alerts-api-update-path-params]]
+==== Path parameters
+
+`id`::
+ (Required, string) The ID of the alert that you want to update.
+
+[[alerts-api-update-request-body]]
+==== Request body
+
+`name`::
+ (Required, string) A name to reference and search.
+
+`tags`::
+ (Optional, string array) A list of keywords to reference and search.
+
+`schedule`::
+ (Required, object) When to run this alert. Use one of the available schedule formats.
++
+._Schedule Formats_.
+[%collapsible%open]
+=====
+A schedule uses a key: value format. {kib} currently supports the _Interval format_ , which specifies the interval in seconds, minutes, hours, or days at which to execute the alert.
+
+Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`.
+
+=====
+
+`throttle`::
+ (Optional, string) How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications during this period.
+
+`notifyWhen`::
+ (Required, string) The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`.
+
+`params`::
+ (Required, object) The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
+
+`actions`::
+ (Optional, object array) An array of the following action objects.
++
+.Properties of the action objects:
+[%collapsible%open]
+=====
+ `group`:::
+ (Required, string) Grouping actions is recommended for escalations for different types of alert instances. If you don't need this, set the value to `default`.
+
+ `id`:::
+ (Required, string) The ID of the action that saved object executes.
+
+ `actionTypeId`:::
+ (Required, string) The id of the <>.
+
+ `params`:::
+ (Required, object) The map to the `params` that the <> will receive. `params` are handled as Mustache templates and passed a default set of context.
+=====
+
+
+[[alerts-api-update-errors-codes]]
+==== Response code
+
+`200`::
+ Indicates a successful call.
+
+[[alerts-api-update-example]]
+==== Example
+
+Update an alert with ID `ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74` with a different name:
+
+[source,sh]
+--------------------------------------------------
+$ curl -X PUT api/alerts/alert/ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74
+
+{
+ "notifyWhen": "onActionGroupChange",
+ "params": {
+ "aggType": "avg",
+ },
+ "schedule": {
+ "interval": "1m"
+ },
+ "actions": [],
+ "tags": [],
+ "name": "new name",
+ "throttle": null,
+}
+--------------------------------------------------
+// KIBANA
+
+The API returns the following:
+
+[source,sh]
+--------------------------------------------------
+{
+ "id": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74",
+ "notifyWhen": "onActionGroupChange",
+ "params": {
+ "aggType": "avg",
+ },
+ "consumer": "alerts",
+ "alertTypeId": "test.alert.type",
+ "schedule": {
+ "interval": "1m"
+ },
+ "actions": [],
+ "tags": [],
+ "name": "new name",
+ "enabled": true,
+ "throttle": null,
+ "apiKeyOwner": "elastic",
+ "createdBy": "elastic",
+ "updatedBy": "elastic",
+ "muteAll": false,
+ "mutedInstanceIds": [],
+ "updatedAt": "2021-02-10T05:37:19.086Z",
+ "createdAt": "2021-02-10T05:37:19.086Z",
+ "scheduledTaskId": "0b092d90-6b62-11eb-9e0d-85d233e3ee35",
+ "executionStatus": {
+ "lastExecutionDate": "2021-02-10T17:55:14.262Z",
+ "status": "ok"
+ }
+}
+--------------------------------------------------
diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc
index d911c2154ea4c..aaaca867a5a01 100644
--- a/docs/apm/agent-configuration.asciidoc
+++ b/docs/apm/agent-configuration.asciidoc
@@ -46,6 +46,7 @@ Go Agent:: {apm-go-ref}/configuration.html[Configuration reference]
Java Agent:: {apm-java-ref}/configuration.html[Configuration reference]
.NET Agent:: {apm-dotnet-ref}/configuration.html[Configuration reference]
Node.js Agent:: {apm-node-ref}/configuration.html[Configuration reference]
+PHP Agent:: _Not yet supported_
Python Agent:: {apm-py-ref}/configuration.html[Configuration reference]
Ruby Agent:: {apm-ruby-ref}/configuration.html[Configuration reference]
Real User Monitoring (RUM) Agent:: {apm-rum-ref}/configuration.html[Configuration reference]
diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc
index c405ea10ade3d..3fe9146658eef 100644
--- a/docs/apm/filters.asciidoc
+++ b/docs/apm/filters.asciidoc
@@ -52,8 +52,9 @@ See the documentation for each agent you're using to learn how to configure serv
* *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`]
* *Java:* {apm-java-ref}/config-core.html#config-environment[`environment`]
-* *.NET* {apm-dotnet-ref}/config-core.html#config-environment[`Environment`]
+* *.NET:* {apm-dotnet-ref}/config-core.html#config-environment[`Environment`]
* *Node.js:* {apm-node-ref}/configuration.html#environment[`environment`]
+* *PHP:* {apm-php-ref}/configuration-reference.html#config-environment[`environment`]
* *Python:* {apm-py-ref}/configuration.html#config-environment[`environment`]
* *Ruby:* {apm-ruby-ref}/configuration.html#config-environment[`environment`]
* *Real User Monitoring:* {apm-rum-ref}/configuration.html#environment[`environment`]
diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc
index 7cc4da8a1fc1d..a3ac62a4c8343 100644
--- a/docs/apm/service-maps.asciidoc
+++ b/docs/apm/service-maps.asciidoc
@@ -87,10 +87,11 @@ Type and subtype are based on `span.type`, and `span.subtype`.
Service maps are supported for the following Agent versions:
[horizontal]
-Go Agent:: ≥ v1.7.0
-Java Agent:: ≥ v1.13.0
-.NET Agent:: ≥ v1.3.0
-Node.js Agent:: ≥ v3.6.0
-Python Agent:: ≥ v5.5.0
-Ruby Agent:: ≥ v3.6.0
-Real User Monitoring (RUM) Agent:: ≥ v4.7.0
+Go agent:: ≥ v1.7.0
+Java agent:: ≥ v1.13.0
+.NET agent:: ≥ v1.3.0
+Node.js agent:: ≥ v3.6.0
+PHP agent:: _Not yet supported_
+Python agent:: ≥ v5.5.0
+Ruby agent:: ≥ v3.6.0
+Real User Monitoring (RUM) agent:: ≥ v4.7.0
diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc
index 465a3d652046d..5049321363f88 100644
--- a/docs/apm/troubleshooting.asciidoc
+++ b/docs/apm/troubleshooting.asciidoc
@@ -17,6 +17,7 @@ don't forget to check our other troubleshooting guides or discussion forum:
* {apm-go-ref}/troubleshooting.html[Go agent troubleshooting]
* {apm-java-ref}/trouble-shooting.html[Java agent troubleshooting]
* {apm-node-ref}/troubleshooting.html[Node.js agent troubleshooting]
+* {apm-php-ref}/troubleshooting.html[PHP agent troubleshooting]
* {apm-py-ref}/troubleshooting.html[Python agent troubleshooting]
* {apm-ruby-ref}/debugging.html[Ruby agent troubleshooting]
* {apm-rum-ref/troubleshooting.html[RUM troubleshooting]
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index dc0405b22942f..c7d5242da69de 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -457,7 +457,7 @@ of buckets to try to represent.
[horizontal]
[[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`::
-Enables legacy charts library for area, line and bar charts in visualize.
+Enables the legacy charts library for aggregation-based area, line, and bar charts in *Visualize*.
[[visualization-colormapping]]`visualization:colorMapping`::
**This setting is deprecated and will not be supported as of 8.0.**
@@ -465,24 +465,25 @@ Maps values to specific colors in *Visualize* charts and *TSVB*. This setting do
[[visualization-dimmingopacity]]`visualization:dimmingOpacity`::
The opacity of the chart items that are dimmed when highlighting another element
-of the chart. The lower this number, the more the highlighted element stands out.
-This must be a number between 0 and 1.
+of the chart. Use numbers between 0 and 1. The lower the number, the more the highlighted element stands out.
+
+[[visualization-heatmap-maxbuckets]]`visualization:heatmap:maxBuckets`::
+The maximum number of buckets a datasource can return. High numbers can have a negative impact on your browser rendering performance.
[[visualization-regionmap-showwarnings]]`visualization:regionmap:showWarnings`::
Shows a warning in a region map when terms cannot be joined to a shape.
[[visualization-tilemap-wmsdefaults]]`visualization:tileMap:WMSdefaults`::
-The default properties for the WMS map server support in the coordinate map.
+The default properties for the WMS map server supported in the coordinate map.
[[visualization-tilemap-maxprecision]]`visualization:tileMap:maxPrecision`::
-The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high,
-and 12 is the maximum. See this
-{ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[explanation of cell dimensions].
+The maximum geoHash precision displayed in tile maps. 7 is high, 10 is very high,
+and 12 is the maximum. For more information, refer to
+{ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[Cell dimensions at the equator].
[[visualize-enablelabs]]`visualize:enableLabs`::
-Enables users to create, view, and edit experimental visualizations. If disabled,
-only visualizations that are considered production-ready are available to the
-user.
+Enables users to create, view, and edit experimental visualizations. When disabled,
+only production-ready visualizations are available to users.
[float]
diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc
index 2ae83bee1e06c..9916ab42186dc 100644
--- a/docs/user/api.asciidoc
+++ b/docs/user/api.asciidoc
@@ -36,6 +36,7 @@ include::{kib-repo-dir}/api/features.asciidoc[]
include::{kib-repo-dir}/api/spaces-management.asciidoc[]
include::{kib-repo-dir}/api/role-management.asciidoc[]
include::{kib-repo-dir}/api/saved-objects.asciidoc[]
+include::{kib-repo-dir}/api/alerts.asciidoc[]
include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[]
include::{kib-repo-dir}/api/dashboard-api.asciidoc[]
include::{kib-repo-dir}/api/logstash-configuration-management.asciidoc[]
diff --git a/packages/kbn-storybook/lib/default_config.ts b/packages/kbn-storybook/lib/default_config.ts
index 53c51e9cf29fe..1b049761a3a98 100644
--- a/packages/kbn-storybook/lib/default_config.ts
+++ b/packages/kbn-storybook/lib/default_config.ts
@@ -14,4 +14,11 @@ export const defaultConfig: StorybookConfig = {
typescript: {
reactDocgen: false,
},
+ webpackFinal: (config, options) => {
+ if (process.env.CI) {
+ config.parallelism = 4;
+ config.cache = true;
+ }
+ return config;
+ },
};
diff --git a/packages/kbn-storybook/lib/templates/index.ejs b/packages/kbn-storybook/lib/templates/index.ejs
index a4f8204c95d7a..b193c87824d40 100644
--- a/packages/kbn-storybook/lib/templates/index.ejs
+++ b/packages/kbn-storybook/lib/templates/index.ejs
@@ -16,12 +16,12 @@
-
-
-
-
+
+
+
+
<% if (typeof headHtmlSnippet !== 'undefined') { %> <%= headHtmlSnippet %> <% } %> <%
diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts
index c72c81f489fb9..f1a3737747573 100644
--- a/src/dev/storybook/aliases.ts
+++ b/src/dev/storybook/aliases.ts
@@ -6,10 +6,12 @@
* Side Public License, v 1.
*/
+// Please also add new aliases to test/scripts/jenkins_storybook.sh
export const storybookAliases = {
apm: 'x-pack/plugins/apm/.storybook',
canvas: 'x-pack/plugins/canvas/storybook',
codeeditor: 'src/plugins/kibana_react/public/code_editor/.storybook',
+ ci_composite: '.ci/.storybook',
url_template_editor: 'src/plugins/kibana_react/public/url_template_editor/.storybook',
dashboard: 'src/plugins/dashboard/.storybook',
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook',
diff --git a/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap b/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap
index bc4ed477d9eea..f8ba1b3868527 100644
--- a/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap
+++ b/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap
@@ -2,6 +2,7 @@
exports[`renders DashboardSaveModal 1`] = `
{
onClose={this.props.onClose}
title={this.props.title}
showCopyOnSave={this.props.showCopyOnSave}
+ initialCopyOnSave={this.props.showCopyOnSave}
objectType="dashboard"
options={this.renderDashboardSaveOptions()}
showDescription={false}
diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts
index 9c571f0f0ef86..465deed4d9039 100644
--- a/test/functional/page_objects/dashboard_page.ts
+++ b/test/functional/page_objects/dashboard_page.ts
@@ -425,8 +425,9 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide
await this.setStoreTimeWithDashboard(saveOptions.storeTimeWithDashboard);
}
- if (saveOptions.saveAsNew !== undefined) {
- await this.setSaveAsNewCheckBox(saveOptions.saveAsNew);
+ const saveAsNewCheckboxExists = await testSubjects.exists('saveAsNewCheckbox');
+ if (saveAsNewCheckboxExists) {
+ await this.setSaveAsNewCheckBox(Boolean(saveOptions.saveAsNew));
}
if (saveOptions.tags) {
diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh
new file mode 100755
index 0000000000000..8ebfc1035fe1f
--- /dev/null
+++ b/test/scripts/jenkins_storybook.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+source src/dev/ci_setup/setup_env.sh
+
+cd "$XPACK_DIR/plugins/canvas"
+node scripts/storybook --dll
+
+cd "$KIBANA_DIR"
+
+# yarn storybook --site apm # TODO re-enable after being fixed
+yarn storybook --site canvas
+yarn storybook --site ci_composite
+yarn storybook --site url_template_editor
+yarn storybook --site codeeditor
+yarn storybook --site dashboard
+yarn storybook --site dashboard_enhanced
+yarn storybook --site data_enhanced
+yarn storybook --site embeddable
+yarn storybook --site infra
+yarn storybook --site security_solution
+yarn storybook --site ui_actions_enhanced
+yarn storybook --site observability
+yarn storybook --site presentation
diff --git a/vars/githubCommitStatus.groovy b/vars/githubCommitStatus.groovy
index 248d226169a61..175dbe0c90542 100644
--- a/vars/githubCommitStatus.groovy
+++ b/vars/githubCommitStatus.groovy
@@ -41,13 +41,15 @@ def trackBuild(commit, context, Closure closure) {
}
// state: error|failure|pending|success
-def create(sha, state, description, context) {
+def create(sha, state, description, context, targetUrl = null) {
+ targetUrl = targetUrl ?: env.BUILD_URL
+
withGithubCredentials {
return githubApi.post("repos/elastic/kibana/statuses/${sha}", [
state: state,
description: description,
context: context,
- target_url: env.BUILD_URL
+ target_url: targetUrl.toString()
])
}
}
diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy
index eead00c082ba7..a2a3a81f253a0 100644
--- a/vars/githubPr.groovy
+++ b/vars/githubPr.groovy
@@ -169,12 +169,18 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) {
? getBuildStatusIncludingMetrics()
: buildUtils.getBuildStatus()
+ def storybooksUrl = buildState.get('storybooksUrl')
+ def storybooksMessage = storybooksUrl ? "* [Storybooks Preview](${storybooksUrl})" : "* Storybooks not built"
+
if (!isFinal) {
+ storybooksMessage = storybooksUrl ? storybooksMessage : "* Storybooks not built yet"
+
def failuresPart = status != 'SUCCESS' ? ', with failures' : ''
messages << """
## :hourglass_flowing_sand: Build in-progress${failuresPart}
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
* Commit: ${getCommitHash()}
+ ${storybooksMessage}
* This comment will update when the build is complete
"""
} else if (status == 'SUCCESS') {
@@ -182,6 +188,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) {
## :green_heart: Build Succeeded
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
* Commit: ${getCommitHash()}
+ ${storybooksMessage}
${getDocsChangesLink()}
"""
} else if(status == 'UNSTABLE') {
@@ -189,6 +196,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) {
## :yellow_heart: Build succeeded, but was flaky
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
* Commit: ${getCommitHash()}
+ ${storybooksMessage}
${getDocsChangesLink()}
""".stripIndent()
@@ -204,6 +212,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) {
## :broken_heart: Build Failed
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
* Commit: ${getCommitHash()}
+ ${storybooksMessage}
* [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps)
* [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html)
${getDocsChangesLink()}
@@ -235,6 +244,13 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) {
messages << "To update your PR or re-run it, just comment with:\n`@elasticmachine merge upstream`"
+ catchErrors {
+ def assignees = getAssignees()
+ if (assignees) {
+ messages << "cc " + assignees.collect { "@${it}"}.join(" ")
+ }
+ }
+
info.builds << [
status: status,
url: env.BUILD_URL,
@@ -329,3 +345,19 @@ def shouldCheckCiMetricSuccess() {
return true
}
+
+def getPR() {
+ withGithubCredentials {
+ def path = "repos/elastic/kibana/pulls/${env.ghprbPullId}"
+ return githubApi.get(path)
+ }
+}
+
+def getAssignees() {
+ def pr = getPR()
+ if (!pr) {
+ return []
+ }
+
+ return pr.assignees.collect { it.login }
+}
diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy
index 7adf755bfc583..466a04d9b6b39 100644
--- a/vars/kibanaPipeline.groovy
+++ b/vars/kibanaPipeline.groovy
@@ -425,12 +425,13 @@ def buildXpackPlugins() {
runbld('./test/scripts/jenkins_xpack_build_plugins.sh', 'Build X-Pack Plugins')
}
-def withTasks(Map params = [worker: [:]], Closure closure) {
+def withTasks(Map params = [:], Closure closure) {
catchErrors {
- def config = [name: 'ci-worker', size: 'xxl', ramDisk: true] + (params.worker ?: [:])
+ def config = [setupWork: {}, worker: [:], parallel: 24] + params
+ def workerConfig = [name: 'ci-worker', size: 'xxl', ramDisk: true] + config.worker
- workers.ci(config) {
- withCiTaskQueue(parallel: 24) {
+ workers.ci(workerConfig) {
+ withCiTaskQueue([parallel: config.parallel]) {
parallel([
docker: {
retry(2) {
@@ -443,6 +444,8 @@ def withTasks(Map params = [worker: [:]], Closure closure) {
xpackPlugins: { buildXpackPlugins() },
])
+ config.setupWork()
+
catchErrors {
closure()
}
@@ -460,6 +463,7 @@ def allCiTasks() {
tasks.test()
tasks.functionalOss()
tasks.functionalXpack()
+ tasks.storybooksCi()
}
},
jest: {
diff --git a/vars/storybooks.groovy b/vars/storybooks.groovy
new file mode 100644
index 0000000000000..f3c4a97a7d436
--- /dev/null
+++ b/vars/storybooks.groovy
@@ -0,0 +1,83 @@
+def getStorybooksBucket() {
+ return "ci-artifacts.kibana.dev/storybooks"
+}
+
+def getDestinationDir() {
+ return env.ghprbPullId ? "pr-${env.ghprbPullId}" : buildState.get('checkoutInfo').branch.replace("/", "__")
+}
+
+def getUrl() {
+ return "https://${getStorybooksBucket()}/${getDestinationDir()}"
+}
+
+def getUrlLatest() {
+ return "${getUrl()}/latest"
+}
+
+def getUrlForCommit() {
+ return "${getUrl()}/${buildState.get('checkoutInfo').commit}"
+}
+
+def upload() {
+ dir("built_assets/storybook") {
+ sh "mv ci_composite composite"
+
+ def storybooks = sh(
+ script: 'ls -1d */',
+ returnStdout: true
+ ).trim()
+ .split('\n')
+ .collect { it.replace('/', '') }
+ .findAll { it != 'composite' }
+
+ def listHtml = storybooks.collect { """${it}""" }.join("\n")
+
+ def html = """
+
+
+ Storybooks
+ Composite Storybook
+ All
+
+
+
+ """
+
+ writeFile(file: 'index.html', text: html)
+
+ withGcpServiceAccount.fromVaultSecret('secret/kibana-issues/dev/ci-artifacts-key', 'value') {
+ kibanaPipeline.bash("""
+ gsutil -q -m cp -r -z js,css,html,json,map,txt,svg '*' 'gs://${getStorybooksBucket()}/${getDestinationDir()}/${buildState.get('checkoutInfo').commit}/'
+ gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp -z html 'index.html' 'gs://${getStorybooksBucket()}/${getDestinationDir()}/latest/'
+ """, "Upload Storybooks to GCS")
+ }
+
+ buildState.set('storybooksUrl', getUrlForCommit())
+ }
+}
+
+def build() {
+ withEnv(["STORYBOOK_BASE_URL=${getUrlForCommit()}"]) {
+ kibanaPipeline.bash('test/scripts/jenkins_storybook.sh', 'Build Storybooks')
+ }
+}
+
+def buildAndUpload() {
+ def sha = buildState.get('checkoutInfo').commit
+ def context = 'Build and Publish Storybooks'
+
+ githubCommitStatus.create(sha, 'pending', 'Building Storybooks', context)
+
+ try {
+ build()
+ upload()
+ githubCommitStatus.create(sha, 'success', 'Storybooks built', context, getUrlForCommit())
+ } catch(ex) {
+ githubCommitStatus.create(sha, 'error', 'Building Storybooks failed', context)
+ throw ex
+ }
+}
+
+return this
diff --git a/vars/tasks.groovy b/vars/tasks.groovy
index 7c40966ff5e04..846eed85fb076 100644
--- a/vars/tasks.groovy
+++ b/vars/tasks.groovy
@@ -124,4 +124,10 @@ def functionalXpack(Map params = [:]) {
}
}
+def storybooksCi() {
+ task {
+ storybooks.buildAndUpload()
+ }
+}
+
return this
diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx
index 0c1eacb6e800c..3cb31aa10c4fe 100644
--- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx
+++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx
@@ -30,8 +30,9 @@ export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/a
const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false };
export function AnomalyDetectionSetupLink() {
- const { uiFilters } = useUrlParams();
- const environment = uiFilters.environment;
+ const {
+ urlParams: { environment },
+ } = useUrlParams();
const { core } = useApmPluginContext();
const canGetJobs = !!core.application.capabilities.ml?.canGetJobs;
const license = useLicenseContext();
diff --git a/x-pack/plugins/canvas/storybook/preview-head.html b/x-pack/plugins/canvas/storybook/preview-head.html
index bef08a5120a36..f8a7de6ddbaf1 100644
--- a/x-pack/plugins/canvas/storybook/preview-head.html
+++ b/x-pack/plugins/canvas/storybook/preview-head.html
@@ -2,5 +2,5 @@
This file is looked for by Storybook and included in the HEAD element
if it exists. This is how we load the DLL content into the Storybook UI.
-->
-
-
+
+