diff --git a/.buildkite/scripts/bootstrap.sh b/.buildkite/scripts/bootstrap.sh
index 7ae925b262b9f..05a250f8e9e8c 100755
--- a/.buildkite/scripts/bootstrap.sh
+++ b/.buildkite/scripts/bootstrap.sh
@@ -5,7 +5,7 @@ set -euo pipefail
source .buildkite/scripts/common/util.sh
echo "--- yarn install and bootstrap"
-yarn kbn bootstrap --verbose
+yarn kbn bootstrap
###
### upload ts-refs-cache artifacts as quickly as possible so they are available for download
diff --git a/.buildkite/scripts/build_kibana_plugins.sh b/.buildkite/scripts/build_kibana_plugins.sh
index f4d82699ef92d..14ea71a75bae6 100644
--- a/.buildkite/scripts/build_kibana_plugins.sh
+++ b/.buildkite/scripts/build_kibana_plugins.sh
@@ -18,8 +18,7 @@ node scripts/build_kibana_platform_plugins \
--scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \
--scan-dir "$XPACK_DIR/test/usage_collection/plugins" \
--scan-dir "$XPACK_DIR/test/security_functional/fixtures/common" \
- --scan-dir "$XPACK_DIR/examples" \
- --verbose
+ --scan-dir "$XPACK_DIR/examples"
echo "--- Archive built plugins"
shopt -s globstar
diff --git a/.buildkite/scripts/lifecycle/build_status.js b/.buildkite/scripts/lifecycle/build_status.js
index 2c1d51ecac0a7..f2a5024c96013 100644
--- a/.buildkite/scripts/lifecycle/build_status.js
+++ b/.buildkite/scripts/lifecycle/build_status.js
@@ -7,11 +7,11 @@ const { BuildkiteClient } = require('kibana-buildkite-library');
console.log(status.success ? 'true' : 'false');
process.exit(0);
} catch (ex) {
+ console.error('Buildkite API Error', ex.message);
if (ex.response) {
- console.error('HTTP Error Response Body', ex.response.data);
console.error('HTTP Error Response Status', ex.response.status);
+ console.error('HTTP Error Response Body', ex.response.data);
}
- console.error(ex);
process.exit(1);
}
})();
diff --git a/.buildkite/scripts/lifecycle/ci_stats_complete.js b/.buildkite/scripts/lifecycle/ci_stats_complete.js
index d86e2ec7efcae..d9411178799ab 100644
--- a/.buildkite/scripts/lifecycle/ci_stats_complete.js
+++ b/.buildkite/scripts/lifecycle/ci_stats_complete.js
@@ -4,7 +4,11 @@ const { CiStats } = require('kibana-buildkite-library');
try {
await CiStats.onComplete();
} catch (ex) {
- console.error(ex);
+ console.error('CI Stats Error', ex.message);
+ if (ex.response) {
+ console.error('HTTP Error Response Status', ex.response.status);
+ console.error('HTTP Error Response Body', ex.response.data);
+ }
process.exit(1);
}
})();
diff --git a/.buildkite/scripts/lifecycle/ci_stats_start.js b/.buildkite/scripts/lifecycle/ci_stats_start.js
index 115aa9bd23954..ec0e4c713499e 100644
--- a/.buildkite/scripts/lifecycle/ci_stats_start.js
+++ b/.buildkite/scripts/lifecycle/ci_stats_start.js
@@ -4,7 +4,11 @@ const { CiStats } = require('kibana-buildkite-library');
try {
await CiStats.onStart();
} catch (ex) {
- console.error(ex);
+ console.error('CI Stats Error', ex.message);
+ if (ex.response) {
+ console.error('HTTP Error Response Status', ex.response.status);
+ console.error('HTTP Error Response Body', ex.response.data);
+ }
process.exit(1);
}
})();
diff --git a/.ci/Dockerfile b/.ci/Dockerfile
index 947242ecc0ece..d3ea74ca38969 100644
--- a/.ci/Dockerfile
+++ b/.ci/Dockerfile
@@ -1,7 +1,7 @@
# NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable.
# If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
-ARG NODE_VERSION=14.17.5
+ARG NODE_VERSION=14.17.6
FROM node:${NODE_VERSION} AS base
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 3829121aa5fe9..381fad404ca73 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -439,6 +439,9 @@
/x-pack/test/reporting_api_integration/ @elastic/kibana-reporting-services @elastic/kibana-app-services
/x-pack/test/reporting_functional/ @elastic/kibana-reporting-services @elastic/kibana-app-services
/x-pack/test/stack_functional_integration/apps/reporting/ @elastic/kibana-reporting-services @elastic/kibana-app-services
+/docs/user/reporting @elastic/kibana-reporting-services @elastic/kibana-app-services
+/docs/settings/reporting-settings.asciidoc @elastic/kibana-reporting-services @elastic/kibana-app-services
+/docs/setup/configuring-reporting.asciidoc @elastic/kibana-reporting-services @elastic/kibana-app-services
#CC# /x-pack/plugins/reporting/ @elastic/kibana-reporting-services
diff --git a/.github/workflows/sync-main-branch.yml b/.github/workflows/sync-main-branch.yml
new file mode 100644
index 0000000000000..63465602e8436
--- /dev/null
+++ b/.github/workflows/sync-main-branch.yml
@@ -0,0 +1,26 @@
+# Synchronize all pushes to 'master' branch with 'main' branch to facilitate migration
+name: "Sync main branch"
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ sync_latest_from_upstream:
+ runs-on: ubuntu-latest
+ name: Sync latest commits from master branch
+
+ steps:
+ - name: Checkout target repo
+ uses: actions/checkout@v2
+ with:
+ ref: main
+
+ - name: Sync upstream changes
+ id: sync
+ uses: aormsby/Fork-Sync-With-Upstream-action@v3.0
+ with:
+ target_sync_branch: main
+ target_repo_token: ${{ secrets.KIBANAMACHINE_TOKEN }}
+ upstream_sync_branch: master
+ upstream_sync_repo: elastic/kibana
diff --git a/.i18nrc.json b/.i18nrc.json
index 3301cd04ad06c..f38d6b8faae7e 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -2,6 +2,7 @@
"paths": {
"alerts": "packages/kbn-alerts/src",
"autocomplete": "packages/kbn-securitysolution-autocomplete/src",
+ "kbnConfig": "packages/kbn-config/src",
"console": "src/plugins/console",
"core": "src/core",
"discover": "src/plugins/discover",
diff --git a/.node-version b/.node-version
index 18711d290eac4..5595ae1aa9e4c 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-14.17.5
+14.17.6
diff --git a/.nvmrc b/.nvmrc
index 18711d290eac4..5595ae1aa9e4c 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-14.17.5
+14.17.6
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
index 384277822709c..3ae3f202a3bfd 100644
--- a/WORKSPACE.bazel
+++ b/WORKSPACE.bazel
@@ -27,13 +27,13 @@ check_rules_nodejs_version(minimum_version_string = "3.8.0")
# we can update that rule.
node_repositories(
node_repositories = {
- "14.17.5-darwin_amd64": ("node-v14.17.5-darwin-x64.tar.gz", "node-v14.17.5-darwin-x64", "2e40ab625b45b9bdfcb963ddd4d65d87ddf1dd37a86b6f8b075cf3d77fe9dc09"),
- "14.17.5-linux_arm64": ("node-v14.17.5-linux-arm64.tar.xz", "node-v14.17.5-linux-arm64", "3a2e674b6db50dfde767c427e8f077235bbf6f9236e1b12a4cc3496b12f94bae"),
- "14.17.5-linux_s390x": ("node-v14.17.5-linux-s390x.tar.xz", "node-v14.17.5-linux-s390x", "7d40eee3d54241403db12fb3bc420cd776e2b02e89100c45cf5e74a73942e7f6"),
- "14.17.5-linux_amd64": ("node-v14.17.5-linux-x64.tar.xz", "node-v14.17.5-linux-x64", "2d759de07a50cd7f75bd73d67e97b0d0e095ee3c413efac7d1b3d1e84ed76fff"),
- "14.17.5-windows_amd64": ("node-v14.17.5-win-x64.zip", "node-v14.17.5-win-x64", "a99b7ee08e846e5d1f4e70c4396265542819d79ed9cebcc27760b89571f03cbf"),
+ "14.17.6-darwin_amd64": ("node-v14.17.6-darwin-x64.tar.gz", "node-v14.17.6-darwin-x64", "e3e4c02240d74fb1dc8a514daa62e5de04f7eaee0bcbca06a366ece73a52ad88"),
+ "14.17.6-linux_arm64": ("node-v14.17.6-linux-arm64.tar.xz", "node-v14.17.6-linux-arm64", "9c4f3a651e03cd9b5bddd33a80e8be6a6eb15e518513e410bb0852a658699156"),
+ "14.17.6-linux_s390x": ("node-v14.17.6-linux-s390x.tar.xz", "node-v14.17.6-linux-s390x", "3677f35b97608056013b5368f86eecdb044bdccc1b3976c1d4448736c37b6a0c"),
+ "14.17.6-linux_amd64": ("node-v14.17.6-linux-x64.tar.xz", "node-v14.17.6-linux-x64", "3bbe4faf356738d88b45be222bf5e858330541ff16bd0d4cfad36540c331461b"),
+ "14.17.6-windows_amd64": ("node-v14.17.6-win-x64.zip", "node-v14.17.6-win-x64", "b83e9ce542fda7fc519cec6eb24a2575a84862ea4227dedc171a8e0b5b614ac0"),
},
- node_version = "14.17.5",
+ node_version = "14.17.6",
node_urls = [
"https://nodejs.org/dist/v{version}/{filename}",
],
diff --git a/docs/apm/correlations.asciidoc b/docs/apm/correlations.asciidoc
index 45781228cd200..c0c18433c9021 100644
--- a/docs/apm/correlations.asciidoc
+++ b/docs/apm/correlations.asciidoc
@@ -12,7 +12,7 @@ piece of hardware, like a host or pod. Or, perhaps a set of users, based on IP
address or region, is facing increased latency due to local data center issues.
To find correlations, select a service on the *Services* page in the {apm-app}
-and click **View correlations**.
+then select a transaction group from the *Transactions* tab.
NOTE: Queries within the {apm-app} are also applied to the correlations.
@@ -20,26 +20,25 @@ NOTE: Queries within the {apm-app} are also applied to the correlations.
[[correlations-latency]]
==== Find high transaction latency correlations
-The correlations on the *Latency* tab help you discover which attributes are
-contributing to increased transaction latency.
+The correlations on the *Latency correlations* tab help you discover which
+attributes are contributing to increased transaction latency.
[role="screenshot"]
image::apm/images/correlations-hover.png[Latency correlations]
The progress bar indicates the status of the asynchronous analysis, which
performs statistical searches across a large number of attributes. For large
-time ranges and services with high transaction throughput this might take some
-time. To improve performance, reduce the time range on the service overview
-page.
+time ranges and services with high transaction throughput, this might take some
+time. To improve performance, reduce the time range.
The latency distribution chart visualizes the overall latency of the
-transactions in the service. If there are attributes that have a statistically
-significant correlation with slow response times, they are listed in a table
-below the chart. The table is sorted by correlation coefficients that range from
-0 to 1. Attributes with higher correlation values are more likely to contribute
-to high latency transactions. By default, the attribute with the highest
-correlation value is added to the chart. To see the latency distribution for
-other attributes, hover over their row in the table.
+transactions in the transaction group. If there are attributes that have a
+statistically significant correlation with slow response times, they are listed
+in a table below the chart. The table is sorted by correlation coefficients that
+range from 0 to 1. Attributes with higher correlation values are more likely to
+contribute to high latency transactions. By default, the attribute with the
+highest correlation value is added to the chart. To see the latency distribution
+for other attributes, hover over their row in the table.
If a correlated attribute seems noteworthy, use the **Filter** quick links:
diff --git a/docs/apm/images/correlations-hover.png b/docs/apm/images/correlations-hover.png
index c8d5622156b4c..80c1fa41adbdf 100644
Binary files a/docs/apm/images/correlations-hover.png and b/docs/apm/images/correlations-hover.png differ
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
index cadb34ae63b86..bc6075176cd22 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
@@ -9,6 +9,10 @@
```typescript
readonly links: {
readonly settings: string;
+ readonly apm: {
+ readonly kibanaSettings: string;
+ readonly supportedServiceMaps: string;
+ };
readonly canvas: {
readonly guide: string;
};
@@ -128,6 +132,7 @@ readonly links: {
readonly rollupJobs: string;
readonly elasticsearch: Record;
readonly siem: {
+ readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index aded69733b58b..aa3f958018041 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
@@ -17,5 +17,5 @@ export interface DocLinksStart
| --- | --- | --- |
| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string
| |
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string
| |
-| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
}
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md
index e5f08213da510..bd0fc1e5b3713 100644
--- a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md
+++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md
@@ -18,6 +18,7 @@ export interface DeprecationsDetails
| [deprecationType](./kibana-plugin-core-server.deprecationsdetails.deprecationtype.md) | 'config' | 'feature'
| (optional) Used to identify between different deprecation types. Example use case: in Upgrade Assistant, we may want to allow the user to sort by deprecation type or show each type in a separate tab.Feel free to add new types if necessary. Predefined types are necessary to reduce having similar definitions with different keywords across kibana deprecations. |
| [documentationUrl](./kibana-plugin-core-server.deprecationsdetails.documentationurl.md) | string
| |
| [level](./kibana-plugin-core-server.deprecationsdetails.level.md) | 'warning' | 'critical' | 'fetch_error'
| levels: - warning: will not break deployment upon upgrade - critical: needs to be addressed before upgrade. - fetch\_error: Deprecations service failed to grab the deprecation details for the domain. |
-| [message](./kibana-plugin-core-server.deprecationsdetails.message.md) | string
| |
+| [message](./kibana-plugin-core-server.deprecationsdetails.message.md) | string
| The description message to be displayed for the deprecation. Check the README for writing deprecations in src/core/server/deprecations/README.mdx
|
| [requireRestart](./kibana-plugin-core-server.deprecationsdetails.requirerestart.md) | boolean
| |
+| [title](./kibana-plugin-core-server.deprecationsdetails.title.md) | string
| The title of the deprecation. Check the README for writing deprecations in src/core/server/deprecations/README.mdx
|
diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md
index d79a4c9bd7995..906ce8118f95b 100644
--- a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md
+++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md
@@ -4,6 +4,8 @@
## DeprecationsDetails.message property
+The description message to be displayed for the deprecation. Check the README for writing deprecations in `src/core/server/deprecations/README.mdx`
+
Signature:
```typescript
diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.title.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.title.md
new file mode 100644
index 0000000000000..e8907688f6e5e
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.title.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [title](./kibana-plugin-core-server.deprecationsdetails.title.md)
+
+## DeprecationsDetails.title property
+
+The title of the deprecation. Check the README for writing deprecations in `src/core/server/deprecations/README.mdx`
+
+Signature:
+
+```typescript
+title: string;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md
index 7d9d3dcdda4da..75732f59f1b3f 100644
--- a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md
+++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md
@@ -21,6 +21,7 @@ export interface DeprecationsServiceSetup
```ts
import { DeprecationsDetails, GetDeprecationsContext, CoreSetup } from 'src/core/server';
+import { i18n } from '@kbn/i18n';
async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise {
const deprecations: DeprecationsDetails[] = [];
@@ -29,52 +30,44 @@ async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecations
if (count > 0) {
// Example of a manual correctiveAction
deprecations.push({
- message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`,
+ title: i18n.translate('xpack.timelion.deprecations.worksheetsTitle', {
+ defaultMessage: 'Found Timelion worksheets.'
+ }),
+ message: i18n.translate('xpack.timelion.deprecations.worksheetsMessage', {
+ defaultMessage: 'You have {count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.',
+ values: { count },
+ }),
documentationUrl:
'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html',
level: 'warning',
correctiveActions: {
manualSteps: [
- 'Navigate to the Kibana Dashboard and click "Create dashboard".',
- 'Select Timelion from the "New Visualization" window.',
- 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.',
- 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.',
- 'In the toolbar, click Save.',
- 'On the Save visualization window, enter the visualization Title, then click Save and return.',
+ i18n.translate('xpack.timelion.deprecations.worksheets.manualStepOneMessage', {
+ defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".',
+ }),
+ i18n.translate('xpack.timelion.deprecations.worksheets.manualStepTwoMessage', {
+ defaultMessage: 'Select Timelion from the "New Visualization" window.',
+ }),
],
+ api: {
+ path: '/internal/security/users/test_dashboard_user',
+ method: 'POST',
+ body: {
+ username: 'test_dashboard_user',
+ roles: [
+ "machine_learning_user",
+ "enrich_user",
+ "kibana_admin"
+ ],
+ full_name: "Alison Goryachev",
+ email: "alisongoryachev@gmail.com",
+ metadata: {},
+ enabled: true
+ }
+ },
},
});
}
-
- // Example of an api correctiveAction
- deprecations.push({
- "message": "User 'test_dashboard_user' is using a deprecated role: 'kibana_user'",
- "documentationUrl": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html",
- "level": "critical",
- "correctiveActions": {
- "api": {
- "path": "/internal/security/users/test_dashboard_user",
- "method": "POST",
- "body": {
- "username": "test_dashboard_user",
- "roles": [
- "machine_learning_user",
- "enrich_user",
- "kibana_admin"
- ],
- "full_name": "Alison Goryachev",
- "email": "alisongoryachev@gmail.com",
- "metadata": {},
- "enabled": true
- }
- },
- "manualSteps": [
- "Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.",
- "Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role."
- ]
- },
- });
-
return deprecations;
}
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index 49adc72bbe346..a4863bd60089b 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -186,7 +186,7 @@ Set to `true` to enable a dark mode for the {kib} UI. You must refresh the page
to apply the setting.
[[theme-version]]`theme:version`::
-Specifies the {kib} theme. If you change the setting, refresh the page to apply the setting.
+Specifies the {kib} theme. If you change the setting, refresh the page to apply the setting.
[[timepicker-quickranges]]`timepicker:quickRanges`::
The list of ranges to show in the Quick section of the time filter. This should
@@ -214,7 +214,7 @@ truncation.
When enabled, provides access to the experimental *Labs* features for *Canvas*.
[[labs-dashboard-defer-below-fold]]`labs:dashboard:deferBelowFold`::
-When enabled, the panels that appear below the fold are loaded when they become visible on the dashboard.
+When enabled, the panels that appear below the fold are loaded when they become visible on the dashboard.
_Below the fold_ refers to panels that are not immediately visible when you open a dashboard, but become visible as you scroll. For additional information, refer to <>.
[[labs-dashboard-enable-ui]]`labs:dashboard:enable_ui`::
@@ -240,7 +240,7 @@ Banners are a https://www.elastic.co/subscriptions[subscription feature].
[horizontal]
[[banners-placement]]`banners:placement`::
-Set to `Top` to display a banner above the Elastic header for this space. Defaults to the value of
+Set to `Top` to display a banner above the Elastic header for this space. Defaults to the value of
the `xpack.banners.placement` configuration property.
[[banners-textcontent]]`banners:textContent`::
@@ -443,6 +443,9 @@ The threshold above which {ml} job anomalies are displayed in the {security-app}
A comma-delimited list of {es} indices from which the {security-app} collects
events.
+[[securitysolution-threatindices]]`securitySolution:defaultThreatIndex`::
+A comma-delimited list of Threat Intelligence indices from which the {security-app} collects indicators.
+
[[securitysolution-enablenewsfeed]]`securitySolution:enableNewsFeed`:: Enables
the security news feed on the Security *Overview* page.
@@ -544,4 +547,4 @@ only production-ready visualizations are available to users.
[horizontal]
[[telemetry-enabled-advanced-setting]]`telemetry:enabled`::
When enabled, helps improve the Elastic Stack by providing usage statistics for
-basic features. This data will not be shared outside of Elastic.
\ No newline at end of file
+basic features. This data will not be shared outside of Elastic.
diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc
index b339daf3d36f7..694f8c53f6745 100644
--- a/docs/settings/reporting-settings.asciidoc
+++ b/docs/settings/reporting-settings.asciidoc
@@ -281,16 +281,15 @@ NOTE: This setting exists for backwards compatibility, but is unused and hardcod
[[reporting-advanced-settings]]
==== Security settings
-[[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled`::
-deprecated:[7.14.0,This setting must be set to `false` in 8.0.] When `true`, grants users access to the {report-features} by assigning reporting roles, specified by `xpack.reporting.roles.allow`. Granting access to users this way is deprecated. Set to `false` and use {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. Defaults to `true`.
+With Security enabled, Reporting has two forms of access control: each user can only access their own reports, and custom roles determine who has privilege to generate reports. When Reporting is configured with <>, you can control the spaces and applications where users are allowed to generate reports.
[NOTE]
============================================================================
-In 7.x, the default value of `xpack.reporting.roles.enabled` is `true`. To migrate users to the
-new method of securing access to *Reporting*, you must set `xpack.reporting.roles.enabled: false`. In the next major version of {kib}, `false` will be the only valid configuration.
+The `xpack.reporting.roles` settings are for a deprecated system of access control in Reporting. It does not allow API Keys to generate reports, and it doesn't allow {kib} application privileges. We recommend you explicitly turn off reporting's deprecated access control feature by adding `xpack.reporting.roles.enabled: false` in kibana.yml. This will enable you to create custom roles that provide application privileges for reporting, as described in <>.
============================================================================
-`xpack.reporting.roles.allow`::
-deprecated:[7.14.0,This setting will be removed in 8.0.] Specifies the roles, in addition to superusers, that can generate reports, using the {ref}/security-api.html#security-role-apis[{es} role management APIs]. Requires `xpack.reporting.roles.enabled` to be `true`. Granting access to users this way is deprecated. Use {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. Defaults to `[ "reporting_user" ]`.
+[[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled`::
+deprecated:[7.14.0,The default for this setting will be `false` in an upcoming version of {kib}.] Sets access control to a set of assigned reporting roles, specified by `xpack.reporting.roles.allow`. Defaults to `true`.
-NOTE: Each user has access to only their own reports.
+`xpack.reporting.roles.allow`::
+deprecated:[7.14.0] In addition to superusers, specifies the roles that can generate reports using the {ref}/security-api.html#security-role-apis[{es} role management APIs]. Requires `xpack.reporting.roles.enabled` to be `true`. Defaults to `[ "reporting_user" ]`.
diff --git a/docs/setup/configuring-reporting.asciidoc b/docs/setup/configuring-reporting.asciidoc
index 0dba7befa2931..6d209092d3338 100644
--- a/docs/setup/configuring-reporting.asciidoc
+++ b/docs/setup/configuring-reporting.asciidoc
@@ -41,11 +41,16 @@ To troubleshoot the problem, start the {kib} server with environment variables t
[float]
[[grant-user-access]]
=== Grant users access to reporting
+When security is enabled, you grant users access to generate reports with <>, which allow you to create custom roles that control the spaces and applications where users generate reports.
-When security is enabled, access to the {report-features} is controlled by roles and <>. With privileges, you can define custom roles that grant *Reporting* privileges as sub-features of {kib} applications. To grant users permission to generate reports and view their reports in *Reporting*, create and assign the reporting role.
-
-[[reporting-app-users]]
-NOTE: In 7.12.0 and earlier, you grant access to the {report-features} by assigning users the `reporting_user` role in {es}.
+. Enable application privileges in Reporting. To enable, turn off the default user access control features in `kibana.yml`:
++
+[source,yaml]
+------------------------------------
+xpack.reporting.roles.enabled: false
+------------------------------------
++
+NOTE: If you use the default settings, you can still create a custom role that grants reporting privileges. The default role is `reporting_user`. This behavior is being deprecated and does not allow application-level access controls for {report-features}, and does not allow API keys or authentication tokens to authorize report generation. Refer to <> for information and caveats about the deprecated access control features.
. Create the reporting role.
@@ -90,10 +95,12 @@ If the *Reporting* option is unavailable, contact your administrator, or < Reporting*. Users can only access their own reports.
+
[float]
[[reporting-roles-user-api]]
==== Grant access with the role API
-You can also use the {ref}/security-api-put-role.html[role API] to grant access to the reporting features. Grant the reporting role to users in combination with other roles that grant read access to the data in {es}, and at least read access in the applications where users can generate reports.
+With <> enabled in Reporting, you can also use the {ref}/security-api-put-role.html[role API] to grant access to the {report-features}. Grant custom reporting roles to users in combination with other roles that grant read access to the data in {es}, and at least read access in the applications where users can generate reports.
[source, sh]
---------------------------------------------------------------
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index ac50062470d78..203339be638ab 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -406,7 +406,10 @@ override this parameter to use their own Tile Map Service. For example:
`"https://tiles.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana"`
| `migrations.batchSize:`
- | Defines the number of documents migrated at a time. The higher the value, the faster the Saved Objects migration process performs at the cost of higher memory consumption. If the migration fails due to a `circuit_breaking_exception`, set a smaller `batchSize` value. *Default: `1000`*
+ | Defines the number of documents migrated at a time. The higher the value, the faster the Saved Objects migration process performs at the cost of higher memory consumption. If upgrade migrations results in {kib} crashing with an out of memory exception or fails due to an Elasticsearch `circuit_breaking_exception`, use a smaller `batchSize` value to reduce the memory pressure. *Default: `1000`*
+
+ | `migrations.maxBatchSizeBytes:`
+ | Defines the maximum payload size for indexing batches of upgraded saved objects to avoid migrations failing due to a 413 Request Entity Too Large response from Elasticsearch. This value should be lower than or equal to your Elasticsearch cluster's `http.max_content_length` configuration option. *Default: `100mb`*
| `migrations.enableV2:`
| experimental[]. Enables the new Saved Objects migration algorithm. For information about the migration algorithm, refer to <>. When `migrations v2` is stable, the setting will be removed in an upcoming release without any further notice. Setting the value to `false` causes {kib} to use the legacy migration algorithm, which shipped in 7.11 and earlier versions. *Default: `true`*
diff --git a/package.json b/package.json
index 836e5336b7b50..e603190c72698 100644
--- a/package.json
+++ b/package.json
@@ -89,7 +89,7 @@
"**/underscore": "^1.13.1"
},
"engines": {
- "node": "14.17.5",
+ "node": "14.17.6",
"yarn": "^1.21.1"
},
"dependencies": {
@@ -100,7 +100,7 @@
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.19",
"@elastic/ems-client": "7.15.0",
- "@elastic/eui": "37.3.0",
+ "@elastic/eui": "37.3.1",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "^9.0.1-kibana3",
"@elastic/maki": "6.3.0",
@@ -655,6 +655,7 @@
"@types/yauzl": "^2.9.1",
"@types/zen-observable": "^0.8.0",
"@typescript-eslint/eslint-plugin": "^4.14.1",
+ "@typescript-eslint/typescript-estree": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"@yarnpkg/lockfile": "^1.1.0",
"abab": "^2.0.4",
@@ -725,6 +726,7 @@
"eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-react-perf": "^3.2.3",
+ "eslint-traverse": "^1.0.0",
"expose-loader": "^0.7.5",
"faker": "^5.1.0",
"fancy-log": "^1.3.2",
diff --git a/packages/elastic-eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js
index 1b3e852e5a502..38c0c43132564 100644
--- a/packages/elastic-eslint-config-kibana/.eslintrc.js
+++ b/packages/elastic-eslint-config-kibana/.eslintrc.js
@@ -90,5 +90,7 @@ module.exports = {
},
],
],
+
+ '@kbn/eslint/no_async_promise_body': 'error',
},
};
diff --git a/packages/kbn-config/BUILD.bazel b/packages/kbn-config/BUILD.bazel
index 75e4428ed2d70..e0cf4d2205d65 100644
--- a/packages/kbn-config/BUILD.bazel
+++ b/packages/kbn-config/BUILD.bazel
@@ -35,6 +35,7 @@ RUNTIME_DEPS = [
"//packages/kbn-logging",
"//packages/kbn-std",
"//packages/kbn-utility-types",
+ "//packages/kbn-i18n",
"@npm//js-yaml",
"@npm//load-json-file",
"@npm//lodash",
@@ -48,6 +49,7 @@ TYPES_DEPS = [
"//packages/kbn-logging",
"//packages/kbn-std",
"//packages/kbn-utility-types",
+ "//packages/kbn-i18n",
"@npm//load-json-file",
"@npm//rxjs",
"@npm//@types/jest",
diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts
index 563d4017f5ed9..0a605cbc1c532 100644
--- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts
+++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts
@@ -48,7 +48,8 @@ describe('DeprecationFactory', () => {
"Replace \\"myplugin.deprecated\\" with \\"myplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).",
],
},
- "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"",
+ "message": "Setting \\"myplugin.deprecated\\" has been replaced by \\"myplugin.renamed\\"",
+ "title": "Setting \\"myplugin.deprecated\\" is deprecated",
},
],
]
@@ -103,7 +104,8 @@ describe('DeprecationFactory', () => {
"Replace \\"myplugin.oldsection.deprecated\\" with \\"myplugin.newsection.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).",
],
},
- "message": "\\"myplugin.oldsection.deprecated\\" is deprecated and has been replaced by \\"myplugin.newsection.renamed\\"",
+ "message": "Setting \\"myplugin.oldsection.deprecated\\" has been replaced by \\"myplugin.newsection.renamed\\"",
+ "title": "Setting \\"myplugin.oldsection.deprecated\\" is deprecated",
},
],
]
@@ -130,7 +132,8 @@ describe('DeprecationFactory', () => {
"Remove \\"myplugin.deprecated\\" from the config.",
],
},
- "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"",
+ "message": "Setting \\"$myplugin.deprecated\\" has been replaced by \\"$myplugin.renamed\\". However, both keys are present. Ignoring \\"$myplugin.deprecated\\"",
+ "title": "Setting \\"myplugin.deprecated\\" is deprecated",
},
],
]
@@ -172,7 +175,8 @@ describe('DeprecationFactory', () => {
"Replace \\"myplugin.deprecated\\" with \\"myplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).",
],
},
- "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"",
+ "message": "Setting \\"myplugin.deprecated\\" has been replaced by \\"myplugin.renamed\\"",
+ "title": "Setting \\"myplugin.deprecated\\" is deprecated",
},
],
]
@@ -212,7 +216,8 @@ describe('DeprecationFactory', () => {
"Replace \\"oldplugin.deprecated\\" with \\"newplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).",
],
},
- "message": "\\"oldplugin.deprecated\\" is deprecated and has been replaced by \\"newplugin.renamed\\"",
+ "message": "Setting \\"oldplugin.deprecated\\" has been replaced by \\"newplugin.renamed\\"",
+ "title": "Setting \\"oldplugin.deprecated\\" is deprecated",
},
],
]
@@ -264,7 +269,8 @@ describe('DeprecationFactory', () => {
"Remove \\"myplugin.deprecated\\" from the config.",
],
},
- "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"",
+ "message": "Setting \\"$myplugin.deprecated\\" has been replaced by \\"$myplugin.renamed\\". However, both keys are present. Ignoring \\"$myplugin.deprecated\\"",
+ "title": "Setting \\"myplugin.deprecated\\" is deprecated",
},
],
]
@@ -293,10 +299,11 @@ describe('DeprecationFactory', () => {
Object {
"correctiveActions": Object {
"manualSteps": Array [
- "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)",
+ "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only).",
],
},
- "message": "myplugin.deprecated is deprecated and is no longer used",
+ "message": "You no longer need to configure \\"myplugin.deprecated\\".",
+ "title": "Setting \\"myplugin.deprecated\\" is deprecated",
},
],
]
@@ -325,10 +332,11 @@ describe('DeprecationFactory', () => {
Object {
"correctiveActions": Object {
"manualSteps": Array [
- "Remove \\"myplugin.section.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)",
+ "Remove \\"myplugin.section.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only).",
],
},
- "message": "myplugin.section.deprecated is deprecated and is no longer used",
+ "message": "You no longer need to configure \\"myplugin.section.deprecated\\".",
+ "title": "Setting \\"myplugin.section.deprecated\\" is deprecated",
},
],
]
@@ -375,10 +383,11 @@ describe('DeprecationFactory', () => {
Object {
"correctiveActions": Object {
"manualSteps": Array [
- "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)",
+ "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only).",
],
},
- "message": "myplugin.deprecated is deprecated and is no longer used",
+ "message": "You no longer need to configure \\"myplugin.deprecated\\".",
+ "title": "Setting \\"myplugin.deprecated\\" is deprecated",
},
],
]
diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts
index 76bcc1958d0de..6d7669cef04f2 100644
--- a/packages/kbn-config/src/deprecation/deprecation_factory.ts
+++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts
@@ -7,6 +7,8 @@
*/
import { get } from 'lodash';
+import { i18n } from '@kbn/i18n';
+
import {
ConfigDeprecation,
AddConfigDeprecation,
@@ -15,6 +17,13 @@ import {
ConfigDeprecationCommand,
} from './types';
+const getDeprecationTitle = (deprecationPath: string) => {
+ return i18n.translate('kbnConfig.deprecations.deprecatedSettingTitle', {
+ defaultMessage: 'Setting "{deprecationPath}" is deprecated',
+ values: { deprecationPath },
+ });
+};
+
const _rename = (
config: Record,
rootPath: string,
@@ -33,10 +42,18 @@ const _rename = (
const newValue = get(config, fullNewPath);
if (newValue === undefined) {
addDeprecation({
- message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`,
+ title: getDeprecationTitle(fullOldPath),
+ message: i18n.translate('kbnConfig.deprecations.replacedSettingMessage', {
+ defaultMessage: `Setting "{fullOldPath}" has been replaced by "{fullNewPath}"`,
+ values: { fullOldPath, fullNewPath },
+ }),
correctiveActions: {
manualSteps: [
- `Replace "${fullOldPath}" with "${fullNewPath}" in the Kibana config file, CLI flag, or environment variable (in Docker only).`,
+ i18n.translate('kbnConfig.deprecations.replacedSetting.manualStepOneMessage', {
+ defaultMessage:
+ 'Replace "{fullOldPath}" with "{fullNewPath}" in the Kibana config file, CLI flag, or environment variable (in Docker only).',
+ values: { fullOldPath, fullNewPath },
+ }),
],
},
...details,
@@ -47,11 +64,23 @@ const _rename = (
};
} else {
addDeprecation({
- message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"`,
+ title: getDeprecationTitle(fullOldPath),
+ message: i18n.translate('kbnConfig.deprecations.conflictSettingMessage', {
+ defaultMessage:
+ 'Setting "${fullOldPath}" has been replaced by "${fullNewPath}". However, both keys are present. Ignoring "${fullOldPath}"',
+ values: { fullOldPath, fullNewPath },
+ }),
correctiveActions: {
manualSteps: [
- `Make sure "${fullNewPath}" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).`,
- `Remove "${fullOldPath}" from the config.`,
+ i18n.translate('kbnConfig.deprecations.conflictSetting.manualStepOneMessage', {
+ defaultMessage:
+ 'Make sure "{fullNewPath}" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).',
+ values: { fullNewPath },
+ }),
+ i18n.translate('kbnConfig.deprecations.conflictSetting.manualStepTwoMessage', {
+ defaultMessage: 'Remove "{fullOldPath}" from the config.',
+ values: { fullOldPath },
+ }),
],
},
...details,
@@ -75,10 +104,18 @@ const _unused = (
return;
}
addDeprecation({
- message: `${fullPath} is deprecated and is no longer used`,
+ title: getDeprecationTitle(fullPath),
+ message: i18n.translate('kbnConfig.deprecations.unusedSettingMessage', {
+ defaultMessage: 'You no longer need to configure "{fullPath}".',
+ values: { fullPath },
+ }),
correctiveActions: {
manualSteps: [
- `Remove "${fullPath}" from the Kibana config file, CLI flag, or environment variable (in Docker only)`,
+ i18n.translate('kbnConfig.deprecations.unusedSetting.manualStepOneMessage', {
+ defaultMessage:
+ 'Remove "{fullPath}" from the Kibana config file, CLI flag, or environment variable (in Docker only).',
+ values: { fullPath },
+ }),
],
},
...details,
diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts
index 1791dac060e2b..007c3ec54113b 100644
--- a/packages/kbn-config/src/deprecation/types.ts
+++ b/packages/kbn-config/src/deprecation/types.ts
@@ -19,6 +19,8 @@ export type AddConfigDeprecation = (details: DeprecatedConfigDetails) => void;
* @public
*/
export interface DeprecatedConfigDetails {
+ /* The title to be displayed for the deprecation. */
+ title?: string;
/* The message to be displayed for the deprecation. */
message: string;
/* (optional) set false to prevent the config service from logging the deprecation message. */
diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts
index 7847cad0fd5e7..0584ee27aa5f6 100644
--- a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts
+++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts
@@ -221,11 +221,12 @@ export class CiStatsReporter {
? `${error.response.status} response`
: 'no response';
+ const seconds = attempt * 10;
this.log.warning(
- `failed to reach ci-stats service [reason=${reason}], retrying in ${attempt} seconds`
+ `failed to reach ci-stats service, retrying in ${seconds} seconds, [reason=${reason}], [error=${error.message}]`
);
- await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
+ await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
}
}
diff --git a/packages/kbn-eslint-plugin-eslint/index.js b/packages/kbn-eslint-plugin-eslint/index.js
index e5a38e5f09529..a7a9c6b5bebdf 100644
--- a/packages/kbn-eslint-plugin-eslint/index.js
+++ b/packages/kbn-eslint-plugin-eslint/index.js
@@ -12,5 +12,6 @@ module.exports = {
'disallow-license-headers': require('./rules/disallow_license_headers'),
'no-restricted-paths': require('./rules/no_restricted_paths'),
module_migration: require('./rules/module_migration'),
+ no_async_promise_body: require('./rules/no_async_promise_body'),
},
};
diff --git a/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.js b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.js
new file mode 100644
index 0000000000000..317758fd3629a
--- /dev/null
+++ b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.js
@@ -0,0 +1,165 @@
+/*
+ * 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 { parseExpression } = require('@babel/parser');
+const { default: generate } = require('@babel/generator');
+const tsEstree = require('@typescript-eslint/typescript-estree');
+const traverse = require('eslint-traverse');
+const esTypes = tsEstree.AST_NODE_TYPES;
+const babelTypes = require('@babel/types');
+
+/** @typedef {import("eslint").Rule.RuleModule} Rule */
+/** @typedef {import("@typescript-eslint/parser").ParserServices} ParserServices */
+/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.Expression} Expression */
+/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.ArrowFunctionExpression} ArrowFunctionExpression */
+/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.FunctionExpression} FunctionExpression */
+/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.TryStatement} TryStatement */
+/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.NewExpression} NewExpression */
+/** @typedef {import("typescript").ExportDeclaration} ExportDeclaration */
+/** @typedef {import("eslint").Rule.RuleFixer} Fixer */
+
+const ERROR_MSG =
+ 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections';
+
+/**
+ * @param {Expression} node
+ */
+const isPromise = (node) => node.type === esTypes.Identifier && node.name === 'Promise';
+
+/**
+ * @param {Expression} node
+ * @returns {node is ArrowFunctionExpression | FunctionExpression}
+ */
+const isFunc = (node) =>
+ node.type === esTypes.ArrowFunctionExpression || node.type === esTypes.FunctionExpression;
+
+/**
+ * @param {any} context
+ * @param {ArrowFunctionExpression | FunctionExpression} node
+ */
+const isFuncBodySafe = (context, node) => {
+ // if the body isn't wrapped in a blockStatement it can't have a try/catch at the root
+ if (node.body.type !== esTypes.BlockStatement) {
+ return false;
+ }
+
+ // when the entire body is wrapped in a try/catch it is the only node
+ if (node.body.body.length !== 1) {
+ return false;
+ }
+
+ const tryNode = node.body.body[0];
+ // ensure we have a try node with a handler
+ if (tryNode.type !== esTypes.TryStatement || !tryNode.handler) {
+ return false;
+ }
+
+ // ensure the handler doesn't throw
+ let hasThrow = false;
+ traverse(context, tryNode.handler, (path) => {
+ if (path.node.type === esTypes.ThrowStatement) {
+ hasThrow = true;
+ return traverse.STOP;
+ }
+ });
+ return !hasThrow;
+};
+
+/**
+ * @param {string} code
+ */
+const wrapFunctionInTryCatch = (code) => {
+ // parse the code with babel so we can mutate the AST
+ const ast = parseExpression(code, {
+ plugins: ['typescript', 'jsx'],
+ });
+
+ // validate that the code reperesents an arrow or function expression
+ if (!babelTypes.isArrowFunctionExpression(ast) && !babelTypes.isFunctionExpression(ast)) {
+ throw new Error('expected function to be an arrow or function expression');
+ }
+
+ // ensure that the function receives the second argument, and capture its name if already defined
+ let rejectName = 'reject';
+ if (ast.params.length === 0) {
+ ast.params.push(babelTypes.identifier('resolve'), babelTypes.identifier(rejectName));
+ } else if (ast.params.length === 1) {
+ ast.params.push(babelTypes.identifier(rejectName));
+ } else if (ast.params.length === 2) {
+ if (babelTypes.isIdentifier(ast.params[1])) {
+ rejectName = ast.params[1].name;
+ } else {
+ throw new Error('expected second param of promise definition function to be an identifier');
+ }
+ }
+
+ // ensure that the body of the function is a blockStatement
+ let block = ast.body;
+ if (!babelTypes.isBlockStatement(block)) {
+ block = babelTypes.blockStatement([babelTypes.returnStatement(block)]);
+ }
+
+ // redefine the body of the function as a new blockStatement containing a tryStatement
+ // which catches errors and forwards them to reject() when caught
+ ast.body = babelTypes.blockStatement([
+ // try {
+ babelTypes.tryStatement(
+ block,
+ // catch (error) {
+ babelTypes.catchClause(
+ babelTypes.identifier('error'),
+ babelTypes.blockStatement([
+ // reject(error)
+ babelTypes.expressionStatement(
+ babelTypes.callExpression(babelTypes.identifier(rejectName), [
+ babelTypes.identifier('error'),
+ ])
+ ),
+ ])
+ )
+ ),
+ ]);
+
+ return generate(ast).code;
+};
+
+/** @type {Rule} */
+module.exports = {
+ meta: {
+ fixable: 'code',
+ schema: [],
+ },
+ create: (context) => ({
+ NewExpression(_) {
+ const node = /** @type {NewExpression} */ (_);
+
+ // ensure we are newing up a promise with a single argument
+ if (!isPromise(node.callee) || node.arguments.length !== 1) {
+ return;
+ }
+
+ const func = node.arguments[0];
+ // ensure the argument is an arrow or function expression and is async
+ if (!isFunc(func) || !func.async) {
+ return;
+ }
+
+ // body must be a blockStatement, try/catch can't exist outside of a block
+ if (!isFuncBodySafe(context, func)) {
+ context.report({
+ message: ERROR_MSG,
+ loc: func.loc,
+ fix(fixer) {
+ const source = context.getSourceCode();
+ return fixer.replaceText(func, wrapFunctionInTryCatch(source.getText(func)));
+ },
+ });
+ }
+ },
+ }),
+};
diff --git a/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.test.js b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.test.js
new file mode 100644
index 0000000000000..f5929b1b3966f
--- /dev/null
+++ b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.test.js
@@ -0,0 +1,254 @@
+/*
+ * 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 { RuleTester } = require('eslint');
+const rule = require('./no_async_promise_body');
+const dedent = require('dedent');
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('@typescript-eslint/parser'),
+ parserOptions: {
+ sourceType: 'module',
+ ecmaVersion: 2018,
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+});
+
+ruleTester.run('@kbn/eslint/no_async_promise_body', rule, {
+ valid: [
+ // caught but no resolve
+ {
+ code: dedent`
+ new Promise(async function (resolve) {
+ try {
+ await asyncOperation();
+ } catch (error) {
+ // noop
+ }
+ })
+ `,
+ },
+ // arrow caught but no resolve
+ {
+ code: dedent`
+ new Promise(async (resolve) => {
+ try {
+ await asyncOperation();
+ } catch (error) {
+ // noop
+ }
+ })
+ `,
+ },
+ // caught with reject
+ {
+ code: dedent`
+ new Promise(async function (resolve, reject) {
+ try {
+ await asyncOperation();
+ } catch (error) {
+ reject(error)
+ }
+ })
+ `,
+ },
+ // arrow caught with reject
+ {
+ code: dedent`
+ new Promise(async (resolve, reject) => {
+ try {
+ await asyncOperation();
+ } catch (error) {
+ reject(error)
+ }
+ })
+ `,
+ },
+ // non async
+ {
+ code: dedent`
+ new Promise(function (resolve) {
+ setTimeout(resolve, 10);
+ })
+ `,
+ },
+ // arrow non async
+ {
+ code: dedent`
+ new Promise((resolve) => setTimeout(resolve, 10))
+ `,
+ },
+ ],
+
+ invalid: [
+ // no catch
+ {
+ code: dedent`
+ new Promise(async function (resolve) {
+ const result = await asyncOperation();
+ resolve(result);
+ })
+ `,
+ errors: [
+ {
+ line: 1,
+ message:
+ 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections',
+ },
+ ],
+ output: dedent`
+ new Promise(async function (resolve, reject) {
+ try {
+ const result = await asyncOperation();
+ resolve(result);
+ } catch (error) {
+ reject(error);
+ }
+ })
+ `,
+ },
+ // arrow no catch
+ {
+ code: dedent`
+ new Promise(async (resolve) => {
+ const result = await asyncOperation();
+ resolve(result);
+ })
+ `,
+ errors: [
+ {
+ line: 1,
+ message:
+ 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections',
+ },
+ ],
+ output: dedent`
+ new Promise(async (resolve, reject) => {
+ try {
+ const result = await asyncOperation();
+ resolve(result);
+ } catch (error) {
+ reject(error);
+ }
+ })
+ `,
+ },
+ // catch, but it throws
+ {
+ code: dedent`
+ new Promise(async function (resolve) {
+ try {
+ const result = await asyncOperation();
+ resolve(result);
+ } catch (error) {
+ if (error.code === 'foo') {
+ throw error;
+ }
+ }
+ })
+ `,
+ errors: [
+ {
+ line: 1,
+ message:
+ 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections',
+ },
+ ],
+ output: dedent`
+ new Promise(async function (resolve, reject) {
+ try {
+ try {
+ const result = await asyncOperation();
+ resolve(result);
+ } catch (error) {
+ if (error.code === 'foo') {
+ throw error;
+ }
+ }
+ } catch (error) {
+ reject(error);
+ }
+ })
+ `,
+ },
+ // no catch without block
+ {
+ code: dedent`
+ new Promise(async (resolve) => resolve(await asyncOperation()));
+ `,
+ errors: [
+ {
+ line: 1,
+ message:
+ 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections',
+ },
+ ],
+ output: dedent`
+ new Promise(async (resolve, reject) => {
+ try {
+ return resolve(await asyncOperation());
+ } catch (error) {
+ reject(error);
+ }
+ });
+ `,
+ },
+ // no catch with named reject
+ {
+ code: dedent`
+ new Promise(async (resolve, rej) => {
+ const result = await asyncOperation();
+ result ? resolve(true) : rej()
+ });
+ `,
+ errors: [
+ {
+ line: 1,
+ message:
+ 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections',
+ },
+ ],
+ output: dedent`
+ new Promise(async (resolve, rej) => {
+ try {
+ const result = await asyncOperation();
+ result ? resolve(true) : rej();
+ } catch (error) {
+ rej(error);
+ }
+ });
+ `,
+ },
+ // no catch with no args
+ {
+ code: dedent`
+ new Promise(async () => {
+ await asyncOperation();
+ });
+ `,
+ errors: [
+ {
+ line: 1,
+ message:
+ 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections',
+ },
+ ],
+ output: dedent`
+ new Promise(async (resolve, reject) => {
+ try {
+ await asyncOperation();
+ } catch (error) {
+ reject(error);
+ }
+ });
+ `,
+ },
+ ],
+});
diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js
index f0a95a612f02c..c7f4bbe253777 100644
--- a/packages/kbn-pm/dist/index.js
+++ b/packages/kbn-pm/dist/index.js
@@ -59656,8 +59656,9 @@ class CiStatsReporter {
const reason = error !== null && error !== void 0 && (_error$response = error.response) !== null && _error$response !== void 0 && _error$response.status ? `${error.response.status} response` : 'no response';
- this.log.warning(`failed to reach ci-stats service [reason=${reason}], retrying in ${attempt} seconds`);
- await new Promise(resolve => setTimeout(resolve, attempt * 1000));
+ const seconds = attempt * 10;
+ this.log.warning(`failed to reach ci-stats service, retrying in ${seconds} seconds, [reason=${reason}], [error=${error.message}]`);
+ await new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
}
}
diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts
index fa3d61d00529c..86a036bbb9fe2 100644
--- a/packages/kbn-rule-data-utils/src/technical_field_names.ts
+++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts
@@ -28,7 +28,7 @@ const ALERT_DURATION = `${ALERT_NAMESPACE}.duration.us` as const;
const ALERT_END = `${ALERT_NAMESPACE}.end` as const;
const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const;
const ALERT_EVALUATION_VALUE = `${ALERT_NAMESPACE}.evaluation.value` as const;
-const ALERT_ID = `${ALERT_NAMESPACE}.id` as const;
+const ALERT_INSTANCE_ID = `${ALERT_NAMESPACE}.instance.id` as const;
const ALERT_REASON = `${ALERT_NAMESPACE}.reason` as const;
const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const;
const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const;
@@ -94,7 +94,7 @@ const fields = {
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_RULE_CONSUMER,
ALERT_RULE_PRODUCER,
ALERT_REASON,
@@ -143,7 +143,7 @@ export {
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_NAMESPACE,
ALERT_RULE_NAMESPACE,
ALERT_RULE_CONSUMER,
diff --git a/renovate.json5 b/renovate.json5
index 5ea38e589da4d..b1464ad5040f0 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -1,6 +1,7 @@
{
extends: [
'config:base',
+ ':disableDependencyDashboard',
],
ignorePaths: [
'**/__fixtures__/**',
@@ -12,12 +13,11 @@
baseBranches: [
'master',
'7.x',
- '7.13',
+ '7.15',
],
prConcurrentLimit: 0,
prHourlyLimit: 0,
separateMajorMinor: false,
- masterIssue: true,
rangeStrategy: 'bump',
semanticCommits: false,
vulnerabilityAlerts: {
@@ -39,7 +39,7 @@
packageNames: ['@elastic/charts'],
reviewers: ['markov00', 'nickofthyme'],
matchBaseBranches: ['master'],
- labels: ['release_note:skip', 'v8.0.0', 'v7.14.0', 'auto-backport'],
+ labels: ['release_note:skip', 'v8.0.0', 'v7.16.0', 'auto-backport'],
enabled: true,
},
{
diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx
index dcf071719c11a..455d19956f7e8 100644
--- a/src/core/public/application/integration_tests/utils.tsx
+++ b/src/core/public/application/integration_tests/utils.tsx
@@ -21,13 +21,18 @@ export const createRenderer = (element: ReactElement | null): Renderer => {
const dom: Dom = element && mount({element} );
return () =>
- new Promise(async (resolve) => {
- if (dom) {
- await act(async () => {
- dom.update();
- });
+ new Promise(async (resolve, reject) => {
+ try {
+ if (dom) {
+ await act(async () => {
+ dom.update();
+ });
+ }
+
+ setImmediate(() => resolve(dom)); // flushes any pending promises
+ } catch (error) {
+ reject(error);
}
- setImmediate(() => resolve(dom)); // flushes any pending promises
});
};
diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx
index 86cb9198e0699..4c056e748f06e 100644
--- a/src/core/public/application/ui/app_container.test.tsx
+++ b/src/core/public/application/ui/app_container.test.tsx
@@ -27,8 +27,12 @@ describe('AppContainer', () => {
});
const flushPromises = async () => {
- await new Promise(async (resolve) => {
- setImmediate(() => resolve());
+ await new Promise(async (resolve, reject) => {
+ try {
+ setImmediate(() => resolve());
+ } catch (error) {
+ reject(error);
+ }
});
};
diff --git a/src/core/public/chrome/ui/header/header_action_menu.test.tsx b/src/core/public/chrome/ui/header/header_action_menu.test.tsx
index 386e48e745e80..201be8848bac8 100644
--- a/src/core/public/chrome/ui/header/header_action_menu.test.tsx
+++ b/src/core/public/chrome/ui/header/header_action_menu.test.tsx
@@ -26,13 +26,18 @@ describe('HeaderActionMenu', () => {
});
const refresh = () => {
- new Promise(async (resolve) => {
- if (component) {
- act(() => {
- component.update();
- });
+ new Promise(async (resolve, reject) => {
+ try {
+ if (component) {
+ act(() => {
+ component.update();
+ });
+ }
+
+ setImmediate(() => resolve(component)); // flushes any pending promises
+ } catch (error) {
+ reject(error);
}
- setImmediate(() => resolve(component)); // flushes any pending promises
});
};
diff --git a/src/core/public/deprecations/deprecations_client.test.ts b/src/core/public/deprecations/deprecations_client.test.ts
index a998a03772cca..cca81f4687a97 100644
--- a/src/core/public/deprecations/deprecations_client.test.ts
+++ b/src/core/public/deprecations/deprecations_client.test.ts
@@ -82,6 +82,7 @@ describe('DeprecationsClient', () => {
it('returns true if deprecation has correctiveActions.api', async () => {
const deprecationsClient = new DeprecationsClient({ http });
const mockDeprecationDetails: DomainDeprecationDetails = {
+ title: 'some-title',
domainId: 'testPluginId-1',
message: 'some-message',
level: 'warning',
@@ -102,6 +103,7 @@ describe('DeprecationsClient', () => {
it('returns false if deprecation is missing correctiveActions.api', async () => {
const deprecationsClient = new DeprecationsClient({ http });
const mockDeprecationDetails: DomainDeprecationDetails = {
+ title: 'some-title',
domainId: 'testPluginId-1',
message: 'some-message',
level: 'warning',
@@ -120,6 +122,7 @@ describe('DeprecationsClient', () => {
it('fails if deprecation is not resolvable', async () => {
const deprecationsClient = new DeprecationsClient({ http });
const mockDeprecationDetails: DomainDeprecationDetails = {
+ title: 'some-title',
domainId: 'testPluginId-1',
message: 'some-message',
level: 'warning',
@@ -129,15 +132,18 @@ describe('DeprecationsClient', () => {
};
const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails);
- expect(result).toEqual({
- status: 'fail',
- reason: 'deprecation has no correctiveAction via api.',
- });
+ expect(result).toMatchInlineSnapshot(`
+ Object {
+ "reason": "This deprecation cannot be resolved automatically.",
+ "status": "fail",
+ }
+ `);
});
it('fetches the deprecation api', async () => {
const deprecationsClient = new DeprecationsClient({ http });
const mockDeprecationDetails: DomainDeprecationDetails = {
+ title: 'some-title',
domainId: 'testPluginId-1',
message: 'some-message',
level: 'warning',
@@ -171,6 +177,7 @@ describe('DeprecationsClient', () => {
const deprecationsClient = new DeprecationsClient({ http });
const mockResponse = 'Failed to fetch';
const mockDeprecationDetails: DomainDeprecationDetails = {
+ title: 'some-title',
domainId: 'testPluginId-1',
message: 'some-message',
level: 'warning',
diff --git a/src/core/public/deprecations/deprecations_client.ts b/src/core/public/deprecations/deprecations_client.ts
index e510ab1e79d17..4b9cfca1986ba 100644
--- a/src/core/public/deprecations/deprecations_client.ts
+++ b/src/core/public/deprecations/deprecations_client.ts
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { i18n } from '@kbn/i18n';
import type { HttpStart } from '../http';
import type { DomainDeprecationDetails, DeprecationsGetResponse } from '../../server/types';
@@ -52,7 +53,9 @@ export class DeprecationsClient {
if (typeof correctiveActions.api !== 'object') {
return {
status: 'fail',
- reason: 'deprecation has no correctiveAction via api.',
+ reason: i18n.translate('core.deprecations.noCorrectiveAction', {
+ defaultMessage: 'This deprecation cannot be resolved automatically.',
+ }),
};
}
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 4b1aaf9eb19c1..f3ef7c550e57d 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -30,6 +30,10 @@ export class DocLinksService {
ELASTIC_WEBSITE_URL,
links: {
settings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/settings.html`,
+ apm: {
+ kibanaSettings: `${KIBANA_DOCS}apm-settings-in-kibana.html`,
+ supportedServiceMaps: `${KIBANA_DOCS}service-maps.html#service-maps-supported`,
+ },
canvas: {
guide: `${KIBANA_DOCS}canvas.html`,
},
@@ -204,6 +208,7 @@ export class DocLinksService {
siem: {
guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,
gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,
+ privileges: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/sec-requirements.html`,
ml: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/machine-learning.html`,
ruleChangeLog: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/prebuilt-rules-changelog.html`,
detectionsReq: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/detections-permissions-section.html`,
@@ -450,6 +455,10 @@ export interface DocLinksStart {
readonly ELASTIC_WEBSITE_URL: string;
readonly links: {
readonly settings: string;
+ readonly apm: {
+ readonly kibanaSettings: string;
+ readonly supportedServiceMaps: string;
+ };
readonly canvas: {
readonly guide: string;
};
@@ -569,6 +578,7 @@ export interface DocLinksStart {
readonly rollupJobs: string;
readonly elasticsearch: Record;
readonly siem: {
+ readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
diff --git a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap
index 4ef5eb8f56d2f..54e223cdc5d41 100644
--- a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap
+++ b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap
@@ -57,7 +57,7 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiColumnSelector.searchcolumns": "Search columns",
"euiColumnSelector.selectAll": "Show all",
"euiColumnSorting.button": "Sort fields",
- "euiColumnSorting.buttonActive": "fields sorted",
+ "euiColumnSorting.buttonActive": [Function],
"euiColumnSorting.clearAll": "Clear sorting",
"euiColumnSorting.emptySorting": "Currently no fields are sorted",
"euiColumnSorting.pickFields": "Pick fields to sort by",
@@ -104,9 +104,11 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiFieldPassword.maskPassword": "Mask password",
"euiFieldPassword.showPassword": "Show password as plain text. Note: this will visually expose your password on the screen.",
"euiFilePicker.clearSelectedFiles": "Clear selected files",
- "euiFilePicker.filesSelected": "files selected",
+ "euiFilePicker.filesSelected": [Function],
+ "euiFilePicker.promptText": "Select or drag and drop a file",
"euiFilePicker.removeSelected": "Remove",
- "euiFilterButton.filterBadge": [Function],
+ "euiFilterButton.filterBadgeActiveAriaLabel": [Function],
+ "euiFilterButton.filterBadgeAvailableAriaLabel": [Function],
"euiFlyout.closeAriaLabel": "Close this dialog",
"euiForm.addressFormErrors": "Please address the highlighted errors.",
"euiFormControlLayoutClearButton.label": "Clear input",
diff --git a/src/core/public/i18n/i18n_eui_mapping.test.ts b/src/core/public/i18n/i18n_eui_mapping.test.ts
index 1b80257266d4c..d8d48a8e5f1d5 100644
--- a/src/core/public/i18n/i18n_eui_mapping.test.ts
+++ b/src/core/public/i18n/i18n_eui_mapping.test.ts
@@ -74,6 +74,11 @@ describe('@elastic/eui i18n tokens', () => {
});
test('defaultMessage is in sync with defString', () => {
+ // Certain complex tokens (e.g. ones that have a function as a defaultMessage)
+ // need custom i18n handling, and can't be checked for basic defString equality
+ const tokensToSkip = ['euiColumnSorting.buttonActive'];
+ if (tokensToSkip.includes(token)) return;
+
// Clean up typical errors from the `@elastic/eui` extraction token tool
const normalizedDefString = defString
// Quoted words should use double-quotes
diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx
index 133a2155f7430..4175dac712e82 100644
--- a/src/core/public/i18n/i18n_eui_mapping.tsx
+++ b/src/core/public/i18n/i18n_eui_mapping.tsx
@@ -272,9 +272,11 @@ export const getEuiContextMapping = (): EuiTokensObject => {
'euiColumnSorting.button': i18n.translate('core.euiColumnSorting.button', {
defaultMessage: 'Sort fields',
}),
- 'euiColumnSorting.buttonActive': i18n.translate('core.euiColumnSorting.buttonActive', {
- defaultMessage: 'fields sorted',
- }),
+ 'euiColumnSorting.buttonActive': ({ numberOfSortedFields }: EuiValues) =>
+ i18n.translate('core.euiColumnSorting.buttonActive', {
+ defaultMessage: '{numberOfSortedFields, plural, one {# field} other {# fields}} sorted',
+ values: { numberOfSortedFields },
+ }),
'euiColumnSortingDraggable.activeSortLabel': ({ display }: EuiValues) =>
i18n.translate('core.euiColumnSortingDraggable.activeSortLabel', {
defaultMessage: '{display} is sorting this data grid',
@@ -514,16 +516,26 @@ export const getEuiContextMapping = (): EuiTokensObject => {
'euiFilePicker.clearSelectedFiles': i18n.translate('core.euiFilePicker.clearSelectedFiles', {
defaultMessage: 'Clear selected files',
}),
- 'euiFilePicker.filesSelected': i18n.translate('core.euiFilePicker.filesSelected', {
- defaultMessage: 'files selected',
+ 'euiFilePicker.filesSelected': ({ fileCount }: EuiValues) =>
+ i18n.translate('core.euiFilePicker.filesSelected', {
+ defaultMessage: '{fileCount} files selected',
+ values: { fileCount },
+ }),
+ 'euiFilePicker.promptText': i18n.translate('core.euiFilePicker.promptText', {
+ defaultMessage: 'Select or drag and drop a file',
}),
'euiFilePicker.removeSelected': i18n.translate('core.euiFilePicker.removeSelected', {
defaultMessage: 'Remove',
}),
- 'euiFilterButton.filterBadge': ({ count, hasActiveFilters }: EuiValues) =>
- i18n.translate('core.euiFilterButton.filterBadge', {
- defaultMessage: '{count} {hasActiveFilters} filters',
- values: { count, hasActiveFilters: hasActiveFilters ? 'active' : 'available' },
+ 'euiFilterButton.filterBadgeActiveAriaLabel': ({ count }: EuiValues) =>
+ i18n.translate('core.euiFilterButton.filterBadgeActiveAriaLabel', {
+ defaultMessage: '{count} active filters',
+ values: { count },
+ }),
+ 'euiFilterButton.filterBadgeAvailableAriaLabel': ({ count }: EuiValues) =>
+ i18n.translate('core.euiFilterButton.filterBadgeAvailableAriaLabel', {
+ defaultMessage: '{count} available filters',
+ values: { count },
}),
'euiFlyout.closeAriaLabel': i18n.translate('core.euiFlyout.closeAriaLabel', {
defaultMessage: 'Close this dialog',
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 043759378faa3..f18e1dc26bd87 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -473,6 +473,10 @@ export interface DocLinksStart {
// (undocumented)
readonly links: {
readonly settings: string;
+ readonly apm: {
+ readonly kibanaSettings: string;
+ readonly supportedServiceMaps: string;
+ };
readonly canvas: {
readonly guide: string;
};
@@ -592,6 +596,7 @@ export interface DocLinksStart {
readonly rollupJobs: string;
readonly elasticsearch: Record;
readonly siem: {
+ readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts
index 06c7116c8bebb..759e2375ce987 100644
--- a/src/core/server/config/deprecation/core_deprecations.test.ts
+++ b/src/core/server/config/deprecation/core_deprecations.test.ts
@@ -62,7 +62,7 @@ describe('core deprecations', () => {
expect(migrated.server.xsrf.allowlist).toEqual(['/path']);
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"server.xsrf.whitelist\\" is deprecated and has been replaced by \\"server.xsrf.allowlist\\"",
+ "Setting \\"server.xsrf.whitelist\\" has been replaced by \\"server.xsrf.allowlist\\"",
]
`);
});
diff --git a/src/core/server/config/integration_tests/config_deprecation.test.ts b/src/core/server/config/integration_tests/config_deprecation.test.ts
index 2d86281ce40d6..c941053a2f0a1 100644
--- a/src/core/server/config/integration_tests/config_deprecation.test.ts
+++ b/src/core/server/config/integration_tests/config_deprecation.test.ts
@@ -51,8 +51,8 @@ describe('configuration deprecations', () => {
const logs = loggingSystemMock.collect(mockLoggingSystem);
expect(logs.warn.flat()).toMatchInlineSnapshot(`
Array [
- "optimize.lazy is deprecated and is no longer used",
- "optimize.lazyPort is deprecated and is no longer used",
+ "You no longer need to configure \\"optimize.lazy\\".",
+ "You no longer need to configure \\"optimize.lazyPort\\".",
"\\"logging.silent\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level:off\\" in your logging configuration. ",
]
`);
diff --git a/src/core/server/core_usage_data/core_usage_data_service.mock.ts b/src/core/server/core_usage_data/core_usage_data_service.mock.ts
index a03f79096004b..941ac5afacb40 100644
--- a/src/core/server/core_usage_data/core_usage_data_service.mock.ts
+++ b/src/core/server/core_usage_data/core_usage_data_service.mock.ts
@@ -47,6 +47,7 @@ const createStartContractMock = () => {
keystoreConfigured: false,
truststoreConfigured: false,
},
+ principal: 'unknown',
},
http: {
basePathConfigured: false,
diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts
index 7ecfa37492242..478cfe5daff46 100644
--- a/src/core/server/core_usage_data/core_usage_data_service.test.ts
+++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import type { ConfigPath } from '@kbn/config';
import { BehaviorSubject, Observable } from 'rxjs';
import { HotObservable } from 'rxjs/internal/testing/HotObservable';
import { TestScheduler } from 'rxjs/testing';
@@ -29,12 +30,31 @@ import { CORE_USAGE_STATS_TYPE } from './constants';
import { CoreUsageStatsClient } from './core_usage_stats_client';
describe('CoreUsageDataService', () => {
+ function getConfigServiceAtPathMockImplementation() {
+ return (path: ConfigPath) => {
+ if (path === 'elasticsearch') {
+ return new BehaviorSubject(RawElasticsearchConfig.schema.validate({}));
+ } else if (path === 'server') {
+ return new BehaviorSubject(RawHttpConfig.schema.validate({}));
+ } else if (path === 'logging') {
+ return new BehaviorSubject(RawLoggingConfig.schema.validate({}));
+ } else if (path === 'savedObjects') {
+ return new BehaviorSubject(RawSavedObjectsConfig.schema.validate({}));
+ } else if (path === 'kibana') {
+ return new BehaviorSubject(RawKibanaConfig.schema.validate({}));
+ }
+ return new BehaviorSubject({});
+ };
+ }
+
const getTestScheduler = () =>
new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
let service: CoreUsageDataService;
+ let configService: ReturnType;
+
const mockConfig = {
unused_config: {},
elasticsearch: { username: 'kibana_system', password: 'changeme' },
@@ -60,27 +80,11 @@ describe('CoreUsageDataService', () => {
},
};
- const configService = configServiceMock.create({
- getConfig$: mockConfig,
- });
-
- configService.atPath.mockImplementation((path) => {
- if (path === 'elasticsearch') {
- return new BehaviorSubject(RawElasticsearchConfig.schema.validate({}));
- } else if (path === 'server') {
- return new BehaviorSubject(RawHttpConfig.schema.validate({}));
- } else if (path === 'logging') {
- return new BehaviorSubject(RawLoggingConfig.schema.validate({}));
- } else if (path === 'savedObjects') {
- return new BehaviorSubject(RawSavedObjectsConfig.schema.validate({}));
- } else if (path === 'kibana') {
- return new BehaviorSubject(RawKibanaConfig.schema.validate({}));
- }
- return new BehaviorSubject({});
- });
- const coreContext = mockCoreContext.create({ configService });
-
beforeEach(() => {
+ configService = configServiceMock.create({ getConfig$: mockConfig });
+ configService.atPath.mockImplementation(getConfigServiceAtPathMockImplementation());
+
+ const coreContext = mockCoreContext.create({ configService });
service = new CoreUsageDataService(coreContext);
});
@@ -150,7 +154,7 @@ describe('CoreUsageDataService', () => {
describe('start', () => {
describe('getCoreUsageData', () => {
- it('returns core metrics for default config', async () => {
+ function setup() {
const http = httpServiceMock.createInternalSetupContract();
const metrics = metricsServiceMock.createInternalSetupContract();
const savedObjectsStartPromise = Promise.resolve(
@@ -208,6 +212,11 @@ describe('CoreUsageDataService', () => {
exposedConfigsToUsage: new Map(),
elasticsearch,
});
+ return { getCoreUsageData };
+ }
+
+ it('returns core metrics for default config', async () => {
+ const { getCoreUsageData } = setup();
expect(getCoreUsageData()).resolves.toMatchInlineSnapshot(`
Object {
"config": Object {
@@ -226,6 +235,7 @@ describe('CoreUsageDataService', () => {
"logQueries": false,
"numberOfHostsConfigured": 1,
"pingTimeoutMs": 30000,
+ "principal": "unknown",
"requestHeadersWhitelistConfigured": false,
"requestTimeoutMs": 30000,
"shardTimeoutMs": 30000,
@@ -354,6 +364,60 @@ describe('CoreUsageDataService', () => {
}
`);
});
+
+ describe('elasticsearch.principal', () => {
+ async function doTest({
+ username,
+ serviceAccountToken,
+ expectedPrincipal,
+ }: {
+ username?: string;
+ serviceAccountToken?: string;
+ expectedPrincipal: string;
+ }) {
+ const defaultMockImplementation = getConfigServiceAtPathMockImplementation();
+ configService.atPath.mockImplementation((path) => {
+ if (path === 'elasticsearch') {
+ return new BehaviorSubject(
+ RawElasticsearchConfig.schema.validate({ username, serviceAccountToken })
+ );
+ }
+ return defaultMockImplementation(path);
+ });
+ const { getCoreUsageData } = setup();
+ return expect(getCoreUsageData()).resolves.toEqual(
+ expect.objectContaining({
+ config: expect.objectContaining({
+ elasticsearch: expect.objectContaining({ principal: expectedPrincipal }),
+ }),
+ })
+ );
+ }
+
+ it('returns expected usage data for elastic.username "elastic"', async () => {
+ return doTest({ username: 'elastic', expectedPrincipal: 'elastic_user' });
+ });
+
+ it('returns expected usage data for elastic.username "kibana"', async () => {
+ return doTest({ username: 'kibana', expectedPrincipal: 'kibana_user' });
+ });
+
+ it('returns expected usage data for elastic.username "kibana_system"', async () => {
+ return doTest({ username: 'kibana_system', expectedPrincipal: 'kibana_system_user' });
+ });
+
+ it('returns expected usage data for elastic.username anything else', async () => {
+ return doTest({ username: 'anything else', expectedPrincipal: 'other_user' });
+ });
+
+ it('returns expected usage data for elastic.serviceAccountToken', async () => {
+ // Note: elastic.username and elastic.serviceAccountToken are mutually exclusive
+ return doTest({
+ serviceAccountToken: 'any',
+ expectedPrincipal: 'kibana_service_account',
+ });
+ });
+ });
});
describe('getConfigsUsageData', () => {
diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts
index 7cf38dddc563e..73f63d4d634df 100644
--- a/src/core/server/core_usage_data/core_usage_data_service.ts
+++ b/src/core/server/core_usage_data/core_usage_data_service.ts
@@ -29,6 +29,7 @@ import type {
CoreUsageDataStart,
CoreUsageDataSetup,
ConfigUsageData,
+ CoreConfigUsageData,
} from './types';
import { isConfigured } from './is_configured';
import { ElasticsearchServiceStart } from '../elasticsearch';
@@ -253,6 +254,7 @@ export class CoreUsageDataService implements CoreService = {
exposeToBrowser: {
defaultAppId: true,
@@ -97,12 +108,23 @@ export const config: PluginConfigDescriptor = {
return completeConfig;
}
addDeprecation({
- message: `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the "defaultRoute" advanced setting instead`,
+ title: i18n.translate('kibana_legacy.deprecations.defaultAppIdTitle', {
+ defaultMessage: 'Setting "kibana.defaultAppId" is deprecated',
+ }),
+ message: i18n.translate('kibana_legacy.deprecations.defaultAppIdMessage', {
+ defaultMessage: 'Use the "defaultRoute" advanced setting instead of "kibana.defaultAppId".',
+ }),
correctiveActions: {
manualSteps: [
- 'Go to Stack Management > Advanced Settings',
- 'Update the "defaultRoute" setting under the General section',
- 'Remove "kibana.defaultAppId" from the kibana.yml config file',
+ i18n.translate('kibana_legacy.deprecations.defaultAppId.manualStepOneMessage', {
+ defaultMessage: 'Go to Stack Management > Advanced Settings.',
+ }),
+ i18n.translate('kibana_legacy.deprecations.defaultAppId.manualStepTwoMessage', {
+ defaultMessage: 'Update the "defaultRoute" setting in the General section.',
+ }),
+ i18n.translate('kibana_legacy.deprecations.defaultAppId.manualStepThreeMessage', {
+ defaultMessage: 'Remove "kibana.defaultAppId" from the kibana.yml config file.',
+ }),
],
},
});
@@ -138,39 +160,49 @@ To check the full TS types of the service please check the [generated core docs]
### Example
```ts
import { DeprecationsDetails, GetDeprecationsContext } from 'src/core/server';
+import { i18n } from '@kbn/i18n';
async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise {
const deprecations: DeprecationsDetails[] = [];
const testDashboardUser = await getTestDashboardUser(savedObjectsClient);
if (testDashboardUser) {
- deprecations.push({
- message: 'User "test_dashboard_user" is using a deprecated role: "kibana_user"',
- documentationUrl: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html',
- level: 'critical',
- correctiveActions: {
- api: {
- path: '/internal/security/users/test_dashboard_user',
- method: 'POST',
- body: {
- username: 'test_dashboard_user',
- roles: [
- 'machine_learning_user',
- 'enrich_user',
- 'kibana_admin'
- ],
- full_name: 'Alison Goryachev',
- email: 'alisongoryachev@gmail.com',
- metadata: {},
- enabled: true
- }
+ deprecations.push({
+ title: i18n.translate('security.deprecations.kibanaUserRoleTitle', {
+ defaultMessage: 'Deprecated roles are assigned to some users',
+ }),
+ message: i18n.translate('security.deprecations.kibanaUserRoleMessage', {
+ defaultMessage: 'User "test_dashboard_user" is using a deprecated role: "kibana_user".',
+ }),
+ documentationUrl: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html',
+ level: 'critical',
+ correctiveActions: {
+ api: {
+ path: '/internal/security/users/test_dashboard_user',
+ method: 'POST',
+ body: {
+ username: 'test_dashboard_user',
+ roles: [
+ 'machine_learning_user',
+ 'enrich_user',
+ 'kibana_admin'
+ ],
+ full_name: 'Alison Goryachev',
+ email: 'alisongoryachev@gmail.com',
+ metadata: {},
+ enabled: true
+ }
+ },
+ manualSteps: [
+ i18n.translate('security.deprecations.kibanaUserRole.manualStepOneMessage', {
+ defaultMessage: 'Switch all users with the "kibana_user" role to the kibana_admin role in Management > Security > Users.',
+ }),
+ i18n.translate('security.deprecations.kibanaUserRole.manualStepTwoMessage', {
+ defaultMessage: 'Update all mappings in Management > Security > Role Mappings to assign the "kibana_admin" role instead of the "kibana_user" role.'
+ }),
+ ],
},
- manualSteps: [
- 'Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.',
- 'Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role.'
- ]
- },
- });
+ });
}
return deprecations;
@@ -204,7 +236,128 @@ Currently we do not have test objects to run functional tests against the Upgrad
Yes. Using this service should help users find and resolve any issues specific to their deployment before upgrading.
We recommend adding a `documentationUrl` for every deprecation you expose to further assist our users if they need extra help.
+## Writing deprecation details
+
+State what is being deprecated and what action the user needs to take:
+
+> Abc is deprecated. Use Xyz to do the thing.
+
+Provide as much context as possible for what triggered the deprecation warning.
+If action is not required (for example the default behavior is changing), describe the impact of doing nothing.
+
+Examples:
+- > Setting `xpack.reporting.roles.enabled` is deprecated. Use feature controls to grant reporting privileges.
+- > The Joda Time century-of era-formatter (C) is deprecated. Use a `java.time` formatter instead.
+- > The default for the `cluster.routing.allocation.disk.watermark` setting is changing from false to true.
+ > If you do not explicitly configure this setting when you upgrade, indices in this one node cluster will
+ > become read-only if disk usage reaches 95%.
+
## Note on i18n
-We have decided to support i18n to the exposed deprecations for a better user experience when using the UA.
-We will inject `i18n` into the deprecation function to enable teams to use it before fully documenting its usage.
-For context follow [this issue](https://github.com/elastic/kibana/issues/99072).
+All deprecation titles, messsages, and manual steps should be wrapped in `i18n.translate`. This
+provides a better user experience using different locales. Follow the writing guidelines below for
+best practices to writing the i18n messages and ids.
+
+### Writing guidelines
+The deprecation service enables you to specify a `title`, `message`, `documentationUrl`,
+and the `manual steps` for resolving a deprecation issue.
+
+#### Title:
+No end punctuation is required.
+i18n id: `{plugin_domain}.deprecations.{deprecationTitle}Title`
+
+Example:
+```ts
+title: i18n.translate('xpack.reporting.deprecations.reportingRoleTitle', {
+ defaultMessage: `Found deprecated reporting roles`,
+})
+```
+
+#### Message
+Keep it brief, but multiple sentences are allowed if needed.
+i18n id: `{plugin_domain}.deprecations.{deprecationTitle}Message`
+
+Example:
+```ts
+message: i18n.translate('xpack.reporting.deprecations.reportingRoleMessage', {
+ defaultMessage: `The deprecated "${deprecatedRole}" role has been found for ${numReportingUsers} user(s): "${usernames}"`,
+ values: { deprecatedRole, numReportingUsers, usernames },
+}),
+```
+
+#### Documentation URL
+Don’t link to the Migration guide/breaking changes.
+Only specify a doc URL if the user truly needs to “learn more” to understand what actions they need to take.
+
+Example:
+```ts
+documentationUrl: 'https://www.elastic.co/guide/en/kibana/current/secure-reporting.html',
+```
+#### Manual steps
+State the action first for each step.
+i18n id: `{plugin_domain}.deprecations.{deprecationTitle}.manualStep{Step#}Message`
+
+Example:
+```ts
+manualSteps: [
+ i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepTwoMessage', {
+ defaultMessage: `Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.`,
+ }),
+ i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepThreeMessage', {
+ defaultMessage: `Assign the custom role(s) as desired, and remove the "${deprecatedRole}" role from the user(s).`,
+ values: { deprecatedRole },
+ }),
+]
+```
+
+#### General Guidelines
+
+##### What is deprecated
+Use the present tense:
+- Types are deprecated in geo_shape queries.
+- Sorting is deprecated in reindex requests.
+
+Avoid:
+- The type should no longer be specified in geo_shape queries.
+- Sorting has been deprecated in reindex requests.
+
+##### What action the user needs to take
+Use the imperative voice:
+- Do not specify a type in the indexed_shape section.
+- Use query filtering to reindex a subset of documents.
+
+Avoid:
+- Please use query filtering instead.
+- You should use query filtering instead.
+- Instead consider using query filtering to find the desired subset of data.
+
+##### Context
+Where possible, provide the specific context that resulted in the warning:
+- The Abc timezone used by rollup job Def is deprecated. Use Xyz instead.
+
+##### Impact
+Many deprecations are clearcut--you are using this old thing and need to switch to using this new thing.
+Others are more nuanced and don’t necessarily require any changes. In this case, the warning needs to address
+the impact of not taking action:
+- The default for the `cluster.routing.allocation.disk.watermark` setting is changing from false to true.
+ If you do not explicitly configure this setting when you upgrade, indices in this one node cluster will
+ become read-only if disk usage reaches 95%.
+
+##### Version
+You do not need to include any form of "and will be removed in a future release".
+The assumption is that deprecated things are going to be removed, and the standard schedule for removal
+is the next major version.
+
+If things are targeted for removal in a specific minor release, the message should include that information:
+- Abc is deprecated. Use Xyz to do the thing. Support for Abc will be removed in n.n.
+
+If an item is deprecated, but won’t be removed in the next major version, the message should indicate that:
+- Abc is deprecated. Use Xyz to do the thing. Support for Abc will be removed following the release of n.0.
+
+Avoid:
+- Xyz is deprecated and will be removed in 8.0.
+- Xyz is deprecated and will be unsupported in future.
+- Xyz is deprecated and will not be supported in the next major version of Elasticsearch.
+
+##### Formatting
+- Sentence style capitalization and punctuation.
+- Avoid quotes for emphasis.
diff --git a/src/core/server/deprecations/deprecations_factory.test.ts b/src/core/server/deprecations/deprecations_factory.test.ts
index 187f3880f9998..73beb84f57fa6 100644
--- a/src/core/server/deprecations/deprecations_factory.test.ts
+++ b/src/core/server/deprecations/deprecations_factory.test.ts
@@ -124,16 +124,21 @@ describe('DeprecationsFactory', () => {
`Failed to get deprecations info for plugin "${domainId}".`,
mockError
);
- expect(derpecations).toStrictEqual([
- {
- domainId,
- message: `Failed to get deprecations info for plugin "${domainId}".`,
- level: 'fetch_error',
- correctiveActions: {
- manualSteps: ['Check Kibana server logs for error message.'],
+ expect(derpecations).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "correctiveActions": Object {
+ "manualSteps": Array [
+ "Check Kibana server logs for error message.",
+ ],
+ },
+ "domainId": "mockPlugin",
+ "level": "fetch_error",
+ "message": "Unable to fetch deprecations info for plugin mockPlugin.",
+ "title": "Failed to fetch deprecations for mockPlugin",
},
- },
- ]);
+ ]
+ `);
});
it(`returns successful results even when some getDeprecations fail`, async () => {
@@ -167,7 +172,8 @@ describe('DeprecationsFactory', () => {
...mockPluginDeprecationsInfo.map((info) => ({ ...info, domainId: 'mockPlugin' })),
{
domainId: 'anotherMockPlugin',
- message: `Failed to get deprecations info for plugin "anotherMockPlugin".`,
+ title: 'Failed to fetch deprecations for anotherMockPlugin',
+ message: 'Unable to fetch deprecations info for plugin anotherMockPlugin.',
level: 'fetch_error',
correctiveActions: {
manualSteps: ['Check Kibana server logs for error message.'],
diff --git a/src/core/server/deprecations/deprecations_factory.ts b/src/core/server/deprecations/deprecations_factory.ts
index 3699c088e20f1..9905f0b26b4f3 100644
--- a/src/core/server/deprecations/deprecations_factory.ts
+++ b/src/core/server/deprecations/deprecations_factory.ts
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { i18n } from '@kbn/i18n';
import { DeprecationsRegistry } from './deprecations_registry';
import type { Logger } from '../logging';
import type {
@@ -89,10 +90,24 @@ export class DeprecationsFactory {
);
return [
{
- message: `Failed to get deprecations info for plugin "${domainId}".`,
+ title: i18n.translate('core.deprecations.deprecations.fetchFailedTitle', {
+ defaultMessage: `Failed to fetch deprecations for {domainId}`,
+ values: { domainId },
+ }),
+ message: i18n.translate('core.deprecations.deprecations.fetchFailedMessage', {
+ defaultMessage: 'Unable to fetch deprecations info for plugin {domainId}.',
+ values: { domainId },
+ }),
level: 'fetch_error',
correctiveActions: {
- manualSteps: ['Check Kibana server logs for error message.'],
+ manualSteps: [
+ i18n.translate(
+ 'core.deprecations.deprecations.fetchFailed.manualStepOneMessage',
+ {
+ defaultMessage: 'Check Kibana server logs for error message.',
+ }
+ ),
+ ],
},
},
];
diff --git a/src/core/server/deprecations/deprecations_service.test.ts b/src/core/server/deprecations/deprecations_service.test.ts
index 75a0d6a63d919..0e8aaf3de49c9 100644
--- a/src/core/server/deprecations/deprecations_service.test.ts
+++ b/src/core/server/deprecations/deprecations_service.test.ts
@@ -110,6 +110,7 @@ describe('DeprecationsService', () => {
"level": "critical",
"message": "testMessage",
"requireRestart": true,
+ "title": "testDomain has a deprecated setting",
},
]
`);
diff --git a/src/core/server/deprecations/deprecations_service.ts b/src/core/server/deprecations/deprecations_service.ts
index 7c4f74fe7d0ec..c41567d88a2aa 100644
--- a/src/core/server/deprecations/deprecations_service.ts
+++ b/src/core/server/deprecations/deprecations_service.ts
@@ -33,6 +33,7 @@ import { SavedObjectsClientContract } from '../saved_objects/types';
* @example
* ```ts
* import { DeprecationsDetails, GetDeprecationsContext, CoreSetup } from 'src/core/server';
+ * import { i18n } from '@kbn/i18n';
*
* async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise {
* const deprecations: DeprecationsDetails[] = [];
@@ -41,52 +42,44 @@ import { SavedObjectsClientContract } from '../saved_objects/types';
* if (count > 0) {
* // Example of a manual correctiveAction
* deprecations.push({
- * message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`,
+ * title: i18n.translate('xpack.timelion.deprecations.worksheetsTitle', {
+ * defaultMessage: 'Timelion worksheets are deprecated'
+ * }),
+ * message: i18n.translate('xpack.timelion.deprecations.worksheetsMessage', {
+ * defaultMessage: 'You have {count} Timelion worksheets. Migrate your Timelion worksheets to a dashboard to continue using them.',
+ * values: { count },
+ * }),
* documentationUrl:
* 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html',
* level: 'warning',
* correctiveActions: {
* manualSteps: [
- * 'Navigate to the Kibana Dashboard and click "Create dashboard".',
- * 'Select Timelion from the "New Visualization" window.',
- * 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.',
- * 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.',
- * 'In the toolbar, click Save.',
- * 'On the Save visualization window, enter the visualization Title, then click Save and return.',
+ * i18n.translate('xpack.timelion.deprecations.worksheets.manualStepOneMessage', {
+ * defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".',
+ * }),
+ * i18n.translate('xpack.timelion.deprecations.worksheets.manualStepTwoMessage', {
+ * defaultMessage: 'Select Timelion from the "New Visualization" window.',
+ * }),
* ],
+ * api: {
+ * path: '/internal/security/users/test_dashboard_user',
+ * method: 'POST',
+ * body: {
+ * username: 'test_dashboard_user',
+ * roles: [
+ * "machine_learning_user",
+ * "enrich_user",
+ * "kibana_admin"
+ * ],
+ * full_name: "Alison Goryachev",
+ * email: "alisongoryachev@gmail.com",
+ * metadata: {},
+ * enabled: true
+ * }
+ * },
* },
* });
* }
- *
- * // Example of an api correctiveAction
- * deprecations.push({
- * "message": "User 'test_dashboard_user' is using a deprecated role: 'kibana_user'",
- * "documentationUrl": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html",
- * "level": "critical",
- * "correctiveActions": {
- * "api": {
- * "path": "/internal/security/users/test_dashboard_user",
- * "method": "POST",
- * "body": {
- * "username": "test_dashboard_user",
- * "roles": [
- * "machine_learning_user",
- * "enrich_user",
- * "kibana_admin"
- * ],
- * "full_name": "Alison Goryachev",
- * "email": "alisongoryachev@gmail.com",
- * "metadata": {},
- * "enabled": true
- * }
- * },
- * "manualSteps": [
- * "Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.",
- * "Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role."
- * ]
- * },
- * });
- *
* return deprecations;
* }
*
@@ -192,16 +185,19 @@ export class DeprecationsService
const deprecationsRegistry = deprecationsFactory.getRegistry(domainId);
deprecationsRegistry.registerDeprecations({
getDeprecations: () => {
- return deprecationsContexts.map(({ message, correctiveActions, documentationUrl }) => {
- return {
- level: 'critical',
- deprecationType: 'config',
- message,
- correctiveActions,
- documentationUrl,
- requireRestart: true,
- };
- });
+ return deprecationsContexts.map(
+ ({ title, message, correctiveActions, documentationUrl }) => {
+ return {
+ title: title || `${domainId} has a deprecated setting`,
+ level: 'critical',
+ deprecationType: 'config',
+ message,
+ correctiveActions,
+ documentationUrl,
+ requireRestart: true,
+ };
+ }
+ );
},
});
}
diff --git a/src/core/server/deprecations/types.ts b/src/core/server/deprecations/types.ts
index 486fec5dfd8be..c924cacd02e28 100644
--- a/src/core/server/deprecations/types.ts
+++ b/src/core/server/deprecations/types.ts
@@ -16,7 +16,15 @@ export interface DomainDeprecationDetails extends DeprecationsDetails {
}
export interface DeprecationsDetails {
- /* The message to be displayed for the deprecation. */
+ /**
+ * The title of the deprecation.
+ * Check the README for writing deprecations in `src/core/server/deprecations/README.mdx`
+ */
+ title: string;
+ /**
+ * The description message to be displayed for the deprecation.
+ * Check the README for writing deprecations in `src/core/server/deprecations/README.mdx`
+ */
message: string;
/**
* levels:
@@ -60,6 +68,7 @@ export interface DeprecationsDetails {
* Specify a list of manual steps users need to follow to
* fix the deprecation before upgrade. Required even if an API
* corrective action is set in case the API fails.
+ * Check the README for writing deprecations in `src/core/server/deprecations/README.mdx`
*/
manualSteps: string[];
};
diff --git a/src/core/server/elasticsearch/client/client_config.ts b/src/core/server/elasticsearch/client/client_config.ts
index 27d6f877a5572..a6b0891fc12dd 100644
--- a/src/core/server/elasticsearch/client/client_config.ts
+++ b/src/core/server/elasticsearch/client/client_config.ts
@@ -56,6 +56,9 @@ export function parseClientOptions(
...DEFAULT_HEADERS,
...config.customHeaders,
},
+ // do not make assumption on user-supplied data content
+ // fixes https://github.com/elastic/kibana/issues/101944
+ disablePrototypePoisoningProtection: true,
};
if (config.pingTimeout != null) {
diff --git a/src/core/server/elasticsearch/client/configure_client.test.ts b/src/core/server/elasticsearch/client/configure_client.test.ts
index f954b121320fe..4e2c9c22f42f8 100644
--- a/src/core/server/elasticsearch/client/configure_client.test.ts
+++ b/src/core/server/elasticsearch/client/configure_client.test.ts
@@ -10,6 +10,7 @@ import { Buffer } from 'buffer';
import { Readable } from 'stream';
import { RequestEvent, errors } from '@elastic/elasticsearch';
+import type { Client } from '@elastic/elasticsearch';
import type {
TransportRequestOptions,
TransportRequestParams,
@@ -18,7 +19,6 @@ import type {
import { parseClientOptionsMock, ClientMock } from './configure_client.test.mocks';
import { loggingSystemMock } from '../../logging/logging_system.mock';
-import { EventEmitter } from 'events';
import type { ElasticsearchClientConfig } from './client_config';
import { configureClient } from './configure_client';
@@ -32,7 +32,10 @@ const createFakeConfig = (
};
const createFakeClient = () => {
- const client = new EventEmitter();
+ const actualEs = jest.requireActual('@elastic/elasticsearch');
+ const client = new actualEs.Client({
+ nodes: ['http://localhost'], // Enforcing `nodes` because it's mandatory
+ });
jest.spyOn(client, 'on');
return client;
};
@@ -67,6 +70,14 @@ const createApiResponse = ({
};
};
+function getProductCheckValue(client: Client) {
+ const tSymbol = Object.getOwnPropertySymbols(client.transport || client).filter(
+ (symbol) => symbol.description === 'product check'
+ )[0];
+ // @ts-expect-error `tSymbol` is missing in the index signature of Transport
+ return (client.transport || client)[tSymbol];
+}
+
describe('configureClient', () => {
let logger: ReturnType;
let config: ElasticsearchClientConfig;
@@ -117,6 +128,24 @@ describe('configureClient', () => {
expect(client.on).toHaveBeenCalledWith('response', expect.any(Function));
});
+ describe('Product check', () => {
+ it('should not skip the product check for the unscoped client', () => {
+ const client = configureClient(config, { logger, type: 'test', scoped: false });
+ expect(getProductCheckValue(client)).toBe(0);
+ });
+
+ it('should skip the product check for the scoped client', () => {
+ const client = configureClient(config, { logger, type: 'test', scoped: true });
+ expect(getProductCheckValue(client)).toBe(2);
+ });
+
+ it('should skip the product check for the children of the scoped client', () => {
+ const client = configureClient(config, { logger, type: 'test', scoped: true });
+ const asScoped = client.child({ headers: { 'x-custom-header': 'Custom value' } });
+ expect(getProductCheckValue(asScoped)).toBe(2);
+ });
+ });
+
describe('Client logging', () => {
function createResponseWithBody(body?: RequestBody) {
return createApiResponse({
diff --git a/src/core/server/elasticsearch/client/configure_client.ts b/src/core/server/elasticsearch/client/configure_client.ts
index 35825ef765dbf..efd22365d44f3 100644
--- a/src/core/server/elasticsearch/client/configure_client.ts
+++ b/src/core/server/elasticsearch/client/configure_client.ts
@@ -49,6 +49,12 @@ export const configureClient = (
const client = new Client({ ...clientOptions, Transport: KibanaTransport });
addLogging(client, logger.get('query', type));
+ // --------------------------------------------------------------------------------- //
+ // Hack to disable the "Product check" only in the scoped clients while we //
+ // come up with a better approach in https://github.com/elastic/kibana/issues/110675 //
+ if (scoped) skipProductCheck(client);
+ // --------------------------------------------------------------------------------- //
+
return client;
};
@@ -131,3 +137,21 @@ const addLogging = (client: Client, logger: Logger) => {
}
});
};
+
+/**
+ * Hack to skip the Product Check performed by the Elasticsearch-js client.
+ * We noticed that the scoped clients are always performing this check because
+ * of the way we initialize the clients. We'll discuss changing this in the issue
+ * https://github.com/elastic/kibana/issues/110675. In the meanwhile, let's skip
+ * it for the scoped clients.
+ *
+ * The hack is copied from the test/utils in the elasticsearch-js repo
+ * (https://github.com/elastic/elasticsearch-js/blob/master/test/utils/index.js#L45-L56)
+ */
+function skipProductCheck(client: Client) {
+ const tSymbol = Object.getOwnPropertySymbols(client.transport || client).filter(
+ (symbol) => symbol.description === 'product check'
+ )[0];
+ // @ts-expect-error `tSymbol` is missing in the index signature of Transport
+ (client.transport || client)[tSymbol] = 2;
+}
diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts
index 530203e659086..9471bbc1b87a6 100644
--- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts
+++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts
@@ -11,6 +11,7 @@ import { buildActiveMappings } from '../core';
const { mergeTypes } = jest.requireActual('./kibana_migrator');
import { SavedObjectsType } from '../../types';
import { BehaviorSubject } from 'rxjs';
+import { ByteSizeValue } from '@kbn/config-schema';
const defaultSavedObjectTypes: SavedObjectsType[] = [
{
@@ -37,6 +38,7 @@ const createMigrator = (
kibanaVersion: '8.0.0-testing',
soMigrationsConfig: {
batchSize: 100,
+ maxBatchSizeBytes: ByteSizeValue.parse('30kb'),
scrollDuration: '15m',
pollInterval: 1500,
skip: false,
diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts
index d0cc52f2dd9bd..6e10349f4b57c 100644
--- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts
+++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts
@@ -15,6 +15,7 @@ import { loggingSystemMock } from '../../../logging/logging_system.mock';
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { SavedObjectsType } from '../../types';
import { DocumentMigrator } from '../core/document_migrator';
+import { ByteSizeValue } from '@kbn/config-schema';
jest.mock('../core/document_migrator', () => {
return {
// Create a mock for spying on the constructor
@@ -396,6 +397,7 @@ const mockOptions = ({ enableV2 }: { enableV2: boolean } = { enableV2: false })
} as KibanaMigratorOptions['kibanaConfig'],
soMigrationsConfig: {
batchSize: 20,
+ maxBatchSizeBytes: ByteSizeValue.parse('20mb'),
pollInterval: 20000,
scrollDuration: '10m',
skip: false,
diff --git a/src/core/server/saved_objects/migrationsv2/README.md b/src/core/server/saved_objects/migrationsv2/README.md
index 5bdc548987842..5121e66052f40 100644
--- a/src/core/server/saved_objects/migrationsv2/README.md
+++ b/src/core/server/saved_objects/migrationsv2/README.md
@@ -316,7 +316,10 @@ completed this step:
- temp index has a write block
- temp index is not found
### New control state
+1. If `currentBatch` is the last batch in `transformedDocBatches`
→ `REINDEX_SOURCE_TO_TEMP_READ`
+2. If there are more batches left in `transformedDocBatches`
+ → `REINDEX_SOURCE_TO_TEMP_INDEX_BULK`
## REINDEX_SOURCE_TO_TEMP_CLOSE_PIT
### Next action
diff --git a/src/core/server/saved_objects/migrationsv2/actions/bulk_overwrite_transformed_documents.ts b/src/core/server/saved_objects/migrationsv2/actions/bulk_overwrite_transformed_documents.ts
index 4217ca599297a..82f642b928058 100644
--- a/src/core/server/saved_objects/migrationsv2/actions/bulk_overwrite_transformed_documents.ts
+++ b/src/core/server/saved_objects/migrationsv2/actions/bulk_overwrite_transformed_documents.ts
@@ -23,6 +23,27 @@ import type {
IndexNotFound,
} from './index';
+/**
+ * Given a document and index, creates a valid body for the Bulk API.
+ */
+export const createBulkOperationBody = (doc: SavedObjectsRawDoc, index: string) => {
+ return [
+ {
+ index: {
+ _index: index,
+ _id: doc._id,
+ // overwrite existing documents
+ op_type: 'index',
+ // use optimistic concurrency control to ensure that outdated
+ // documents are only overwritten once with the latest version
+ if_seq_no: doc._seq_no,
+ if_primary_term: doc._primary_term,
+ },
+ },
+ doc._source,
+ ];
+};
+
/** @internal */
export interface BulkOverwriteTransformedDocumentsParams {
client: ElasticsearchClient;
@@ -47,6 +68,10 @@ export const bulkOverwriteTransformedDocuments = ({
| RequestEntityTooLargeException,
'bulk_index_succeeded'
> => () => {
+ const body = transformedDocs.flatMap((doc) => {
+ return createBulkOperationBody(doc, index);
+ });
+
return client
.bulk({
// Because we only add aliases in the MARK_VERSION_INDEX_READY step we
@@ -60,23 +85,7 @@ export const bulkOverwriteTransformedDocuments = ({
wait_for_active_shards: WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE,
refresh,
filter_path: ['items.*.error'],
- body: transformedDocs.flatMap((doc) => {
- return [
- {
- index: {
- _index: index,
- _id: doc._id,
- // overwrite existing documents
- op_type: 'index',
- // use optimistic concurrency control to ensure that outdated
- // documents are only overwritten once with the latest version
- if_seq_no: doc._seq_no,
- if_primary_term: doc._primary_term,
- },
- },
- doc._source,
- ];
- }),
+ body,
})
.then((res) => {
// Filter out version_conflict_engine_exception since these just mean
diff --git a/src/core/server/saved_objects/migrationsv2/initial_state.test.ts b/src/core/server/saved_objects/migrationsv2/initial_state.test.ts
index 4066efeb65de0..26ba129cbeab4 100644
--- a/src/core/server/saved_objects/migrationsv2/initial_state.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/initial_state.test.ts
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { ByteSizeValue } from '@kbn/config-schema';
import * as Option from 'fp-ts/Option';
import { SavedObjectsMigrationConfigType } from '../saved_objects_config';
import { SavedObjectTypeRegistry } from '../saved_objects_type_registry';
@@ -21,6 +22,7 @@ describe('createInitialState', () => {
const migrationsConfig = ({
retryAttempts: 15,
batchSize: 1000,
+ maxBatchSizeBytes: ByteSizeValue.parse('100mb'),
} as unknown) as SavedObjectsMigrationConfigType;
it('creates the initial state for the model based on the passed in parameters', () => {
expect(
@@ -37,6 +39,7 @@ describe('createInitialState', () => {
})
).toEqual({
batchSize: 1000,
+ maxBatchSizeBytes: ByteSizeValue.parse('100mb').getValueInBytes(),
controlState: 'INIT',
currentAlias: '.kibana_task_manager',
excludeFromUpgradeFilterHooks: {},
diff --git a/src/core/server/saved_objects/migrationsv2/initial_state.ts b/src/core/server/saved_objects/migrationsv2/initial_state.ts
index dce37b384a4f7..a61967be9242c 100644
--- a/src/core/server/saved_objects/migrationsv2/initial_state.ts
+++ b/src/core/server/saved_objects/migrationsv2/initial_state.ts
@@ -82,6 +82,7 @@ export const createInitialState = ({
retryDelay: 0,
retryAttempts: migrationsConfig.retryAttempts,
batchSize: migrationsConfig.batchSize,
+ maxBatchSizeBytes: migrationsConfig.maxBatchSizeBytes.getValueInBytes(),
logs: [],
unusedTypesQuery: excludeUnusedTypesQuery,
knownTypes,
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/7.7.2_xpack_100k.test.ts
similarity index 94%
rename from src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts
rename to src/core/server/saved_objects/migrationsv2/integration_tests/7.7.2_xpack_100k.test.ts
index ed21349a700fc..41d89e2a01541 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/7.7.2_xpack_100k.test.ts
@@ -17,7 +17,7 @@ import { InternalCoreStart } from '../../../internal_types';
import { Root } from '../../../root';
const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version;
-const logFilePath = path.join(__dirname, 'migration_test_kibana.log');
+const logFilePath = path.join(__dirname, '7.7.2_xpack_100k.log');
async function removeLogFile() {
// ignore errors if it doesn't exist
@@ -61,9 +61,12 @@ describe('migration from 7.7.2-xpack with 100k objects', () => {
},
},
},
- root: {
- appenders: ['default', 'file'],
- },
+ loggers: [
+ {
+ name: 'root',
+ appenders: ['file'],
+ },
+ ],
},
},
{
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_failed_action_tasks.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_failed_action_tasks.test.ts
similarity index 99%
rename from src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_failed_action_tasks.test.ts
rename to src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_failed_action_tasks.test.ts
index 0788a7ecdf0b1..d70e034703158 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_failed_action_tasks.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_failed_action_tasks.test.ts
@@ -12,7 +12,7 @@ import * as kbnTestServer from '../../../../test_helpers/kbn_server';
import { Root } from '../../../root';
import { ElasticsearchClient } from '../../../elasticsearch';
-const logFilePath = Path.join(__dirname, '7_13_failed_action_tasks_test.log');
+const logFilePath = Path.join(__dirname, '7_13_failed_action_tasks.log');
async function removeLogFile() {
// ignore errors if it doesn't exist
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_transform_failures.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_transform_failures.test.ts
similarity index 99%
rename from src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_transform_failures.test.ts
rename to src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_transform_failures.test.ts
index 3258732c6fdd2..fb40bda81cba5 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_transform_failures.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_transform_failures.test.ts
@@ -12,7 +12,7 @@ import Util from 'util';
import * as kbnTestServer from '../../../../test_helpers/kbn_server';
import { Root } from '../../../root';
-const logFilePath = Path.join(__dirname, '7_13_corrupt_transform_failures_test.log');
+const logFilePath = Path.join(__dirname, '7_13_corrupt_transform_failures.log');
const asyncUnlink = Util.promisify(Fs.unlink);
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_unknown_types.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_unknown_types.test.ts
similarity index 86%
rename from src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_unknown_types.test.ts
rename to src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_unknown_types.test.ts
index aded389bbb595..0be8b1187af71 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7_13_0_unknown_types.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/7_13_0_unknown_types.test.ts
@@ -16,10 +16,12 @@ import { ElasticsearchClient } from '../../../elasticsearch';
import { Env } from '@kbn/config';
import { REPO_ROOT } from '@kbn/utils';
import { getEnvOptions } from '../../../config/mocks';
+import { retryAsync } from '../test_helpers/retry_async';
+import { LogRecord } from '@kbn/logging';
const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version;
const targetIndex = `.kibana_${kibanaVersion}_001`;
-const logFilePath = Path.join(__dirname, '7_13_unknown_types_test.log');
+const logFilePath = Path.join(__dirname, '7_13_unknown_types.log');
async function removeLogFile() {
// ignore errors if it doesn't exist
@@ -68,23 +70,30 @@ describe('migration v2', () => {
await root.setup();
await root.start();
- const logFileContent = await fs.readFile(logFilePath, 'utf-8');
- const records = logFileContent
- .split('\n')
- .filter(Boolean)
- .map((str) => JSON5.parse(str));
+ let unknownDocsWarningLog: LogRecord;
- const unknownDocsWarningLog = records.find((rec) =>
- rec.message.startsWith(`[.kibana] CHECK_UNKNOWN_DOCUMENTS`)
- );
+ await retryAsync(
+ async () => {
+ const logFileContent = await fs.readFile(logFilePath, 'utf-8');
+ const records = logFileContent
+ .split('\n')
+ .filter(Boolean)
+ .map((str) => JSON5.parse(str));
+
+ unknownDocsWarningLog = records.find((rec) =>
+ rec.message.startsWith(`[.kibana] CHECK_UNKNOWN_DOCUMENTS`)
+ );
- expect(
- unknownDocsWarningLog.message.startsWith(
- '[.kibana] CHECK_UNKNOWN_DOCUMENTS Upgrades will fail for 8.0+ because documents were found for unknown saved ' +
- 'object types. To ensure that upgrades will succeed in the future, either re-enable plugins or delete ' +
- `these documents from the "${targetIndex}" index after the current upgrade completes.`
- )
- ).toBeTruthy();
+ expect(
+ unknownDocsWarningLog.message.startsWith(
+ '[.kibana] CHECK_UNKNOWN_DOCUMENTS Upgrades will fail for 8.0+ because documents were found for unknown saved ' +
+ 'object types. To ensure that upgrades will succeed in the future, either re-enable plugins or delete ' +
+ `these documents from the "${targetIndex}" index after the current upgrade completes.`
+ )
+ ).toBeTruthy();
+ },
+ { retryAttempts: 10, retryDelayMs: 200 }
+ );
const unknownDocs = [
{ type: 'space', id: 'space:default' },
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/archives/7.14.0_xpack_sample_saved_objects.zip b/src/core/server/saved_objects/migrationsv2/integration_tests/archives/7.14.0_xpack_sample_saved_objects.zip
new file mode 100644
index 0000000000000..70d68587e3603
Binary files /dev/null and b/src/core/server/saved_objects/migrationsv2/integration_tests/archives/7.14.0_xpack_sample_saved_objects.zip differ
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes.test.ts
new file mode 100644
index 0000000000000..e96aeb6a93b65
--- /dev/null
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes.test.ts
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+import Path from 'path';
+import fs from 'fs/promises';
+import JSON5 from 'json5';
+import * as kbnTestServer from '../../../../test_helpers/kbn_server';
+import { Root } from '../../../root';
+import { ElasticsearchClient } from '../../../elasticsearch';
+import { Env } from '@kbn/config';
+import { REPO_ROOT } from '@kbn/utils';
+import { getEnvOptions } from '../../../config/mocks';
+import { LogRecord } from '@kbn/logging';
+import { retryAsync } from '../test_helpers/retry_async';
+
+const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version;
+const targetIndex = `.kibana_${kibanaVersion}_001`;
+const logFilePath = Path.join(__dirname, 'batch_size_bytes.log');
+
+async function removeLogFile() {
+ // ignore errors if it doesn't exist
+ await fs.unlink(logFilePath).catch(() => void 0);
+}
+
+describe('migration v2', () => {
+ let esServer: kbnTestServer.TestElasticsearchUtils;
+ let root: Root;
+ let startES: () => Promise;
+
+ beforeAll(async () => {
+ await removeLogFile();
+ });
+
+ beforeEach(() => {
+ ({ startES } = kbnTestServer.createTestServers({
+ adjustTimeout: (t: number) => jest.setTimeout(t),
+ settings: {
+ es: {
+ license: 'basic',
+ dataArchive: Path.join(__dirname, 'archives', '7.14.0_xpack_sample_saved_objects.zip'),
+ esArgs: ['http.max_content_length=1715275b'],
+ },
+ },
+ }));
+ });
+
+ afterEach(async () => {
+ if (root) {
+ await root.shutdown();
+ }
+ if (esServer) {
+ await esServer.stop();
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, 10000));
+ });
+
+ it('completes the migration even when a full batch would exceed ES http.max_content_length', async () => {
+ root = createRoot({ maxBatchSizeBytes: 1715275 });
+ esServer = await startES();
+ await root.preboot();
+ await root.setup();
+ await expect(root.start()).resolves.toBeTruthy();
+
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ const esClient: ElasticsearchClient = esServer.es.getClient();
+ const migratedIndexResponse = await esClient.count({
+ index: targetIndex,
+ });
+ const oldIndexResponse = await esClient.count({
+ index: '.kibana_7.14.0_001',
+ });
+
+ // Use a >= comparison since once Kibana has started it might create new
+ // documents like telemetry tasks
+ expect(migratedIndexResponse.body.count).toBeGreaterThanOrEqual(oldIndexResponse.body.count);
+ });
+
+ it('fails with a descriptive message when a single document exceeds maxBatchSizeBytes', async () => {
+ root = createRoot({ maxBatchSizeBytes: 1015275 });
+ esServer = await startES();
+ await root.preboot();
+ await root.setup();
+ await expect(root.start()).rejects.toMatchInlineSnapshot(
+ `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715275 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]`
+ );
+
+ await retryAsync(
+ async () => {
+ const logFileContent = await fs.readFile(logFilePath, 'utf-8');
+ const records = logFileContent
+ .split('\n')
+ .filter(Boolean)
+ .map((str) => JSON5.parse(str)) as LogRecord[];
+ expect(
+ records.find((rec) =>
+ rec.message.startsWith(
+ `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715275 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.`
+ )
+ )
+ ).toBeDefined();
+ },
+ { retryAttempts: 10, retryDelayMs: 200 }
+ );
+ });
+});
+
+function createRoot(options: { maxBatchSizeBytes?: number }) {
+ return kbnTestServer.createRootWithCorePlugins(
+ {
+ migrations: {
+ skip: false,
+ enableV2: true,
+ batchSize: 1000,
+ maxBatchSizeBytes: options.maxBatchSizeBytes,
+ },
+ logging: {
+ appenders: {
+ file: {
+ type: 'file',
+ fileName: logFilePath,
+ layout: {
+ type: 'json',
+ },
+ },
+ },
+ loggers: [
+ {
+ name: 'root',
+ appenders: ['file'],
+ },
+ ],
+ },
+ },
+ {
+ oss: true,
+ }
+ );
+}
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes_exceeds_es_content_length.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes_exceeds_es_content_length.test.ts
new file mode 100644
index 0000000000000..192321227d4ae
--- /dev/null
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes_exceeds_es_content_length.test.ts
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+import Path from 'path';
+import fs from 'fs/promises';
+import JSON5 from 'json5';
+import * as kbnTestServer from '../../../../test_helpers/kbn_server';
+import { Root } from '../../../root';
+import { retryAsync } from '../test_helpers/retry_async';
+
+const logFilePath = Path.join(__dirname, 'batch_size_bytes_exceeds_es_content_length.log');
+
+async function removeLogFile() {
+ // ignore errors if it doesn't exist
+ await fs.unlink(logFilePath).catch(() => void 0);
+}
+
+describe('migration v2', () => {
+ let esServer: kbnTestServer.TestElasticsearchUtils;
+ let root: Root;
+ let startES: () => Promise;
+
+ beforeAll(async () => {
+ await removeLogFile();
+ });
+
+ beforeEach(() => {
+ ({ startES } = kbnTestServer.createTestServers({
+ adjustTimeout: (t: number) => jest.setTimeout(t),
+ settings: {
+ es: {
+ license: 'basic',
+ dataArchive: Path.join(__dirname, 'archives', '7.14.0_xpack_sample_saved_objects.zip'),
+ esArgs: ['http.max_content_length=1mb'],
+ },
+ },
+ }));
+ });
+
+ afterEach(async () => {
+ if (root) {
+ await root.shutdown();
+ }
+ if (esServer) {
+ await esServer.stop();
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, 10000));
+ });
+
+ it('fails with a descriptive message when maxBatchSizeBytes exceeds ES http.max_content_length', async () => {
+ root = createRoot({ maxBatchSizeBytes: 1715275 });
+ esServer = await startES();
+ await root.preboot();
+ await root.setup();
+ await expect(root.start()).rejects.toMatchInlineSnapshot(
+ `[Error: Unable to complete saved object migrations for the [.kibana] index: While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option.]`
+ );
+
+ await retryAsync(
+ async () => {
+ const logFileContent = await fs.readFile(logFilePath, 'utf-8');
+ const records = logFileContent
+ .split('\n')
+ .filter(Boolean)
+ .map((str) => JSON5.parse(str)) as any[];
+
+ expect(
+ records.find((rec) =>
+ rec.message.startsWith(
+ `Unable to complete saved object migrations for the [.kibana] index: While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option.`
+ )
+ )
+ ).toBeDefined();
+ },
+ { retryAttempts: 10, retryDelayMs: 200 }
+ );
+ });
+});
+
+function createRoot(options: { maxBatchSizeBytes?: number }) {
+ return kbnTestServer.createRootWithCorePlugins(
+ {
+ migrations: {
+ skip: false,
+ enableV2: true,
+ batchSize: 1000,
+ maxBatchSizeBytes: options.maxBatchSizeBytes,
+ },
+ logging: {
+ appenders: {
+ file: {
+ type: 'file',
+ fileName: logFilePath,
+ layout: {
+ type: 'json',
+ },
+ },
+ },
+ loggers: [
+ {
+ name: 'root',
+ appenders: ['file'],
+ },
+ ],
+ },
+ },
+ {
+ oss: true,
+ }
+ );
+}
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts
index 684b75056bf44..bb408d14df6d7 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/cleanup.test.ts
@@ -13,7 +13,7 @@ import JSON5 from 'json5';
import * as kbnTestServer from '../../../../test_helpers/kbn_server';
import type { Root } from '../../../root';
-const logFilePath = Path.join(__dirname, 'cleanup_test.log');
+const logFilePath = Path.join(__dirname, 'cleanup.log');
const asyncUnlink = Util.promisify(Fs.unlink);
const asyncReadFile = Util.promisify(Fs.readFile);
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/type_migration_failure.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/collects_corrupt_docs.test.ts
similarity index 98%
rename from src/core/server/saved_objects/migrationsv2/integration_tests/type_migration_failure.test.ts
rename to src/core/server/saved_objects/migrationsv2/integration_tests/collects_corrupt_docs.test.ts
index b3721d603d7d9..02b7d0eae2a90 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/type_migration_failure.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/collects_corrupt_docs.test.ts
@@ -12,7 +12,7 @@ import Util from 'util';
import * as kbnTestServer from '../../../../test_helpers/kbn_server';
import { Root } from '../../../root';
-const logFilePath = Path.join(__dirname, 'migration_test_corrupt_docs_kibana.log');
+const logFilePath = Path.join(__dirname, 'collects_corrupt_docs.log');
const asyncUnlink = Util.promisify(Fs.unlink);
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/corrupt_outdated_docs.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/corrupt_outdated_docs.test.ts
index de58dded69422..446542cc37306 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/corrupt_outdated_docs.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/corrupt_outdated_docs.test.ts
@@ -12,7 +12,7 @@ import Util from 'util';
import * as kbnTestServer from '../../../../test_helpers/kbn_server';
import { Root } from '../../../root';
-const logFilePath = Path.join(__dirname, 'migration_test_corrupt_docs_kibana.log');
+const logFilePath = Path.join(__dirname, 'corrupt_outdated_docs.log');
const asyncUnlink = Util.promisify(Fs.unlink);
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_from_v1.test.ts
similarity index 99%
rename from src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts
rename to src/core/server/saved_objects/migrationsv2/integration_tests/migration_from_v1.test.ts
index 2a1d6bff0c247..fc01e6a408497 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_from_v1.test.ts
@@ -21,7 +21,7 @@ import { Root } from '../../../root';
const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version;
-const logFilePath = Path.join(__dirname, 'migration_test_kibana_from_v1.log');
+const logFilePath = Path.join(__dirname, 'migration_from_v1.log');
const asyncUnlink = Util.promisify(Fs.unlink);
async function removeLogFile() {
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/outdated_docs.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/outdated_docs.test.ts
index 822a44fb22dc1..58ff34913f5d4 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/outdated_docs.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/outdated_docs.test.ts
@@ -14,7 +14,7 @@ import * as kbnTestServer from '../../../../test_helpers/kbn_server';
import type { ElasticsearchClient } from '../../../elasticsearch';
import { Root } from '../../../root';
-const logFilePath = Path.join(__dirname, 'migration_test_kibana.log');
+const logFilePath = Path.join(__dirname, 'outdated_docs.log');
const asyncUnlink = Util.promisify(Fs.unlink);
async function removeLogFile() {
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/rewriting_id.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/rewriting_id.test.ts
index 0bdf7a0d98766..4564a89ee0816 100644
--- a/src/core/server/saved_objects/migrationsv2/integration_tests/rewriting_id.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/integration_tests/rewriting_id.test.ts
@@ -15,7 +15,7 @@ import type { ElasticsearchClient } from '../../../elasticsearch';
import { Root } from '../../../root';
import { deterministicallyRegenerateObjectId } from '../../migrations/core/document_migrator';
-const logFilePath = Path.join(__dirname, 'migration_test_kibana.log');
+const logFilePath = Path.join(__dirname, 'rewriting_id.log');
const asyncUnlink = Util.promisify(Fs.unlink);
async function removeLogFile() {
diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts
index 773a0af469bd4..a312ac6be0c3d 100644
--- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts
@@ -17,6 +17,7 @@ import { elasticsearchClientMock } from '../../elasticsearch/client/mocks';
import { LoggerAdapter } from '../../logging/logger_adapter';
import { AllControlStates, State } from './types';
import { createInitialState } from './initial_state';
+import { ByteSizeValue } from '@kbn/config-schema';
const esClient = elasticsearchServiceMock.createElasticsearchClient();
@@ -40,6 +41,7 @@ describe('migrationsStateActionMachine', () => {
indexPrefix: '.my-so-index',
migrationsConfig: {
batchSize: 1000,
+ maxBatchSizeBytes: new ByteSizeValue(1e8),
pollInterval: 0,
scrollDuration: '0s',
skip: false,
@@ -235,6 +237,7 @@ describe('migrationsStateActionMachine', () => {
...initialState,
reason: 'the fatal reason',
outdatedDocuments: [{ _id: '1234', password: 'sensitive password' }],
+ transformedDocBatches: [[{ _id: '1234', password: 'sensitive transformed password' }]],
} as State,
logger: mockLogger.get(),
model: transitionModel(['LEGACY_DELETE', 'FATAL']),
@@ -257,6 +260,7 @@ describe('migrationsStateActionMachine', () => {
kibana: {
migrationState: {
batchSize: 1000,
+ maxBatchSizeBytes: 1e8,
controlState: 'LEGACY_DELETE',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
@@ -270,7 +274,7 @@ describe('migrationsStateActionMachine', () => {
message: 'Log from LEGACY_DELETE control state',
},
],
- outdatedDocuments: ['1234'],
+ outdatedDocuments: [{ _id: '1234' }],
outdatedDocumentsQuery: expect.any(Object),
preMigrationScript: {
_tag: 'None',
@@ -284,6 +288,7 @@ describe('migrationsStateActionMachine', () => {
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
+ transformedDocBatches: [[{ _id: '1234' }]],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
@@ -304,6 +309,7 @@ describe('migrationsStateActionMachine', () => {
kibana: {
migrationState: {
batchSize: 1000,
+ maxBatchSizeBytes: 1e8,
controlState: 'FATAL',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
@@ -321,7 +327,7 @@ describe('migrationsStateActionMachine', () => {
message: 'Log from FATAL control state',
},
],
- outdatedDocuments: ['1234'],
+ outdatedDocuments: [{ _id: '1234' }],
outdatedDocumentsQuery: expect.any(Object),
preMigrationScript: {
_tag: 'None',
@@ -335,6 +341,7 @@ describe('migrationsStateActionMachine', () => {
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
+ transformedDocBatches: [[{ _id: '1234' }]],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
@@ -447,6 +454,7 @@ describe('migrationsStateActionMachine', () => {
kibana: {
migrationState: {
batchSize: 1000,
+ maxBatchSizeBytes: 1e8,
controlState: 'LEGACY_REINDEX',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
@@ -474,6 +482,7 @@ describe('migrationsStateActionMachine', () => {
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
+ transformedDocBatches: [],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
@@ -488,6 +497,7 @@ describe('migrationsStateActionMachine', () => {
kibana: {
migrationState: {
batchSize: 1000,
+ maxBatchSizeBytes: 1e8,
controlState: 'LEGACY_DELETE',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
@@ -519,6 +529,7 @@ describe('migrationsStateActionMachine', () => {
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
+ transformedDocBatches: [],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts
index 8e3b8ee4ab556..58c299b77fc60 100644
--- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts
+++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts
@@ -13,7 +13,8 @@ import type { ElasticsearchClient } from '../../elasticsearch';
import { getErrorMessage, getRequestDebugMeta } from '../../elasticsearch';
import { Model, Next, stateActionMachine } from './state_action_machine';
import { cleanup } from './migrations_state_machine_cleanup';
-import { State } from './types';
+import { ReindexSourceToTempIndex, ReindexSourceToTempIndexBulk, State } from './types';
+import { SavedObjectsRawDoc } from '../serialization';
interface StateLogMeta extends LogMeta {
kibana: {
@@ -140,11 +141,22 @@ export async function migrationStateActionMachine({
const newState = model(state, res);
// Redact the state to reduce the memory consumption and so that we
// don't log sensitive information inside documents by only keeping
- // the _id's of outdatedDocuments
+ // the _id's of documents
const redactedNewState = {
...newState,
- // @ts-expect-error outdatedDocuments don't exist in all states
- ...{ outdatedDocuments: (newState.outdatedDocuments ?? []).map((doc) => doc._id) },
+ ...{
+ outdatedDocuments: ((newState as ReindexSourceToTempIndex).outdatedDocuments ?? []).map(
+ (doc) =>
+ ({
+ _id: doc._id,
+ } as SavedObjectsRawDoc)
+ ),
+ },
+ ...{
+ transformedDocBatches: (
+ (newState as ReindexSourceToTempIndexBulk).transformedDocBatches ?? []
+ ).map((batches) => batches.map((doc) => ({ _id: doc._id }))) as [SavedObjectsRawDoc[]],
+ },
};
executionLog.push({
type: 'transition',
diff --git a/src/core/server/saved_objects/migrationsv2/model/create_batches.test.ts b/src/core/server/saved_objects/migrationsv2/model/create_batches.test.ts
new file mode 100644
index 0000000000000..552c4c237675f
--- /dev/null
+++ b/src/core/server/saved_objects/migrationsv2/model/create_batches.test.ts
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+import * as Either from 'fp-ts/lib/Either';
+import { SavedObjectsRawDoc } from '../../serialization';
+import { createBatches } from './create_batches';
+
+describe('createBatches', () => {
+ const DOCUMENT_SIZE_BYTES = 128;
+ const INDEX = '.kibana_version_index';
+ it('returns right one batch if all documents fit in maxBatchSizeBytes', () => {
+ const documents = [
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } },
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ²' } },
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } },
+ ];
+
+ expect(createBatches(documents, INDEX, DOCUMENT_SIZE_BYTES * 3)).toEqual(
+ Either.right([documents])
+ );
+ });
+ it('creates multiple batches with each batch limited to maxBatchSizeBytes', () => {
+ const documents = [
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } },
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ²' } },
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } },
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title 44' } },
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title 55' } },
+ ];
+ expect(createBatches(documents, INDEX, DOCUMENT_SIZE_BYTES * 2)).toEqual(
+ Either.right([[documents[0], documents[1]], [documents[2], documents[3]], [documents[4]]])
+ );
+ });
+ it('creates a single empty batch if there are no documents', () => {
+ const documents = [] as SavedObjectsRawDoc[];
+ expect(createBatches(documents, INDEX, 100)).toEqual(Either.right([[]]));
+ });
+ it('throws if any one document exceeds the maxBatchSizeBytes', () => {
+ const documents = [
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } },
+ {
+ _id: '',
+ _source: {
+ type: 'dashboard',
+ title: 'my saved object title ² with a very long title that exceeds max size bytes',
+ },
+ },
+ { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } },
+ ];
+ expect(createBatches(documents, INDEX, 178)).toEqual(
+ Either.left({
+ maxBatchSizeBytes: 178,
+ docSizeBytes: 179,
+ type: 'document_exceeds_batch_size_bytes',
+ document: documents[1],
+ })
+ );
+ });
+});
diff --git a/src/core/server/saved_objects/migrationsv2/model/create_batches.ts b/src/core/server/saved_objects/migrationsv2/model/create_batches.ts
new file mode 100644
index 0000000000000..c80003fef09fb
--- /dev/null
+++ b/src/core/server/saved_objects/migrationsv2/model/create_batches.ts
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+import * as Either from 'fp-ts/lib/Either';
+import { SavedObjectsRawDoc } from '../..';
+import { createBulkOperationBody } from '../actions/bulk_overwrite_transformed_documents';
+
+/**
+ * Creates batches of documents to be used by the bulk API. Each batch will
+ * have a request body content length that's <= maxBatchSizeBytes
+ */
+export function createBatches(
+ docs: SavedObjectsRawDoc[],
+ index: string,
+ maxBatchSizeBytes: number
+) {
+ /* To build up the NDJSON request body we construct an array of objects like:
+ * [
+ * {"index": ...}
+ * {"title": "my saved object"}
+ * ...
+ * ]
+ * However, when we call JSON.stringify on this array the resulting string
+ * will be surrounded by `[]` which won't be present in the NDJSON so these
+ * two characters need to be removed from the size calculation.
+ */
+ const BRACKETS_BYTES = 2;
+ /* Each document in the NDJSON (including the last one) needs to be
+ * terminated by a newline, so we need to account for an extra newline
+ * character
+ */
+ const NDJSON_NEW_LINE_BYTES = 1;
+
+ const batches = [[]] as [SavedObjectsRawDoc[]];
+ let currBatch = 0;
+ let currBatchSizeBytes = 0;
+ for (const doc of docs) {
+ const bulkOperationBody = createBulkOperationBody(doc, index);
+ const docSizeBytes =
+ Buffer.byteLength(JSON.stringify(bulkOperationBody), 'utf8') -
+ BRACKETS_BYTES +
+ NDJSON_NEW_LINE_BYTES;
+ if (docSizeBytes > maxBatchSizeBytes) {
+ return Either.left({
+ type: 'document_exceeds_batch_size_bytes',
+ docSizeBytes,
+ maxBatchSizeBytes,
+ document: doc,
+ });
+ } else if (currBatchSizeBytes + docSizeBytes <= maxBatchSizeBytes) {
+ batches[currBatch].push(doc);
+ currBatchSizeBytes = currBatchSizeBytes + docSizeBytes;
+ } else {
+ currBatch++;
+ batches[currBatch] = [doc];
+ currBatchSizeBytes = docSizeBytes;
+ }
+ }
+
+ return Either.right(batches);
+}
diff --git a/src/core/server/saved_objects/migrationsv2/model/model.test.ts b/src/core/server/saved_objects/migrationsv2/model/model.test.ts
index f24d175f416a7..1d017116bf3fd 100644
--- a/src/core/server/saved_objects/migrationsv2/model/model.test.ts
+++ b/src/core/server/saved_objects/migrationsv2/model/model.test.ts
@@ -58,6 +58,7 @@ describe('migrations v2 model', () => {
retryDelay: 0,
retryAttempts: 15,
batchSize: 1000,
+ maxBatchSizeBytes: 1e8,
indexPrefix: '.kibana',
outdatedDocumentsQuery: {},
targetIndexMappings: {
@@ -1065,6 +1066,8 @@ describe('migrations v2 model', () => {
});
const newState = model(state, res) as ReindexSourceToTempIndexBulk;
expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_INDEX_BULK');
+ expect(newState.currentBatch).toEqual(0);
+ expect(newState.transformedDocBatches).toEqual([processedDocs]);
expect(newState.progress.processed).toBe(0); // Result of `(undefined ?? 0) + corruptDocumentsId.length`
});
@@ -1119,16 +1122,19 @@ describe('migrations v2 model', () => {
});
describe('REINDEX_SOURCE_TO_TEMP_INDEX_BULK', () => {
- const transformedDocs = [
- {
- _id: 'a:b',
- _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
- },
- ] as SavedObjectsRawDoc[];
+ const transformedDocBatches = [
+ [
+ {
+ _id: 'a:b',
+ _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
+ },
+ ],
+ ] as [SavedObjectsRawDoc[]];
const reindexSourceToTempIndexBulkState: ReindexSourceToTempIndexBulk = {
...baseState,
controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK',
- transformedDocs,
+ transformedDocBatches,
+ currentBatch: 0,
versionIndexReadyActions: Option.none,
sourceIndex: Option.some('.kibana') as Option.Some,
sourceIndexPitId: 'pit_id',
@@ -1171,7 +1177,7 @@ describe('migrations v2 model', () => {
const newState = model(reindexSourceToTempIndexBulkState, res) as FatalState;
expect(newState.controlState).toEqual('FATAL');
expect(newState.reason).toMatchInlineSnapshot(
- `"While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Try to use smaller batches by changing the Kibana 'migrations.batchSize' configuration option and restarting Kibana."`
+ `"While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option."`
);
});
test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK should throw a throwBadResponse error if action failed', () => {
@@ -1438,7 +1444,8 @@ describe('migrations v2 model', () => {
res
) as TransformedDocumentsBulkIndex;
expect(newState.controlState).toEqual('TRANSFORMED_DOCUMENTS_BULK_INDEX');
- expect(newState.transformedDocs).toEqual(processedDocs);
+ expect(newState.transformedDocBatches).toEqual([processedDocs]);
+ expect(newState.currentBatch).toEqual(0);
expect(newState.retryCount).toEqual(0);
expect(newState.retryDelay).toEqual(0);
expect(newState.progress.processed).toBe(outdatedDocuments.length);
@@ -1521,16 +1528,31 @@ describe('migrations v2 model', () => {
});
describe('TRANSFORMED_DOCUMENTS_BULK_INDEX', () => {
- const transformedDocs = [
- {
- _id: 'a:b',
- _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
- },
- ] as SavedObjectsRawDoc[];
+ const transformedDocBatches = [
+ [
+ // batch 0
+ {
+ _id: 'a:b',
+ _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
+ },
+ {
+ _id: 'a:c',
+ _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
+ },
+ ],
+ [
+ // batch 1
+ {
+ _id: 'a:d',
+ _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
+ },
+ ],
+ ] as SavedObjectsRawDoc[][];
const transformedDocumentsBulkIndexState: TransformedDocumentsBulkIndex = {
...baseState,
controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX',
- transformedDocs,
+ transformedDocBatches,
+ currentBatch: 0,
versionIndexReadyActions: Option.none,
sourceIndex: Option.some('.kibana') as Option.Some,
targetIndex: '.kibana_7.11.0_001',
@@ -1540,6 +1562,29 @@ describe('migrations v2 model', () => {
progress: createInitialProgress(),
};
+ test('TRANSFORMED_DOCUMENTS_BULK_INDEX -> TRANSFORMED_DOCUMENTS_BULK_INDEX and increments currentBatch if more batches are left', () => {
+ const res: ResponseType<'TRANSFORMED_DOCUMENTS_BULK_INDEX'> = Either.right(
+ 'bulk_index_succeeded'
+ );
+ const newState = model(
+ transformedDocumentsBulkIndexState,
+ res
+ ) as TransformedDocumentsBulkIndex;
+ expect(newState.controlState).toEqual('TRANSFORMED_DOCUMENTS_BULK_INDEX');
+ expect(newState.currentBatch).toEqual(1);
+ });
+
+ test('TRANSFORMED_DOCUMENTS_BULK_INDEX -> OUTDATED_DOCUMENTS_SEARCH_READ if all batches were written', () => {
+ const res: ResponseType<'TRANSFORMED_DOCUMENTS_BULK_INDEX'> = Either.right(
+ 'bulk_index_succeeded'
+ );
+ const newState = model(
+ { ...transformedDocumentsBulkIndexState, ...{ currentBatch: 1 } },
+ res
+ );
+ expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_READ');
+ });
+
test('TRANSFORMED_DOCUMENTS_BULK_INDEX throws if action returns left index_not_found_exception', () => {
const res: ResponseType<'TRANSFORMED_DOCUMENTS_BULK_INDEX'> = Either.left({
type: 'index_not_found_exception',
@@ -1570,7 +1615,7 @@ describe('migrations v2 model', () => {
const newState = model(transformedDocumentsBulkIndexState, res) as FatalState;
expect(newState.controlState).toEqual('FATAL');
expect(newState.reason).toMatchInlineSnapshot(
- `"While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Try to use smaller batches by changing the Kibana 'migrations.batchSize' configuration option and restarting Kibana."`
+ `"While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option."`
);
});
});
diff --git a/src/core/server/saved_objects/migrationsv2/model/model.ts b/src/core/server/saved_objects/migrationsv2/model/model.ts
index 50be4a524f5c5..8aa3d7b83b295 100644
--- a/src/core/server/saved_objects/migrationsv2/model/model.ts
+++ b/src/core/server/saved_objects/migrationsv2/model/model.ts
@@ -31,6 +31,19 @@ import {
throwBadControlState,
throwBadResponse,
} from './helpers';
+import { createBatches } from './create_batches';
+
+const FATAL_REASON_REQUEST_ENTITY_TOO_LARGE = `While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option.`;
+const fatalReasonDocumentExceedsMaxBatchSizeBytes = ({
+ _id,
+ docSizeBytes,
+ maxBatchSizeBytes,
+}: {
+ _id: string;
+ docSizeBytes: number;
+ maxBatchSizeBytes: number;
+}) =>
+ `The document with _id "${_id}" is ${docSizeBytes} bytes which exceeds the configured maximum batch size of ${maxBatchSizeBytes} bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.`;
export const model = (currentState: State, resW: ResponseType): State => {
// The action response `resW` is weakly typed, the type includes all action
@@ -489,12 +502,30 @@ export const model = (currentState: State, resW: ResponseType):
if (Either.isRight(res)) {
if (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) {
- return {
- ...stateP,
- controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK', // handles the actual bulk indexing into temp index
- transformedDocs: [...res.right.processedDocs],
- progress,
- };
+ const batches = createBatches(
+ res.right.processedDocs,
+ stateP.tempIndex,
+ stateP.maxBatchSizeBytes
+ );
+ if (Either.isRight(batches)) {
+ return {
+ ...stateP,
+ controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK', // handles the actual bulk indexing into temp index
+ transformedDocBatches: batches.right,
+ currentBatch: 0,
+ progress,
+ };
+ } else {
+ return {
+ ...stateP,
+ controlState: 'FATAL',
+ reason: fatalReasonDocumentExceedsMaxBatchSizeBytes({
+ _id: batches.left.document._id,
+ docSizeBytes: batches.left.docSizeBytes,
+ maxBatchSizeBytes: batches.left.maxBatchSizeBytes,
+ }),
+ };
+ }
} else {
// we don't have any transform issues with the current batch of outdated docs but
// we have carried through previous transformation issues.
@@ -525,13 +556,21 @@ export const model = (currentState: State, resW: ResponseType):
} else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK') {
const res = resW as ExcludeRetryableEsError>;
if (Either.isRight(res)) {
- return {
- ...stateP,
- controlState: 'REINDEX_SOURCE_TO_TEMP_READ',
- // we're still on the happy path with no transformation failures seen.
- corruptDocumentIds: [],
- transformErrors: [],
- };
+ if (stateP.currentBatch + 1 < stateP.transformedDocBatches.length) {
+ return {
+ ...stateP,
+ controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK',
+ currentBatch: stateP.currentBatch + 1,
+ };
+ } else {
+ return {
+ ...stateP,
+ controlState: 'REINDEX_SOURCE_TO_TEMP_READ',
+ // we're still on the happy path with no transformation failures seen.
+ corruptDocumentIds: [],
+ transformErrors: [],
+ };
+ }
} else {
if (
isLeftTypeof(res.left, 'target_index_had_write_block') ||
@@ -548,7 +587,7 @@ export const model = (currentState: State, resW: ResponseType):
return {
...stateP,
controlState: 'FATAL',
- reason: `While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Try to use smaller batches by changing the Kibana 'migrations.batchSize' configuration option and restarting Kibana.`,
+ reason: FATAL_REASON_REQUEST_ENTITY_TOO_LARGE,
};
}
throwBadResponse(stateP, res.left);
@@ -677,13 +716,31 @@ export const model = (currentState: State, resW: ResponseType):
// we haven't seen corrupt documents or any transformation errors thus far in the migration
// index the migrated docs
if (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) {
- return {
- ...stateP,
- controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX',
- transformedDocs: [...res.right.processedDocs],
- hasTransformedDocs: true,
- progress,
- };
+ const batches = createBatches(
+ res.right.processedDocs,
+ stateP.targetIndex,
+ stateP.maxBatchSizeBytes
+ );
+ if (Either.isRight(batches)) {
+ return {
+ ...stateP,
+ controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX',
+ transformedDocBatches: batches.right,
+ currentBatch: 0,
+ hasTransformedDocs: true,
+ progress,
+ };
+ } else {
+ return {
+ ...stateP,
+ controlState: 'FATAL',
+ reason: fatalReasonDocumentExceedsMaxBatchSizeBytes({
+ _id: batches.left.document._id,
+ docSizeBytes: batches.left.docSizeBytes,
+ maxBatchSizeBytes: batches.left.maxBatchSizeBytes,
+ }),
+ };
+ }
} else {
// We have seen corrupt documents and/or transformation errors
// skip indexing and go straight to reading and transforming more docs
@@ -711,6 +768,13 @@ export const model = (currentState: State, resW: ResponseType):
} else if (stateP.controlState === 'TRANSFORMED_DOCUMENTS_BULK_INDEX') {
const res = resW as ExcludeRetryableEsError>;
if (Either.isRight(res)) {
+ if (stateP.currentBatch + 1 < stateP.transformedDocBatches.length) {
+ return {
+ ...stateP,
+ controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX',
+ currentBatch: stateP.currentBatch + 1,
+ };
+ }
return {
...stateP,
controlState: 'OUTDATED_DOCUMENTS_SEARCH_READ',
@@ -723,7 +787,7 @@ export const model = (currentState: State, resW: ResponseType):
return {
...stateP,
controlState: 'FATAL',
- reason: `While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Try to use smaller batches by changing the Kibana 'migrations.batchSize' configuration option and restarting Kibana.`,
+ reason: FATAL_REASON_REQUEST_ENTITY_TOO_LARGE,
};
} else if (
isLeftTypeof(res.left, 'target_index_had_write_block') ||
diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts
index 9b091b6fc8509..3f3714552725b 100644
--- a/src/core/server/saved_objects/migrationsv2/next.ts
+++ b/src/core/server/saved_objects/migrationsv2/next.ts
@@ -111,7 +111,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra
Actions.bulkOverwriteTransformedDocuments({
client,
index: state.tempIndex,
- transformedDocs: state.transformedDocs,
+ transformedDocs: state.transformedDocBatches[state.currentBatch],
/**
* Since we don't run a search against the target index, we disable "refresh" to speed up
* the migration process.
@@ -160,7 +160,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra
Actions.bulkOverwriteTransformedDocuments({
client,
index: state.targetIndex,
- transformedDocs: state.transformedDocs,
+ transformedDocs: state.transformedDocBatches[state.currentBatch],
/**
* Since we don't run a search against the target index, we disable "refresh" to speed up
* the migration process.
diff --git a/src/core/server/saved_objects/migrationsv2/test_helpers/retry.test.ts b/src/core/server/saved_objects/migrationsv2/test_helpers/retry.test.ts
new file mode 100644
index 0000000000000..246f61c71ae4d
--- /dev/null
+++ b/src/core/server/saved_objects/migrationsv2/test_helpers/retry.test.ts
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+import { retryAsync } from './retry_async';
+
+describe('retry', () => {
+ it('retries throwing functions until they succeed', async () => {
+ let i = 0;
+ await expect(
+ retryAsync(
+ () => {
+ if (i++ < 2) {
+ return Promise.reject(new Error('boom'));
+ } else {
+ return Promise.resolve('done');
+ }
+ },
+ { retryAttempts: 3, retryDelayMs: 1 }
+ )
+ ).resolves.toEqual('done');
+ });
+
+ it('throws if all attempts are exhausted before success', async () => {
+ let attempts = 0;
+ await expect(() =>
+ retryAsync(
+ () => {
+ attempts++;
+ return Promise.reject(new Error('boom'));
+ },
+ { retryAttempts: 3, retryDelayMs: 1 }
+ )
+ ).rejects.toMatchInlineSnapshot(`[Error: boom]`);
+ expect(attempts).toEqual(3);
+ });
+
+ it('waits retryDelayMs between each attempt ', async () => {
+ const now = Date.now();
+ let i = 0;
+ await retryAsync(
+ () => {
+ if (i++ < 2) {
+ return Promise.reject(new Error('boom'));
+ } else {
+ return Promise.resolve('done');
+ }
+ },
+ { retryAttempts: 3, retryDelayMs: 100 }
+ );
+ expect(Date.now() - now).toBeGreaterThanOrEqual(200);
+ });
+});
diff --git a/src/core/server/saved_objects/migrationsv2/test_helpers/retry_async.ts b/src/core/server/saved_objects/migrationsv2/test_helpers/retry_async.ts
new file mode 100644
index 0000000000000..f5dffede67a16
--- /dev/null
+++ b/src/core/server/saved_objects/migrationsv2/test_helpers/retry_async.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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.
+ */
+
+function delay(delayInMs: number) {
+ return new Promise((resolve) => setTimeout(resolve, delayInMs));
+}
+
+export async function retryAsync(
+ fn: () => Promise,
+ options: { retryAttempts: number; retryDelayMs: number }
+): Promise {
+ try {
+ return await fn();
+ } catch (e) {
+ if (options.retryAttempts > 1) {
+ await delay(options.retryDelayMs);
+ return retryAsync(fn, {
+ retryAttempts: options.retryAttempts - 1,
+ retryDelayMs: options.retryDelayMs,
+ });
+ } else {
+ throw e;
+ }
+ }
+}
diff --git a/src/core/server/saved_objects/migrationsv2/types.ts b/src/core/server/saved_objects/migrationsv2/types.ts
index ea03b64e03dc8..49ce12c53aa1a 100644
--- a/src/core/server/saved_objects/migrationsv2/types.ts
+++ b/src/core/server/saved_objects/migrationsv2/types.ts
@@ -76,19 +76,31 @@ export interface BaseState extends ControlState {
readonly retryAttempts: number;
/**
- * The number of documents to fetch from Elasticsearch server to run migration over.
+ * The number of documents to process in each batch. This determines the
+ * maximum number of documents that will be read and written in a single
+ * request.
*
- * The higher the value, the faster the migration process will be performed since it reduces
- * the number of round trips between Kibana and Elasticsearch servers.
- * For the migration speed, we have to pay the price of increased memory consumption.
+ * The higher the value, the faster the migration process will be performed
+ * since it reduces the number of round trips between Kibana and
+ * Elasticsearch servers. For the migration speed, we have to pay the price
+ * of increased memory consumption and HTTP payload size.
*
- * Since batchSize defines the number of documents, not their size, it might happen that
- * Elasticsearch fails a request with circuit_breaking_exception when it retrieves a set of
- * saved objects of significant size.
+ * Since we cannot control the size in bytes of a batch when reading,
+ * Elasticsearch might fail with a circuit_breaking_exception when it
+ * retrieves a set of saved objects of significant size. In this case, you
+ * should set a smaller batchSize value and restart the migration process
+ * again.
*
- * In this case, you should set a smaller batchSize value and restart the migration process again.
+ * When writing batches, we limit the number of documents in a batch
+ * (batchSize) as well as the size of the batch in bytes (maxBatchSizeBytes).
*/
readonly batchSize: number;
+ /**
+ * When writing batches, limits the batch size in bytes to ensure that we
+ * don't construct HTTP requests which would exceed Elasticsearch's
+ * http.max_content_length which defaults to 100mb.
+ */
+ readonly maxBatchSizeBytes: number;
readonly logs: MigrationLog[];
/**
* The current alias e.g. `.kibana` which always points to the latest
@@ -233,7 +245,8 @@ export interface ReindexSourceToTempIndex extends PostInitState {
export interface ReindexSourceToTempIndexBulk extends PostInitState {
readonly controlState: 'REINDEX_SOURCE_TO_TEMP_INDEX_BULK';
- readonly transformedDocs: SavedObjectsRawDoc[];
+ readonly transformedDocBatches: [SavedObjectsRawDoc[]];
+ readonly currentBatch: number;
readonly sourceIndexPitId: string;
readonly lastHitSortValue: number[] | undefined;
readonly progress: Progress;
@@ -318,7 +331,8 @@ export interface TransformedDocumentsBulkIndex extends PostInitState {
* Write the up-to-date transformed documents to the target index
*/
readonly controlState: 'TRANSFORMED_DOCUMENTS_BULK_INDEX';
- readonly transformedDocs: SavedObjectsRawDoc[];
+ readonly transformedDocBatches: SavedObjectsRawDoc[][];
+ readonly currentBatch: number;
readonly lastHitSortValue: number[] | undefined;
readonly hasTransformedDocs: boolean;
readonly pitId: string;
diff --git a/src/core/server/saved_objects/saved_objects_config.ts b/src/core/server/saved_objects/saved_objects_config.ts
index c62d322f0bf8d..e7bbd706762f5 100644
--- a/src/core/server/saved_objects/saved_objects_config.ts
+++ b/src/core/server/saved_objects/saved_objects_config.ts
@@ -12,6 +12,7 @@ import type { ConfigDeprecationProvider } from '../config';
const migrationSchema = schema.object({
batchSize: schema.number({ defaultValue: 1_000 }),
+ maxBatchSizeBytes: schema.byteSize({ defaultValue: '100mb' }), // 100mb is the default http.max_content_length Elasticsearch config value
scrollDuration: schema.string({ defaultValue: '15m' }),
pollInterval: schema.number({ defaultValue: 1_500 }),
skip: schema.boolean({ defaultValue: false }),
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 333ef8e7bf34c..aa421fe393059 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -293,6 +293,7 @@ export interface CoreConfigUsageData {
};
apiVersion: string;
healthCheckDelayMs: number;
+ principal: 'elastic_user' | 'kibana_user' | 'kibana_system_user' | 'other_user' | 'kibana_service_account' | 'unknown';
};
// (undocumented)
http: {
@@ -754,10 +755,10 @@ export interface DeprecationsDetails {
// (undocumented)
documentationUrl?: string;
level: 'warning' | 'critical' | 'fetch_error';
- // (undocumented)
message: string;
// (undocumented)
requireRestart?: boolean;
+ title: string;
}
// @public
diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts
index 9ddf02e101a19..1042cdc484c12 100644
--- a/src/dev/build/build_distributables.ts
+++ b/src/dev/build/build_distributables.ts
@@ -105,6 +105,10 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions
// control w/ --skip-archives
await run(Tasks.CreateArchives);
}
+
+ if (options.createDebPackage || options.createRpmPackage) {
+ await run(Tasks.CreatePackageConfig);
+ }
if (options.createDebPackage) {
// control w/ --deb or --skip-os-packages
await run(Tasks.CreateDebPackage);
diff --git a/src/dev/build/tasks/os_packages/create_os_package_kibana_yml.ts b/src/dev/build/tasks/os_packages/create_os_package_kibana_yml.ts
new file mode 100644
index 0000000000000..e7137ada02182
--- /dev/null
+++ b/src/dev/build/tasks/os_packages/create_os_package_kibana_yml.ts
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+import { readFileSync, writeFileSync } from 'fs';
+import { resolve } from 'path';
+import { Build, Config, mkdirp } from '../../lib';
+
+export async function createOSPackageKibanaYML(config: Config, build: Build) {
+ const configReadPath = config.resolveFromRepo('config', 'kibana.yml');
+ const configWriteDir = config.resolveFromRepo('build', 'os_packages', 'config');
+ const configWritePath = resolve(configWriteDir, 'kibana.yml');
+
+ await mkdirp(configWriteDir);
+
+ let kibanaYML = readFileSync(configReadPath, { encoding: 'utf8' });
+
+ [
+ [/#pid.file:.*/g, 'pid.file: /run/kibana/kibana.pid'],
+ [/#logging.dest:.*/g, 'logging.dest: /var/log/kibana/kibana.log'],
+ ].forEach((options) => {
+ const [regex, setting] = options;
+ const diff = kibanaYML;
+ const match = kibanaYML.search(regex) >= 0;
+ if (match) {
+ if (typeof setting === 'string') {
+ kibanaYML = kibanaYML.replace(regex, setting);
+ }
+ }
+
+ if (!diff.localeCompare(kibanaYML)) {
+ throw new Error(
+ `OS package configuration unmodified. Verify match for ${regex} is available`
+ );
+ }
+ });
+
+ try {
+ writeFileSync(configWritePath, kibanaYML, { flag: 'wx' });
+ } catch (err) {
+ if (err.code === 'EEXIST') {
+ return;
+ }
+ throw err;
+ }
+}
diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts
index 99d0e1998e78a..67a9e86ee2073 100644
--- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts
+++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts
@@ -9,6 +9,15 @@
import { Task } from '../../lib';
import { runFpm } from './run_fpm';
import { runDockerGenerator } from './docker_generator';
+import { createOSPackageKibanaYML } from './create_os_package_kibana_yml';
+
+export const CreatePackageConfig: Task = {
+ description: 'Creating OS package kibana.yml',
+
+ async run(config, log, build) {
+ await createOSPackageKibanaYML(config, build);
+ },
+};
export const CreateDebPackage: Task = {
description: 'Creating deb package',
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
index c883e0b68114e..0af087f1427d7 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
@@ -108,6 +108,7 @@ kibana_vars=(
map.tilemap.options.subdomains
map.tilemap.url
migrations.batchSize
+ migrations.maxBatchSizeBytes
migrations.enableV2
migrations.pollInterval
migrations.retryAttempts
diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts
index b732e4c80ea37..c7d9f6997cdf2 100644
--- a/src/dev/build/tasks/os_packages/run_fpm.ts
+++ b/src/dev/build/tasks/os_packages/run_fpm.ts
@@ -123,6 +123,7 @@ export async function runFpm(
`${resolveWithTrailingSlash(fromBuild('.'))}=/usr/share/kibana/`,
// copy the config directory to /etc/kibana
+ `${config.resolveFromRepo('build/os_packages/config/kibana.yml')}=/etc/kibana/kibana.yml`,
`${resolveWithTrailingSlash(fromBuild('config'))}=/etc/kibana/`,
// copy the data directory at /var/lib/kibana
diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/systemd/system/kibana.service b/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/systemd/system/kibana.service
index 7a1508d91b213..df33b82f1f967 100644
--- a/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/systemd/system/kibana.service
+++ b/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/systemd/system/kibana.service
@@ -15,7 +15,7 @@ Environment=KBN_PATH_CONF=/etc/kibana
EnvironmentFile=-/etc/default/kibana
EnvironmentFile=-/etc/sysconfig/kibana
-ExecStart=/usr/share/kibana/bin/kibana --logging.dest="/var/log/kibana/kibana.log" --pid.file="/run/kibana/kibana.pid"
+ExecStart=/usr/share/kibana/bin/kibana
Restart=on-failure
RestartSec=3
diff --git a/src/dev/build/tasks/package_json/find_used_dependencies.ts b/src/dev/build/tasks/package_json/find_used_dependencies.ts
index 004e17b87ac8b..8cb8b3c986de7 100644
--- a/src/dev/build/tasks/package_json/find_used_dependencies.ts
+++ b/src/dev/build/tasks/package_json/find_used_dependencies.ts
@@ -29,9 +29,9 @@ export async function findUsedDependencies(listedPkgDependencies: any, baseDir:
];
const discoveredPluginEntries = await globby([
- normalize(Path.resolve(baseDir, `src/plugins/*/server/index.js`)),
+ normalize(Path.resolve(baseDir, `src/plugins/**/server/index.js`)),
`!${normalize(Path.resolve(baseDir, `/src/plugins/**/public`))}`,
- normalize(Path.resolve(baseDir, `x-pack/plugins/*/server/index.js`)),
+ normalize(Path.resolve(baseDir, `x-pack/plugins/**/server/index.js`)),
`!${normalize(Path.resolve(baseDir, `/x-pack/plugins/**/public`))}`,
]);
diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts
index cb7e3781e2511..ee355d6a9811b 100644
--- a/src/dev/license_checker/config.ts
+++ b/src/dev/license_checker/config.ts
@@ -75,7 +75,7 @@ export const LICENSE_OVERRIDES = {
'@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint
'node-sql-parser@3.6.1': ['(GPL-2.0 OR MIT)'], // GPL-2.0* https://github.com/taozhi8833998/node-sql-parser
'@elastic/ems-client@7.15.0': ['Elastic License 2.0'],
- '@elastic/eui@37.3.0': ['SSPL-1.0 OR Elastic License 2.0'],
+ '@elastic/eui@37.3.1': ['SSPL-1.0 OR Elastic License 2.0'],
// TODO can be removed if the https://github.com/jindw/xmldom/issues/239 is released
'xmldom@0.1.27': ['MIT'],
diff --git a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap
index be5163e89367c..9249f5f98e9c9 100644
--- a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap
+++ b/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap
@@ -1326,7 +1326,12 @@ exports[`Field for image setting should render as read only if saving is disable
disabled={true}
display="large"
fullWidth={true}
- initialPromptText="Select or drag and drop a file"
+ initialPromptText={
+
+ }
onChange={[Function]}
/>
@@ -1472,7 +1477,12 @@ exports[`Field for image setting should render custom setting icon if it is cust
disabled={false}
display="large"
fullWidth={true}
- initialPromptText="Select or drag and drop a file"
+ initialPromptText={
+
+ }
onChange={[Function]}
/>
@@ -1526,7 +1536,12 @@ exports[`Field for image setting should render default value if there is no user
disabled={false}
display="large"
fullWidth={true}
- initialPromptText="Select or drag and drop a file"
+ initialPromptText={
+
+ }
onChange={[Function]}
/>
@@ -1597,7 +1612,12 @@ exports[`Field for image setting should render unsaved value if there are unsave
disabled={false}
display="large"
fullWidth={true}
- initialPromptText="Select or drag and drop a file"
+ initialPromptText={
+
+ }
onChange={[Function]}
/>
diff --git a/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts b/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts
index efe5c9b49849f..50e7c995a1250 100644
--- a/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts
+++ b/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts
@@ -15,7 +15,8 @@ import type { ActiveCursorSyncOption, ActiveCursorPayload } from './types';
import type { Chart, PointerEvent } from '@elastic/charts';
import type { Datatable } from '../../../../expressions/public';
-describe('useActiveCursor', () => {
+// FLAKY: https://github.com/elastic/kibana/issues/110038
+describe.skip('useActiveCursor', () => {
let cursor: ActiveCursorPayload['cursor'];
let dispatchExternalPointerEvent: jest.Mock;
@@ -24,42 +25,47 @@ describe('useActiveCursor', () => {
events: Array>,
eventsTimeout = 1
) =>
- new Promise(async (resolve) => {
- const activeCursor = new ActiveCursor();
- let allEventsExecuted = false;
-
- activeCursor.setup();
+ new Promise(async (resolve, reject) => {
+ try {
+ const activeCursor = new ActiveCursor();
+ let allEventsExecuted = false;
+ activeCursor.setup();
+ dispatchExternalPointerEvent.mockImplementation((pointerEvent) => {
+ if (allEventsExecuted) {
+ resolve(pointerEvent);
+ }
+ });
+ renderHook(() =>
+ useActiveCursor(
+ activeCursor,
+ {
+ current: {
+ dispatchExternalPointerEvent: dispatchExternalPointerEvent as (
+ pointerEvent: PointerEvent
+ ) => void,
+ },
+ } as RefObject,
+ { ...syncOption, debounce: syncOption.debounce ?? 1 }
+ )
+ );
- dispatchExternalPointerEvent.mockImplementation((pointerEvent) => {
- if (allEventsExecuted) {
- resolve(pointerEvent);
+ for (const e of events) {
+ await new Promise((eventResolve) =>
+ setTimeout(() => {
+ if (e === events[events.length - 1]) {
+ allEventsExecuted = true;
+ }
+
+ activeCursor.activeCursor$!.next({
+ cursor,
+ ...e,
+ });
+ eventResolve(null);
+ }, eventsTimeout)
+ );
}
- });
-
- renderHook(() =>
- useActiveCursor(
- activeCursor,
- {
- current: {
- dispatchExternalPointerEvent: dispatchExternalPointerEvent as (
- pointerEvent: PointerEvent
- ) => void,
- },
- } as RefObject,
- { ...syncOption, debounce: syncOption.debounce ?? 1 }
- )
- );
-
- for (const e of events) {
- await new Promise((eventResolve) =>
- setTimeout(() => {
- if (e === events[events.length - 1]) {
- allEventsExecuted = true;
- }
- activeCursor.activeCursor$!.next({ cursor, ...e });
- eventResolve(null);
- }, eventsTimeout)
- );
+ } catch (error) {
+ reject(error);
}
});
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index 986e794c48488..f70733d1b3e8a 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -62,7 +62,13 @@ export const indexPatterns = {
flattenHitWrapper,
};
-export { IndexPatternsContract, IndexPattern, IndexPatternField, TypeMeta } from './index_patterns';
+export {
+ IndexPatternsContract,
+ DataViewsContract,
+ IndexPattern,
+ IndexPatternField,
+ TypeMeta,
+} from './index_patterns';
export {
IIndexPattern,
diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts
index 7229ca5750a38..d1a2b0f28f1d2 100644
--- a/src/plugins/data/public/index_patterns/index.ts
+++ b/src/plugins/data/public/index_patterns/index.ts
@@ -23,6 +23,9 @@ export {
IndexPatternsContract,
IndexPattern,
IndexPatternsApiClient,
+ DataViewsService,
+ DataViewsContract,
+ DataView,
} from './index_patterns';
export { UiSettingsPublicToCommon } from './ui_settings_wrapper';
export { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper';
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index b9b859fd96625..40882fa1134e9 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { DataPlugin, IndexPatternsContract } from '.';
+import { DataPlugin, DataViewsContract } from '.';
import { fieldFormatsServiceMock } from '../../field_formats/public/mocks';
import { searchServiceMock } from './search/mocks';
import { queryServiceMock } from './query/mocks';
@@ -38,6 +38,20 @@ const createSetupContract = (): Setup => {
const createStartContract = (): Start => {
const queryStartMock = queryServiceMock.createStartContract();
+ const dataViews = ({
+ find: jest.fn((search) => [{ id: search, title: search }]),
+ createField: jest.fn(() => {}),
+ createFieldList: jest.fn(() => []),
+ ensureDefaultIndexPattern: jest.fn(),
+ make: () => ({
+ fieldsFetcher: {
+ fetchForWildcard: jest.fn(),
+ },
+ }),
+ get: jest.fn().mockReturnValue(Promise.resolve({})),
+ clearCache: jest.fn(),
+ } as unknown) as DataViewsContract;
+
return {
actions: {
createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']),
@@ -51,19 +65,11 @@ const createStartContract = (): Start => {
IndexPatternSelect: jest.fn(),
SearchBar: jest.fn().mockReturnValue(null),
},
- indexPatterns: ({
- find: jest.fn((search) => [{ id: search, title: search }]),
- createField: jest.fn(() => {}),
- createFieldList: jest.fn(() => []),
- ensureDefaultIndexPattern: jest.fn(),
- make: () => ({
- fieldsFetcher: {
- fetchForWildcard: jest.fn(),
- },
- }),
- get: jest.fn().mockReturnValue(Promise.resolve({})),
- clearCache: jest.fn(),
- } as unknown) as IndexPatternsContract,
+ dataViews,
+ /**
+ * @deprecated Use dataViews service instead. All index pattern interfaces were renamed.
+ */
+ indexPatterns: dataViews,
nowProvider: createNowProviderMock(),
};
};
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 67adcc7a1716d..a12bb50815982 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -197,6 +197,7 @@ export class DataPublicPlugin
autocomplete: this.autocomplete.start(),
fieldFormats,
indexPatterns,
+ dataViews: indexPatterns,
query,
search,
nowProvider: this.nowProvider,
diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts
index 4b52ddfb68824..b31a4ab933ae2 100644
--- a/src/plugins/data/public/types.ts
+++ b/src/plugins/data/public/types.ts
@@ -17,7 +17,7 @@ import { AutocompleteSetup, AutocompleteStart } from './autocomplete';
import { createFiltersFromRangeSelectAction, createFiltersFromValueClickAction } from './actions';
import { ISearchSetup, ISearchStart } from './search';
import { QuerySetup, QueryStart } from './query';
-import { IndexPatternsContract } from './index_patterns';
+import { DataViewsContract } from './index_patterns';
import { IndexPatternSelectProps, StatefulSearchBarProps } from './ui';
import { UsageCollectionSetup, UsageCollectionStart } from '../../usage_collection/public';
import { Setup as InspectorSetup } from '../../inspector/public';
@@ -76,11 +76,17 @@ export interface DataPublicPluginStart {
* {@link AutocompleteStart}
*/
autocomplete: AutocompleteStart;
+ /**
+ * data views service
+ * {@link DataViewsContract}
+ */
+ dataViews: DataViewsContract;
/**
* index patterns service
- * {@link IndexPatternsContract}
+ * {@link DataViewsContract}
+ * @deprecated Use dataViews service instead. All index pattern interfaces were renamed.
*/
- indexPatterns: IndexPatternsContract;
+ indexPatterns: DataViewsContract;
/**
* search service
* {@link ISearchStart}
diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
index 612ffdcf5029e..9cd0687a1074d 100644
--- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
+++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
@@ -1403,7 +1403,7 @@ exports[`Inspector Data View component should render single table without select
>
-
-
+
-
-
+
{
expect(migrated.data.autocomplete.valueSuggestions.terminateAfter).toEqual(123);
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"kibana.autocompleteTerminateAfter\\" is deprecated and has been replaced by \\"data.autocomplete.valueSuggestions.terminateAfter\\"",
+ "Setting \\"kibana.autocompleteTerminateAfter\\" has been replaced by \\"data.autocomplete.valueSuggestions.terminateAfter\\"",
]
`);
});
@@ -64,7 +64,7 @@ describe('Config Deprecations', () => {
expect(migrated.data.autocomplete.valueSuggestions.timeout).toEqual(123);
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"kibana.autocompleteTimeout\\" is deprecated and has been replaced by \\"data.autocomplete.valueSuggestions.timeout\\"",
+ "Setting \\"kibana.autocompleteTimeout\\" has been replaced by \\"data.autocomplete.valueSuggestions.timeout\\"",
]
`);
});
diff --git a/src/plugins/data/server/index_patterns/deprecations/scripted_fields.ts b/src/plugins/data/server/index_patterns/deprecations/scripted_fields.ts
index 7c9ce6f9ed335..0dedfeb72028b 100644
--- a/src/plugins/data/server/index_patterns/deprecations/scripted_fields.ts
+++ b/src/plugins/data/server/index_patterns/deprecations/scripted_fields.ts
@@ -12,6 +12,7 @@ import {
GetDeprecationsContext,
RegisterDeprecationsConfig,
} from 'kibana/server';
+import { i18n } from '@kbn/i18n';
import { IndexPatternAttributes } from '../../../common';
type IndexPatternAttributesWithFields = Pick;
@@ -38,19 +39,35 @@ export const createScriptedFieldsDeprecationsConfig: (
if (indexPatternsWithScriptedFields.length > 0) {
const PREVIEW_LIMIT = 3;
const indexPatternTitles = indexPatternsWithScriptedFields.map((ip) => ip.title);
- const titlesPreview = indexPatternTitles.slice(0, PREVIEW_LIMIT).join('; ');
- const allTitles = indexPatternTitles.join('; ');
return [
{
- message: `You have ${indexPatternsWithScriptedFields.length} index patterns (${titlesPreview}...) that use scripted fields. Scripted fields are deprecated and will be removed in future. Use runtime fields instead.`,
+ title: i18n.translate('data.deprecations.scriptedFieldsTitle', {
+ defaultMessage: 'Found index patterns using scripted fields',
+ }),
+ message: i18n.translate('data.deprecations.scriptedFieldsMessage', {
+ defaultMessage: `You have {numberOfIndexPatternsWithScriptedFields} index patterns ({titlesPreview}...) that use scripted fields. Scripted fields are deprecated and will be removed in future. Use runtime fields instead.`,
+ values: {
+ titlesPreview: indexPatternTitles.slice(0, PREVIEW_LIMIT).join('; '),
+ numberOfIndexPatternsWithScriptedFields: indexPatternsWithScriptedFields.length,
+ },
+ }),
documentationUrl:
'https://www.elastic.co/guide/en/elasticsearch/reference/7.x/runtime.html', // TODO: documentation service is not available serverside https://github.com/elastic/kibana/issues/95389
level: 'warning', // warning because it is not set in stone WHEN we remove scripted fields, hence this deprecation is not a blocker for 8.0 upgrade
correctiveActions: {
manualSteps: [
- 'Navigate to Stack Management > Kibana > Index Patterns.',
- `Update ${indexPatternsWithScriptedFields.length} index patterns that have scripted fields to use runtime fields instead. In most cases, to migrate existing scripts, you'll need to change "return ;" to "emit();". Index patterns with at least one scripted field: ${allTitles}`,
+ i18n.translate('data.deprecations.scriptedFields.manualStepOneMessage', {
+ defaultMessage: 'Navigate to Stack Management > Kibana > Index Patterns.',
+ }),
+ i18n.translate('data.deprecations.scriptedFields.manualStepTwoMessage', {
+ defaultMessage:
+ 'Update {numberOfIndexPatternsWithScriptedFields} index patterns that have scripted fields to use runtime fields instead. In most cases, to migrate existing scripts, you will need to change "return ;" to "emit();". Index patterns with at least one scripted field: {allTitles}',
+ values: {
+ allTitles: indexPatternTitles.join('; '),
+ numberOfIndexPatternsWithScriptedFields: indexPatternsWithScriptedFields.length,
+ },
+ }),
],
},
},
diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx
index a8f89b471e4eb..f23696ac6c7db 100644
--- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx
+++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx
@@ -88,7 +88,7 @@ export const EditIndexPattern = withRouter(
const removePattern = () => {
async function doRemove() {
if (indexPattern.id === defaultIndex) {
- const indexPatterns = await data.indexPatterns.getIdsWithTitle();
+ const indexPatterns = await data.dataViews.getIdsWithTitle();
uiSettings.remove('defaultIndex');
const otherPatterns = filter(indexPatterns, (pattern) => {
return pattern.id !== indexPattern.id;
@@ -99,7 +99,7 @@ export const EditIndexPattern = withRouter(
}
}
if (indexPattern.id) {
- Promise.resolve(data.indexPatterns.delete(indexPattern.id)).then(function () {
+ Promise.resolve(data.dataViews.delete(indexPattern.id)).then(function () {
history.push('');
});
}
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx
index 0a436f1541613..c8617808b10ec 100644
--- a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx
+++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx
@@ -77,13 +77,13 @@ export const IndexPatternTable = ({
(async function () {
const gettedIndexPatterns: IndexPatternTableItem[] = await getIndexPatterns(
uiSettings.get('defaultIndex'),
- data.indexPatterns
+ data.dataViews
);
setIndexPatterns(gettedIndexPatterns);
setIsLoadingIndexPatterns(false);
if (
gettedIndexPatterns.length === 0 ||
- !(await data.indexPatterns.hasUserIndexPattern().catch(() => false))
+ !(await data.dataViews.hasUserIndexPattern().catch(() => false))
) {
setShowCreateDialog(true);
}
diff --git a/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx b/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx
index 53503b197567e..39e345568a298 100644
--- a/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx
+++ b/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx
@@ -19,13 +19,18 @@ describe('MountPointPortal', () => {
let dom: ReactWrapper;
const refresh = () => {
- new Promise(async (resolve) => {
- if (dom) {
- act(() => {
- dom.update();
- });
+ new Promise(async (resolve, reject) => {
+ try {
+ if (dom) {
+ act(() => {
+ dom.update();
+ });
+ }
+
+ setImmediate(() => resolve(dom)); // flushes any pending promises
+ } catch (error) {
+ reject(error);
}
- setImmediate(() => resolve(dom)); // flushes any pending promises
});
};
diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts
index ffc4559876b2b..f7a16b3f563bd 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts
@@ -131,6 +131,13 @@ export function getCoreUsageCollector(
'The interval in miliseconds between health check requests Kibana sends to the Elasticsearch.',
},
},
+ principal: {
+ type: 'keyword',
+ _meta: {
+ description:
+ 'Indicates how Kibana authenticates itself to Elasticsearch. If elasticsearch.username is configured, this can be any of: "elastic_user", "kibana_user", "kibana_system_user", or "other_user". Otherwise, if elasticsearch.serviceAccountToken is configured, this will be "kibana_service_account". Otherwise, this value will be "unknown", because some other principal might be used to authenticate Kibana to Elasticsearch (such as an x509 certificate), or authentication may be skipped altogether.',
+ },
+ },
},
http: {
diff --git a/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts b/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts
index 6e32ff5d4e41e..8eafada176e7a 100644
--- a/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts
+++ b/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts
@@ -16,10 +16,14 @@ export async function getServiceSettings(): Promise {
return loadPromise;
}
- loadPromise = new Promise(async (resolve) => {
- const { ServiceSettings } = await import('./lazy');
- const config = getMapsEmsConfig();
- resolve(new ServiceSettings(config, config.tilemap));
+ loadPromise = new Promise(async (resolve, reject) => {
+ try {
+ const { ServiceSettings } = await import('./lazy');
+ const config = getMapsEmsConfig();
+ resolve(new ServiceSettings(config, config.tilemap));
+ } catch (error) {
+ reject(error);
+ }
});
return loadPromise;
}
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx
index 558739a44dd41..45b9b4c7a885b 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx
@@ -109,13 +109,18 @@ describe('TopNavMenu', () => {
let dom: ReactWrapper;
const refresh = () => {
- new Promise(async (resolve) => {
- if (dom) {
- act(() => {
- dom.update();
- });
+ new Promise(async (resolve, reject) => {
+ try {
+ if (dom) {
+ act(() => {
+ dom.update();
+ });
+ }
+
+ setImmediate(() => resolve(dom)); // flushes any pending promises
+ } catch (error) {
+ reject(error);
}
- setImmediate(() => resolve(dom)); // flushes any pending promises
});
};
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index 11b56f045e22c..b64c2fbe41265 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -5957,6 +5957,12 @@
"_meta": {
"description": "The interval in miliseconds between health check requests Kibana sends to the Elasticsearch."
}
+ },
+ "principal": {
+ "type": "keyword",
+ "_meta": {
+ "description": "Indicates how Kibana authenticates itself to Elasticsearch. If elasticsearch.username is configured, this can be any of: \"elastic_user\", \"kibana_user\", \"kibana_system_user\", or \"other_user\". Otherwise, if elasticsearch.serviceAccountToken is configured, this will be \"kibana_service_account\". Otherwise, this value will be \"unknown\", because some other principal might be used to authenticate Kibana to Elasticsearch (such as an x509 certificate), or authentication may be skipped altogether."
+ }
}
}
},
diff --git a/src/plugins/timelion/server/deprecations.ts b/src/plugins/timelion/server/deprecations.ts
index 3c344e1d4a8d8..2358dd313b74f 100644
--- a/src/plugins/timelion/server/deprecations.ts
+++ b/src/plugins/timelion/server/deprecations.ts
@@ -49,6 +49,7 @@ export async function getDeprecations({
if (count > 0) {
deprecations.push({
+ title: 'Found Timelion worksheets',
message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 7.16. To continue using your Timelion worksheets, migrate them to a dashboard.`,
documentationUrl:
'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html',
diff --git a/src/plugins/vis_type_table/public/legacy/vis_controller.ts b/src/plugins/vis_type_table/public/legacy/vis_controller.ts
index ec198aa96f1f9..a9cb22a056913 100644
--- a/src/plugins/vis_type_table/public/legacy/vis_controller.ts
+++ b/src/plugins/vis_type_table/public/legacy/vis_controller.ts
@@ -71,35 +71,42 @@ export function getTableVisualizationControllerClass(
await this.initLocalAngular();
return new Promise(async (resolve, reject) => {
- if (!this.$rootScope) {
- const $injector = this.getInjector();
- this.$rootScope = $injector.get('$rootScope');
- this.$compile = $injector.get('$compile');
- }
- const updateScope = () => {
- if (!this.$scope) {
- return;
+ try {
+ if (!this.$rootScope) {
+ const $injector = this.getInjector();
+ this.$rootScope = $injector.get('$rootScope');
+ this.$compile = $injector.get('$compile');
}
- this.$scope.visState = { params: visParams, title: visParams.title };
- this.$scope.esResponse = esResponse;
-
- this.$scope.visParams = visParams;
- this.$scope.renderComplete = resolve;
- this.$scope.renderFailed = reject;
- this.$scope.resize = Date.now();
- this.$scope.$apply();
- };
-
- if (!this.$scope && this.$compile) {
- this.$scope = this.$rootScope.$new();
- this.$scope.uiState = handlers.uiState;
- this.$scope.filter = handlers.event;
- updateScope();
- this.el.find('div').append(this.$compile(tableVisTemplate)(this.$scope));
- this.$scope.$apply();
- } else {
- updateScope();
+ const updateScope = () => {
+ if (!this.$scope) {
+ return;
+ }
+
+ this.$scope.visState = {
+ params: visParams,
+ title: visParams.title,
+ };
+ this.$scope.esResponse = esResponse;
+ this.$scope.visParams = visParams;
+ this.$scope.renderComplete = resolve;
+ this.$scope.renderFailed = reject;
+ this.$scope.resize = Date.now();
+ this.$scope.$apply();
+ };
+
+ if (!this.$scope && this.$compile) {
+ this.$scope = this.$rootScope.$new();
+ this.$scope.uiState = handlers.uiState;
+ this.$scope.filter = handlers.event;
+ updateScope();
+ this.el.find('div').append(this.$compile(tableVisTemplate)(this.$scope));
+ this.$scope.$apply();
+ } else {
+ updateScope();
+ }
+ } catch (error) {
+ reject(error);
}
});
}
diff --git a/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx b/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx
index 56f9025a6bd0b..4701d07ab83e6 100644
--- a/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx
+++ b/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx
@@ -124,16 +124,25 @@ export class VisLegend extends PureComponent {
};
setFilterableLabels = (items: LegendItem[]): Promise =>
- new Promise(async (resolve) => {
- const filterableLabels = new Set();
- items.forEach(async (item) => {
- const canFilter = await this.canFilter(item);
- if (canFilter) {
- filterableLabels.add(item.label);
- }
- });
-
- this.setState({ filterableLabels }, resolve);
+ new Promise(async (resolve, reject) => {
+ try {
+ const filterableLabels = new Set();
+ items.forEach(async (item) => {
+ const canFilter = await this.canFilter(item);
+
+ if (canFilter) {
+ filterableLabels.add(item.label);
+ }
+ });
+ this.setState(
+ {
+ filterableLabels,
+ },
+ resolve
+ );
+ } catch (error) {
+ reject(error);
+ }
});
setLabels = (data: any, type: string) => {
diff --git a/src/setup_node_env/exit_on_warning.js b/src/setup_node_env/exit_on_warning.js
index 5fbee02708083..e9c96f2c49bb4 100644
--- a/src/setup_node_env/exit_on_warning.js
+++ b/src/setup_node_env/exit_on_warning.js
@@ -34,8 +34,7 @@ var IGNORE_WARNINGS = [
// that the security features are blocking such check.
// Such emit is causing Node.js to crash unless we explicitly catch it.
// We need to discard that warning
- message:
- 'The client is unable to verify that the server is Elasticsearch due to security privileges on the server side. Some functionality may not be compatible if the server is running an unsupported product.',
+ name: 'ProductNotSupportedSecurityError',
},
];
diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts
index 9922e56f44bd9..8e78a2c279d67 100644
--- a/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts
+++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts
@@ -15,6 +15,7 @@ async function getDeprecations({
const { total } = await savedObjectsClient.find({ type: 'test-deprecations-plugin', perPage: 1 });
deprecations.push({
+ title: 'CorePluginDeprecationsPlugin plugin is deprecated',
message: `CorePluginDeprecationsPlugin is a deprecated feature for testing.`,
documentationUrl: 'test-url',
level: 'warning',
@@ -26,6 +27,7 @@ async function getDeprecations({
if (total > 0) {
deprecations.push({
+ title: 'Detected saved objects in test-deprecations-plugin',
message: `SavedObject test-deprecations-plugin is still being used.`,
documentationUrl: 'another-test-url',
level: 'critical',
diff --git a/test/plugin_functional/test_suites/core/deprecations.ts b/test/plugin_functional/test_suites/core/deprecations.ts
index 2bdd3cf59549b..25ac5f24a97c9 100644
--- a/test/plugin_functional/test_suites/core/deprecations.ts
+++ b/test/plugin_functional/test_suites/core/deprecations.ts
@@ -19,9 +19,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
const CorePluginDeprecationsPluginDeprecations: DomainDeprecationDetails[] = [
{
+ title: 'Setting "corePluginDeprecations.oldProperty" is deprecated',
level: 'critical',
message:
- '"corePluginDeprecations.oldProperty" is deprecated and has been replaced by "corePluginDeprecations.newProperty"',
+ 'Setting "corePluginDeprecations.oldProperty" has been replaced by "corePluginDeprecations.newProperty"',
correctiveActions: {
manualSteps: [
'Replace "corePluginDeprecations.oldProperty" with "corePluginDeprecations.newProperty" in the Kibana config file, CLI flag, or environment variable (in Docker only).',
@@ -32,11 +33,12 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
requireRestart: true,
},
{
+ title: 'Setting "corePluginDeprecations.noLongerUsed" is deprecated',
level: 'critical',
- message: 'corePluginDeprecations.noLongerUsed is deprecated and is no longer used',
+ message: 'You no longer need to configure "corePluginDeprecations.noLongerUsed".',
correctiveActions: {
manualSteps: [
- 'Remove "corePluginDeprecations.noLongerUsed" from the Kibana config file, CLI flag, or environment variable (in Docker only)',
+ 'Remove "corePluginDeprecations.noLongerUsed" from the Kibana config file, CLI flag, or environment variable (in Docker only).',
],
},
deprecationType: 'config',
@@ -44,6 +46,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
requireRestart: true,
},
{
+ title: 'corePluginDeprecations has a deprecated setting',
level: 'critical',
message:
'Kibana plugin functional tests will no longer allow corePluginDeprecations.secret config to be set to anything except 42.',
@@ -58,6 +61,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
requireRestart: true,
},
{
+ title: 'CorePluginDeprecationsPlugin plugin is deprecated',
message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.',
documentationUrl: 'test-url',
level: 'warning',
@@ -68,6 +72,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
domainId: 'corePluginDeprecations',
},
{
+ title: 'Detected saved objects in test-deprecations-plugin',
message: 'SavedObject test-deprecations-plugin is still being used.',
documentationUrl: 'another-test-url',
level: 'critical',
@@ -128,6 +133,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
const resolveResult = await browser.executeAsync((cb) => {
return window._coreProvider.start.core.deprecations
.resolveDeprecation({
+ title: 'CorePluginDeprecationsPlugin plugin is deprecated',
message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.',
documentationUrl: 'test-url',
level: 'warning',
@@ -140,7 +146,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
});
expect(resolveResult).to.eql({
- reason: 'deprecation has no correctiveAction via api.',
+ reason: 'This deprecation cannot be resolved automatically.',
status: 'fail',
});
});
@@ -149,6 +155,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
const resolveResult = await browser.executeAsync((cb) => {
return window._coreProvider.start.core.deprecations
.resolveDeprecation({
+ title: 'CorePluginDeprecationsPlugin plugin is deprecated',
message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.',
documentationUrl: 'test-url',
level: 'warning',
@@ -177,6 +184,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
const resolveResult = await browser.executeAsync((cb) => {
return window._coreProvider.start.core.deprecations
.resolveDeprecation({
+ title: 'CorePluginDeprecationsPlugin plugin is deprecated',
message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.',
documentationUrl: 'test-url',
level: 'warning',
@@ -215,6 +223,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
(keyId, cb) => {
return window._coreProvider.start.core.deprecations
.resolveDeprecation({
+ title: 'CorePluginDeprecationsPlugin plugin is deprecated',
message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.',
documentationUrl: 'test-url',
level: 'warning',
diff --git a/test/scripts/checks/commit/commit_check_runner.sh b/test/scripts/checks/commit/commit_check_runner.sh
index 8d35c3698f3e1..65ca9a6ecef06 100755
--- a/test/scripts/checks/commit/commit_check_runner.sh
+++ b/test/scripts/checks/commit/commit_check_runner.sh
@@ -7,7 +7,7 @@ If you want, you can still manually install the pre-commit hook locally by runni
!!!!!!!!!!!!!!!!!!!!!!!!!!!
"
- node scripts/precommit_hook.js --ref HEAD~1..HEAD --max-files 200 --verbose
+ node scripts/precommit_hook.js --ref HEAD~1..HEAD --max-files 200
}
run_quick_commit_checks
diff --git a/test/scripts/jenkins_apm_cypress.sh b/test/scripts/jenkins_apm_cypress.sh
index a1d2ab73b5552..ac9baa8066743 100755
--- a/test/scripts/jenkins_apm_cypress.sh
+++ b/test/scripts/jenkins_apm_cypress.sh
@@ -6,7 +6,7 @@ echo " -> Running APM cypress tests"
cd "$XPACK_DIR"
checks-reporter-with-killswitch "APM Cypress Tests" \
- node plugins/apm/scripts/ftr_e2e/cypress_run
+ node plugins/apm/scripts/test/e2e.js
echo ""
echo ""
diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh
index 3300ed2d0884f..b852a5b313ad9 100755
--- a/test/scripts/jenkins_unit.sh
+++ b/test/scripts/jenkins_unit.sh
@@ -28,10 +28,10 @@ if [[ -z "$CODE_COVERAGE" ]] ; then
./test/scripts/checks/test_hardening.sh
else
echo " -> Running jest tests with coverage"
- node scripts/jest --ci --verbose --maxWorkers=8 --coverage || true;
+ node scripts/jest --ci --maxWorkers=8 --coverage || true;
echo " -> Running jest integration tests with coverage"
- node scripts/jest_integration --ci --verbose --coverage || true;
+ node scripts/jest_integration --ci --coverage || true;
echo " -> Combine code coverage in a single report"
yarn nyc report --nycrc-path src/dev/code_coverage/nyc_config/nyc.jest.config.js
diff --git a/test/scripts/jenkins_xpack_build_plugins.sh b/test/scripts/jenkins_xpack_build_plugins.sh
index cb0b5ec1d56da..521d8c120f606 100755
--- a/test/scripts/jenkins_xpack_build_plugins.sh
+++ b/test/scripts/jenkins_xpack_build_plugins.sh
@@ -16,5 +16,4 @@ node scripts/build_kibana_platform_plugins \
--scan-dir "$XPACK_DIR/test/security_functional/fixtures/common" \
--scan-dir "$KIBANA_DIR/examples" \
--scan-dir "$XPACK_DIR/examples" \
- --workers 12 \
- --verbose
+ --workers 12
diff --git a/test/scripts/test/jest_integration.sh b/test/scripts/test/jest_integration.sh
index 78ed804f88430..aaffdb2fd9e90 100755
--- a/test/scripts/test/jest_integration.sh
+++ b/test/scripts/test/jest_integration.sh
@@ -3,4 +3,4 @@
source src/dev/ci_setup/setup_env.sh
checks-reporter-with-killswitch "Jest Integration Tests" \
- node scripts/jest_integration --ci --verbose
+ node scripts/jest_integration --ci
diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh
index 74d3c5b0cad18..539a64fbe1b7f 100755
--- a/test/scripts/test/jest_unit.sh
+++ b/test/scripts/test/jest_unit.sh
@@ -3,4 +3,4 @@
source src/dev/ci_setup/setup_env.sh
checks-reporter-with-killswitch "Jest Unit Tests" \
- node scripts/jest --ci --verbose --maxWorkers=6
+ node scripts/jest --ci --maxWorkers=6
diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts
index ceea9f3cff18f..6d7fd940612f3 100644
--- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts
+++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts
@@ -356,6 +356,14 @@ describe('successful migrations', () => {
});
});
});
+
+ describe('8.0.0', () => {
+ test('no op migration for rules SO', () => {
+ const migration800 = getActionTaskParamsMigrations(encryptedSavedObjectsSetup, [])['8.0.0'];
+ const actionTaskParam = getMockData();
+ expect(migration800(actionTaskParam, context)).toEqual(actionTaskParam);
+ });
+ });
});
describe('handles errors during migrations', () => {
diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts
index 3612642160443..ceb82146a03eb 100644
--- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts
+++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts
@@ -48,8 +48,17 @@ export function getActionTaskParamsMigrations(
pipeMigrations(getUseSavedObjectReferencesFn(preconfiguredActions))
);
+ const migrationActionsTaskParams800 = createEsoMigration(
+ encryptedSavedObjects,
+ (
+ doc: SavedObjectUnsanitizedDoc
+ ): doc is SavedObjectUnsanitizedDoc => true,
+ (doc) => doc // no-op
+ );
+
return {
'7.16.0': executeMigrationWithErrorHandling(migrationActionTaskParamsSixteen, '7.16.0'),
+ '8.0.0': executeMigrationWithErrorHandling(migrationActionsTaskParams800, '8.0.0'),
};
}
diff --git a/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts
index bc0e59279abc1..7dc1426c13a4b 100644
--- a/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts
+++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts
@@ -118,6 +118,14 @@ describe('successful migrations', () => {
});
});
});
+
+ describe('8.0.0', () => {
+ test('no op migration for rules SO', () => {
+ const migration800 = getActionsMigrations(encryptedSavedObjectsSetup)['8.0.0'];
+ const action = getMockData({});
+ expect(migration800(action, context)).toEqual(action);
+ });
+ });
});
describe('handles errors during migrations', () => {
diff --git a/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts
index a72565e00ef7b..7857a9e1f833f 100644
--- a/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts
+++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts
@@ -62,10 +62,18 @@ export function getActionsMigrations(
pipeMigrations(addisMissingSecretsField)
);
+ const migrationActions800 = createEsoMigration(
+ encryptedSavedObjects,
+ (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc =>
+ true,
+ (doc) => doc // no-op
+ );
+
return {
'7.10.0': executeMigrationWithErrorHandling(migrationActionsTen, '7.10.0'),
'7.11.0': executeMigrationWithErrorHandling(migrationActionsEleven, '7.11.0'),
'7.14.0': executeMigrationWithErrorHandling(migrationActionsFourteen, '7.14.0'),
+ '8.0.0': executeMigrationWithErrorHandling(migrationActions800, '8.0.0'),
};
}
diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts
index 71ec92645b249..14b425d20af13 100644
--- a/x-pack/plugins/actions/server/saved_objects/index.ts
+++ b/x-pack/plugins/actions/server/saved_objects/index.ts
@@ -35,7 +35,8 @@ export function setupSavedObjects(
savedObjects.registerType({
name: ACTION_SAVED_OBJECT_TYPE,
hidden: true,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
+ convertToMultiNamespaceTypeVersion: '8.0.0',
mappings: mappings.action as SavedObjectsTypeMappingDefinition,
migrations: getActionsMigrations(encryptedSavedObjects),
management: {
@@ -71,7 +72,8 @@ export function setupSavedObjects(
savedObjects.registerType({
name: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
hidden: true,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
+ convertToMultiNamespaceTypeVersion: '8.0.0',
mappings: mappings.action_task_params as SavedObjectsTypeMappingDefinition,
migrations: getActionTaskParamsMigrations(encryptedSavedObjects, preconfiguredActions),
excludeOnUpgrade: async ({ readonlyEsClient }) => {
diff --git a/x-pack/plugins/alerting/server/health/get_state.test.ts b/x-pack/plugins/alerting/server/health/get_state.test.ts
index b58a127941880..9429dcc07d927 100644
--- a/x-pack/plugins/alerting/server/health/get_state.test.ts
+++ b/x-pack/plugins/alerting/server/health/get_state.test.ts
@@ -216,8 +216,8 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
})
).toPromise();
- expect(status.level).toEqual(ServiceStatusLevels.unavailable);
- expect(status.summary).toEqual('Alerting framework is unavailable');
+ expect(status.level).toEqual(ServiceStatusLevels.degraded);
+ expect(status.summary).toEqual('Alerting framework is degraded');
expect(status.meta).toBeUndefined();
});
@@ -275,8 +275,8 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
}),
retryDelay
).subscribe((status) => {
- expect(status.level).toEqual(ServiceStatusLevels.unavailable);
- expect(status.summary).toEqual('Alerting framework is unavailable');
+ expect(status.level).toEqual(ServiceStatusLevels.degraded);
+ expect(status.summary).toEqual('Alerting framework is degraded');
expect(status.meta).toEqual({ error: err });
});
diff --git a/x-pack/plugins/alerting/server/health/get_state.ts b/x-pack/plugins/alerting/server/health/get_state.ts
index 255037d7015a2..34f897ad5b73c 100644
--- a/x-pack/plugins/alerting/server/health/get_state.ts
+++ b/x-pack/plugins/alerting/server/health/get_state.ts
@@ -73,9 +73,7 @@ const getHealthServiceStatus = async (
const level =
doc.state?.health_status === HealthStatus.OK
? ServiceStatusLevels.available
- : doc.state?.health_status === HealthStatus.Warning
- ? ServiceStatusLevels.degraded
- : ServiceStatusLevels.unavailable;
+ : ServiceStatusLevels.degraded;
return {
level,
summary: LEVEL_SUMMARY[level.toString()],
@@ -102,10 +100,10 @@ export const getHealthServiceStatusWithRetryAndErrorHandling = (
);
}),
catchError((error) => {
- logger.warn(`Alerting framework is unavailable due to the error: ${error}`);
+ logger.warn(`Alerting framework is degraded due to the error: ${error}`);
return of({
- level: ServiceStatusLevels.unavailable,
- summary: LEVEL_SUMMARY[ServiceStatusLevels.unavailable.toString()],
+ level: ServiceStatusLevels.degraded,
+ summary: LEVEL_SUMMARY[ServiceStatusLevels.degraded.toString()],
meta: { error },
});
})
diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts
index 17361a3619557..bb42beba6e237 100644
--- a/x-pack/plugins/alerting/server/plugin.ts
+++ b/x-pack/plugins/alerting/server/plugin.ts
@@ -240,7 +240,7 @@ export class AlertingPlugin {
);
const serviceStatus$ = new BehaviorSubject({
- level: ServiceStatusLevels.unavailable,
+ level: ServiceStatusLevels.degraded,
summary: 'Alerting is initializing',
});
core.status.set(serviceStatus$);
diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts
index b1d56a364a3dd..f1afba147a2f7 100644
--- a/x-pack/plugins/alerting/server/saved_objects/index.ts
+++ b/x-pack/plugins/alerting/server/saved_objects/index.ts
@@ -53,7 +53,8 @@ export function setupSavedObjects(
savedObjects.registerType({
name: 'alert',
hidden: true,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
+ convertToMultiNamespaceTypeVersion: '8.0.0',
migrations: getMigrations(encryptedSavedObjects, isPreconfigured),
mappings: mappings.alert as SavedObjectsTypeMappingDefinition,
management: {
diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts
index e460167b40d23..5e850ad3226f8 100644
--- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts
+++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts
@@ -1700,6 +1700,14 @@ describe('successful migrations', () => {
});
});
});
+
+ describe('8.0.0', () => {
+ test('no op migration for rules SO', () => {
+ const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0'];
+ const alert = getMockData({}, true);
+ expect(migration800(alert, migrationContext)).toEqual(alert);
+ });
+ });
});
describe('handles errors during migrations', () => {
diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts
index c0af554cd7a44..287636c69bb75 100644
--- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts
+++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts
@@ -106,6 +106,12 @@ export function getMigrations(
pipeMigrations(setLegacyId, getRemovePreconfiguredConnectorsFromReferencesFn(isPreconfigured))
);
+ const migrationRules800 = createEsoMigration(
+ encryptedSavedObjects,
+ (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true,
+ (doc) => doc // no-op
+ );
+
return {
'7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'),
'7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyWhen, '7.11.0'),
@@ -114,6 +120,7 @@ export function getMigrations(
'7.14.1': executeMigrationWithErrorHandling(migrationSecurityRules714, '7.14.1'),
'7.15.0': executeMigrationWithErrorHandling(migrationSecurityRules715, '7.15.0'),
'7.16.0': executeMigrationWithErrorHandling(migrateRules716, '7.16.0'),
+ '8.0.0': executeMigrationWithErrorHandling(migrationRules800, '8.0.0'),
};
}
diff --git a/x-pack/plugins/apm/common/backends.ts b/x-pack/plugins/apm/common/backends.ts
index 35a52cf3778f1..53418fe7f9b62 100644
--- a/x-pack/plugins/apm/common/backends.ts
+++ b/x-pack/plugins/apm/common/backends.ts
@@ -14,9 +14,9 @@ import {
import { environmentQuery } from './utils/environment_query';
export const kueryBarPlaceholder = i18n.translate(
- 'xpack.apm.backends.kueryBarPlaceholder',
+ 'xpack.apm.dependencies.kueryBarPlaceholder',
{
- defaultMessage: `Search backend metrics (e.g. span.destination.service.resource:elasticsearch)`,
+ defaultMessage: `Search dependency metrics (e.g. span.destination.service.resource:elasticsearch)`,
}
);
diff --git a/x-pack/plugins/apm/ftr_e2e/README.md b/x-pack/plugins/apm/ftr_e2e/README.md
index 007200a6a549a..2df4e837d2e55 100644
--- a/x-pack/plugins/apm/ftr_e2e/README.md
+++ b/x-pack/plugins/apm/ftr_e2e/README.md
@@ -4,23 +4,4 @@ APM uses [FTR](../../../../packages/kbn-test/README.md) (functional test runner)
## Running tests
-**Run all tests**
-
-```sh
-//kibana directory
-node x-pack/plugins/apm/scripts/ftr_e2e/cypress_run.js
-```
-
-**Run specific test**
-
-```sh
-//kibana directory
-node x-pack/plugins/apm/scripts/ftr_e2e/cypress_run.js --spec ./cypress/integration/read_only_user/home.spec.ts
-```
-
-## Opening tests
-
-```sh
-//kibana directory
-node x-pack/plugins/apm/scripts/ftr_e2e/cypress_open.js
-```
+Go to [tests documentation](../scripts/test#e2e-tests-cypress/README.md)
\ No newline at end of file
diff --git a/x-pack/plugins/apm/ftr_e2e/config.ts b/x-pack/plugins/apm/ftr_e2e/config.ts
index 5f919fb7f075d..12cc8845264c2 100644
--- a/x-pack/plugins/apm/ftr_e2e/config.ts
+++ b/x-pack/plugins/apm/ftr_e2e/config.ts
@@ -35,6 +35,7 @@ async function config({ readConfigFile }: FtrConfigProviderContext) {
...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'),
'--home.disableWelcomeScreen=true',
'--csp.strict=false',
+ '--csp.warnLegacyBrowsers=false',
// define custom kibana server args here
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
],
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/custom_links.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/custom_links.spec.ts
new file mode 100644
index 0000000000000..eeb46db04b9d4
--- /dev/null
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/settings/custom_links.spec.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+const basePath = '/app/apm/settings/customize-ui';
+
+describe('Custom links', () => {
+ beforeEach(() => {
+ cy.loginAsPowerUser();
+ });
+
+ it('shows empty message and create button', () => {
+ cy.visit(basePath);
+ cy.contains('No links found');
+ cy.contains('Create custom link');
+ });
+
+ it('creates custom link', () => {
+ cy.visit(basePath);
+ const emptyPrompt = cy.get('[data-test-subj="customLinksEmptyPrompt"]');
+ cy.contains('Create custom link').click();
+ cy.contains('Create link');
+ cy.contains('Save').should('be.disabled');
+ cy.get('input[name="label"]').type('foo');
+ cy.get('input[name="url"]').type('https://foo.com');
+ cy.contains('Save').should('not.be.disabled');
+ cy.contains('Save').click();
+ emptyPrompt.should('not.exist');
+ cy.contains('foo');
+ cy.contains('https://foo.com');
+ cy.get('[data-test-subj="editCustomLink"]').click();
+ cy.contains('Delete').click();
+ });
+});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts
index de8969cbecdeb..b90ad12b46025 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts
@@ -74,6 +74,10 @@ describe('Service Overview', () => {
cy.contains('Service Map');
// Waits until the agent request is finished to check the tab.
cy.wait('@agentRequest');
- cy.contains('Dependencies').should('not.exist');
+ cy.get('.euiTabs .euiTab__content').then((elements) => {
+ elements.map((index, element) => {
+ expect(element.innerText).to.not.equal('Dependencies');
+ });
+ });
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
index 31eab9507ef5e..93dbe4ba51226 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
@@ -11,8 +11,8 @@ Cypress.Commands.add('loginAsReadOnlyUser', () => {
cy.loginAs({ username: 'apm_read_user', password: 'changeme' });
});
-Cypress.Commands.add('loginAsSuperUser', () => {
- cy.loginAs({ username: 'elastic', password: 'changeme' });
+Cypress.Commands.add('loginAsPowerUser', () => {
+ cy.loginAs({ username: 'apm_power_user', password: 'changeme' });
});
Cypress.Commands.add(
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
index b47e664e0a0f8..2d9ef090eef65 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
@@ -8,7 +8,7 @@
declare namespace Cypress {
interface Chainable {
loginAsReadOnlyUser(): void;
- loginAsSuperUser(): void;
+ loginAsPowerUser(): void;
loginAs(params: { username: string; password: string }): void;
changeTimeRange(value: string): void;
expectAPIsToHaveBeenCalledWith(params: {
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress_start.ts b/x-pack/plugins/apm/ftr_e2e/cypress_start.ts
index 67617f5a21fd8..caf87d2627459 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress_start.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress_start.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+/* eslint-disable no-console */
+
import Url from 'url';
import cypress from 'cypress';
import { FtrProviderContext } from './ftr_provider_context';
@@ -59,6 +61,7 @@ async function cypressStart(
},
});
+ console.log('Loading esArchiver...');
await esArchiverLoad('apm_8.0.0');
const res = await cypressExecution({
@@ -71,6 +74,7 @@ async function cypressStart(
},
});
+ console.log('Removing esArchiver...');
await esArchiverUnload('apm_8.0.0');
return res;
diff --git a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/EmptyPrompt.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/EmptyPrompt.tsx
index 9d6a3eef3f7eb..498e17b9c359d 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/EmptyPrompt.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/EmptyPrompt.tsx
@@ -17,6 +17,7 @@ export function EmptyPrompt({
}) {
return (
)}
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
-
-
+
+
+
);
}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/custom_link_table.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/custom_link_table.tsx
index 4a242bb661e3a..86a7a8742eaea 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/custom_link_table.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/custom_link_table.tsx
@@ -79,6 +79,7 @@ export function CustomLinkTable({ items = [], onCustomLinkSelected }: Props) {
icon: 'pencil',
color: 'primary',
type: 'icon',
+ 'data-test-subj': 'editCustomLink',
onClick: (customLink: CustomLink) => {
onCustomLinkSelected(customLink);
},
diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx
index 1adb41acab70a..2c9ec0a232974 100644
--- a/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx
@@ -18,7 +18,7 @@ import { useApmParams } from '../../../hooks/use_apm_params';
import { useApmRouter } from '../../../hooks/use_apm_router';
import { SearchBar } from '../../shared/search_bar';
import { BackendLatencyChart } from './backend_latency_chart';
-import { BackendInventoryTitle } from '../../routing/home';
+import { DependenciesInventoryTitle } from '../../routing/home';
import { BackendDetailDependenciesTable } from './backend_detail_dependencies_table';
import { BackendThroughputChart } from './backend_throughput_chart';
import { BackendFailedTransactionRateChart } from './backend_error_rate_chart';
@@ -39,7 +39,7 @@ export function BackendDetailOverview() {
useBreadcrumb([
{
- title: BackendInventoryTitle,
+ title: DependenciesInventoryTitle,
href: apmRouter.link('/backends', {
query: { rangeFrom, rangeTo, environment, kuery },
}),
diff --git a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx
index 7ccf3f166fc65..ea135104982e5 100644
--- a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx
@@ -98,9 +98,9 @@ export function BackendInventoryDependenciesTable() {
dependencies={dependencies}
title={null}
nameColumnTitle={i18n.translate(
- 'xpack.apm.backendInventory.dependenciesTableColumnBackend',
+ 'xpack.apm.backendInventory.dependencyTableColumn',
{
- defaultMessage: 'Backend',
+ defaultMessage: 'Dependency',
}
)}
status={status}
diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations_help_popover.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations_help_popover.tsx
index 64adf0e11fca5..97bd13ebe37b5 100644
--- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations_help_popover.tsx
+++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations_help_popover.tsx
@@ -39,7 +39,7 @@ export function LatencyCorrelationsHelpPopover() {
diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
index 6ce38b4470093..8fc51bd05be31 100644
--- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
@@ -1287,7 +1287,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
>
List should render with data 1`] = `
theme.eui.gutterTypes.gutterSmall};
@@ -28,6 +28,7 @@ export function EmptyBanner() {
const theme = useTheme();
const cy = useContext(CytoscapeContext);
const [nodeCount, setNodeCount] = useState(0);
+ const { docLinks } = useApmPluginContext().core;
useEffect(() => {
const handler: cytoscape.EventHandler = (event) =>
@@ -67,14 +68,11 @@ export function EmptyBanner() {
defaultMessage:
"We will map out connected services and external requests if we can detect them. Please make sure you're running the latest version of the APM agent.",
})}{' '}
-
+
{i18n.translate('xpack.apm.serviceMap.emptyBanner.docsLink', {
defaultMessage: 'Learn more in the docs',
})}
-
+
);
diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx
index 0a42dbab9a452..9bc30ee67d2c7 100644
--- a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx
@@ -85,8 +85,8 @@ export function BackendContents({
});
}}
>
- {i18n.translate('xpack.apm.serviceMap.backendDetailsButtonText', {
- defaultMessage: 'Backend Details',
+ {i18n.translate('xpack.apm.serviceMap.dependencyDetailsButtonText', {
+ defaultMessage: 'Dependency Details',
})}
diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/popover.test.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/popover.test.tsx
index 9678258c4740c..5bec70b9eb841 100644
--- a/x-pack/plugins/apm/public/components/app/service_map/Popover/popover.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/popover.test.tsx
@@ -14,12 +14,12 @@ const { Backend, ExternalsList, Resource, Service } = composeStories(stories);
describe('Popover', () => {
describe('with backend data', () => {
- it('renders a backend link', async () => {
+ it('renders a dependency link', async () => {
render( );
await waitFor(() => {
expect(
- screen.getByRole('link', { name: /backend details/i })
+ screen.getByRole('link', { name: /Dependency Details/i })
).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx b/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx
index 43128c6013397..75602474de3e5 100644
--- a/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_map/timeout_prompt.tsx
@@ -5,10 +5,10 @@
* 2.0.
*/
-import { EuiEmptyPrompt } from '@elastic/eui';
+import { EuiEmptyPrompt, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
+import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
export function TimeoutPrompt({
isGlobalServiceMap,
@@ -44,11 +44,12 @@ export function TimeoutPrompt({
}
function ApmSettingsDocLink() {
+ const { docLinks } = useApmPluginContext().core;
return (
-
+
{i18n.translate('xpack.apm.serviceMap.timeoutPrompt.docsLink', {
defaultMessage: 'Learn more about APM settings in the docs',
})}
-
+
);
}
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
index 0d0842582335b..08f29d7727cda 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
@@ -125,13 +125,13 @@ export function ServiceOverviewDependenciesTable({
title={i18n.translate(
'xpack.apm.serviceOverview.dependenciesTableTitle',
{
- defaultMessage: 'Downstream services and backends',
+ defaultMessage: 'Dependencies',
}
)}
nameColumnTitle={i18n.translate(
- 'xpack.apm.serviceOverview.dependenciesTableColumnBackend',
+ 'xpack.apm.serviceOverview.dependenciesTableColumn',
{
- defaultMessage: 'Backend',
+ defaultMessage: 'Dependency',
}
)}
status={status}
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx
index 9b8706fe11035..6751e76cfa335 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx
@@ -37,10 +37,12 @@ export function ServiceOverviewThroughputChart({
height,
environment,
kuery,
+ transactionName,
}: {
height?: number;
environment: string;
kuery: string;
+ transactionName?: string;
}) {
const theme = useTheme();
@@ -80,6 +82,7 @@ export function ServiceOverviewThroughputChart({
transactionType,
comparisonStart,
comparisonEnd,
+ transactionName,
},
},
});
@@ -94,6 +97,7 @@ export function ServiceOverviewThroughputChart({
transactionType,
comparisonStart,
comparisonEnd,
+ transactionName,
]
);
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
index c4ecc71941b8c..06acaeeb5dd3b 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
@@ -12,6 +12,8 @@ import { ChartPointerEventContextProvider } from '../../../context/chart_pointer
import { useApmParams } from '../../../hooks/use_apm_params';
import { useApmRouter } from '../../../hooks/use_apm_router';
import { useTimeRange } from '../../../hooks/use_time_range';
+import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher';
+import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge';
import { TransactionCharts } from '../../shared/charts/transaction_charts';
import { TransactionDetailsTabs } from './transaction_details_tabs';
@@ -34,8 +36,14 @@ export function TransactionDetails() {
}),
});
+ const { kuery } = query;
+ const { fallbackToTransactions } = useFallbackToTransactionsFetcher({
+ kuery,
+ });
+
return (
<>
+ {fallbackToTransactions && }
@@ -50,6 +58,7 @@ export function TransactionDetails() {
environment={query.environment}
start={start}
end={end}
+ transactionName={transactionName}
/>
diff --git a/x-pack/plugins/apm/public/components/routing/home/index.tsx b/x-pack/plugins/apm/public/components/routing/home/index.tsx
index d1304e192ddce..1430f5d8e4756 100644
--- a/x-pack/plugins/apm/public/components/routing/home/index.tsx
+++ b/x-pack/plugins/apm/public/components/routing/home/index.tsx
@@ -45,10 +45,10 @@ export const ServiceInventoryTitle = i18n.translate(
}
);
-export const BackendInventoryTitle = i18n.translate(
- 'xpack.apm.views.backendInventory.title',
+export const DependenciesInventoryTitle = i18n.translate(
+ 'xpack.apm.views.dependenciesInventory.title',
{
- defaultMessage: 'Backends',
+ defaultMessage: 'Dependencies',
}
);
@@ -114,7 +114,7 @@ export const home = {
},
page({
path: '/',
- title: BackendInventoryTitle,
+ title: DependenciesInventoryTitle,
element: ,
}),
],
diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx
index 2b45e6872f295..2b463d1cdd064 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx
@@ -10,7 +10,7 @@ import {
ALERT_EVALUATION_THRESHOLD,
ALERT_RULE_TYPE_ID,
ALERT_EVALUATION_VALUE,
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_RULE_PRODUCER,
ALERT_RULE_CONSUMER,
ALERT_SEVERITY,
@@ -54,7 +54,7 @@ const alert: Alert = {
[ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T16:16:05.183Z'],
- [ALERT_ID]: ['apm.transaction_duration_All'],
+ [ALERT_INSTANCE_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-01T16:15:02.304Z'],
diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx
index da3b24197c4c2..204ebf577cc37 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx
@@ -10,7 +10,7 @@ import {
ALERT_EVALUATION_THRESHOLD,
ALERT_RULE_TYPE_ID,
ALERT_EVALUATION_VALUE,
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_SEVERITY,
ALERT_START,
ALERT_STATUS,
@@ -142,7 +142,7 @@ Example.args = {
[ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T20:27:48.833Z'],
- [ALERT_ID]: ['apm.transaction_duration_All'],
+ [ALERT_INSTANCE_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-02T04:00:00.000Z'],
@@ -164,7 +164,7 @@ Example.args = {
[ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T20:27:48.833Z'],
- [ALERT_ID]: ['apm.transaction_duration_All'],
+ [ALERT_INSTANCE_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-02T10:45:00.000Z'],
@@ -186,7 +186,7 @@ Example.args = {
[ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T20:27:48.833Z'],
- [ALERT_ID]: ['apm.transaction_duration_All'],
+ [ALERT_INSTANCE_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-02T16:50:00.000Z'],
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx
index 6e2ed04776e1c..4fdce0dfa705e 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx
@@ -19,11 +19,13 @@ export function TransactionCharts({
environment,
start,
end,
+ transactionName,
}: {
kuery: string;
environment: string;
start: string;
end: string;
+ transactionName?: string;
}) {
return (
<>
@@ -44,6 +46,7 @@ export function TransactionCharts({
diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
index 7f06dee4827b9..0a94dd14b3ca0 100644
--- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
+++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
@@ -61,6 +61,9 @@ const mockCore = {
docLinks: {
DOC_LINK_VERSION: '0',
ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
+ links: {
+ apm: {},
+ },
},
http: {
basePath: {
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index a329ad57e2b33..da2ea0ba8ae5c 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -93,9 +93,12 @@ const serviceMapTitle = i18n.translate('xpack.apm.navigation.serviceMapTitle', {
defaultMessage: 'Service Map',
});
-const backendsTitle = i18n.translate('xpack.apm.navigation.backendsTitle', {
- defaultMessage: 'Backends',
-});
+const dependenciesTitle = i18n.translate(
+ 'xpack.apm.navigation.dependenciesTitle',
+ {
+ defaultMessage: 'Dependencies',
+ }
+);
export class ApmPlugin implements Plugin {
constructor(
@@ -126,7 +129,7 @@ export class ApmPlugin implements Plugin {
{ label: servicesTitle, app: 'apm', path: '/services' },
{ label: tracesTitle, app: 'apm', path: '/traces' },
{
- label: backendsTitle,
+ label: dependenciesTitle,
app: 'apm',
path: '/backends',
isNewFeature: true,
@@ -270,7 +273,7 @@ export class ApmPlugin implements Plugin {
{ id: 'services', title: servicesTitle, path: '/services' },
{ id: 'traces', title: tracesTitle, path: '/traces' },
{ id: 'service-map', title: serviceMapTitle, path: '/service-map' },
- { id: 'backends', title: backendsTitle, path: '/backends' },
+ { id: 'backends', title: dependenciesTitle, path: '/backends' },
],
async mount(appMountParameters: AppMountParameters) {
diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md
index bf29ad1633315..a331bb4e9f116 100644
--- a/x-pack/plugins/apm/readme.md
+++ b/x-pack/plugins/apm/readme.md
@@ -31,88 +31,7 @@ _Docker Compose is required_
## Testing
-### Cypress tests
-
-See [ftr_e2e](./ftr_e2e)
-
-### Jest tests
-
-Note: Run the following commands from `kibana/x-pack/plugins/apm`.
-
-#### Run
-
-```
-npx jest --watch
-```
-
-#### Update snapshots
-
-```
-npx jest --updateSnapshot
-```
-
-#### Coverage
-
-HTML coverage report can be found in target/coverage/jest after tests have run.
-
-```
-open target/coverage/jest/index.html
-```
-
-### Functional tests
-
-**Start server**
-
-```
-node scripts/functional_tests_server --config x-pack/test/functional/config.js
-```
-
-**Run tests**
-
-```
-node scripts/functional_test_runner --config x-pack/test/functional/config.js --grep='APM specs'
-```
-
-APM tests are located in `x-pack/test/functional/apps/apm`.
-For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
-
-### API integration tests
-
-API tests are separated in two suites:
-
-- a basic license test suite
-- a trial license test suite (the equivalent of gold+)
-
-This requires separate test servers and test runners.
-
-**Basic**
-
-```
-# Start server
-node scripts/functional_tests_server --config x-pack/test/apm_api_integration/basic/config.ts
-
-# Run tests
-node scripts/functional_test_runner --config x-pack/test/apm_api_integration/basic/config.ts
-```
-
-The API tests for "basic" are located in `x-pack/test/apm_api_integration/basic/tests`.
-
-**Trial**
-
-```
-# Start server
-node scripts/functional_tests_server --config x-pack/test/apm_api_integration/trial/config.ts
-
-# Run tests
-node scripts/functional_test_runner --config x-pack/test/apm_api_integration/trial/config.ts
-```
-
-The API tests for "trial" are located in `x-pack/test/apm_api_integration/trial/tests`.
-
-**API Test tips**
-
-- For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
-- To update snapshots append `--updateSnapshots` to the functional_test_runner command
+Go to [tests documentation](./scripts/test/README.md)
## Linting
diff --git a/x-pack/plugins/apm/scripts/ftr_e2e/cypress_open.js b/x-pack/plugins/apm/scripts/ftr_e2e/cypress_open.js
deleted file mode 100644
index ce734c4c6e7b4..0000000000000
--- a/x-pack/plugins/apm/scripts/ftr_e2e/cypress_open.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-const childProcess = require('child_process');
-const path = require('path');
-
-const e2eDir = path.join(__dirname, '../../ftr_e2e');
-
-childProcess.execSync(
- `node ../../../../scripts/functional_tests --config ./cypress_open.ts`,
- { cwd: e2eDir, stdio: 'inherit' }
-);
diff --git a/x-pack/plugins/apm/scripts/ftr_e2e/cypress_run.js b/x-pack/plugins/apm/scripts/ftr_e2e/cypress_run.js
deleted file mode 100644
index acae621553f8a..0000000000000
--- a/x-pack/plugins/apm/scripts/ftr_e2e/cypress_run.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-const { argv } = require('yargs');
-const childProcess = require('child_process');
-const path = require('path');
-
-const { grep } = argv;
-
-const e2eDir = path.join(__dirname, '../../ftr_e2e');
-
-childProcess.execSync(
- `node ../../../../scripts/functional_tests --config ./cypress_run.ts --grep ${grep}`,
- { cwd: e2eDir, stdio: 'inherit' }
-);
diff --git a/x-pack/plugins/apm/scripts/test/README.md b/x-pack/plugins/apm/scripts/test/README.md
new file mode 100644
index 0000000000000..b241b2efdfd99
--- /dev/null
+++ b/x-pack/plugins/apm/scripts/test/README.md
@@ -0,0 +1,63 @@
+## Unit Tests (Jest)
+
+```
+node scripts/tests/jest [--watch] [--updateSnapshot]
+```
+
+#### Coverage
+
+HTML coverage report can be found in target/coverage/jest after tests have run.
+
+```
+open target/coverage/jest/index.html
+```
+
+---
+
+## API Tests
+
+API tests are separated in two suites:
+
+- a basic license test suite [default]
+- a trial license test suite (the equivalent of gold+)
+
+```
+node scripts/tests/api [--trial] [--help]
+```
+
+The API tests are located in `x-pack/test/apm_api_integration/`.
+
+**API Test tips**
+
+- For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
+- To update snapshots append `--updateSnapshots` to the functional_test_runner command
+
+---
+
+## E2E Tests (Cypress)
+
+```
+node scripts/tests/e2e [--trial] [--help]
+```
+
+The E2E tests are located [here](../../ftr_e2e)
+
+---
+
+## Functional tests (Security and Correlations tests)
+TODO: We could try moving this tests to the new e2e tests located at `x-pack/plugins/apm/ftr_e2e`.
+
+**Start server**
+
+```
+node scripts/functional_tests_server --config x-pack/test/functional/config.js
+```
+
+**Run tests**
+
+```
+node scripts/functional_test_runner --config x-pack/test/functional/config.js --grep='APM specs'
+```
+
+APM tests are located in `x-pack/test/functional/apps/apm`.
+For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
diff --git a/x-pack/plugins/apm/scripts/test/api.js b/x-pack/plugins/apm/scripts/test/api.js
new file mode 100644
index 0000000000000..4f0d82d0c1163
--- /dev/null
+++ b/x-pack/plugins/apm/scripts/test/api.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/* eslint-disable no-console */
+
+const yargs = require('yargs');
+const path = require('path');
+const childProcess = require('child_process');
+
+const { argv } = yargs(process.argv.slice(2))
+ .option('basic', {
+ default: true,
+ type: 'boolean',
+ description: 'Run tests with basic license',
+ })
+ .option('trial', {
+ default: false,
+ type: 'boolean',
+ description: 'Run tests with trial license',
+ })
+ .option('server', {
+ default: false,
+ type: 'boolean',
+ description: 'Start Elasticsearch and kibana',
+ })
+ .option('runner', {
+ default: false,
+ type: 'boolean',
+ description:
+ 'Run all tests (an instance of Elasticsearch and kibana are needs to be available)',
+ })
+ .help();
+
+const { trial, server, runner } = argv;
+
+const license = trial ? 'trial' : 'basic';
+console.log(`License: ${license}`);
+
+let ftrScript = 'functional_tests';
+if (server) {
+ ftrScript = 'functional_tests_server';
+} else if (runner) {
+ ftrScript = 'functional_test_runner';
+}
+childProcess.execSync(
+ `node ../../../../scripts/${ftrScript} --config ../../../../test/apm_api_integration/${license}/config.ts`,
+ { cwd: path.join(__dirname), stdio: 'inherit' }
+);
diff --git a/x-pack/plugins/apm/scripts/test/e2e.js b/x-pack/plugins/apm/scripts/test/e2e.js
new file mode 100644
index 0000000000000..629cdc2498414
--- /dev/null
+++ b/x-pack/plugins/apm/scripts/test/e2e.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/* eslint-disable no-console */
+
+const path = require('path');
+const yargs = require('yargs');
+const childProcess = require('child_process');
+
+const { argv } = yargs(process.argv.slice(2))
+ .option('server', {
+ default: false,
+ type: 'boolean',
+ description: 'Start Elasticsearch and kibana',
+ })
+ .option('runner', {
+ default: false,
+ type: 'boolean',
+ description:
+ 'Run all tests (an instance of Elasticsearch and kibana are needs to be available)',
+ })
+ .option('open', {
+ default: false,
+ type: 'boolean',
+ description: 'Opens the Cypress Test Runner',
+ })
+ .help();
+
+const { server, runner, open } = argv;
+
+const e2eDir = path.join(__dirname, '../../ftr_e2e');
+
+let ftrScript = 'functional_tests';
+if (server) {
+ ftrScript = 'functional_tests_server';
+} else if (runner || open) {
+ ftrScript = 'functional_test_runner';
+}
+
+const config = open ? './cypress_open.ts' : './cypress_run.ts';
+
+childProcess.execSync(
+ `node ../../../../scripts/${ftrScript} --config ${config}`,
+ { cwd: e2eDir, stdio: 'inherit' }
+);
diff --git a/x-pack/plugins/apm/scripts/jest.js b/x-pack/plugins/apm/scripts/test/jest.js
similarity index 92%
rename from x-pack/plugins/apm/scripts/jest.js
rename to x-pack/plugins/apm/scripts/test/jest.js
index 1294204d5f512..9ff5c9a4a3793 100644
--- a/x-pack/plugins/apm/scripts/jest.js
+++ b/x-pack/plugins/apm/scripts/test/jest.js
@@ -13,7 +13,7 @@ const { run } = require('jest');
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
-const config = require('../jest.config.js');
+const config = require('../../jest.config.js');
const argv = [...process.argv.slice(2), '--config', JSON.stringify(config)];
diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts
index b339c1f1f0be9..afe3a95d79023 100644
--- a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts
+++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts
@@ -16,6 +16,7 @@ interface GetApmPackagePolicyDefinitionOptions {
export function getApmPackagePolicyDefinition(
options: GetApmPackagePolicyDefinitionOptions
) {
+ const { apmServerSchema, cloudPluginSetup } = options;
return {
name: 'Elastic APM',
namespace: 'default',
@@ -27,7 +28,10 @@ export function getApmPackagePolicyDefinition(
type: 'apm',
enabled: true,
streams: [],
- vars: getApmPackageInputVars(options),
+ vars: getApmPackageInputVars({
+ cloudPluginSetup,
+ apmServerSchema: preprocessLegacyFields({ apmServerSchema }),
+ }),
},
],
package: {
@@ -38,6 +42,34 @@ export function getApmPackagePolicyDefinition(
};
}
+function preprocessLegacyFields({
+ apmServerSchema,
+}: {
+ apmServerSchema: Record;
+}) {
+ const copyOfApmServerSchema = { ...apmServerSchema };
+ [
+ {
+ key: 'apm-server.auth.anonymous.rate_limit.event_limit',
+ legacyKey: 'apm-server.rum.event_rate.limit',
+ },
+ {
+ key: 'apm-server.auth.anonymous.rate_limit.ip_limit',
+ legacyKey: 'apm-server.rum.event_rate.lru_size',
+ },
+ {
+ key: 'apm-server.auth.anonymous.allow_service',
+ legacyKey: 'apm-server.rum.allow_service_names',
+ },
+ ].forEach(({ key, legacyKey }) => {
+ if (!copyOfApmServerSchema[key]) {
+ copyOfApmServerSchema[key] = copyOfApmServerSchema[legacyKey];
+ delete copyOfApmServerSchema[legacyKey];
+ }
+ });
+ return copyOfApmServerSchema;
+}
+
function getApmPackageInputVars(options: GetApmPackagePolicyDefinitionOptions) {
const { apmServerSchema } = options;
const apmServerConfigs = Object.entries(
@@ -90,6 +122,18 @@ export const apmConfigMapping: Record<
name: 'rum_allow_headers',
type: 'text',
},
+ 'apm-server.rum.event_rate.limit': {
+ name: 'rum_event_rate_limit',
+ type: 'integer',
+ },
+ 'apm-server.rum.allow_service_names': {
+ name: 'rum_allow_service_names',
+ type: 'text',
+ },
+ 'apm-server.rum.event_rate.lru_size': {
+ name: 'rum_event_rate_lru_size',
+ type: 'integer',
+ },
'apm-server.rum.response_headers': {
name: 'rum_response_headers',
type: 'yaml',
diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts
index e866918fc29bb..76d6000a161e6 100644
--- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts
@@ -8,6 +8,7 @@
import { ESFilter } from '../../../../../../src/core/types/elasticsearch';
import {
SERVICE_NAME,
+ TRANSACTION_NAME,
TRANSACTION_TYPE,
} from '../../../common/elasticsearch_fieldnames';
import { kqlQuery, rangeQuery } from '../../../../observability/server';
@@ -25,6 +26,7 @@ interface Options {
serviceName: string;
setup: Setup;
transactionType: string;
+ transactionName?: string;
start: number;
end: number;
intervalString: string;
@@ -38,6 +40,7 @@ export async function getThroughput({
serviceName,
setup,
transactionType,
+ transactionName,
start,
end,
intervalString,
@@ -56,6 +59,14 @@ export async function getThroughput({
...kqlQuery(kuery),
];
+ if (transactionName) {
+ filter.push({
+ term: {
+ [TRANSACTION_NAME]: transactionName,
+ },
+ });
+ }
+
const params = {
apm: {
events: [
diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts
index 32a7dcefb5cc8..550781cc1a020 100644
--- a/x-pack/plugins/apm/server/routes/services.ts
+++ b/x-pack/plugins/apm/server/routes/services.ts
@@ -451,10 +451,8 @@ const serviceThroughputRoute = createApmServerRoute({
}),
query: t.intersection([
t.type({ transactionType: t.string }),
- environmentRt,
- kueryRt,
- rangeRt,
- comparisonRangeRt,
+ t.partial({ transactionName: t.string }),
+ t.intersection([environmentRt, kueryRt, rangeRt, comparisonRangeRt]),
]),
}),
options: { tags: ['access:apm'] },
@@ -466,6 +464,7 @@ const serviceThroughputRoute = createApmServerRoute({
environment,
kuery,
transactionType,
+ transactionName,
comparisonStart,
comparisonEnd,
} = params.query;
@@ -493,6 +492,7 @@ const serviceThroughputRoute = createApmServerRoute({
serviceName,
setup,
transactionType,
+ transactionName,
throughputUnit,
intervalString,
};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot b/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot
index 44c9160cdf544..0de7012aee1a3 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/__snapshots__/workpad_table.stories.storyshot
@@ -906,7 +906,7 @@ exports[`Storyshots Home/Components/Workpad Table Workpad Table 1`] = `
>
"
+"
`;
exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
"`;
@@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
"
+"
`;
exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
"`;
@@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
"
+"
`;
exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
"`;
@@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
"
+"
`;
exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
"`;
@@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
"
+"
`;
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot
index c5b6d768c89d8..a5eefde192371 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot
+++ b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot
@@ -1375,7 +1375,7 @@ exports[`Storyshots shareables/Canvas component 1`] = `
>
{
});
});
});
+
+ it('hides the sync alerts toggle', () => {
+ const { queryByText } = render(
+
+
+
+ );
+
+ expect(queryByText('Sync alert')).not.toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx
index cbd4fd7654259..d9b9287534601 100644
--- a/x-pack/plugins/cases/public/components/create/form.tsx
+++ b/x-pack/plugins/cases/public/components/create/form.tsx
@@ -53,7 +53,6 @@ export const CreateCaseForm: React.FC = React.memo(
withSteps = true,
}) => {
const { isSubmitting } = useFormContext();
-
const firstStep = useMemo(
() => ({
title: i18n.STEP_ONE_TITLE,
diff --git a/x-pack/plugins/cases/server/client/alerts/get.ts b/x-pack/plugins/cases/server/client/alerts/get.ts
index 391279aab5a83..2048ccae4fa60 100644
--- a/x-pack/plugins/cases/server/client/alerts/get.ts
+++ b/x-pack/plugins/cases/server/client/alerts/get.ts
@@ -12,11 +12,19 @@ export const get = async (
{ alertsInfo }: AlertGet,
clientArgs: CasesClientArgs
): Promise => {
- const { alertsService, logger } = clientArgs;
+ const { alertsService, scopedClusterClient, logger } = clientArgs;
if (alertsInfo.length === 0) {
return [];
}
- const alerts = await alertsService.getAlerts({ alertsInfo, logger });
- return alerts ?? [];
+ const alerts = await alertsService.getAlerts({ alertsInfo, scopedClusterClient, logger });
+ if (!alerts) {
+ return [];
+ }
+
+ return alerts.docs.map((alert) => ({
+ id: alert._id,
+ index: alert._index,
+ ...alert._source,
+ }));
};
diff --git a/x-pack/plugins/cases/server/client/alerts/types.ts b/x-pack/plugins/cases/server/client/alerts/types.ts
index 6b3a49f20d1e5..95cd9ae33bff9 100644
--- a/x-pack/plugins/cases/server/client/alerts/types.ts
+++ b/x-pack/plugins/cases/server/client/alerts/types.ts
@@ -7,7 +7,17 @@
import { CaseStatuses } from '../../../common/api';
import { AlertInfo } from '../../common';
-import { Alert } from '../../services/alerts/types';
+
+interface Alert {
+ id: string;
+ index: string;
+ destination?: {
+ ip: string;
+ };
+ source?: {
+ ip: string;
+ };
+}
export type CasesClientGetAlertsResponse = Alert[];
diff --git a/x-pack/plugins/cases/server/client/alerts/update_status.ts b/x-pack/plugins/cases/server/client/alerts/update_status.ts
index 9c8cc33264413..a0684b59241b0 100644
--- a/x-pack/plugins/cases/server/client/alerts/update_status.ts
+++ b/x-pack/plugins/cases/server/client/alerts/update_status.ts
@@ -16,6 +16,6 @@ export const updateStatus = async (
{ alerts }: UpdateAlertsStatusArgs,
clientArgs: CasesClientArgs
): Promise => {
- const { alertsService, logger } = clientArgs;
- await alertsService.updateAlertsStatus({ alerts, logger });
+ const { alertsService, scopedClusterClient, logger } = clientArgs;
+ await alertsService.updateAlertsStatus({ alerts, scopedClusterClient, logger });
};
diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts
index 5393a108d6af2..166ae2ae65012 100644
--- a/x-pack/plugins/cases/server/client/attachments/add.ts
+++ b/x-pack/plugins/cases/server/client/attachments/add.ts
@@ -40,7 +40,12 @@ import {
} from '../../services/user_actions/helpers';
import { AttachmentService, CasesService, CaseUserActionService } from '../../services';
-import { createCaseError, CommentableCase, isCommentRequestTypeGenAlert } from '../../common';
+import {
+ createCaseError,
+ CommentableCase,
+ createAlertUpdateRequest,
+ isCommentRequestTypeGenAlert,
+} from '../../common';
import { CasesClientArgs, CasesClientInternal } from '..';
import { decodeCommentRequest } from '../utils';
@@ -190,9 +195,22 @@ const addGeneratedAlerts = async (
user: userDetails,
commentReq: query,
id: savedObjectID,
- casesClientInternal,
});
+ if (
+ (newComment.attributes.type === CommentType.alert ||
+ newComment.attributes.type === CommentType.generatedAlert) &&
+ caseInfo.attributes.settings.syncAlerts
+ ) {
+ const alertsToUpdate = createAlertUpdateRequest({
+ comment: query,
+ status: subCase.attributes.status,
+ });
+ await casesClientInternal.alerts.updateStatus({
+ alerts: alertsToUpdate,
+ });
+ }
+
await userActionService.bulkCreate({
unsecuredSavedObjectsClient,
actions: [
@@ -368,9 +386,19 @@ export const addComment = async (
user: userInfo,
commentReq: query,
id: savedObjectID,
- casesClientInternal,
});
+ if (newComment.attributes.type === CommentType.alert && updatedCase.settings.syncAlerts) {
+ const alertsToUpdate = createAlertUpdateRequest({
+ comment: query,
+ status: updatedCase.status,
+ });
+
+ await casesClientInternal.alerts.updateStatus({
+ alerts: alertsToUpdate,
+ });
+ }
+
await userActionService.bulkCreate({
unsecuredSavedObjectsClient,
actions: [
diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts
index 80e69d53e9e8b..3048cf01bb3ba 100644
--- a/x-pack/plugins/cases/server/client/cases/push.ts
+++ b/x-pack/plugins/cases/server/client/cases/push.ts
@@ -6,7 +6,7 @@
*/
import Boom from '@hapi/boom';
-import { SavedObjectsFindResponse, SavedObject, Logger } from 'kibana/server';
+import { SavedObjectsFindResponse, SavedObject } from 'kibana/server';
import {
ActionConnector,
@@ -22,16 +22,26 @@ import {
import { buildCaseUserActionItem } from '../../services/user_actions/helpers';
import { createIncident, getCommentContextFromAttributes } from './utils';
-import {
- AlertInfo,
- createCaseError,
- flattenCaseSavedObject,
- getAlertInfoFromComments,
-} from '../../common';
+import { createCaseError, flattenCaseSavedObject, getAlertInfoFromComments } from '../../common';
import { CasesClient, CasesClientArgs, CasesClientInternal } from '..';
import { Operations } from '../../authorization';
import { casesConnectors } from '../../connectors';
-import { CasesClientGetAlertsResponse } from '../alerts/types';
+
+/**
+ * Returns true if the case should be closed based on the configuration settings and whether the case
+ * is a collection. Collections are not closable because we aren't allowing their status to be changed.
+ * In the future we could allow push to close all the sub cases of a collection but that's not currently supported.
+ */
+function shouldCloseByPush(
+ configureSettings: SavedObjectsFindResponse,
+ caseInfo: SavedObject
+): boolean {
+ return (
+ configureSettings.total > 0 &&
+ configureSettings.saved_objects[0].attributes.closure_type === 'close-by-pushing' &&
+ caseInfo.attributes.type !== CaseType.collection
+ );
+}
/**
* Parameters for pushing a case to an external system
@@ -96,7 +106,9 @@ export const push = async (
const alertsInfo = getAlertInfoFromComments(theCase?.comments);
- const alerts = await getAlertsCatchErrors({ casesClientInternal, alertsInfo, logger });
+ const alerts = await casesClientInternal.alerts.get({
+ alertsInfo,
+ });
const getMappingsResponse = await casesClientInternal.configuration.getMappings({
connector: theCase.connector,
@@ -266,38 +278,3 @@ export const push = async (
throw createCaseError({ message: `Failed to push case: ${error}`, error, logger });
}
};
-
-async function getAlertsCatchErrors({
- casesClientInternal,
- alertsInfo,
- logger,
-}: {
- casesClientInternal: CasesClientInternal;
- alertsInfo: AlertInfo[];
- logger: Logger;
-}): Promise {
- try {
- return await casesClientInternal.alerts.get({
- alertsInfo,
- });
- } catch (error) {
- logger.error(`Failed to retrieve alerts during push: ${error}`);
- return [];
- }
-}
-
-/**
- * Returns true if the case should be closed based on the configuration settings and whether the case
- * is a collection. Collections are not closable because we aren't allowing their status to be changed.
- * In the future we could allow push to close all the sub cases of a collection but that's not currently supported.
- */
-function shouldCloseByPush(
- configureSettings: SavedObjectsFindResponse,
- caseInfo: SavedObject
-): boolean {
- return (
- configureSettings.total > 0 &&
- configureSettings.saved_objects[0].attributes.closure_type === 'close-by-pushing' &&
- caseInfo.attributes.type !== CaseType.collection
- );
-}
diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts
index 611c9e09fa76e..ed19444414d57 100644
--- a/x-pack/plugins/cases/server/client/cases/update.ts
+++ b/x-pack/plugins/cases/server/client/cases/update.ts
@@ -12,7 +12,6 @@ import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import {
- Logger,
SavedObject,
SavedObjectsClientContract,
SavedObjectsFindResponse,
@@ -308,14 +307,12 @@ async function updateAlerts({
caseService,
unsecuredSavedObjectsClient,
casesClientInternal,
- logger,
}: {
casesWithSyncSettingChangedToOn: UpdateRequestWithOriginalCase[];
casesWithStatusChangedAndSynced: UpdateRequestWithOriginalCase[];
caseService: CasesService;
unsecuredSavedObjectsClient: SavedObjectsClientContract;
casesClientInternal: CasesClientInternal;
- logger: Logger;
}) {
/**
* It's possible that a case ID can appear multiple times in each array. I'm intentionally placing the status changes
@@ -364,9 +361,7 @@ async function updateAlerts({
[]
);
- await casesClientInternal.alerts.updateStatus({
- alerts: alertsToUpdate,
- });
+ await casesClientInternal.alerts.updateStatus({ alerts: alertsToUpdate });
}
function partitionPatchRequest(
@@ -567,6 +562,15 @@ export const update = async (
);
});
+ // Update the alert's status to match any case status or sync settings changes
+ await updateAlerts({
+ casesWithStatusChangedAndSynced,
+ casesWithSyncSettingChangedToOn,
+ caseService,
+ unsecuredSavedObjectsClient,
+ casesClientInternal,
+ });
+
const returnUpdatedCase = myCases.saved_objects
.filter((myCase) =>
updatedCases.saved_objects.some((updatedCase) => updatedCase.id === myCase.id)
@@ -594,17 +598,6 @@ export const update = async (
}),
});
- // Update the alert's status to match any case status or sync settings changes
- // Attempt to do this after creating/changing the other entities just in case it fails
- await updateAlerts({
- casesWithStatusChangedAndSynced,
- casesWithSyncSettingChangedToOn,
- caseService,
- unsecuredSavedObjectsClient,
- casesClientInternal,
- logger,
- });
-
return CasesResponseRt.encode(returnUpdatedCase);
} catch (error) {
const idVersions = cases.cases.map((caseInfo) => ({
diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts
index a1a3ccdd3bc52..2fae6996f4aa2 100644
--- a/x-pack/plugins/cases/server/client/factory.ts
+++ b/x-pack/plugins/cases/server/client/factory.ts
@@ -5,7 +5,12 @@
* 2.0.
*/
-import { KibanaRequest, SavedObjectsServiceStart, Logger } from 'kibana/server';
+import {
+ KibanaRequest,
+ SavedObjectsServiceStart,
+ Logger,
+ ElasticsearchClient,
+} from 'kibana/server';
import { SecurityPluginSetup, SecurityPluginStart } from '../../../security/server';
import { SAVED_OBJECT_TYPES } from '../../common';
import { Authorization } from '../authorization/authorization';
@@ -20,8 +25,8 @@ import {
} from '../services';
import { PluginStartContract as FeaturesPluginStart } from '../../../features/server';
import { PluginStartContract as ActionsPluginStart } from '../../../actions/server';
-import { RuleRegistryPluginStartContract } from '../../../rule_registry/server';
import { LensServerPluginSetup } from '../../../lens/server';
+
import { AuthorizationAuditLogger } from '../authorization';
import { CasesClient, createCasesClient } from '.';
@@ -31,7 +36,6 @@ interface CasesClientFactoryArgs {
getSpace: GetSpaceFn;
featuresPluginStart: FeaturesPluginStart;
actionsPluginStart: ActionsPluginStart;
- ruleRegistryPluginStart?: RuleRegistryPluginStartContract;
lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'];
}
@@ -65,10 +69,12 @@ export class CasesClientFactory {
*/
public async create({
request,
+ scopedClusterClient,
savedObjectsService,
}: {
request: KibanaRequest;
savedObjectsService: SavedObjectsServiceStart;
+ scopedClusterClient: ElasticsearchClient;
}): Promise {
if (!this.isInitialized || !this.options) {
throw new Error('CasesClientFactory must be initialized before calling create');
@@ -88,12 +94,9 @@ export class CasesClientFactory {
const caseService = new CasesService(this.logger, this.options?.securityPluginStart?.authc);
const userInfo = caseService.getUser({ request });
- const alertsClient = await this.options.ruleRegistryPluginStart?.getRacClientWithRequest(
- request
- );
-
return createCasesClient({
- alertsService: new AlertService(alertsClient),
+ alertsService: new AlertService(),
+ scopedClusterClient,
unsecuredSavedObjectsClient: savedObjectsService.getScopedClient(request, {
includedHiddenTypes: SAVED_OBJECT_TYPES,
// this tells the security plugin to not perform SO authorization and audit logging since we are handling
diff --git a/x-pack/plugins/cases/server/client/sub_cases/update.ts b/x-pack/plugins/cases/server/client/sub_cases/update.ts
index 56610ea6858e3..c8cb96cbb6b8c 100644
--- a/x-pack/plugins/cases/server/client/sub_cases/update.ts
+++ b/x-pack/plugins/cases/server/client/sub_cases/update.ts
@@ -246,9 +246,7 @@ async function updateAlerts({
[]
);
- await casesClientInternal.alerts.updateStatus({
- alerts: alertsToUpdate,
- });
+ await casesClientInternal.alerts.updateStatus({ alerts: alertsToUpdate });
} catch (error) {
throw createCaseError({
message: `Failed to update alert status while updating sub cases: ${JSON.stringify(
@@ -357,6 +355,14 @@ export async function update({
);
});
+ await updateAlerts({
+ caseService,
+ unsecuredSavedObjectsClient,
+ casesClientInternal,
+ subCasesToSync: subCasesToSyncAlertsFor,
+ logger: clientArgs.logger,
+ });
+
const returnUpdatedSubCases = updatedCases.saved_objects.reduce(
(acc, updatedSO) => {
const originalSubCase = subCasesMap.get(updatedSO.id);
@@ -388,15 +394,6 @@ export async function update({
}),
});
- // attempt to update the status of the alerts after creating all the user actions just in case it fails
- await updateAlerts({
- caseService,
- unsecuredSavedObjectsClient,
- casesClientInternal,
- subCasesToSync: subCasesToSyncAlertsFor,
- logger: clientArgs.logger,
- });
-
return SubCasesResponseRt.encode(returnUpdatedSubCases);
} catch (error) {
const idVersions = query.subCases.map((subCase) => ({
diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts
index 3979c19949d9a..27829d2539c7d 100644
--- a/x-pack/plugins/cases/server/client/types.ts
+++ b/x-pack/plugins/cases/server/client/types.ts
@@ -6,7 +6,7 @@
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
-import { SavedObjectsClientContract, Logger } from 'kibana/server';
+import { ElasticsearchClient, SavedObjectsClientContract, Logger } from 'kibana/server';
import { User } from '../../common';
import { Authorization } from '../authorization/authorization';
import {
@@ -24,6 +24,7 @@ import { LensServerPluginSetup } from '../../../lens/server';
* Parameters for initializing a cases client
*/
export interface CasesClientArgs {
+ readonly scopedClusterClient: ElasticsearchClient;
readonly caseConfigureService: CaseConfigureService;
readonly caseService: CasesService;
readonly connectorMappingsService: ConnectorMappingsService;
diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts
index e540332b1ff84..856d6378d5900 100644
--- a/x-pack/plugins/cases/server/common/models/commentable_case.ts
+++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts
@@ -34,16 +34,10 @@ import {
CommentRequestUserType,
CaseAttributes,
} from '../../../common';
-import {
- createAlertUpdateRequest,
- flattenCommentSavedObjects,
- flattenSubCaseSavedObject,
- transformNewComment,
-} from '..';
+import { flattenCommentSavedObjects, flattenSubCaseSavedObject, transformNewComment } from '..';
import { AttachmentService, CasesService } from '../../services';
import { createCaseError } from '../error';
import { countAlertsForID } from '../index';
-import { CasesClientInternal } from '../../client';
import { getOrUpdateLensReferences } from '../utils';
interface UpdateCommentResp {
@@ -279,13 +273,11 @@ export class CommentableCase {
user,
commentReq,
id,
- casesClientInternal,
}: {
createdDate: string;
user: User;
commentReq: CommentRequest;
id: string;
- casesClientInternal: CasesClientInternal;
}): Promise {
try {
if (commentReq.type === CommentType.alert) {
@@ -302,10 +294,6 @@ export class CommentableCase {
throw Boom.badRequest('The owner field of the comment must match the case');
}
- // Let's try to sync the alert's status before creating the attachment, that way if the alert doesn't exist
- // we'll throw an error early before creating the attachment
- await this.syncAlertStatus(commentReq, casesClientInternal);
-
let references = this.buildRefsToCase();
if (commentReq.type === CommentType.user && commentReq?.comment) {
@@ -343,26 +331,6 @@ export class CommentableCase {
}
}
- private async syncAlertStatus(
- commentRequest: CommentRequest,
- casesClientInternal: CasesClientInternal
- ) {
- if (
- (commentRequest.type === CommentType.alert ||
- commentRequest.type === CommentType.generatedAlert) &&
- this.settings.syncAlerts
- ) {
- const alertsToUpdate = createAlertUpdateRequest({
- comment: commentRequest,
- status: this.status,
- });
-
- await casesClientInternal.alerts.updateStatus({
- alerts: alertsToUpdate,
- });
- }
- }
-
private formatCollectionForEncoding(totalComment: number) {
return {
id: this.collection.id,
diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts
index 7a1efe8b366d0..fa103d4c1142d 100644
--- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts
+++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts
@@ -24,7 +24,7 @@ describe('ITSM formatter', () => {
} as CaseResponse;
it('it formats correctly without alerts', async () => {
- const res = format(theCase, []);
+ const res = await format(theCase, []);
expect(res).toEqual({
dest_ip: null,
source_ip: null,
@@ -38,7 +38,7 @@ describe('ITSM formatter', () => {
it('it formats correctly when fields do not exist ', async () => {
const invalidFields = { connector: { fields: null } } as CaseResponse;
- const res = format(invalidFields, []);
+ const res = await format(invalidFields, []);
expect(res).toEqual({
dest_ip: null,
source_ip: null,
@@ -55,31 +55,25 @@ describe('ITSM formatter', () => {
{
id: 'alert-1',
index: 'index-1',
- source: {
- destination: { ip: '192.168.1.1' },
- source: { ip: '192.168.1.2' },
- file: {
- hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
- },
- url: { full: 'https://attack.com' },
+ destination: { ip: '192.168.1.1' },
+ source: { ip: '192.168.1.2' },
+ file: {
+ hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
+ url: { full: 'https://attack.com' },
},
{
id: 'alert-2',
index: 'index-2',
- source: {
- source: {
- ip: '192.168.1.3',
- },
- destination: { ip: '192.168.1.4' },
- file: {
- hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
- },
- url: { full: 'https://attack.com/api' },
+ destination: { ip: '192.168.1.4' },
+ source: { ip: '192.168.1.3' },
+ file: {
+ hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
},
+ url: { full: 'https://attack.com/api' },
},
];
- const res = format(theCase, alerts);
+ const res = await format(theCase, alerts);
expect(res).toEqual({
dest_ip: '192.168.1.1,192.168.1.4',
source_ip: '192.168.1.2,192.168.1.3',
@@ -92,109 +86,30 @@ describe('ITSM formatter', () => {
});
});
- it('it ignores alerts with an error', async () => {
- const alerts = [
- {
- id: 'alert-1',
- index: 'index-1',
- error: new Error('an error'),
- source: {
- destination: { ip: '192.168.1.1' },
- source: { ip: '192.168.1.2' },
- file: {
- hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
- },
- url: { full: 'https://attack.com' },
- },
- },
- {
- id: 'alert-2',
- index: 'index-2',
- source: {
- source: {
- ip: '192.168.1.3',
- },
- destination: { ip: '192.168.1.4' },
- file: {
- hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
- },
- url: { full: 'https://attack.com/api' },
- },
- },
- ];
- const res = format(theCase, alerts);
- expect(res).toEqual({
- dest_ip: '192.168.1.4',
- source_ip: '192.168.1.3',
- category: 'Denial of Service',
- subcategory: 'Inbound DDos',
- malware_hash: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752',
- malware_url: 'https://attack.com/api',
- priority: '2 - High',
- });
- });
-
- it('it ignores alerts without a source field', async () => {
- const alerts = [
- {
- id: 'alert-1',
- index: 'index-1',
- },
- {
- id: 'alert-2',
- index: 'index-2',
- source: {
- source: {
- ip: '192.168.1.3',
- },
- destination: { ip: '192.168.1.4' },
- file: {
- hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
- },
- url: { full: 'https://attack.com/api' },
- },
- },
- ];
- const res = format(theCase, alerts);
- expect(res).toEqual({
- dest_ip: '192.168.1.4',
- source_ip: '192.168.1.3',
- category: 'Denial of Service',
- subcategory: 'Inbound DDos',
- malware_hash: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752',
- malware_url: 'https://attack.com/api',
- priority: '2 - High',
- });
- });
-
it('it handles duplicates correctly', async () => {
const alerts = [
{
id: 'alert-1',
index: 'index-1',
- source: {
- destination: { ip: '192.168.1.1' },
- source: { ip: '192.168.1.2' },
- file: {
- hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
- },
- url: { full: 'https://attack.com' },
+ destination: { ip: '192.168.1.1' },
+ source: { ip: '192.168.1.2' },
+ file: {
+ hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
+ url: { full: 'https://attack.com' },
},
{
id: 'alert-2',
index: 'index-2',
- source: {
- destination: { ip: '192.168.1.1' },
- source: { ip: '192.168.1.3' },
- file: {
- hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
- },
- url: { full: 'https://attack.com/api' },
+ destination: { ip: '192.168.1.1' },
+ source: { ip: '192.168.1.3' },
+ file: {
+ hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
+ url: { full: 'https://attack.com/api' },
},
];
- const res = format(theCase, alerts);
+ const res = await format(theCase, alerts);
expect(res).toEqual({
dest_ip: '192.168.1.1',
source_ip: '192.168.1.2,192.168.1.3',
@@ -211,26 +126,22 @@ describe('ITSM formatter', () => {
{
id: 'alert-1',
index: 'index-1',
- source: {
- destination: { ip: '192.168.1.1' },
- source: { ip: '192.168.1.2' },
- file: {
- hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
- },
- url: { full: 'https://attack.com' },
+ destination: { ip: '192.168.1.1' },
+ source: { ip: '192.168.1.2' },
+ file: {
+ hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
+ url: { full: 'https://attack.com' },
},
{
id: 'alert-2',
index: 'index-2',
- source: {
- destination: { ip: '192.168.1.1' },
- source: { ip: '192.168.1.3' },
- file: {
- hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
- },
- url: { full: 'https://attack.com/api' },
+ destination: { ip: '192.168.1.1' },
+ source: { ip: '192.168.1.3' },
+ file: {
+ hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
+ url: { full: 'https://attack.com/api' },
},
];
@@ -239,7 +150,7 @@ describe('ITSM formatter', () => {
connector: { fields: { ...theCase.connector.fields, destIp: false, malwareHash: false } },
} as CaseResponse;
- const res = format(newCase, alerts);
+ const res = await format(newCase, alerts);
expect(res).toEqual({
dest_ip: null,
source_ip: '192.168.1.2,192.168.1.3',
diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts
index 88b8f79d3ba5b..b48a1b7f734c8 100644
--- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts
+++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts
@@ -44,25 +44,23 @@ export const format: ServiceNowSIRFormat = (theCase, alerts) => {
);
if (fieldsToAdd.length > 0) {
- sirFields = alerts
- .filter((alert) => !alert.error && alert.source != null)
- .reduce>((acc, alert) => {
- fieldsToAdd.forEach((alertField) => {
- const field = get(alertFieldMapping[alertField].alertPath, alert.source);
- if (field && !manageDuplicate[alertFieldMapping[alertField].sirFieldKey].has(field)) {
- manageDuplicate[alertFieldMapping[alertField].sirFieldKey].add(field);
- acc = {
- ...acc,
- [alertFieldMapping[alertField].sirFieldKey]: `${
- acc[alertFieldMapping[alertField].sirFieldKey] != null
- ? `${acc[alertFieldMapping[alertField].sirFieldKey]},${field}`
- : field
- }`,
- };
- }
- });
- return acc;
- }, sirFields);
+ sirFields = alerts.reduce>((acc, alert) => {
+ fieldsToAdd.forEach((alertField) => {
+ const field = get(alertFieldMapping[alertField].alertPath, alert);
+ if (field && !manageDuplicate[alertFieldMapping[alertField].sirFieldKey].has(field)) {
+ manageDuplicate[alertFieldMapping[alertField].sirFieldKey].add(field);
+ acc = {
+ ...acc,
+ [alertFieldMapping[alertField].sirFieldKey]: `${
+ acc[alertFieldMapping[alertField].sirFieldKey] != null
+ ? `${acc[alertFieldMapping[alertField].sirFieldKey]},${field}`
+ : field
+ }`,
+ };
+ }
+ });
+ return acc;
+ }, sirFields);
}
return {
diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts
index 49220fc716034..bb1be163585a8 100644
--- a/x-pack/plugins/cases/server/plugin.ts
+++ b/x-pack/plugins/cases/server/plugin.ts
@@ -32,7 +32,6 @@ import type { CasesRequestHandlerContext } from './types';
import { CasesClientFactory } from './client/factory';
import { SpacesPluginStart } from '../../spaces/server';
import { PluginStartContract as FeaturesPluginStart } from '../../features/server';
-import { RuleRegistryPluginStartContract } from '../../rule_registry/server';
import { LensServerPluginSetup } from '../../lens/server';
function createConfig(context: PluginInitializerContext) {
@@ -50,7 +49,6 @@ export interface PluginsStart {
features: FeaturesPluginStart;
spaces?: SpacesPluginStart;
actions: ActionsPluginStart;
- ruleRegistry?: RuleRegistryPluginStartContract;
}
/**
@@ -139,13 +137,15 @@ export class CasePlugin {
},
featuresPluginStart: plugins.features,
actionsPluginStart: plugins.actions,
- ruleRegistryPluginStart: plugins.ruleRegistry,
lensEmbeddableFactory: this.lensEmbeddableFactory!,
});
+ const client = core.elasticsearch.client;
+
const getCasesClientWithRequest = async (request: KibanaRequest): Promise => {
return this.clientFactory.create({
request,
+ scopedClusterClient: client.asScoped(request).asCurrentUser,
savedObjectsService: core.savedObjects,
});
};
@@ -171,6 +171,7 @@ export class CasePlugin {
return this.clientFactory.create({
request,
+ scopedClusterClient: context.core.elasticsearch.client.asCurrentUser,
savedObjectsService: savedObjects,
});
},
diff --git a/x-pack/plugins/cases/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts
index 0e1ad03a32af2..d7dd44b33628b 100644
--- a/x-pack/plugins/cases/server/services/alerts/index.test.ts
+++ b/x-pack/plugins/cases/server/services/alerts/index.test.ts
@@ -7,73 +7,280 @@
import { CaseStatuses } from '../../../common';
import { AlertService, AlertServiceContract } from '.';
-import { loggingSystemMock } from 'src/core/server/mocks';
-import { ruleRegistryMocks } from '../../../../rule_registry/server/mocks';
-import { AlertsClient } from '../../../../rule_registry/server';
-import { PublicMethodsOf } from '@kbn/utility-types';
+import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks';
+import { ALERT_WORKFLOW_STATUS } from '../../../../rule_registry/common/technical_rule_data_field_names';
describe('updateAlertsStatus', () => {
+ const esClient = elasticsearchServiceMock.createElasticsearchClient();
const logger = loggingSystemMock.create().get('case');
- let alertsClient: jest.Mocked>;
- let alertService: AlertServiceContract;
-
- beforeEach(async () => {
- alertsClient = ruleRegistryMocks.createAlertsClientMock.create();
- alertService = new AlertService(alertsClient);
- jest.restoreAllMocks();
- });
describe('happy path', () => {
- const args = {
- alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }],
- logger,
- };
+ let alertService: AlertServiceContract;
+
+ beforeEach(async () => {
+ alertService = new AlertService();
+ jest.resetAllMocks();
+ });
it('updates the status of the alert correctly', async () => {
+ const args = {
+ alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }],
+ scopedClusterClient: esClient,
+ logger,
+ };
+
await alertService.updateAlertsStatus(args);
- expect(alertsClient.update).toHaveBeenCalledWith({
- id: 'alert-id-1',
+ expect(esClient.updateByQuery).toHaveBeenCalledWith({
index: '.siem-signals',
- status: CaseStatuses.closed,
+ conflicts: 'abort',
+ body: {
+ script: {
+ source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) {
+ ctx._source['${ALERT_WORKFLOW_STATUS}'] = 'closed'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = 'closed'
+ }`,
+ lang: 'painless',
+ },
+ query: {
+ ids: {
+ values: ['alert-id-1'],
+ },
+ },
+ },
+ ignore_unavailable: true,
});
});
- it('translates the in-progress status to acknowledged', async () => {
- await alertService.updateAlertsStatus({
- alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses['in-progress'] }],
+ it('buckets the alerts by index', async () => {
+ const args = {
+ alerts: [
+ { id: 'id1', index: '1', status: CaseStatuses.closed },
+ { id: 'id2', index: '1', status: CaseStatuses.closed },
+ ],
+ scopedClusterClient: esClient,
logger,
- });
+ };
- expect(alertsClient.update).toHaveBeenCalledWith({
- id: 'alert-id-1',
- index: '.siem-signals',
- status: 'acknowledged',
+ await alertService.updateAlertsStatus(args);
+
+ expect(esClient.updateByQuery).toBeCalledTimes(1);
+ expect(esClient.updateByQuery).toHaveBeenCalledWith({
+ index: '1',
+ conflicts: 'abort',
+ body: {
+ script: {
+ source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) {
+ ctx._source['${ALERT_WORKFLOW_STATUS}'] = 'closed'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = 'closed'
+ }`,
+ lang: 'painless',
+ },
+ query: {
+ ids: {
+ values: ['id1', 'id2'],
+ },
+ },
+ },
+ ignore_unavailable: true,
});
});
- it('defaults an unknown status to open', async () => {
- await alertService.updateAlertsStatus({
- alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: 'bananas' as CaseStatuses }],
+ it('translates in-progress to acknowledged', async () => {
+ const args = {
+ alerts: [{ id: 'id1', index: '1', status: CaseStatuses['in-progress'] }],
+ scopedClusterClient: esClient,
logger,
- });
+ };
- expect(alertsClient.update).toHaveBeenCalledWith({
- id: 'alert-id-1',
- index: '.siem-signals',
- status: 'open',
- });
+ await alertService.updateAlertsStatus(args);
+
+ expect(esClient.updateByQuery).toBeCalledTimes(1);
+ expect(esClient.updateByQuery.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "body": Object {
+ "query": Object {
+ "ids": Object {
+ "values": Array [
+ "id1",
+ ],
+ },
+ },
+ "script": Object {
+ "lang": "painless",
+ "source": "if (ctx._source['kibana.alert.workflow_status'] != null) {
+ ctx._source['kibana.alert.workflow_status'] = 'acknowledged'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = 'acknowledged'
+ }",
+ },
+ },
+ "conflicts": "abort",
+ "ignore_unavailable": true,
+ "index": "1",
+ },
+ ]
+ `);
+ });
+
+ it('makes two calls when the statuses are different', async () => {
+ const args = {
+ alerts: [
+ { id: 'id1', index: '1', status: CaseStatuses.closed },
+ { id: 'id2', index: '1', status: CaseStatuses.open },
+ ],
+ scopedClusterClient: esClient,
+ logger,
+ };
+
+ await alertService.updateAlertsStatus(args);
+
+ expect(esClient.updateByQuery).toBeCalledTimes(2);
+ // id1 should be closed
+ expect(esClient.updateByQuery.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "body": Object {
+ "query": Object {
+ "ids": Object {
+ "values": Array [
+ "id1",
+ ],
+ },
+ },
+ "script": Object {
+ "lang": "painless",
+ "source": "if (ctx._source['kibana.alert.workflow_status'] != null) {
+ ctx._source['kibana.alert.workflow_status'] = 'closed'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = 'closed'
+ }",
+ },
+ },
+ "conflicts": "abort",
+ "ignore_unavailable": true,
+ "index": "1",
+ },
+ ]
+ `);
+
+ // id2 should be open
+ expect(esClient.updateByQuery.mock.calls[1]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "body": Object {
+ "query": Object {
+ "ids": Object {
+ "values": Array [
+ "id2",
+ ],
+ },
+ },
+ "script": Object {
+ "lang": "painless",
+ "source": "if (ctx._source['kibana.alert.workflow_status'] != null) {
+ ctx._source['kibana.alert.workflow_status'] = 'open'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = 'open'
+ }",
+ },
+ },
+ "conflicts": "abort",
+ "ignore_unavailable": true,
+ "index": "1",
+ },
+ ]
+ `);
+ });
+
+ it('makes two calls when the indices are different', async () => {
+ const args = {
+ alerts: [
+ { id: 'id1', index: '1', status: CaseStatuses.closed },
+ { id: 'id2', index: '2', status: CaseStatuses.open },
+ ],
+ scopedClusterClient: esClient,
+ logger,
+ };
+
+ await alertService.updateAlertsStatus(args);
+
+ expect(esClient.updateByQuery).toBeCalledTimes(2);
+ // id1 should be closed in index 1
+ expect(esClient.updateByQuery.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "body": Object {
+ "query": Object {
+ "ids": Object {
+ "values": Array [
+ "id1",
+ ],
+ },
+ },
+ "script": Object {
+ "lang": "painless",
+ "source": "if (ctx._source['kibana.alert.workflow_status'] != null) {
+ ctx._source['kibana.alert.workflow_status'] = 'closed'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = 'closed'
+ }",
+ },
+ },
+ "conflicts": "abort",
+ "ignore_unavailable": true,
+ "index": "1",
+ },
+ ]
+ `);
+
+ // id2 should be open in index 2
+ expect(esClient.updateByQuery.mock.calls[1]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "body": Object {
+ "query": Object {
+ "ids": Object {
+ "values": Array [
+ "id2",
+ ],
+ },
+ },
+ "script": Object {
+ "lang": "painless",
+ "source": "if (ctx._source['kibana.alert.workflow_status'] != null) {
+ ctx._source['kibana.alert.workflow_status'] = 'open'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = 'open'
+ }",
+ },
+ },
+ "conflicts": "abort",
+ "ignore_unavailable": true,
+ "index": "2",
+ },
+ ]
+ `);
});
- });
- describe('unhappy path', () => {
it('ignores empty indices', async () => {
- expect(
- await alertService.updateAlertsStatus({
- alerts: [{ id: 'alert-id-1', index: '', status: CaseStatuses.closed }],
- logger,
- })
- ).toBeUndefined();
+ await alertService.updateAlertsStatus({
+ alerts: [{ id: 'alert-id-1', index: '', status: CaseStatuses.open }],
+ scopedClusterClient: esClient,
+ logger,
+ });
+
+ expect(esClient.updateByQuery).not.toHaveBeenCalled();
});
});
});
diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts
index ccb0fca4f995f..6bb2fb3ee3c56 100644
--- a/x-pack/plugins/cases/server/services/alerts/index.ts
+++ b/x-pack/plugins/cases/server/services/alerts/index.ts
@@ -5,71 +5,62 @@
* 2.0.
*/
+import pMap from 'p-map';
import { isEmpty } from 'lodash';
import type { PublicMethodsOf } from '@kbn/utility-types';
-import { Logger } from 'kibana/server';
-import { CaseStatuses, MAX_ALERTS_PER_SUB_CASE } from '../../../common';
+import { ElasticsearchClient, Logger } from 'kibana/server';
+import { CaseStatuses, MAX_ALERTS_PER_SUB_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common';
import { AlertInfo, createCaseError } from '../../common';
import { UpdateAlertRequest } from '../../client/alerts/types';
-import { AlertsClient } from '../../../../rule_registry/server';
-import { Alert } from './types';
-import { STATUS_VALUES } from '../../../../rule_registry/common/technical_rule_data_field_names';
+import {
+ ALERT_WORKFLOW_STATUS,
+ STATUS_VALUES,
+} from '../../../../rule_registry/common/technical_rule_data_field_names';
export type AlertServiceContract = PublicMethodsOf;
interface UpdateAlertsStatusArgs {
alerts: UpdateAlertRequest[];
+ scopedClusterClient: ElasticsearchClient;
logger: Logger;
}
interface GetAlertsArgs {
alertsInfo: AlertInfo[];
+ scopedClusterClient: ElasticsearchClient;
logger: Logger;
}
+interface Alert {
+ _id: string;
+ _index: string;
+ _source: Record;
+}
+
+interface AlertsResponse {
+ docs: Alert[];
+}
+
function isEmptyAlert(alert: AlertInfo): boolean {
return isEmpty(alert.id) || isEmpty(alert.index);
}
export class AlertService {
- constructor(private readonly alertsClient?: PublicMethodsOf) {}
+ constructor() {}
- public async updateAlertsStatus({ alerts, logger }: UpdateAlertsStatusArgs) {
+ public async updateAlertsStatus({ alerts, scopedClusterClient, logger }: UpdateAlertsStatusArgs) {
try {
- if (!this.alertsClient) {
- throw new Error(
- 'Alert client is undefined, the rule registry plugin must be enabled to updated the status of alerts'
- );
- }
-
- const alertsToUpdate = alerts.filter((alert) => !isEmptyAlert(alert));
-
- if (alertsToUpdate.length <= 0) {
- return;
- }
-
- const updatedAlerts = await Promise.allSettled(
- alertsToUpdate.map((alert) =>
- this.alertsClient?.update({
- id: alert.id,
- index: alert.index,
- status: translateStatus({ alert, logger }),
- _version: undefined,
- })
- )
+ const bucketedAlerts = bucketAlertsByIndexAndStatus(alerts, logger);
+ const indexBuckets = Array.from(bucketedAlerts.entries());
+
+ await pMap(
+ indexBuckets,
+ async (indexBucket: [string, Map]) =>
+ updateByQuery(indexBucket, scopedClusterClient),
+ { concurrency: MAX_CONCURRENT_SEARCHES }
);
-
- updatedAlerts.forEach((updatedAlert, index) => {
- if (updatedAlert.status === 'rejected') {
- logger.error(
- `Failed to update status for alert: ${JSON.stringify(alertsToUpdate[index])}: ${
- updatedAlert.reason
- }`
- );
- }
- });
} catch (error) {
throw createCaseError({
message: `Failed to update alert status ids: ${JSON.stringify(alerts)}: ${error}`,
@@ -79,51 +70,25 @@ export class AlertService {
}
}
- public async getAlerts({ alertsInfo, logger }: GetAlertsArgs): Promise {
+ public async getAlerts({
+ scopedClusterClient,
+ alertsInfo,
+ logger,
+ }: GetAlertsArgs): Promise {
try {
- if (!this.alertsClient) {
- throw new Error(
- 'Alert client is undefined, the rule registry plugin must be enabled to retrieve alerts'
- );
- }
+ const docs = alertsInfo
+ .filter((alert) => !isEmptyAlert(alert))
+ .slice(0, MAX_ALERTS_PER_SUB_CASE)
+ .map((alert) => ({ _id: alert.id, _index: alert.index }));
- const alertsToGet = alertsInfo
- .filter((alert) => !isEmpty(alert))
- .slice(0, MAX_ALERTS_PER_SUB_CASE);
-
- if (alertsToGet.length <= 0) {
+ if (docs.length <= 0) {
return;
}
- const retrievedAlerts = await Promise.allSettled(
- alertsToGet.map(({ id, index }) => this.alertsClient?.get({ id, index }))
- );
-
- retrievedAlerts.forEach((alert, index) => {
- if (alert.status === 'rejected') {
- logger.error(
- `Failed to retrieve alert: ${JSON.stringify(alertsToGet[index])}: ${alert.reason}`
- );
- }
- });
+ const results = await scopedClusterClient.mget({ body: { docs } });
- return retrievedAlerts.map((alert, index) => {
- let source: unknown | undefined;
- let error: Error | undefined;
-
- if (alert.status === 'fulfilled') {
- source = alert.value;
- } else {
- error = alert.reason;
- }
-
- return {
- id: alertsToGet[index].id,
- index: alertsToGet[index].index,
- source,
- error,
- };
- });
+ // @ts-expect-error @elastic/elasticsearch _source is optional
+ return results.body;
} catch (error) {
throw createCaseError({
message: `Failed to retrieve alerts ids: ${JSON.stringify(alertsInfo)}: ${error}`,
@@ -134,6 +99,44 @@ export class AlertService {
}
}
+interface TranslatedUpdateAlertRequest {
+ id: string;
+ index: string;
+ status: STATUS_VALUES;
+}
+
+function bucketAlertsByIndexAndStatus(
+ alerts: UpdateAlertRequest[],
+ logger: Logger
+): Map> {
+ return alerts.reduce>>(
+ (acc, alert) => {
+ // skip any alerts that are empty
+ if (isEmptyAlert(alert)) {
+ return acc;
+ }
+
+ const translatedAlert = { ...alert, status: translateStatus({ alert, logger }) };
+ const statusToAlertId = acc.get(translatedAlert.index);
+
+ // if we haven't seen the index before
+ if (!statusToAlertId) {
+ // add a new index in the parent map, with an entry for the status the alert set to pointing
+ // to an initial array of only the current alert
+ acc.set(translatedAlert.index, createStatusToAlertMap(translatedAlert));
+ } else {
+ // We had the index in the map so check to see if we have a bucket for the
+ // status, if not add a new status entry with the alert, if so update the status entry
+ // with the alert
+ updateIndexEntryWithStatus(statusToAlertId, translatedAlert);
+ }
+
+ return acc;
+ },
+ new Map()
+ );
+}
+
function translateStatus({
alert,
logger,
@@ -157,3 +160,53 @@ function translateStatus({
}
return translatedStatus ?? 'open';
}
+
+function createStatusToAlertMap(
+ alert: TranslatedUpdateAlertRequest
+): Map {
+ return new Map([[alert.status, [alert]]]);
+}
+
+function updateIndexEntryWithStatus(
+ statusToAlerts: Map,
+ alert: TranslatedUpdateAlertRequest
+) {
+ const statusBucket = statusToAlerts.get(alert.status);
+
+ if (!statusBucket) {
+ statusToAlerts.set(alert.status, [alert]);
+ } else {
+ statusBucket.push(alert);
+ }
+}
+
+async function updateByQuery(
+ [index, statusToAlertMap]: [string, Map],
+ scopedClusterClient: ElasticsearchClient
+) {
+ const statusBuckets = Array.from(statusToAlertMap);
+ return Promise.all(
+ // this will create three update by query calls one for each of the three statuses
+ statusBuckets.map(([status, translatedAlerts]) =>
+ scopedClusterClient.updateByQuery({
+ index,
+ conflicts: 'abort',
+ body: {
+ script: {
+ source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) {
+ ctx._source['${ALERT_WORKFLOW_STATUS}'] = '${status}'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = '${status}'
+ }`,
+ lang: 'painless',
+ },
+ // the query here will contain all the ids that have the same status for the same index
+ // being updated
+ query: { ids: { values: translatedAlerts.map(({ id }) => id) } },
+ },
+ ignore_unavailable: true,
+ })
+ )
+ );
+}
diff --git a/x-pack/plugins/cases/server/services/alerts/types.ts b/x-pack/plugins/cases/server/services/alerts/types.ts
deleted file mode 100644
index 5ddc57fa5861c..0000000000000
--- a/x-pack/plugins/cases/server/services/alerts/types.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export interface Alert {
- id: string;
- index: string;
- error?: Error;
- source?: unknown;
-}
diff --git a/x-pack/plugins/cloud/README.md b/x-pack/plugins/cloud/README.md
index 0e0a1c773979a..77f73a3eaea01 100644
--- a/x-pack/plugins/cloud/README.md
+++ b/x-pack/plugins/cloud/README.md
@@ -30,6 +30,12 @@ This is the path to the Cloud deployment management page for the deployment to w
**Example:** `{baseUrl}/deployments/bfdad4ef99a24212a06d387593686d63`
+### `snapshotsUrl`
+
+This is the path to the Snapshots page for the deployment to which the Kibana instance belongs. The value is already prepended with `deploymentUrl`.
+
+**Example:** `{deploymentUrl}/elasticsearch/snapshots`
+
### `profileUrl`
This is the path to the Cloud User Profile page. The value is already prepended with `baseUrl`.
diff --git a/x-pack/plugins/cloud/common/constants.ts b/x-pack/plugins/cloud/common/constants.ts
index 8d054addc9ce2..f2009223f8ac1 100644
--- a/x-pack/plugins/cloud/common/constants.ts
+++ b/x-pack/plugins/cloud/common/constants.ts
@@ -6,3 +6,8 @@
*/
export const ELASTIC_SUPPORT_LINK = 'https://support.elastic.co/';
+
+/**
+ * This is the page for managing your snapshots on Cloud.
+ */
+export const CLOUD_SNAPSHOTS_PATH = 'elasticsearch/snapshots/';
diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts
index 9b3ddc8e7294e..835a52cb814c8 100644
--- a/x-pack/plugins/cloud/public/plugin.test.ts
+++ b/x-pack/plugins/cloud/public/plugin.test.ts
@@ -141,6 +141,65 @@ describe('Cloud Plugin', () => {
expect(initializeFullStoryMock).not.toHaveBeenCalled();
});
});
+
+ describe('interface', () => {
+ const setupPlugin = () => {
+ const initContext = coreMock.createPluginInitializerContext({
+ id: 'cloudId',
+ cname: 'cloud.elastic.co',
+ base_url: 'https://cloud.elastic.co',
+ deployment_url: '/abc123',
+ profile_url: '/user/settings/',
+ organization_url: '/account/',
+ });
+ const plugin = new CloudPlugin(initContext);
+
+ const coreSetup = coreMock.createSetup();
+ const setup = plugin.setup(coreSetup, {});
+
+ return { setup };
+ };
+
+ it('exposes isCloudEnabled', () => {
+ const { setup } = setupPlugin();
+ expect(setup.isCloudEnabled).toBe(true);
+ });
+
+ it('exposes cloudId', () => {
+ const { setup } = setupPlugin();
+ expect(setup.cloudId).toBe('cloudId');
+ });
+
+ it('exposes baseUrl', () => {
+ const { setup } = setupPlugin();
+ expect(setup.baseUrl).toBe('https://cloud.elastic.co');
+ });
+
+ it('exposes deploymentUrl', () => {
+ const { setup } = setupPlugin();
+ expect(setup.deploymentUrl).toBe('https://cloud.elastic.co/abc123');
+ });
+
+ it('exposes snapshotsUrl', () => {
+ const { setup } = setupPlugin();
+ expect(setup.snapshotsUrl).toBe('https://cloud.elastic.co/abc123/elasticsearch/snapshots/');
+ });
+
+ it('exposes profileUrl', () => {
+ const { setup } = setupPlugin();
+ expect(setup.profileUrl).toBe('https://cloud.elastic.co/user/settings/');
+ });
+
+ it('exposes organizationUrl', () => {
+ const { setup } = setupPlugin();
+ expect(setup.organizationUrl).toBe('https://cloud.elastic.co/account/');
+ });
+
+ it('exposes cname', () => {
+ const { setup } = setupPlugin();
+ expect(setup.cname).toBe('cloud.elastic.co');
+ });
+ });
});
describe('#start', () => {
diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts
index 16c11d569c5f7..29befcee397dd 100644
--- a/x-pack/plugins/cloud/public/plugin.ts
+++ b/x-pack/plugins/cloud/public/plugin.ts
@@ -20,7 +20,7 @@ import type {
SecurityPluginStart,
} from '../../security/public';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
-import { ELASTIC_SUPPORT_LINK } from '../common/constants';
+import { ELASTIC_SUPPORT_LINK, CLOUD_SNAPSHOTS_PATH } from '../common/constants';
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
import { createUserMenuLinks } from './user_menu_links';
import { getFullCloudUrl } from './utils';
@@ -54,6 +54,7 @@ export interface CloudSetup {
deploymentUrl?: string;
profileUrl?: string;
organizationUrl?: string;
+ snapshotsUrl?: string;
isCloudEnabled: boolean;
}
@@ -80,6 +81,7 @@ export class CloudPlugin implements Plugin {
deployment_url: deploymentUrl,
base_url: baseUrl,
} = this.config;
+
this.isCloudEnabled = getIsCloudEnabled(id);
if (home) {
@@ -89,13 +91,19 @@ export class CloudPlugin implements Plugin {
}
}
+ const fullCloudDeploymentUrl = getFullCloudUrl(baseUrl, deploymentUrl);
+ const fullCloudProfileUrl = getFullCloudUrl(baseUrl, profileUrl);
+ const fullCloudOrganizationUrl = getFullCloudUrl(baseUrl, organizationUrl);
+ const fullCloudSnapshotsUrl = `${fullCloudDeploymentUrl}/${CLOUD_SNAPSHOTS_PATH}`;
+
return {
cloudId: id,
cname,
baseUrl,
- deploymentUrl: getFullCloudUrl(baseUrl, deploymentUrl),
- profileUrl: getFullCloudUrl(baseUrl, profileUrl),
- organizationUrl: getFullCloudUrl(baseUrl, organizationUrl),
+ deploymentUrl: fullCloudDeploymentUrl,
+ profileUrl: fullCloudProfileUrl,
+ organizationUrl: fullCloudOrganizationUrl,
+ snapshotsUrl: fullCloudSnapshotsUrl,
isCloudEnabled: this.isCloudEnabled,
};
}
diff --git a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts
index 57f0872d62589..f04c611c2fae9 100644
--- a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts
+++ b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts
@@ -22,13 +22,13 @@ export async function lazyLoadModules(): Promise {
return loadModulesPromise;
}
- loadModulesPromise = new Promise(async (resolve) => {
- const lazyImports = await import('./lazy');
-
- resolve({
- ...lazyImports,
- getHttp: () => getCoreStart().http,
- });
+ loadModulesPromise = new Promise(async (resolve, reject) => {
+ try {
+ const lazyImports = await import('./lazy');
+ resolve({ ...lazyImports, getHttp: () => getCoreStart().http });
+ } catch (error) {
+ reject(error);
+ }
});
return loadModulesPromise;
}
diff --git a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts
index 9c7c6ff1e5180..192a7ffb5e782 100644
--- a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts
+++ b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts
@@ -44,15 +44,18 @@ export async function lazyLoadModules(): Promise {
return loadModulesPromise;
}
- loadModulesPromise = new Promise(async (resolve) => {
- const { JsonUploadAndParse, importerFactory, IndexNameForm } = await import('./lazy');
-
- resolve({
- JsonUploadAndParse,
- importerFactory,
- getHttp,
- IndexNameForm,
- });
+ loadModulesPromise = new Promise(async (resolve, reject) => {
+ try {
+ const { JsonUploadAndParse, importerFactory, IndexNameForm } = await import('./lazy');
+ resolve({
+ JsonUploadAndParse,
+ importerFactory,
+ getHttp,
+ IndexNameForm,
+ });
+ } catch (error) {
+ reject(error);
+ }
});
return loadModulesPromise;
}
diff --git a/x-pack/plugins/fleet/dev_docs/diagrams/README.md b/x-pack/plugins/fleet/dev_docs/diagrams/README.md
new file mode 100644
index 0000000000000..9266f2963bda8
--- /dev/null
+++ b/x-pack/plugins/fleet/dev_docs/diagrams/README.md
@@ -0,0 +1,5 @@
+# Fleet Diagrams
+
+This directory contains diagrams for various components and concepts around the Fleet application. Diagrams are created using https://excalidraw.com/. To edit a diagram, open the `.excalidraw` file in the Excalidraw web application, then save the file and overwrite that same `.excalidraw` file. Also, be sure to export your diagram as a `.png` and update the image version of the diagram you're editing.
+
+Excalidraw is an open source diagramming tool for creating simple, effective diagrams and illustrations. To learn more about it, check out their GitHub repository: https://github.com/excalidraw/excalidraw.
diff --git a/x-pack/plugins/fleet/dev_docs/diagrams/agent_and_package_policies/agent_and_package_policies.excalidraw b/x-pack/plugins/fleet/dev_docs/diagrams/agent_and_package_policies/agent_and_package_policies.excalidraw
new file mode 100644
index 0000000000000..322072e9b3fc1
--- /dev/null
+++ b/x-pack/plugins/fleet/dev_docs/diagrams/agent_and_package_policies/agent_and_package_policies.excalidraw
@@ -0,0 +1,1474 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 51,
+ "versionNonce": 1267934455,
+ "isDeleted": false,
+ "id": "jCSDMPOBujx9LJaInpsmb",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 361.341552734375,
+ "y": 277.3671875,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "width": 337.774169921875,
+ "height": 287.30877685546875,
+ "seed": 153566809,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "ICRtE__eCR4UTo54frrx-",
+ "k37m5R6GkzTaorYtt3Xob"
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 173,
+ "versionNonce": 1761775033,
+ "isDeleted": false,
+ "id": "PAD-D1XTaAjZXPugghCBO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1414.7705078125,
+ "y": 781.1680567930013,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "width": 166.18115234374997,
+ "height": 170.1334263612956,
+ "seed": 1715473209,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 152,
+ "versionNonce": 768008215,
+ "isDeleted": false,
+ "id": "kPJHqu5N5Nz07F4OyyRZ2",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 972.4078369140625,
+ "y": 1035.4315913388996,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "width": 166.18115234374997,
+ "height": 170.1334263612956,
+ "seed": 1147943961,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 200,
+ "versionNonce": 1510222295,
+ "isDeleted": false,
+ "id": "WCA2gYGRwxjLLbGTL7lQz",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 413.5852922712054,
+ "y": 1332.2280739700382,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "width": 166.18115234374997,
+ "height": 170.1334263612956,
+ "seed": 1939820793,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "text",
+ "version": 64,
+ "versionNonce": 267668567,
+ "isDeleted": false,
+ "id": "oAAcAXFQUIh1OXXiBgjIy",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 462.53369140625,
+ "y": 232.99232482910156,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 111,
+ "height": 23,
+ "seed": 2036124121,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Agent Policy",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 710,
+ "versionNonce": 1450617207,
+ "isDeleted": false,
+ "id": "ZFPuYyK96Pnyx_DiXJaIj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 771.77685546875,
+ "y": 28.173828125,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 497,
+ "height": 90,
+ "seed": 1589126841,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "From the Fleet UI:\n\"Agent policies are used to manage settings across a group of agents.\nYou can add integrations to your agent policy to specify what data\nyour agents collect. When you edit an agent policy, you can use Fleet\nto deploy updates to a specified group of agents.\"",
+ "baseline": 86,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "rectangle",
+ "version": 144,
+ "versionNonce": 1857381081,
+ "isDeleted": false,
+ "id": "8IzDo67r_O6baz2uZKak2",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 400.1982421875,
+ "y": 319.10589599609375,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 97.571044921875,
+ "height": 72.79846191406249,
+ "seed": 1045751705,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 169,
+ "versionNonce": 1660536185,
+ "isDeleted": false,
+ "id": "r5G4QOIwl4hHi0J7gGnAH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 555.88818359375,
+ "y": 317.6759948730469,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 97.571044921875,
+ "height": 72.79846191406249,
+ "seed": 308710521,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 231,
+ "versionNonce": 1853140569,
+ "isDeleted": false,
+ "id": "DI_-yig5vVdW0SzPQR9oo",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 480.9833984375,
+ "y": 438.5256042480469,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 97.571044921875,
+ "height": 72.79846191406249,
+ "seed": 1118868825,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "text",
+ "version": 82,
+ "versionNonce": 317743767,
+ "isDeleted": false,
+ "id": "yfHUHwT3vzgVC1MhduPmt",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 413.1236572265625,
+ "y": 334.87489318847656,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 62,
+ "height": 36,
+ "seed": 1921417785,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Package\nPolicy",
+ "baseline": 32,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 105,
+ "versionNonce": 1175844791,
+ "isDeleted": false,
+ "id": "S1f9df1N4LLQftkY_gave",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 568.8135986328125,
+ "y": 333.4449920654297,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 62,
+ "height": 36,
+ "seed": 248238873,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Package\nPolicy",
+ "baseline": 32,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 169,
+ "versionNonce": 1712056535,
+ "isDeleted": false,
+ "id": "4uxqA-SJ4vBI6xhYKNg_v",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 493.9088134765625,
+ "y": 454.2946014404297,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 62,
+ "height": 36,
+ "seed": 468977657,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Package\nPolicy",
+ "baseline": 32,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "line",
+ "version": 123,
+ "versionNonce": 1805071671,
+ "isDeleted": false,
+ "id": "cBItdwVqKieCg1ZEXRgGV",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 159.45556640625,
+ "y": 217.94335937500003,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 222.562255859375,
+ "height": 131.63372802734375,
+ "seed": 1141022937,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 222.562255859375,
+ 131.63372802734375
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 372,
+ "versionNonce": 1502405593,
+ "isDeleted": false,
+ "id": "JQ1mX_4n7ge602lI_TuZN",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -142.84326171875,
+ "y": 127.57491387261291,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 426,
+ "height": 72,
+ "seed": 613096889,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "A Package Policy defines the package-specific configuration\nvalues for a given package. This is things like \"log file path\"\nor \"authentication token\" that each integration needs to\nproperly find and work with your data",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "line",
+ "version": 65,
+ "versionNonce": 1104581495,
+ "isDeleted": false,
+ "id": "C4-1o5prqQ6DoEaEXwJTp",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 762.08740234375,
+ "y": 52.033111572265625,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 178.7421875,
+ "height": 176.862060546875,
+ "seed": 1555103385,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -178.7421875,
+ 176.862060546875
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 52,
+ "versionNonce": 1009442583,
+ "isDeleted": false,
+ "id": "xcT17WwuVaLLV0-QkLnST",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1480.361083984375,
+ "y": 856.234769973649,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 33,
+ "height": 18,
+ "seed": 1529332601,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Host",
+ "baseline": 14,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 31,
+ "versionNonce": 1271718967,
+ "isDeleted": false,
+ "id": "9PEFqQm8bDloEjfLbiv_E",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1037.9984130859375,
+ "y": 1110.4983045195472,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 33,
+ "height": 18,
+ "seed": 1815276633,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Host",
+ "baseline": 14,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 77,
+ "versionNonce": 1597064535,
+ "isDeleted": false,
+ "id": "ZVH75zUF1nLScxrub3BtK",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 479.5023193359375,
+ "y": 1407.6214124297037,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 33,
+ "height": 18,
+ "seed": 128317753,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Host",
+ "baseline": 14,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "rectangle",
+ "version": 78,
+ "versionNonce": 1781660793,
+ "isDeleted": false,
+ "id": "TQHtZyAi5ZtWXfPJOuG4h",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1368.635480608259,
+ "y": 751.2757611955917,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fab005",
+ "width": 73.6383056640625,
+ "height": 61.18141174316406,
+ "seed": 1592027673,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 59,
+ "versionNonce": 114186137,
+ "isDeleted": false,
+ "id": "6Qh6yaaWu_smWgF8lh7ts",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 926.2728097098216,
+ "y": 1005.5392957414899,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fab005",
+ "width": 73.6383056640625,
+ "height": 61.18141174316406,
+ "seed": 719437561,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "zlS4f5wvhfJmvxzL36_uN"
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 104,
+ "versionNonce": 239709239,
+ "isDeleted": false,
+ "id": "dYVSd7vzqd5mQVhpIJhoU",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 367.7767159598217,
+ "y": 1302.6624036516464,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fab005",
+ "width": 73.6383056640625,
+ "height": 61.18141174316406,
+ "seed": 975271897,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "VXjQzRP5z49NVL_qeGJ8K"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 62,
+ "versionNonce": 95191671,
+ "isDeleted": false,
+ "id": "SG27_ypnubzKpWUjog7Ju",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1377.767560686384,
+ "y": 722.1002393450058,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 93,
+ "height": 18,
+ "seed": 1052185785,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Elastic Agent",
+ "baseline": 14,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 41,
+ "versionNonce": 1879251863,
+ "isDeleted": false,
+ "id": "ME2vZZNpg21fqavHSGyAe",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 935.4048897879466,
+ "y": 976.363773890904,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 93,
+ "height": 18,
+ "seed": 1548096921,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Elastic Agent",
+ "baseline": 14,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 88,
+ "versionNonce": 1114214583,
+ "isDeleted": false,
+ "id": "uBaJ2BrvmY41RYKO9AHWc",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 376.9087960379467,
+ "y": 1273.4868818010605,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 93,
+ "height": 18,
+ "seed": 1979841145,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "fvhIgP7_0OlLXgaCi-Gcx"
+ ],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Elastic Agent",
+ "baseline": 14,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "rectangle",
+ "version": 142,
+ "versionNonce": 869511383,
+ "isDeleted": false,
+ "id": "qbJ3HumuDumzmdo6S2uzH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 736.9051339285717,
+ "y": 635.138371058873,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 162.408447265625,
+ "height": 148.80645751953125,
+ "seed": 1018053465,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "ICRtE__eCR4UTo54frrx-",
+ "zovK4QCBSc4despIqL_Px",
+ "zlS4f5wvhfJmvxzL36_uN",
+ "fvhIgP7_0OlLXgaCi-Gcx"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 102,
+ "versionNonce": 646210007,
+ "isDeleted": false,
+ "id": "b-H9gE4GD7_H2Th50aq7g",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 794.1091134207592,
+ "y": 699.2558789934433,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 36,
+ "height": 18,
+ "seed": 2090758201,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Fleet",
+ "baseline": 14,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "arrow",
+ "version": 91,
+ "versionNonce": 326695159,
+ "isDeleted": false,
+ "id": "ICRtE__eCR4UTo54frrx-",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 719.3434884207592,
+ "y": 394.94458443777916,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 146.2176513671875,
+ "height": 229.08706665039068,
+ "seed": 733348121,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "jCSDMPOBujx9LJaInpsmb",
+ "focus": -0.33950373459679334,
+ "gap": 20.227765764509172
+ },
+ "endBinding": {
+ "elementId": "qbJ3HumuDumzmdo6S2uzH",
+ "focus": 0.11061733892271751,
+ "gap": 11.106719970703125
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 146.2176513671875,
+ 25.181121826171875
+ ],
+ [
+ 119.8638916015625,
+ 229.08706665039068
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 62,
+ "versionNonce": 704430615,
+ "isDeleted": false,
+ "id": "zovK4QCBSc4despIqL_Px",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 511.8593575613842,
+ "y": 604.3765760149277,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 210.1806640625,
+ "height": 135.83206176757812,
+ "seed": 929592825,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "qbJ3HumuDumzmdo6S2uzH",
+ "focus": -0.1822256724307969,
+ "gap": 14.8651123046875
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 32.21954345703125,
+ 135.83206176757812
+ ],
+ [
+ 210.1806640625,
+ 125.29045104980469
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 193,
+ "versionNonce": 191522615,
+ "isDeleted": false,
+ "id": "LegMRWZJRxEaNbMqed2xD",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 932.9982735770092,
+ "y": 702.8953290666855,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 411.3313205592334,
+ "height": 77.14139261320236,
+ "seed": 314134233,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 411.3313205592334,
+ 77.14139261320236
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 81,
+ "versionNonce": 1982228567,
+ "isDeleted": false,
+ "id": "zlS4f5wvhfJmvxzL36_uN",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 832.9335763113842,
+ "y": 805.9544873918808,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 84.757080078125,
+ "height": 189.8906707763672,
+ "seed": 71044025,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "qbJ3HumuDumzmdo6S2uzH",
+ "focus": 0.24655542632406335,
+ "gap": 22.009658813476562
+ },
+ "endBinding": {
+ "elementId": "6Qh6yaaWu_smWgF8lh7ts",
+ "focus": -0.5432643496291523,
+ "gap": 9.69413757324196
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 84.757080078125,
+ 189.8906707763672
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 66,
+ "versionNonce": 1745175927,
+ "isDeleted": false,
+ "id": "fvhIgP7_0OlLXgaCi-Gcx",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 741.0500313895092,
+ "y": 812.9967237200058,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 294.090576171875,
+ "height": 447.2994232177732,
+ "seed": 873737369,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "qbJ3HumuDumzmdo6S2uzH",
+ "focus": 0.06946984295991476,
+ "gap": 29.051895141601562
+ },
+ "endBinding": {
+ "elementId": "uBaJ2BrvmY41RYKO9AHWc",
+ "focus": 0.02169307095704409,
+ "gap": 13.190734863281477
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -294.090576171875,
+ 447.2994232177732
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 276,
+ "versionNonce": 493358839,
+ "isDeleted": false,
+ "id": "y1SRfopGf18PxGafEF1WF",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 935.0032784598218,
+ "y": 598.9334302629745,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 375,
+ "height": 72,
+ "seed": 2046112121,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Fleet deploys changes in agent and package policies\nto the appropriate instances of Elastic Agent on \nyour hosts\n",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "diamond",
+ "version": 88,
+ "versionNonce": 714250359,
+ "isDeleted": false,
+ "id": "Ad3mw-Goi0x9E-DxQFE5k",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -271.1817190987721,
+ "y": 827.6216626848495,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 199.51019287109375,
+ "height": 204.44686889648438,
+ "seed": 1797429849,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "VXjQzRP5z49NVL_qeGJ8K",
+ "k37m5R6GkzTaorYtt3Xob",
+ "QyaYrSSog2TXjwVv5EhBZ"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 57,
+ "versionNonce": 1277196855,
+ "isDeleted": false,
+ "id": "0bSe54FwYFi2BASHcvDDp",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -221.1610674176895,
+ "y": 915.6001020159042,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 95,
+ "height": 18,
+ "seed": 678422329,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Elasticsearch",
+ "baseline": 14,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "arrow",
+ "version": 104,
+ "versionNonce": 1741115895,
+ "isDeleted": false,
+ "id": "VXjQzRP5z49NVL_qeGJ8K",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 363.6754586356029,
+ "y": 1358.575184413365,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 541.7135620117188,
+ "height": 325.7031249999998,
+ "seed": 1816172569,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "dYVSd7vzqd5mQVhpIJhoU",
+ "focus": -0.7695999316996307,
+ "gap": 4.10125732421875
+ },
+ "endBinding": {
+ "elementId": "Ad3mw-Goi0x9E-DxQFE5k",
+ "focus": 0.49594887042146074,
+ "gap": 12.8125333757949
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -419.03289794921875,
+ 10.7666015625
+ ],
+ [
+ -541.7135620117188,
+ -314.9365234374998
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 111,
+ "versionNonce": 1964505879,
+ "isDeleted": false,
+ "id": "k37m5R6GkzTaorYtt3Xob",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 345.0048043387279,
+ "y": 562.4162793840683,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 469.6044921875,
+ "height": 305.5834197998047,
+ "seed": 1485319417,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "jCSDMPOBujx9LJaInpsmb",
+ "focus": -0.08228998680334919,
+ "gap": 16.336748395647078
+ },
+ "endBinding": {
+ "elementId": "Ad3mw-Goi0x9E-DxQFE5k",
+ "focus": -0.30691531065972416,
+ "gap": 5.313236572271876
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -469.6044921875,
+ 305.5834197998047
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 277,
+ "versionNonce": 688651575,
+ "isDeleted": false,
+ "id": "SYRzOizCOgBI6P2KVJ7aR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -177.04597691127208,
+ "y": 597.6700025285995,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 376,
+ "height": 36,
+ "seed": 80356825,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Agent and package policies are stored as documents\nin an Elasticsearch index",
+ "baseline": 32,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 220,
+ "versionNonce": 9847287,
+ "isDeleted": false,
+ "id": "epU2HYr0tZyZXYpcYtyKW",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -118.87825230189708,
+ "y": 1152.7445417131696,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 479,
+ "height": 36,
+ "seed": 1276773049,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Instances of Elastic Agent log data into Elasticsearch\nbased on the configuration provided by Agent and Package Policies",
+ "baseline": 32,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 343,
+ "versionNonce": 783899511,
+ "isDeleted": false,
+ "id": "GzkxiI7rD24zcJpAvYSQr",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -286.5030691964283,
+ "y": 272.74301583426353,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 470,
+ "height": 72,
+ "seed": 1528513433,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "A Package Policy also defines the version of its package,\nwhich is where we can have to deal with conflicts when upgrading.\nIf a newer version of a package contains breaking changes, we\ncan't automatically upgrade it for you.",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "rectangle",
+ "version": 61,
+ "versionNonce": 281444761,
+ "isDeleted": false,
+ "id": "oLLo7vMvZV-LCw7Qf5ykm",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -752.4035517838441,
+ "y": 697.4313855852402,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 222.31741373975953,
+ "height": 140.634765625,
+ "seed": 2114021497,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "QyaYrSSog2TXjwVv5EhBZ"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 153,
+ "versionNonce": 1957082329,
+ "isDeleted": false,
+ "id": "MU7q34-wC6O4jjGFpCBfK",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -721.4141758510042,
+ "y": 748.2946515764511,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 153,
+ "height": 36,
+ "seed": 781185369,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Integrations UI\n\"Browse Integrations\"",
+ "baseline": 32,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "arrow",
+ "version": 86,
+ "versionNonce": 703364567,
+ "isDeleted": false,
+ "id": "QyaYrSSog2TXjwVv5EhBZ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -502.6587698800221,
+ "y": 765.8922315325058,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 259.82879638671875,
+ "height": 99.79354858398438,
+ "seed": 1645895225,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "oLLo7vMvZV-LCw7Qf5ykm",
+ "focus": -0.08478862194745694,
+ "gap": 27.4273681640625
+ },
+ "endBinding": {
+ "elementId": "Ad3mw-Goi0x9E-DxQFE5k",
+ "focus": 0.1744581519544163,
+ "gap": 24.518520059056954
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 111.47491455078125,
+ 3.54339599609375
+ ],
+ [
+ 259.82879638671875,
+ 99.79354858398438
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 387,
+ "versionNonce": 1859398071,
+ "isDeleted": false,
+ "id": "nh1QLEj1izgVCnunVEO2_",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -849.8984549386158,
+ "y": 866.8778424944198,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 469,
+ "height": 54,
+ "seed": 394538777,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Installing a package (or updating it) fetches and downloads\ndata/configuration for various Kibana assets like visualization\ndashboards, and saved queries that are persisted to Elasticsearch",
+ "baseline": 50,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 73,
+ "versionNonce": 2083773143,
+ "isDeleted": false,
+ "id": "d7Q1XMjK0XTHyx14X-aqp",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": -454.7702811104908,
+ "y": 724.5928388323105,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 143,
+ "height": 18,
+ "seed": 625009657,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "Package Installation",
+ "baseline": 14,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "id": "A2f1bpo856LR6-K-v3DT_",
+ "type": "text",
+ "x": 910.3316127232135,
+ "y": -119.16315133231035,
+ "width": 540,
+ "height": 23,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 1192664215,
+ "version": 635,
+ "versionNonce": 1706323607,
+ "isDeleted": false,
+ "boundElementIds": null,
+ "text": "https://www.elastic.co/guide/en/fleet/current/agent-policy.html",
+ "fontSize": 20,
+ "fontFamily": 2,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 18
+ },
+ {
+ "id": "-XfKzGYSopxn4u_7uIdbS",
+ "type": "text",
+ "x": 988.6153738839284,
+ "y": -162.61463710239912,
+ "width": 295,
+ "height": 23,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 1130826905,
+ "version": 527,
+ "versionNonce": 1358998105,
+ "isDeleted": false,
+ "boundElementIds": null,
+ "text": "Further reading in the Fleet docs:",
+ "fontSize": 20,
+ "fontFamily": 2,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 18
+ },
+ {
+ "id": "cyDCNynYv30bjeaXKD9Ba",
+ "type": "text",
+ "x": -85.06382533482173,
+ "y": -163.4971575055796,
+ "width": 784,
+ "height": 41,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#868e96",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 73034167,
+ "version": 238,
+ "versionNonce": 20813655,
+ "isDeleted": false,
+ "boundElementIds": null,
+ "text": "Fleet: Agent Policy and Package Policy Overview",
+ "fontSize": 36,
+ "fontFamily": 2,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 33
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/fleet/dev_docs/diagrams/agent_and_package_policies/agent_and_package_policies.png b/x-pack/plugins/fleet/dev_docs/diagrams/agent_and_package_policies/agent_and_package_policies.png
new file mode 100644
index 0000000000000..833aaea548baf
Binary files /dev/null and b/x-pack/plugins/fleet/dev_docs/diagrams/agent_and_package_policies/agent_and_package_policies.png differ
diff --git a/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.excalidraw b/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.excalidraw
new file mode 100644
index 0000000000000..bfd73693d156d
--- /dev/null
+++ b/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.excalidraw
@@ -0,0 +1,1155 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 179,
+ "versionNonce": 685899255,
+ "isDeleted": false,
+ "id": "V2N6gERGsLagZObwBdXd7",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 436.238525390625,
+ "y": 395.07281494140625,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 213.742431640625,
+ "height": 173.1971435546875,
+ "seed": 988730617,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "mTWnMpUdsVV8ceAQPLs9-",
+ "pvB3kVjq9ORzyG3vGuzUv",
+ "nDieSVBYlEY32xmCjba1n",
+ "XfXZwtl0lYMcyaqGm81Kp"
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 735,
+ "versionNonce": 449044823,
+ "isDeleted": false,
+ "id": "kIj9AWA5qKL3NUJwu4eLZ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 992.779296875,
+ "y": 396.75579833984375,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 344.20092773437506,
+ "height": 190.58715820312506,
+ "seed": 1071204249,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "mTWnMpUdsVV8ceAQPLs9-"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 133,
+ "versionNonce": 1290191769,
+ "isDeleted": false,
+ "id": "Sq56zbCalmhyWY5U75WQO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 483.9796142578125,
+ "y": 464.82843017578125,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 119,
+ "height": 23,
+ "seed": 1562982743,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Elasticsearch",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 361,
+ "versionNonce": 1821089399,
+ "isDeleted": false,
+ "id": "CR7I2o5UwGFuW4BgnWZaG",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1128.2576904296875,
+ "y": 417.8507080078125,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 62,
+ "height": 23,
+ "seed": 989026935,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Kibana",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "arrow",
+ "version": 1062,
+ "versionNonce": 710731897,
+ "isDeleted": false,
+ "id": "mTWnMpUdsVV8ceAQPLs9-",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 982.638282831786,
+ "y": 478.7558104844152,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 315.93228450395384,
+ "height": 0,
+ "seed": 36584409,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "kIj9AWA5qKL3NUJwu4eLZ",
+ "focus": 0.1395011823705664,
+ "gap": 10.141014043214
+ },
+ "endBinding": {
+ "elementId": "V2N6gERGsLagZObwBdXd7",
+ "focus": -0.03366771731329646,
+ "gap": 16.725041296582162
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -315.93228450395384,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 408,
+ "versionNonce": 1688123287,
+ "isDeleted": false,
+ "id": "TyDRowB3-A60lvI91x-o2",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 121.66904994419644,
+ "y": 270.96874509538947,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 534,
+ "height": 92,
+ "seed": 1004167511,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Elasticsearch is the database and search engine that serves\nas our primary data storage solution.\n\nhttps://github.com/elastic/elasticsearch",
+ "baseline": 87,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 368,
+ "versionNonce": 1132163417,
+ "isDeleted": false,
+ "id": "5FN2StmgJi8nXLKDFU7EK",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 924.5206560407366,
+ "y": 234.77730233328708,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 691,
+ "height": 115,
+ "seed": 963312409,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Kibana is an application for drawing insights from data stored in Elasticsearch.\nIt allows users to create visualizations, dashboards, and generally \"work with\"\ntheir Elasticsearch data.\n\nhttps://github.com/elastic/kibana",
+ "baseline": 110,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "rectangle",
+ "version": 342,
+ "versionNonce": 1704493145,
+ "isDeleted": false,
+ "id": "2YasfPXKDkgcXLd5j2qZs",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1049.251220703125,
+ "y": 510.0099792480471,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 74.8330078125,
+ "height": 45.19232177734375,
+ "seed": 65317399,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "r-wdwO5ci-a7MpE-y5HHT"
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 487,
+ "versionNonce": 1301667385,
+ "isDeleted": false,
+ "id": "Bjhq9XGHwH9zHMRZsbgW-",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1159.63134765625,
+ "y": 509.9478149414065,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 124.51538085937506,
+ "height": 44.797912597656264,
+ "seed": 476169783,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "1MDWgu4QN3q_Sd2HSW47y"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 46,
+ "versionNonce": 1709768151,
+ "isDeleted": false,
+ "id": "mlrD-I4ZhtIrHXwvei4Wd",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1063.972412109375,
+ "y": 521.0379638671877,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 44,
+ "height": 23,
+ "seed": 241556921,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "XfXZwtl0lYMcyaqGm81Kp"
+ ],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Fleet",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 75,
+ "versionNonce": 126856985,
+ "isDeleted": false,
+ "id": "xcyVzTHZBNYhZvx5HN3XN",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1168.656982421875,
+ "y": 522.1023864746096,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 105,
+ "height": 23,
+ "seed": 743264983,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "ZTtsb0ncxBs3kQPYAaOx6",
+ "1MDWgu4QN3q_Sd2HSW47y"
+ ],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Integrations",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 620,
+ "versionNonce": 1037642487,
+ "isDeleted": false,
+ "id": "rPQSe2HeOxRDG2k3smf1c",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1525.3031005859375,
+ "y": 453.37584686279325,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 690,
+ "height": 184,
+ "seed": 1082783449,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "r-wdwO5ci-a7MpE-y5HHT",
+ "ZTtsb0ncxBs3kQPYAaOx6"
+ ],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Fleet and Integrations are both Kibana applications. Kibana's architecture\nallows for many plugins/application to exist and register themselves as distinct\nexperiences. The Fleet team mainly works on these two Kibana applications \nwithin the `xpack/plugins/fleet` directory in the Kibana codebase.\n\nhttps://github.com/elastic/kibana/tree/master/x-pack/plugins/fleet\nhttps://www.elastic.co/guide/en/fleet/current/fleet-overview.html\n",
+ "baseline": 179,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "arrow",
+ "version": 599,
+ "versionNonce": 131884025,
+ "isDeleted": false,
+ "id": "r-wdwO5ci-a7MpE-y5HHT",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1093.01806640625,
+ "y": 565.9965515136721,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 416.0875244140625,
+ "height": 54.26768493652344,
+ "seed": 776118103,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "2YasfPXKDkgcXLd5j2qZs",
+ "focus": -0.1668170334553714,
+ "gap": 10.79425048828125
+ },
+ "endBinding": {
+ "elementId": "rPQSe2HeOxRDG2k3smf1c",
+ "focus": 0.26039354359839256,
+ "gap": 16.197509765625
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 123.56658935546875,
+ 54.26768493652344
+ ],
+ [
+ 416.0875244140625,
+ 2.990859186771104
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 653,
+ "versionNonce": 1032238103,
+ "isDeleted": false,
+ "id": "ZTtsb0ncxBs3kQPYAaOx6",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1288.723876953125,
+ "y": 533.1626204834321,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 222.1070556640625,
+ "height": 0.7645838520107873,
+ "seed": 2076436631,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "xcyVzTHZBNYhZvx5HN3XN",
+ "focus": -0.06053626019021739,
+ "gap": 15.06689453125
+ },
+ "endBinding": {
+ "elementId": "rPQSe2HeOxRDG2k3smf1c",
+ "focus": 0.10957668138586957,
+ "gap": 14.47216796875
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 222.1070556640625,
+ 0.7645838520107873
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 735,
+ "versionNonce": 1249869017,
+ "isDeleted": false,
+ "id": "Imn7uBkfBKF8X1xHiyTHH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1013.7548479352681,
+ "y": 960.8284748622353,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 138.45977783203125,
+ "height": 104.25430297851562,
+ "seed": 1995457209,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "XfXZwtl0lYMcyaqGm81Kp",
+ "rcBs7GMovRmTAgrwdgREc"
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 879,
+ "versionNonce": 1021712695,
+ "isDeleted": false,
+ "id": "zIFms6BgkiaXuCZ3Dstjq",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1843.5347726004468,
+ "y": 225.40433502197283,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 138.45977783203125,
+ "height": 104.25430297851562,
+ "seed": 800927735,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "XfXZwtl0lYMcyaqGm81Kp",
+ "rcBs7GMovRmTAgrwdgREc",
+ "1MDWgu4QN3q_Sd2HSW47y"
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 551,
+ "versionNonce": 2131524025,
+ "isDeleted": false,
+ "id": "bLDMJ_hQTzNBpgToU5_Yl",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1425.2330322265625,
+ "y": 1206.882377624512,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 138.45977783203125,
+ "height": 104.25430297851562,
+ "seed": 1106799383,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 485,
+ "versionNonce": 559667799,
+ "isDeleted": false,
+ "id": "7JDfqTDwIN2xz4PmaL4gw",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1654.8622436523438,
+ "y": 1208.4525833129885,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 138.45977783203125,
+ "height": 104.25430297851562,
+ "seed": 49925015,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 557,
+ "versionNonce": 380033689,
+ "isDeleted": false,
+ "id": "qDVE2iKSwuTdBdChLu82k",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1543.7459106445312,
+ "y": 1353.0563125610354,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 138.45977783203125,
+ "height": 104.25430297851562,
+ "seed": 1016936217,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": []
+ },
+ {
+ "type": "text",
+ "version": 686,
+ "versionNonce": 800880503,
+ "isDeleted": false,
+ "id": "oj0dvttEN4vwF-Kl_1lHe",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1051.7705644880025,
+ "y": 989.6699055262978,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 59,
+ "height": 46,
+ "seed": 529766617,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Fleet\nServer",
+ "baseline": 41,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 412,
+ "versionNonce": 302129017,
+ "isDeleted": false,
+ "id": "Rlg7jCRCcqGnqdtvrL4rK",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1460.5632019042969,
+ "y": 1230.2098159790041,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 59,
+ "height": 46,
+ "seed": 1214821849,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Elastic\nAgent",
+ "baseline": 41,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 445,
+ "versionNonce": 508326039,
+ "isDeleted": false,
+ "id": "edULedUulQbCH7XzbB3oc",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1689.2619323730469,
+ "y": 1234.8031692504885,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 59,
+ "height": 46,
+ "seed": 453746009,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Elastic\nAgent",
+ "baseline": 41,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 517,
+ "versionNonce": 258066521,
+ "isDeleted": false,
+ "id": "cFajl1LyB1pqxV__qFwAR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1578.1455993652344,
+ "y": 1379.4068984985354,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 59,
+ "height": 46,
+ "seed": 1346904823,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Elastic\nAgent",
+ "baseline": 41,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 1007,
+ "versionNonce": 368912151,
+ "isDeleted": false,
+ "id": "WGfbkzIrz40PuSkc72Cce",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 609.0722743443082,
+ "y": 969.6241356985911,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 532,
+ "height": 138,
+ "seed": 1963132567,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "XfXZwtl0lYMcyaqGm81Kp"
+ ],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Fleet Server is a specialized instance of \nElastic agent that facilitates communication\nbetween agents and Kibana \n\nhttps://github.com/elastic/fleet-server\nhttps://www.elastic.co/guide/en/fleet/current/fleet-server.html",
+ "baseline": 133,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "rectangle",
+ "version": 262,
+ "versionNonce": 412702009,
+ "isDeleted": false,
+ "id": "pvM13ftbV86OXPQbe4T6t",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1384.455749511719,
+ "y": 1174.0904769897463,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 481.5649414062498,
+ "height": 308.5127258300781,
+ "seed": 1990800761,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [
+ "rcBs7GMovRmTAgrwdgREc",
+ "pvB3kVjq9ORzyG3vGuzUv",
+ "yUYmtiUOwJFi9clnke8SS",
+ "nDieSVBYlEY32xmCjba1n"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 310,
+ "versionNonce": 1480357591,
+ "isDeleted": false,
+ "id": "jxL8eFo0sUVQDJe3gOSKD",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1542.77392578125,
+ "y": 1137.9500503540041,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 166,
+ "height": 23,
+ "seed": 1837892663,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "User Infrastructure",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 337,
+ "versionNonce": 822887961,
+ "isDeleted": false,
+ "id": "YrnyLD8I1NiBU9BmUbTJQ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1912.2590942382812,
+ "y": 1175.54305267334,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 775,
+ "height": 138,
+ "seed": 1455174073,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Users run many instances of Elastic Agent across their own infrastructure. Elastic Agent\nis a single unified process that ships data from a user's various hosts to Elasticsearch.\n\nhttps://www.elastic.co/guide/en/fleet/current/elastic-agent-installation-configuration.html\n\n",
+ "baseline": 133,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "arrow",
+ "version": 1049,
+ "versionNonce": 1767212023,
+ "isDeleted": false,
+ "id": "rcBs7GMovRmTAgrwdgREc",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1477.545755637168,
+ "y": 1163.9214096069338,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 312.0071552604936,
+ "height": 147.13359654894543,
+ "seed": 1054102999,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "pvM13ftbV86OXPQbe4T6t",
+ "focus": 0.3546665319688088,
+ "gap": 10.169067382812386
+ },
+ "endBinding": {
+ "elementId": "Imn7uBkfBKF8X1xHiyTHH",
+ "focus": -0.4140166821578487,
+ "gap": 13.323974609375
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -312.0071552604936,
+ -147.13359654894543
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 1183,
+ "versionNonce": 1287536889,
+ "isDeleted": false,
+ "id": "XfXZwtl0lYMcyaqGm81Kp",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 647.0474255528405,
+ "y": 588.5818083021378,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 425.2489452715446,
+ "height": 357.9939657544336,
+ "seed": 265609785,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "V2N6gERGsLagZObwBdXd7",
+ "focus": 0.10993484764691264,
+ "gap": 20.31184980604405
+ },
+ "endBinding": {
+ "elementId": "WGfbkzIrz40PuSkc72Cce",
+ "focus": 0.8810288444398049,
+ "gap": 23.0483616420197
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 425.2489452715446,
+ 357.9939657544336
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 789,
+ "versionNonce": 1242557719,
+ "isDeleted": false,
+ "id": "zkoyoEzKXeU5U4HIjMdDH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1116.3367222377235,
+ "y": 749.5580869402207,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 500,
+ "height": 92,
+ "seed": 577559831,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Fleet allows users to configure and manage their Elastic\nagent instances through policies and integrations. A user\ncan install and configure many integrations and many\nvariations of policies to fit their needs.",
+ "baseline": 87,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 164,
+ "versionNonce": 285365209,
+ "isDeleted": false,
+ "id": "tjvU1XwUIN5tQqqWHNLC0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 492.0342668805806,
+ "y": 1412.2311761038645,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 375,
+ "height": 23,
+ "seed": 47976599,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Elastic Agent ships data into Elasticsearch",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 218,
+ "versionNonce": 903885367,
+ "isDeleted": false,
+ "id": "pSUCExEI8cZ734R4RVt_J",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1873.7646615164624,
+ "y": 243.03148651123064,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 78,
+ "height": 69,
+ "seed": 918997657,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "Elastic\nPackage\nRegistry",
+ "baseline": 64,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "text",
+ "version": 322,
+ "versionNonce": 1025274041,
+ "isDeleted": false,
+ "id": "xd1MPZCvDDnUaOOjuVJ60",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2025.6554391043528,
+ "y": 233.5708541870119,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 599,
+ "height": 92,
+ "seed": 1783431543,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "EPR is a service that hosts Elastic Agent packages/integrations and\nfacilitates their installation\n\nhttps://github.com/elastic/integrations",
+ "baseline": 87,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "arrow",
+ "version": 474,
+ "versionNonce": 95081303,
+ "isDeleted": false,
+ "id": "1MDWgu4QN3q_Sd2HSW47y",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1263.4936615088634,
+ "y": 494.9764491489956,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 558.7165610078778,
+ "height": 213.48906026083841,
+ "seed": 103520537,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "Bjhq9XGHwH9zHMRZsbgW-",
+ "focus": -0.4649028979780671,
+ "gap": 14.971365792410893
+ },
+ "endBinding": {
+ "elementId": "zIFms6BgkiaXuCZ3Dstjq",
+ "focus": 0.3899896247441985,
+ "gap": 21.324550083705617
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 558.7165610078778,
+ -213.48906026083841
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 110,
+ "versionNonce": 497021337,
+ "isDeleted": false,
+ "id": "nDieSVBYlEY32xmCjba1n",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1374.3800223214287,
+ "y": 1324.043823242188,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 907.2858537946428,
+ "height": 745.058855329241,
+ "seed": 1198688215,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "pvM13ftbV86OXPQbe4T6t",
+ "focus": 0.04062669074692265,
+ "gap": 10.075727190290195
+ },
+ "endBinding": {
+ "elementId": "V2N6gERGsLagZObwBdXd7",
+ "focus": 0.7306499810608883,
+ "gap": 17.800667898995812
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "arrow",
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -869.7869001116071,
+ 7.085658482142662
+ ],
+ [
+ -907.2858537946428,
+ -737.9731968470984
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 221,
+ "versionNonce": 29642871,
+ "isDeleted": false,
+ "id": "mdNyGlOBthXbeBh3YNbYi",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 956.0401785714289,
+ "y": -1.6747523716515573,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 642,
+ "height": 41,
+ "seed": 2068439865,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "fontSize": 36,
+ "fontFamily": 2,
+ "text": "Fleet - High Level Architecture Overview",
+ "baseline": 33,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.png b/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.png
new file mode 100644
index 0000000000000..1457228f2e04a
Binary files /dev/null and b/x-pack/plugins/fleet/dev_docs/diagrams/architecture_overview/architecture_overview.png differ
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
index bdcfb1ebb6534..677ca545029fe 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
@@ -220,7 +220,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
const hasUpgrade =
!!updatableIntegrationRecord &&
updatableIntegrationRecord.policiesToUpgrade.some(
- ({ id }) => id === packagePolicy.policy_id
+ ({ pkgPolicyId }) => pkgPolicyId === packagePolicy.id
);
return (
diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts
index 6f74550ad45b7..8ff3c20b7aa15 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.ts
@@ -944,7 +944,7 @@ export function overridePackageInputs(
// If there's no corresponding input on the original package policy, just
// take the override value from the new package as-is. This case typically
// occurs when inputs or package policies are added/removed between versions.
- if (!originalInput) {
+ if (originalInput === undefined) {
inputs.push(override as NewPackagePolicyInput);
continue;
}
@@ -958,7 +958,7 @@ export function overridePackageInputs(
}
if (override.vars) {
- originalInput = deepMergeVars(originalInput, override);
+ originalInput = deepMergeVars(originalInput, override) as NewPackagePolicyInput;
}
if (override.streams) {
@@ -967,6 +967,11 @@ export function overridePackageInputs(
(s) => s.data_stream.dataset === stream.data_stream.dataset
);
+ if (originalStream === undefined) {
+ originalInput.streams.push(stream);
+ continue;
+ }
+
if (typeof stream.enabled !== 'undefined' && originalStream) {
originalStream.enabled = stream.enabled;
}
@@ -1015,12 +1020,12 @@ export function overridePackageInputs(
}
function deepMergeVars(original: any, override: any): any {
- const result = { ...original };
-
- if (!result.vars || !override.vars) {
- return;
+ if (!original.vars) {
+ original.vars = { ...override.vars };
}
+ const result = { ...original };
+
const overrideVars = Array.isArray(override.vars)
? override.vars
: Object.entries(override.vars!).map(([key, rest]) => ({
@@ -1030,11 +1035,6 @@ function deepMergeVars(original: any, override: any): any {
for (const { name, ...overrideVal } of overrideVars) {
const originalVar = original.vars[name];
-
- if (!result.vars) {
- result.vars = {};
- }
-
result.vars[name] = { ...overrideVal, ...originalVar };
}
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
index a8ccb0f5119c8..e7ace1aff3101 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
@@ -94,17 +94,25 @@ export const useMappingsStateListener = ({ onChange, value }: Args) => {
validate: async () => {
const configurationFormValidator =
state.configuration.submitForm !== undefined
- ? new Promise(async (resolve) => {
- const { isValid } = await state.configuration.submitForm!();
- resolve(isValid);
+ ? new Promise(async (resolve, reject) => {
+ try {
+ const { isValid } = await state.configuration.submitForm!();
+ resolve(isValid);
+ } catch (error) {
+ reject(error);
+ }
})
: Promise.resolve(true);
const templatesFormValidator =
state.templates.submitForm !== undefined
- ? new Promise(async (resolve) => {
- const { isValid } = await state.templates.submitForm!();
- resolve(isValid);
+ ? new Promise(async (resolve, reject) => {
+ try {
+ const { isValid } = await state.templates.submitForm!();
+ resolve(isValid);
+ } catch (error) {
+ reject(error);
+ }
})
: Promise.resolve(true);
diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
index f81ec6a73d140..6591afafff00f 100644
--- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
+++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
@@ -718,18 +718,8 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
onChange={[Function]}
>
{
let indexPatternId: string | undefined;
try {
+ const userHasIndexWritePermissions = await this._checkIndexPermissions();
+ if (!userHasIndexWritePermissions) {
+ this._setCreateIndexError(
+ i18n.translate('xpack.maps.layers.newVectorLayerWizard.indexPermissionsError', {
+ defaultMessage: `You must have 'create' and 'create_index' index privileges to create and write data to "{indexName}".`,
+ values: {
+ indexName: this.state.indexName,
+ },
+ }),
+ userHasIndexWritePermissions
+ );
+ return;
+ }
const response = await createNewIndexAndPattern({
indexName: this.state.indexName,
defaultMappings: DEFAULT_MAPPINGS,
@@ -125,10 +149,23 @@ export class NewVectorLayerEditor extends Component
+ {this.state.createIndexError}
+
+ );
+ }
return (
{
return loadModulesPromise;
}
- loadModulesPromise = new Promise(async (resolve) => {
- const {
- MapEmbeddable,
- getIndexPatternService,
- getMapsCapabilities,
- renderApp,
- createSecurityLayerDescriptors,
- registerLayerWizard,
- registerSource,
- createTileMapLayerDescriptor,
- createRegionMapLayerDescriptor,
- createBasemapLayerDescriptor,
- createESSearchSourceLayerDescriptor,
- suggestEMSTermJoinConfig,
- } = await import('./lazy');
-
- resolve({
- MapEmbeddable,
- getIndexPatternService,
- getMapsCapabilities,
- renderApp,
- createSecurityLayerDescriptors,
- registerLayerWizard,
- registerSource,
- createTileMapLayerDescriptor,
- createRegionMapLayerDescriptor,
- createBasemapLayerDescriptor,
- createESSearchSourceLayerDescriptor,
- suggestEMSTermJoinConfig,
- });
+ loadModulesPromise = new Promise(async (resolve, reject) => {
+ try {
+ const {
+ MapEmbeddable,
+ getIndexPatternService,
+ getMapsCapabilities,
+ renderApp,
+ createSecurityLayerDescriptors,
+ registerLayerWizard,
+ registerSource,
+ createTileMapLayerDescriptor,
+ createRegionMapLayerDescriptor,
+ createBasemapLayerDescriptor,
+ createESSearchSourceLayerDescriptor,
+ suggestEMSTermJoinConfig,
+ } = await import('./lazy');
+ resolve({
+ MapEmbeddable,
+ getIndexPatternService,
+ getMapsCapabilities,
+ renderApp,
+ createSecurityLayerDescriptors,
+ registerLayerWizard,
+ registerSource,
+ createTileMapLayerDescriptor,
+ createRegionMapLayerDescriptor,
+ createBasemapLayerDescriptor,
+ createESSearchSourceLayerDescriptor,
+ suggestEMSTermJoinConfig,
+ });
+ } catch (error) {
+ reject(error);
+ }
});
return loadModulesPromise;
}
diff --git a/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.tsx b/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.tsx
index 2fa81504f93cb..73eb91ffd30a8 100644
--- a/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.tsx
+++ b/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.tsx
@@ -8,34 +8,22 @@
import React, { FC, useCallback, useMemo } from 'react';
import { EuiCheckbox, htmlIdGenerator } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { useExplorerUrlState } from '../../../explorer/hooks/use_explorer_url_state';
-const SHOW_CHARTS_DEFAULT = true;
-
-export const useShowCharts = (): [boolean, (v: boolean) => void] => {
- const [explorerUrlState, setExplorerUrlState] = useExplorerUrlState();
-
- const showCharts = explorerUrlState?.mlShowCharts ?? SHOW_CHARTS_DEFAULT;
-
- const setShowCharts = useCallback(
- (v: boolean) => {
- setExplorerUrlState({ mlShowCharts: v });
- },
- [setExplorerUrlState]
- );
-
- return [showCharts, setShowCharts];
-};
+export interface CheckboxShowChartsProps {
+ showCharts: boolean;
+ setShowCharts: (update: boolean) => void;
+}
/*
* React component for a checkbox element to toggle charts display.
*/
-export const CheckboxShowCharts: FC = () => {
- const [showCharts, setShowCharts] = useShowCharts();
-
- const onChange = (e: React.ChangeEvent) => {
- setShowCharts(e.target.checked);
- };
+export const CheckboxShowCharts: FC = ({ showCharts, setShowCharts }) => {
+ const onChange = useCallback(
+ (e: React.ChangeEvent) => {
+ setShowCharts(e.target.checked);
+ },
+ [setShowCharts]
+ );
const id = useMemo(() => htmlIdGenerator()(), []);
diff --git a/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/index.ts b/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/index.ts
index 3ff95bf6e335c..2099abb168283 100644
--- a/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/index.ts
+++ b/x-pack/plugins/ml/public/application/components/controls/checkbox_showcharts/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export { useShowCharts, CheckboxShowCharts } from './checkbox_showcharts';
+export { CheckboxShowCharts } from './checkbox_showcharts';
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js
index c9365c4edbe5f..daecf7585b3ea 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer.js
@@ -498,7 +498,10 @@ export class ExplorerUI extends React.Component {
{chartsData.seriesToPlot.length > 0 && selectedCells !== undefined && (
-
+
)}
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts
index d737c4733b9cb..cd01de31e5e60 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts
@@ -34,6 +34,7 @@ export const EXPLORER_ACTION = {
SET_VIEW_BY_PER_PAGE: 'setViewByPerPage',
SET_VIEW_BY_FROM_PAGE: 'setViewByFromPage',
SET_SWIM_LANE_SEVERITY: 'setSwimLaneSeverity',
+ SET_SHOW_CHARTS: 'setShowCharts',
};
export const FILTER_ACTION = {
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts
index f858c40b32315..1d4a277af0131 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts
@@ -83,6 +83,10 @@ const explorerAppState$: Observable = explorerState$.pipe(
appState.mlExplorerSwimlane.severity = state.swimLaneSeverity;
}
+ if (state.showCharts !== undefined) {
+ appState.mlShowCharts = state.showCharts;
+ }
+
if (state.filterActive) {
appState.mlExplorerFilter.influencersFilterQuery = state.influencersFilterQuery;
appState.mlExplorerFilter.filterActive = state.filterActive;
@@ -168,6 +172,9 @@ export const explorerService = {
setSwimLaneSeverity: (payload: number) => {
explorerAction$.next({ type: EXPLORER_ACTION.SET_SWIM_LANE_SEVERITY, payload });
},
+ setShowCharts: (payload: boolean) => {
+ explorerAction$.next({ type: EXPLORER_ACTION.SET_SHOW_CHARTS, payload });
+ },
};
export type ExplorerService = typeof explorerService;
diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts
index 74867af5f8987..192699afc2cf4 100644
--- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts
+++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts
@@ -158,6 +158,13 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo
};
break;
+ case EXPLORER_ACTION.SET_SHOW_CHARTS:
+ nextState = {
+ ...state,
+ showCharts: payload,
+ };
+ break;
+
default:
nextState = state;
}
diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts
index a06db20210c1b..202a4389ef524 100644
--- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts
+++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts
@@ -59,6 +59,7 @@ export interface ExplorerState {
viewBySwimlaneOptions: string[];
swimlaneLimit?: number;
swimLaneSeverity?: number;
+ showCharts: boolean;
}
function getDefaultIndexPattern() {
@@ -112,5 +113,6 @@ export function getExplorerDefaultState(): ExplorerState {
viewByPerPage: SWIM_LANE_DEFAULT_PAGE_SIZE,
viewByFromPage: 1,
swimlaneLimit: undefined,
+ showCharts: true,
};
}
diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
index 42927d9b4ef50..49e7857eee082 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
@@ -26,7 +26,6 @@ import { useExplorerData } from '../../explorer/actions';
import { explorerService } from '../../explorer/explorer_dashboard_service';
import { getDateFormatTz } from '../../explorer/explorer_utils';
import { useJobSelection } from '../../components/job_selector/use_job_selection';
-import { useShowCharts } from '../../components/controls/checkbox_showcharts';
import { useTableInterval } from '../../components/controls/select_interval';
import { useTableSeverity } from '../../components/controls/select_severity';
import { useUrlState } from '../../util/url_state';
@@ -196,6 +195,10 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
if (severity !== undefined) {
explorerService.setSwimLaneSeverity(severity);
}
+
+ if (explorerUrlState.mlShowCharts !== undefined) {
+ explorerService.setShowCharts(explorerUrlState.mlShowCharts);
+ }
}, []);
/** Sync URL state with {@link explorerService} state */
@@ -214,7 +217,6 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
}
}, [explorerData]);
- const [showCharts] = useShowCharts();
const [tableInterval] = useTableInterval();
const [tableSeverity] = useTableSeverity();
@@ -267,7 +269,11 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
}
}, [JSON.stringify(loadExplorerDataConfig), selectedCells?.showTopFieldValues]);
- if (explorerState === undefined || refresh === undefined || showCharts === undefined) {
+ if (
+ explorerState === undefined ||
+ refresh === undefined ||
+ explorerAppState?.mlShowCharts === undefined
+ ) {
return null;
}
@@ -277,7 +283,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
{...{
explorerState,
setSelectedCells,
- showCharts,
+ showCharts: explorerState.showCharts,
severity: tableSeverity.val,
stoppedPartitions,
invalidTimeRangeError,
diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts
index a998343535249..d3b407c2bb65a 100644
--- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts
+++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts
@@ -23,27 +23,34 @@ export function loadNewJobCapabilities(
jobType: JobType
) {
return new Promise(async (resolve, reject) => {
- const serviceToUse =
- jobType === ANOMALY_DETECTOR ? newJobCapsService : newJobCapsServiceAnalytics;
- if (indexPatternId !== undefined) {
- // index pattern is being used
- const indexPattern: IIndexPattern = await indexPatterns.get(indexPatternId);
- await serviceToUse.initializeFromIndexPattern(indexPattern);
- resolve(serviceToUse.newJobCaps);
- } else if (savedSearchId !== undefined) {
- // saved search is being used
- // load the index pattern from the saved search
- const { indexPattern } = await getIndexPatternAndSavedSearch(savedSearchId);
- if (indexPattern === null) {
- // eslint-disable-next-line no-console
- console.error('Cannot retrieve index pattern from saved search');
+ try {
+ const serviceToUse =
+ jobType === ANOMALY_DETECTOR ? newJobCapsService : newJobCapsServiceAnalytics;
+
+ if (indexPatternId !== undefined) {
+ // index pattern is being used
+ const indexPattern: IIndexPattern = await indexPatterns.get(indexPatternId);
+ await serviceToUse.initializeFromIndexPattern(indexPattern);
+ resolve(serviceToUse.newJobCaps);
+ } else if (savedSearchId !== undefined) {
+ // saved search is being used
+ // load the index pattern from the saved search
+ const { indexPattern } = await getIndexPatternAndSavedSearch(savedSearchId);
+
+ if (indexPattern === null) {
+ // eslint-disable-next-line no-console
+ console.error('Cannot retrieve index pattern from saved search');
+ reject();
+ return;
+ }
+
+ await serviceToUse.initializeFromIndexPattern(indexPattern);
+ resolve(serviceToUse.newJobCaps);
+ } else {
reject();
- return;
}
- await serviceToUse.initializeFromIndexPattern(indexPattern);
- resolve(serviceToUse.newJobCaps);
- } else {
- reject();
+ } catch (error) {
+ reject(error);
}
});
}
diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx
index eb39ba4ab29aa..5090274ca7383 100644
--- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx
+++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx
@@ -25,33 +25,34 @@ export async function resolveEmbeddableAnomalyChartsUserInput(
const anomalyDetectorService = new AnomalyDetectorService(new HttpService(http));
return new Promise(async (resolve, reject) => {
- const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds);
-
- const title = input?.title ?? getDefaultExplorerChartsPanelTitle(jobIds);
- const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise();
- const influencers = anomalyDetectorService.extractInfluencers(jobs);
- influencers.push(VIEW_BY_JOB_LABEL);
-
- const modalSession = overlays.openModal(
- toMountPoint(
- {
- modalSession.close();
-
- resolve({
- jobIds,
- title: panelTitle,
- maxSeriesToPlot,
- });
- }}
- onCancel={() => {
- modalSession.close();
- reject();
- }}
- />
- )
- );
+ try {
+ const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds);
+ const title = input?.title ?? getDefaultExplorerChartsPanelTitle(jobIds);
+ const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise();
+ const influencers = anomalyDetectorService.extractInfluencers(jobs);
+ influencers.push(VIEW_BY_JOB_LABEL);
+ const modalSession = overlays.openModal(
+ toMountPoint(
+ {
+ modalSession.close();
+ resolve({
+ jobIds,
+ title: panelTitle,
+ maxSeriesToPlot,
+ });
+ }}
+ onCancel={() => {
+ modalSession.close();
+ reject();
+ }}
+ />
+ )
+ );
+ } catch (error) {
+ reject(error);
+ }
});
}
diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx
index e183907def57b..5027eb6783a64 100644
--- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx
+++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx
@@ -25,31 +25,36 @@ export async function resolveAnomalySwimlaneUserInput(
const anomalyDetectorService = new AnomalyDetectorService(new HttpService(http));
return new Promise(async (resolve, reject) => {
- const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds);
-
- const title = input?.title ?? getDefaultSwimlanePanelTitle(jobIds);
-
- const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise();
-
- const influencers = anomalyDetectorService.extractInfluencers(jobs);
- influencers.push(VIEW_BY_JOB_LABEL);
-
- const modalSession = overlays.openModal(
- toMountPoint(
- {
- modalSession.close();
- resolve({ jobIds, title: panelTitle, swimlaneType, viewBy });
- }}
- onCancel={() => {
- modalSession.close();
- reject();
- }}
- />
- )
- );
+ try {
+ const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds);
+ const title = input?.title ?? getDefaultSwimlanePanelTitle(jobIds);
+ const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise();
+ const influencers = anomalyDetectorService.extractInfluencers(jobs);
+ influencers.push(VIEW_BY_JOB_LABEL);
+ const modalSession = overlays.openModal(
+ toMountPoint(
+ {
+ modalSession.close();
+ resolve({
+ jobIds,
+ title: panelTitle,
+ swimlaneType,
+ viewBy,
+ });
+ }}
+ onCancel={() => {
+ modalSession.close();
+ reject();
+ }}
+ />
+ )
+ );
+ } catch (error) {
+ reject(error);
+ }
});
}
diff --git a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx
index 1833883447859..fbceeb7f7cf79 100644
--- a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx
+++ b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx
@@ -38,56 +38,65 @@ export async function resolveJobSelection(
} = coreStart;
return new Promise(async (resolve, reject) => {
- const maps = {
- groupsMap: getInitialGroupsMap([]),
- jobsMap: {},
- };
+ try {
+ const maps = {
+ groupsMap: getInitialGroupsMap([]),
+ jobsMap: {},
+ };
+ const tzConfig = uiSettings.get('dateFormat:tz');
+ const dateFormatTz = tzConfig !== 'Browser' ? tzConfig : moment.tz.guess();
- const tzConfig = uiSettings.get('dateFormat:tz');
- const dateFormatTz = tzConfig !== 'Browser' ? tzConfig : moment.tz.guess();
+ const onFlyoutClose = () => {
+ flyoutSession.close();
+ reject();
+ };
- const onFlyoutClose = () => {
- flyoutSession.close();
- reject();
- };
+ const onSelectionConfirmed = async ({
+ jobIds,
+ groups,
+ }: {
+ jobIds: string[];
+ groups: Array<{
+ groupId: string;
+ jobIds: string[];
+ }>;
+ }) => {
+ await flyoutSession.close();
+ resolve({
+ jobIds,
+ groups,
+ });
+ };
- const onSelectionConfirmed = async ({
- jobIds,
- groups,
- }: {
- jobIds: string[];
- groups: Array<{ groupId: string; jobIds: string[] }>;
- }) => {
- await flyoutSession.close();
- resolve({ jobIds, groups });
- };
- const flyoutSession = coreStart.overlays.openFlyout(
- toMountPoint(
-
-
-
- ),
- {
- 'data-test-subj': 'mlFlyoutJobSelector',
- ownFocus: true,
- closeButtonAriaLabel: 'jobSelectorFlyout',
- }
- );
+ const flyoutSession = coreStart.overlays.openFlyout(
+ toMountPoint(
+
+
+
+ ),
+ {
+ 'data-test-subj': 'mlFlyoutJobSelector',
+ ownFocus: true,
+ closeButtonAriaLabel: 'jobSelectorFlyout',
+ }
+ ); // Close the flyout when user navigates out of the dashboard plugin
- // Close the flyout when user navigates out of the dashboard plugin
- currentAppId$.pipe(takeUntil(from(flyoutSession.onClose))).subscribe((appId) => {
- if (appId !== DashboardConstants.DASHBOARDS_ID) {
- flyoutSession.close();
- }
- });
+ currentAppId$.pipe(takeUntil(from(flyoutSession.onClose))).subscribe((appId) => {
+ if (appId !== DashboardConstants.DASHBOARDS_ID) {
+ flyoutSession.close();
+ }
+ });
+ } catch (error) {
+ reject(error);
+ }
});
}
diff --git a/x-pack/plugins/monitoring/public/application/global_state_context.tsx b/x-pack/plugins/monitoring/public/application/global_state_context.tsx
index 042d55418c5ab..dc33316dbd9d9 100644
--- a/x-pack/plugins/monitoring/public/application/global_state_context.tsx
+++ b/x-pack/plugins/monitoring/public/application/global_state_context.tsx
@@ -11,7 +11,6 @@ import { MonitoringStartPluginDependencies } from '../types';
interface GlobalStateProviderProps {
query: MonitoringStartPluginDependencies['data']['query'];
toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts'];
- children: React.ReactNode;
}
interface State {
@@ -21,7 +20,11 @@ interface State {
export const GlobalStateContext = createContext({} as State);
-export const GlobalStateProvider = ({ query, toasts, children }: GlobalStateProviderProps) => {
+export const GlobalStateProvider: React.FC = ({
+ query,
+ toasts,
+ children,
+}) => {
// TODO: remove fakeAngularRootScope and fakeAngularLocation when angular is removed
const fakeAngularRootScope: Partial = {
$on: (
diff --git a/x-pack/plugins/monitoring/public/application/index.tsx b/x-pack/plugins/monitoring/public/application/index.tsx
index ce38b00a359c8..e15ad995ca161 100644
--- a/x-pack/plugins/monitoring/public/application/index.tsx
+++ b/x-pack/plugins/monitoring/public/application/index.tsx
@@ -17,6 +17,7 @@ import { GlobalStateProvider } from './global_state_context';
import { ExternalConfigContext, ExternalConfig } from './external_config_context';
import { createPreserveQueryHistory } from './preserve_query_history';
import { RouteInit } from './route_init';
+import { MonitoringTimeContainer } from './pages/use_monitoring_time';
export const renderApp = (
core: CoreStart,
@@ -45,36 +46,38 @@ const MonitoringApp: React.FC<{
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
index 531de505bf43d..f40c2d3ec5e50 100644
--- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
+++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
@@ -8,6 +8,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui';
import React from 'react';
import { useTitle } from '../hooks/use_title';
+import { MonitoringToolbar } from '../../components/shared/toolbar';
export interface TabMenuItem {
id: string;
@@ -20,11 +21,10 @@ export interface TabMenuItem {
interface PageTemplateProps {
title: string;
pageTitle?: string;
- children: React.ReactNode;
tabs?: TabMenuItem[];
}
-export const PageTemplate = ({ title, pageTitle, tabs, children }: PageTemplateProps) => {
+export const PageTemplate: React.FC = ({ title, pageTitle, tabs, children }) => {
useTitle('', title);
return (
@@ -52,7 +52,9 @@ export const PageTemplate = ({ title, pageTitle, tabs, children }: PageTemplateP
- {/* HERE GOES THE TIMEPICKER */}
+
+
+
{tabs && (
diff --git a/x-pack/plugins/monitoring/public/application/pages/use_monitoring_time.tsx b/x-pack/plugins/monitoring/public/application/pages/use_monitoring_time.tsx
new file mode 100644
index 0000000000000..f54d40ed29a06
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/application/pages/use_monitoring_time.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { useCallback, useState } from 'react';
+import createContainer from 'constate';
+
+interface TimeOptions {
+ from: string;
+ to: string;
+ interval: string;
+}
+
+export const DEFAULT_TIMERANGE: TimeOptions = {
+ from: 'now-1h',
+ to: 'now',
+ interval: '>=10s',
+};
+
+export const useMonitoringTime = () => {
+ const defaultTimeRange = {
+ from: 'now-1h',
+ to: 'now',
+ interval: DEFAULT_TIMERANGE.interval,
+ };
+ const [refreshInterval, setRefreshInterval] = useState(5000);
+ const [isPaused, setIsPaused] = useState(false);
+ const [currentTimerange, setTimeRange] = useState(defaultTimeRange);
+
+ const handleTimeChange = useCallback(
+ (start: string, end: string) => {
+ setTimeRange({ ...currentTimerange, from: start, to: end });
+ },
+ [currentTimerange, setTimeRange]
+ );
+
+ return {
+ currentTimerange,
+ setTimeRange,
+ handleTimeChange,
+ setRefreshInterval,
+ refreshInterval,
+ setIsPaused,
+ isPaused,
+ };
+};
+
+export const MonitoringTimeContainer = createContainer(useMonitoringTime);
diff --git a/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx
new file mode 100644
index 0000000000000..6e45d4d831ec9
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, OnRefreshChangeProps } from '@elastic/eui';
+import React, { useContext, useCallback } from 'react';
+import { MonitoringTimeContainer } from '../../application/pages/use_monitoring_time';
+
+export const MonitoringToolbar = () => {
+ const {
+ currentTimerange,
+ handleTimeChange,
+ setRefreshInterval,
+ refreshInterval,
+ setIsPaused,
+ isPaused,
+ } = useContext(MonitoringTimeContainer.Context);
+
+ const onTimeChange = useCallback(
+ (selectedTime: { start: string; end: string; isInvalid: boolean }) => {
+ if (selectedTime.isInvalid) {
+ return;
+ }
+ handleTimeChange(selectedTime.start, selectedTime.end);
+ },
+ [handleTimeChange]
+ );
+
+ const onRefreshChange = useCallback(
+ ({ refreshInterval: ri, isPaused: isP }: OnRefreshChangeProps) => {
+ setRefreshInterval(ri);
+ setIsPaused(isP);
+ },
+ [setRefreshInterval, setIsPaused]
+ );
+
+ return (
+
+ Setup Button
+
+ {}}
+ isPaused={isPaused}
+ refreshInterval={refreshInterval}
+ onRefreshChange={onRefreshChange}
+ />
+
+
+ );
+};
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js
index 32662ae0efa34..a4645edda73d0 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js
@@ -98,27 +98,39 @@ async function getPaginatedThroughputData(pipelines, req, lsIndexPattern, throug
const metricSeriesData = Object.values(
await Promise.all(
pipelines.map((pipeline) => {
- return new Promise(async (resolve) => {
- const data = await getMetrics(
- req,
- lsIndexPattern,
- [throughputMetric],
- [
- {
- bool: {
- should: [
- { term: { type: 'logstash_stats' } },
- { term: { 'metricset.name': 'stats' } },
- ],
+ return new Promise(async (resolve, reject) => {
+ try {
+ const data = await getMetrics(
+ req,
+ lsIndexPattern,
+ [throughputMetric],
+ [
+ {
+ bool: {
+ should: [
+ {
+ term: {
+ type: 'logstash_stats',
+ },
+ },
+ {
+ term: {
+ 'metricset.name': 'stats',
+ },
+ },
+ ],
+ },
},
+ ],
+ {
+ pipeline,
},
- ],
- {
- pipeline,
- },
- 2
- );
- resolve(reduceData(pipeline, data));
+ 2
+ );
+ resolve(reduceData(pipeline, data));
+ } catch (error) {
+ reject(error);
+ }
});
})
)
@@ -184,27 +196,38 @@ async function getPipelines(req, lsIndexPattern, pipelines, throughputMetric, no
async function getThroughputPipelines(req, lsIndexPattern, pipelines, throughputMetric) {
const metricsResponse = await Promise.all(
pipelines.map((pipeline) => {
- return new Promise(async (resolve) => {
- const data = await getMetrics(
- req,
- lsIndexPattern,
- [throughputMetric],
- [
- {
- bool: {
- should: [
- { term: { type: 'logstash_stats' } },
- { term: { 'metricset.name': 'stats' } },
- ],
+ return new Promise(async (resolve, reject) => {
+ try {
+ const data = await getMetrics(
+ req,
+ lsIndexPattern,
+ [throughputMetric],
+ [
+ {
+ bool: {
+ should: [
+ {
+ term: {
+ type: 'logstash_stats',
+ },
+ },
+ {
+ term: {
+ 'metricset.name': 'stats',
+ },
+ },
+ ],
+ },
},
- },
- ],
- {
- pipeline,
- }
- );
-
- resolve(reduceData(pipeline, data));
+ ],
+ {
+ pipeline,
+ }
+ );
+ resolve(reduceData(pipeline, data));
+ } catch (error) {
+ reject(error);
+ }
});
})
);
diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json
index ac6389bff8a0b..45fe0258dd142 100644
--- a/x-pack/plugins/observability/kibana.json
+++ b/x-pack/plugins/observability/kibana.json
@@ -11,6 +11,7 @@
"observability"
],
"optionalPlugins": [
+ "embeddable",
"home",
"lens",
"licensing",
diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx
index f92f12c79a56d..dc3db695a3fbf 100644
--- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx
+++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx
@@ -10,16 +10,13 @@ import { mount } from 'enzyme';
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
import { CreateCaseFlyout } from './flyout';
+import { render } from '@testing-library/react';
+
+import { useKibana } from '../../../../utils/kibana_react';
+import { CASES_OWNER } from '../constants';
+
+jest.mock('../../../../utils/kibana_react');
-jest.mock('../../../../utils/kibana_react', () => ({
- useKibana: () => ({
- services: {
- cases: {
- getCreateCase: jest.fn(),
- },
- },
- }),
-}));
const onCloseFlyout = jest.fn();
const onSuccess = jest.fn();
const defaultProps = {
@@ -28,8 +25,17 @@ const defaultProps = {
};
describe('CreateCaseFlyout', () => {
+ const mockCreateCase = jest.fn();
+
beforeEach(() => {
jest.resetAllMocks();
+ (useKibana as jest.Mock).mockReturnValue({
+ services: {
+ cases: {
+ getCreateCase: mockCreateCase,
+ },
+ },
+ });
});
it('renders', () => {
@@ -52,4 +58,22 @@ describe('CreateCaseFlyout', () => {
wrapper.find(`[data-test-subj='euiFlyoutCloseButton']`).first().simulate('click');
expect(onCloseFlyout).toBeCalled();
});
+
+ it('does not show the sync alerts toggle', () => {
+ render(
+
+
+
+ );
+
+ expect(mockCreateCase).toBeCalledTimes(1);
+ expect(mockCreateCase).toBeCalledWith({
+ onCancel: onCloseFlyout,
+ onSuccess,
+ afterCaseCreated: undefined,
+ withSteps: false,
+ owner: [CASES_OWNER],
+ disableAlerts: true,
+ });
+ });
});
diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx
index df29d02e8d830..896bc27a97674 100644
--- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx
+++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx
@@ -68,6 +68,7 @@ function CreateCaseFlyoutComponent({
onSuccess,
withSteps: false,
owner: [CASES_OWNER],
+ disableAlerts: true,
})}
diff --git a/x-pack/plugins/observability/public/pages/alerts/example_data.ts b/x-pack/plugins/observability/public/pages/alerts/example_data.ts
index 28f8ecec3f34c..1354da592f796 100644
--- a/x-pack/plugins/observability/public/pages/alerts/example_data.ts
+++ b/x-pack/plugins/observability/public/pages/alerts/example_data.ts
@@ -8,7 +8,7 @@
import {
ALERT_DURATION,
ALERT_END,
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_SEVERITY,
ALERT_RULE_TYPE_ID,
ALERT_START,
@@ -35,7 +35,7 @@ export const apmAlertResponseExample = [
[ALERT_RULE_UUID]: ['474920d0-93e9-11eb-ac86-0b455460de81'],
'event.action': ['active'],
'@timestamp': ['2021-04-12T13:53:49.550Z'],
- [ALERT_ID]: ['apm.error_rate_opbeans-java_production'],
+ [ALERT_INSTANCE_ID]: ['apm.error_rate_opbeans-java_production'],
[ALERT_START]: ['2021-04-12T13:50:49.493Z'],
[ALERT_RULE_PRODUCER]: ['apm'],
'event.kind': ['state'],
@@ -55,7 +55,7 @@ export const apmAlertResponseExample = [
[ALERT_RULE_UUID]: ['474920d0-93e9-11eb-ac86-0b455460de81'],
'event.action': ['close'],
'@timestamp': ['2021-04-12T13:49:49.446Z'],
- [ALERT_ID]: ['apm.error_rate_opbeans-java_production'],
+ [ALERT_INSTANCE_ID]: ['apm.error_rate_opbeans-java_production'],
[ALERT_START]: ['2021-04-12T13:09:30.441Z'],
[ALERT_RULE_PRODUCER]: ['apm'],
'event.kind': ['state'],
@@ -116,7 +116,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
- name: ALERT_ID,
+ name: ALERT_INSTANCE_ID,
type: 'string',
esTypes: ['keyword'],
searchable: true,
diff --git a/x-pack/plugins/reporting/common/poller.ts b/x-pack/plugins/reporting/common/poller.ts
index 13ded0576bdf5..3778454c3a4cc 100644
--- a/x-pack/plugins/reporting/common/poller.ts
+++ b/x-pack/plugins/reporting/common/poller.ts
@@ -8,20 +8,19 @@
import _ from 'lodash';
interface PollerOptions {
- functionToPoll: () => Promise;
+ functionToPoll: () => Promise;
pollFrequencyInMillis: number;
trailing?: boolean;
continuePollingOnError?: boolean;
pollFrequencyErrorMultiplier?: number;
- successFunction?: (...args: any) => any;
- errorFunction?: (error: Error) => any;
+ successFunction?: (...args: unknown[]) => void;
+ errorFunction?: (error: Error) => void;
}
-// @TODO Maybe move to observables someday
export class Poller {
- private readonly functionToPoll: () => Promise;
- private readonly successFunction: (...args: any) => any;
- private readonly errorFunction: (error: Error) => any;
+ private readonly functionToPoll: () => Promise;
+ private readonly successFunction: (...args: unknown[]) => void;
+ private readonly errorFunction: (error: Error) => void;
private _isRunning: boolean;
private _timeoutId: NodeJS.Timeout | null;
private pollFrequencyInMillis: number;
diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
index 352c4dddb9f32..e9fd76eb62c79 100644
--- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
+++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap
@@ -9876,7 +9876,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
>
-
-
-
+
-
+
{
private isInitialJobsFetch: boolean;
private licenseSubscription?: Subscription;
private mounted?: boolean;
- private poller?: any;
+ private poller?: Poller;
constructor(props: Props) {
super(props);
@@ -119,7 +119,7 @@ class ReportListingUi extends Component {
public componentWillUnmount() {
this.mounted = false;
- this.poller.stop();
+ this.poller?.stop();
if (this.licenseSubscription) {
this.licenseSubscription.unsubscribe();
diff --git a/x-pack/plugins/reporting/public/notifier/job_completion_notifications.ts b/x-pack/plugins/reporting/public/notifier/job_completion_notifications.ts
index e764f94105b70..c4addfa3eedef 100644
--- a/x-pack/plugins/reporting/public/notifier/job_completion_notifications.ts
+++ b/x-pack/plugins/reporting/public/notifier/job_completion_notifications.ts
@@ -9,11 +9,11 @@ import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY } from '../../common/constants
type JobId = string;
-const set = (jobs: any) => {
+const set = (jobs: string[]) => {
sessionStorage.setItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JSON.stringify(jobs));
};
-const getAll = () => {
+const getAll = (): string[] => {
const sessionValue = sessionStorage.getItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY);
return sessionValue ? JSON.parse(sessionValue) : [];
};
diff --git a/x-pack/plugins/reporting/server/browsers/safe_child_process.ts b/x-pack/plugins/reporting/server/browsers/safe_child_process.ts
index 9265dae23b896..70e45bf10803f 100644
--- a/x-pack/plugins/reporting/server/browsers/safe_child_process.ts
+++ b/x-pack/plugins/reporting/server/browsers/safe_child_process.ts
@@ -10,7 +10,7 @@ import { take, share, mapTo, delay, tap } from 'rxjs/operators';
import { LevelLogger } from '../lib';
interface IChild {
- kill: (signal: string) => Promise;
+ kill: (signal: string) => Promise;
}
// Our process can get sent various signals, and when these occur we wish to
diff --git a/x-pack/plugins/reporting/server/deprecations/migrage_existing_indices_ilm_policy.test.ts b/x-pack/plugins/reporting/server/deprecations/migrage_existing_indices_ilm_policy.test.ts
index 485c4e62a208f..766e23a90293f 100644
--- a/x-pack/plugins/reporting/server/deprecations/migrage_existing_indices_ilm_policy.test.ts
+++ b/x-pack/plugins/reporting/server/deprecations/migrage_existing_indices_ilm_policy.test.ts
@@ -66,6 +66,7 @@ describe("Migrate existing indices' ILM policy deprecations", () => {
},
"level": "warning",
"message": "New reporting indices will be managed by the \\"kibana-reporting\\" provisioned ILM policy. You must edit this policy to manage the report lifecycle. This change targets all indices prefixed with \\".reporting-*\\".",
+ "title": "Found reporting indices managed by custom ILM policy.",
},
]
`);
diff --git a/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.ts b/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.ts
index a3dd4205b9e65..9e3304e78e000 100644
--- a/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.ts
+++ b/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.ts
@@ -30,6 +30,9 @@ export const getDeprecationsInfo = async (
if (migrationStatus !== 'ok') {
return [
{
+ title: i18n.translate('xpack.reporting.deprecations.migrateIndexIlmPolicyActionTitle', {
+ defaultMessage: 'Found reporting indices managed by custom ILM policy.',
+ }),
level: 'warning',
message: i18n.translate('xpack.reporting.deprecations.migrateIndexIlmPolicyActionMessage', {
defaultMessage: `New reporting indices will be managed by the "{reportingIlmPolicy}" provisioned ILM policy. You must edit this policy to manage the report lifecycle. This change targets all indices prefixed with "{indexPattern}".`,
diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts
index b52d51d3e9311..d8ab0ac0c568a 100644
--- a/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts
+++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts
@@ -53,6 +53,7 @@ test('logs a plain message when only a reporting_user role issue is found', asyn
"documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html",
"level": "critical",
"message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"",
+ "title": "Found deprecated reporting role",
},
]
`);
@@ -82,6 +83,7 @@ test('logs multiple entries when multiple reporting_user role issues are found',
"documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html",
"level": "critical",
"message": "The deprecated \\"reporting_user\\" role has been found for 2 user(s): \\"reportron\\", \\"supercooluser\\"",
+ "title": "Found deprecated reporting role",
},
]
`);
@@ -112,6 +114,7 @@ test('logs an expanded message when a config issue and a reporting_user role iss
"documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html",
"level": "critical",
"message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"",
+ "title": "Found deprecated reporting role",
},
]
`);
diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts
index d5138043060a6..6e08169727d1b 100644
--- a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts
+++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts
@@ -6,6 +6,7 @@
*/
import type { GetDeprecationsContext, DeprecationsDetails } from 'src/core/server';
+import { i18n } from '@kbn/i18n';
import { ReportingCore } from '..';
const deprecatedRole = 'reporting_user';
@@ -24,21 +25,41 @@ export const getDeprecationsInfo = async (
const { body: users } = await esClient.asCurrentUser.security.getUser();
const reportingUsers = Object.entries(users)
- .filter(([username, user]) => user.roles.includes(deprecatedRole))
+ .filter(([, user]) => user.roles.includes(deprecatedRole))
.map(([, user]) => user.username);
+
const numReportingUsers = reportingUsers.length;
if (numReportingUsers > 0) {
- const usernames = reportingUsers.join('", "');
deprecations.push({
- message: `The deprecated "${deprecatedRole}" role has been found for ${numReportingUsers} user(s): "${usernames}"`,
+ title: i18n.translate('xpack.reporting.deprecations.reportingRoleTitle', {
+ defaultMessage: 'Found deprecated reporting role',
+ }),
+ message: i18n.translate('xpack.reporting.deprecations.reportingRoleMessage', {
+ defaultMessage:
+ 'The deprecated "{deprecatedRole}" role has been found for {numReportingUsers} user(s): "{usernames}"',
+ values: { deprecatedRole, numReportingUsers, usernames: reportingUsers.join('", "') },
+ }),
documentationUrl: 'https://www.elastic.co/guide/en/kibana/current/secure-reporting.html',
level: 'critical',
correctiveActions: {
manualSteps: [
- ...(usingDeprecatedConfig ? [`Set "${upgradableConfig}" in kibana.yml`] : []),
- `Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.`,
- `Assign the custom role(s) as desired, and remove the "${deprecatedRole}" role from the user(s).`,
+ ...(usingDeprecatedConfig
+ ? [
+ i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepOneMessage', {
+ defaultMessage: 'Set "{upgradableConfig}" in kibana.yml',
+ values: { upgradableConfig },
+ }),
+ ]
+ : []),
+ i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepTwoMessage', {
+ defaultMessage: `Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.`,
+ }),
+ i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepThreeMessage', {
+ defaultMessage:
+ 'Assign the custom role(s) as desired, and remove the "{deprecatedRole}" role from the user(s).',
+ values: { deprecatedRole },
+ }),
],
},
});
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts
index f806b8a7e5bca..342e1fc7d85de 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import type { KibanaRequest } from 'kibana/server';
+import type { KibanaRequest, SavedObjectsClientContract } from 'kibana/server';
import { url as urlUtils } from '../../../../../../../src/plugins/kibana_utils/server';
import type { LevelLogger } from '../../../lib';
import type { CreateJobFn, ReportingRequestHandlerContext } from '../../../types';
@@ -20,9 +20,9 @@ function isLegacyJob(
const getSavedObjectTitle = async (
objectType: string,
savedObjectId: string,
- savedObjectsClient: any
+ savedObjectsClient: SavedObjectsClientContract
) => {
- const savedObject = await savedObjectsClient.get(objectType, savedObjectId);
+ const savedObject = await savedObjectsClient.get<{ title: string }>(objectType, savedObjectId);
return savedObject.attributes.title;
};
diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts
index 7fc638211e87b..406beb2a56b66 100644
--- a/x-pack/plugins/reporting/server/types.ts
+++ b/x-pack/plugins/reporting/server/types.ts
@@ -63,7 +63,7 @@ export { BaseParams, BasePayload };
export type CreateJobFn = (
jobParams: JobParamsType,
context: ReportingRequestHandlerContext,
- request: KibanaRequest
+ request: KibanaRequest
) => Promise;
// default fn type for RunTaskFnFactory
diff --git a/x-pack/plugins/rule_registry/README.md b/x-pack/plugins/rule_registry/README.md
index b4a9bab9b435d..6643da6e5a52d 100644
--- a/x-pack/plugins/rule_registry/README.md
+++ b/x-pack/plugins/rule_registry/README.md
@@ -130,7 +130,7 @@ The following fields are defined in the technical field component template and s
- `kibana.alert.rule.name`: the name of the rule (as specified by the user).
- `kibana.alert.rule.category`: the name of the rule type (as defined by the rule type producer)
- `kibana.alert.rule.consumer`: the feature which produced the alert (inherited from the rule producer field). Usually a Kibana feature id like `apm`, `siem`...
-- `kibana.alert.id`: the id of the alert, that is unique within the context of the rule execution it was created in. E.g., for a rule that monitors latency for all services in all environments, this might be `opbeans-java:production`.
+- `kibana.alert.instance.id`: the id of the alert instance, that is unique within the context of the rule execution it was created in. E.g., for a rule that monitors latency for all services in all environments, this might be `opbeans-java:production`.
- `kibana.alert.uuid`: the unique identifier for the alert during its lifespan. If an alert recovers (or closes), this identifier is re-generated when it is opened again.
- `kibana.alert.status`: the status of the alert. Can be `active` or `recovered`.
- `kibana.alert.start`: the ISO timestamp of the time at which the alert started.
diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts
index 03d96a24bedd3..3c8b96976792d 100644
--- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts
+++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts
@@ -21,7 +21,7 @@ export const technicalRuleFieldMap = {
[Fields.ALERT_RULE_PRODUCER]: { type: 'keyword', required: true },
[Fields.SPACE_IDS]: { type: 'keyword', array: true, required: true },
[Fields.ALERT_UUID]: { type: 'keyword', required: true },
- [Fields.ALERT_ID]: { type: 'keyword', required: true },
+ [Fields.ALERT_INSTANCE_ID]: { type: 'keyword', required: true },
[Fields.ALERT_START]: { type: 'date' },
[Fields.ALERT_END]: { type: 'date' },
[Fields.ALERT_DURATION]: { type: 'long' },
diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts
index d8640cf5dfe82..cdc39a7597386 100644
--- a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts
+++ b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts
@@ -6,7 +6,7 @@
*/
import {
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_RULE_CATEGORY,
ALERT_RULE_CONSUMER,
ALERT_RULE_NAME,
@@ -30,7 +30,7 @@ import { getReadRequest } from './__mocks__/request_responses';
import { requestMock, serverMock } from './__mocks__/server';
const getMockAlert = (): ParsedTechnicalFields => ({
- [ALERT_ID]: 'fake-alert-id',
+ [ALERT_INSTANCE_ID]: 'fake-alert-id',
[ALERT_RULE_CATEGORY]: 'apm.error_rate',
[ALERT_RULE_CONSUMER]: 'apm',
[ALERT_RULE_NAME]: 'Check error rate',
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts
index ad09228580637..c65fdece6c5f0 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts
@@ -7,7 +7,7 @@
import { loggerMock } from '@kbn/logging/mocks';
import {
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_RULE_CATEGORY,
ALERT_RULE_CONSUMER,
ALERT_RULE_NAME,
@@ -91,14 +91,14 @@ describe('createLifecycleExecutor', () => {
// alert documents
{ index: { _id: expect.any(String) } },
expect.objectContaining({
- [ALERT_ID]: 'TEST_ALERT_0',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_0',
[ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'open',
[EVENT_KIND]: 'signal',
}),
{ index: { _id: expect.any(String) } },
expect.objectContaining({
- [ALERT_ID]: 'TEST_ALERT_1',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_1',
[ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'open',
[EVENT_KIND]: 'signal',
@@ -128,7 +128,7 @@ describe('createLifecycleExecutor', () => {
{
fields: {
'@timestamp': '',
- [ALERT_ID]: 'TEST_ALERT_0',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_0',
[ALERT_UUID]: 'ALERT_0_UUID',
[ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME',
[ALERT_RULE_CONSUMER]: 'CONSUMER',
@@ -145,7 +145,7 @@ describe('createLifecycleExecutor', () => {
{
fields: {
'@timestamp': '',
- [ALERT_ID]: 'TEST_ALERT_1',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_1',
[ALERT_UUID]: 'ALERT_1_UUID',
[ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME',
[ALERT_RULE_CONSUMER]: 'CONSUMER',
@@ -206,7 +206,7 @@ describe('createLifecycleExecutor', () => {
// alert document
{ index: { _id: 'TEST_ALERT_0_UUID' } },
expect.objectContaining({
- [ALERT_ID]: 'TEST_ALERT_0',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_0',
[ALERT_WORKFLOW_STATUS]: 'closed',
[ALERT_STATUS]: ALERT_STATUS_ACTIVE,
labels: { LABEL_0_KEY: 'LABEL_0_VALUE' },
@@ -216,7 +216,7 @@ describe('createLifecycleExecutor', () => {
}),
{ index: { _id: 'TEST_ALERT_1_UUID' } },
expect.objectContaining({
- [ALERT_ID]: 'TEST_ALERT_1',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_1',
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_STATUS]: ALERT_STATUS_ACTIVE,
@@ -248,7 +248,7 @@ describe('createLifecycleExecutor', () => {
{
fields: {
'@timestamp': '',
- [ALERT_ID]: 'TEST_ALERT_0',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_0',
[ALERT_UUID]: 'ALERT_0_UUID',
[ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME',
[ALERT_RULE_CONSUMER]: 'CONSUMER',
@@ -264,7 +264,7 @@ describe('createLifecycleExecutor', () => {
{
fields: {
'@timestamp': '',
- [ALERT_ID]: 'TEST_ALERT_1',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_1',
[ALERT_UUID]: 'ALERT_1_UUID',
[ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME',
[ALERT_RULE_CONSUMER]: 'CONSUMER',
@@ -321,7 +321,7 @@ describe('createLifecycleExecutor', () => {
// alert document
{ index: { _id: 'TEST_ALERT_0_UUID' } },
expect.objectContaining({
- [ALERT_ID]: 'TEST_ALERT_0',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_0',
[ALERT_STATUS]: ALERT_STATUS_RECOVERED,
labels: { LABEL_0_KEY: 'LABEL_0_VALUE' },
[EVENT_ACTION]: 'close',
@@ -329,7 +329,7 @@ describe('createLifecycleExecutor', () => {
}),
{ index: { _id: 'TEST_ALERT_1_UUID' } },
expect.objectContaining({
- [ALERT_ID]: 'TEST_ALERT_1',
+ [ALERT_INSTANCE_ID]: 'TEST_ALERT_1',
[ALERT_STATUS]: ALERT_STATUS_ACTIVE,
[EVENT_ACTION]: 'active',
[EVENT_KIND]: 'signal',
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
index 231d3060a2c54..259a9e9e8de38 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
@@ -22,7 +22,7 @@ import { ParsedTechnicalFields, parseTechnicalFields } from '../../common/parse_
import {
ALERT_DURATION,
ALERT_END,
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_RULE_UUID,
ALERT_START,
ALERT_STATUS,
@@ -228,7 +228,7 @@ export const createLifecycleExecutor = (
hits.hits.forEach((hit) => {
const fields = parseTechnicalFields(hit.fields);
- const alertId = fields[ALERT_ID];
+ const alertId = fields[ALERT_INSTANCE_ID];
alertsDataMap[alertId] = {
...commonRuleFields,
...fields,
@@ -255,7 +255,7 @@ export const createLifecycleExecutor = (
...alertData,
...commonRuleFields,
[ALERT_DURATION]: (options.startedAt.getTime() - new Date(started).getTime()) * 1000,
- [ALERT_ID]: alertId,
+ [ALERT_INSTANCE_ID]: alertId,
[ALERT_START]: started,
[ALERT_STATUS]: isActive ? ALERT_STATUS_ACTIVE : ALERT_STATUS_RECOVERED,
[ALERT_WORKFLOW_STATUS]: alertData[ALERT_WORKFLOW_STATUS] ?? 'open',
@@ -281,7 +281,7 @@ export const createLifecycleExecutor = (
eventsToIndex
.filter((event) => event[ALERT_STATUS] !== 'closed')
.map((event) => {
- const alertId = event[ALERT_ID]!;
+ const alertId = event[ALERT_INSTANCE_ID]!;
const alertUuid = event[ALERT_UUID]!;
const started = new Date(event[ALERT_START]!).toISOString();
return [alertId, { alertId, alertUuid, started }];
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
index b2cd69be7fdad..6ff418b2ebe47 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
@@ -198,7 +198,7 @@ describe('createLifecycleRuleTypeFactory', () => {
"event.action": "open",
"event.kind": "signal",
"kibana.alert.duration.us": 0,
- "kibana.alert.id": "opbeans-java",
+ "kibana.alert.instance.id": "opbeans-java",
"kibana.alert.rule.category": "ruleTypeName",
"kibana.alert.rule.consumer": "consumer",
"kibana.alert.rule.name": "name",
@@ -222,7 +222,7 @@ describe('createLifecycleRuleTypeFactory', () => {
"event.action": "open",
"event.kind": "signal",
"kibana.alert.duration.us": 0,
- "kibana.alert.id": "opbeans-node",
+ "kibana.alert.instance.id": "opbeans-node",
"kibana.alert.rule.category": "ruleTypeName",
"kibana.alert.rule.consumer": "consumer",
"kibana.alert.rule.name": "name",
diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts
index 1fa51d98c8ab5..57562e34bab78 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ALERT_ID, VERSION } from '@kbn/rule-data-utils';
+import { ALERT_INSTANCE_ID, VERSION } from '@kbn/rule-data-utils';
import { getCommonAlertFields } from './get_common_alert_fields';
import { CreatePersistenceRuleTypeFactory } from './persistence_types';
@@ -31,7 +31,7 @@ export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory
body: alerts.flatMap((event) => [
{ index: {} },
{
- [ALERT_ID]: event.id,
+ [ALERT_INSTANCE_ID]: event.id,
[VERSION]: ruleDataClient.kibanaVersion,
...commonRuleFields,
...event.fields,
diff --git a/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts
index 8bba639636ba6..67db667188eaf 100644
--- a/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts
+++ b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts
@@ -9,7 +9,7 @@ import { Values } from '@kbn/utility-types';
import { AlertExecutorOptions } from '../../../alerting/server';
import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
import {
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_UUID,
ALERT_RULE_CATEGORY,
ALERT_RULE_CONSUMER,
@@ -35,7 +35,7 @@ const commonAlertFieldNames = [
];
export type CommonAlertFieldName = Values;
-const commonAlertIdFieldNames = [ALERT_ID, ALERT_UUID];
+const commonAlertIdFieldNames = [ALERT_INSTANCE_ID, ALERT_UUID];
export type CommonAlertIdFieldName = Values;
export type CommonAlertFields = Pick;
diff --git a/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts
index b74fa27879f3d..95a6761152371 100644
--- a/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts
+++ b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts
@@ -24,7 +24,7 @@ export const createDefaultAlertExecutorOptions = <
InstanceContext extends AlertInstanceContext = {},
ActionGroupIds extends string = ''
>({
- alertId = 'ALERT_ID',
+ alertId = 'ALERT_INSTANCE_ID',
ruleName = 'ALERT_RULE_NAME',
params,
state,
diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts
index 4593d9a7ad682..75dfcb6151ea7 100644
--- a/x-pack/plugins/security/server/config.test.ts
+++ b/x-pack/plugins/security/server/config.test.ts
@@ -1689,41 +1689,39 @@ describe('createConfig()', () => {
`);
});
- it('falls back to the global settings if provider is not known', async () => {
- expect(
- createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({
- type: 'some type',
- name: 'some name',
- })
- ).toMatchInlineSnapshot(`
- Object {
- "idleTimeout": "PT0.123S",
- "lifespan": "P30D",
- }
- `);
+ it('falls back to the global settings if provider is not known or is undefined', async () => {
+ [{ type: 'some type', name: 'some name' }, undefined].forEach((provider) => {
+ expect(
+ createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts(
+ provider
+ )
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "P30D",
+ }
+ `);
- expect(
- createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({
- type: 'some type',
- name: 'some name',
- })
- ).toMatchInlineSnapshot(`
- Object {
- "idleTimeout": "PT1H",
- "lifespan": "PT0.456S",
- }
- `);
+ expect(
+ createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts(provider)
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT1H",
+ "lifespan": "PT0.456S",
+ }
+ `);
- expect(
- createMockConfig({
- session: { idleTimeout: 123, lifespan: 456 },
- }).session.getExpirationTimeouts({ type: 'some type', name: 'some name' })
- ).toMatchInlineSnapshot(`
- Object {
- "idleTimeout": "PT0.123S",
- "lifespan": "PT0.456S",
- }
- `);
+ expect(
+ createMockConfig({
+ session: { idleTimeout: 123, lifespan: 456 },
+ }).session.getExpirationTimeouts(provider)
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "PT0.456S",
+ }
+ `);
+ });
});
it('uses provider overrides if specified (only idle timeout)', async () => {
diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts
index 6ce161a898810..9daf0aff4c6cb 100644
--- a/x-pack/plugins/security/server/config.ts
+++ b/x-pack/plugins/security/server/config.ts
@@ -391,11 +391,18 @@ export function createConfig(
function getSessionConfig(session: RawConfigType['session'], providers: ProvidersConfigType) {
return {
cleanupInterval: session.cleanupInterval,
- getExpirationTimeouts({ type, name }: AuthenticationProvider) {
+ getExpirationTimeouts(provider: AuthenticationProvider | undefined) {
// Both idle timeout and lifespan from the provider specific session config can have three
// possible types of values: `Duration`, `null` and `undefined`. The `undefined` type means that
// provider doesn't override session config and we should fall back to the global one instead.
- const providerSessionConfig = providers[type as keyof ProvidersConfigType]?.[name]?.session;
+ // Note: using an `undefined` provider argument returns the global timeouts.
+ let providerSessionConfig:
+ | { idleTimeout?: Duration | null; lifespan?: Duration | null }
+ | undefined;
+ if (provider) {
+ const { type, name } = provider;
+ providerSessionConfig = providers[type as keyof ProvidersConfigType]?.[name]?.session;
+ }
const [idleTimeout, lifespan] = [
[session.idleTimeout, providerSessionConfig?.idleTimeout],
[session.lifespan, providerSessionConfig?.lifespan],
diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts
index bd8b21358e3e8..ad6f81eaeefff 100644
--- a/x-pack/plugins/security/server/config_deprecations.test.ts
+++ b/x-pack/plugins/security/server/config_deprecations.test.ts
@@ -49,7 +49,7 @@ describe('Config Deprecations', () => {
expect(migrated.xpack.security.session.idleTimeout).toEqual(123);
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.security.sessionTimeout\\" is deprecated and has been replaced by \\"xpack.security.session.idleTimeout\\"",
+ "Setting \\"xpack.security.sessionTimeout\\" has been replaced by \\"xpack.security.session.idleTimeout\\"",
]
`);
});
@@ -71,7 +71,7 @@ describe('Config Deprecations', () => {
expect(migrated.xpack.security.audit.appender.type).toEqual('console');
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.security.audit.appender.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.type\\"",
+ "Setting \\"xpack.security.audit.appender.kind\\" has been replaced by \\"xpack.security.audit.appender.type\\"",
]
`);
});
@@ -93,7 +93,7 @@ describe('Config Deprecations', () => {
expect(migrated.xpack.security.audit.appender.layout.type).toEqual('pattern');
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.security.audit.appender.layout.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.layout.type\\"",
+ "Setting \\"xpack.security.audit.appender.layout.kind\\" has been replaced by \\"xpack.security.audit.appender.layout.type\\"",
]
`);
});
@@ -115,7 +115,7 @@ describe('Config Deprecations', () => {
expect(migrated.xpack.security.audit.appender.policy.type).toEqual('time-interval');
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.security.audit.appender.policy.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.policy.type\\"",
+ "Setting \\"xpack.security.audit.appender.policy.kind\\" has been replaced by \\"xpack.security.audit.appender.policy.type\\"",
]
`);
});
@@ -137,7 +137,7 @@ describe('Config Deprecations', () => {
expect(migrated.xpack.security.audit.appender.strategy.type).toEqual('numeric');
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.security.audit.appender.strategy.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.strategy.type\\"",
+ "Setting \\"xpack.security.audit.appender.strategy.kind\\" has been replaced by \\"xpack.security.audit.appender.strategy.type\\"",
]
`);
});
@@ -160,7 +160,7 @@ describe('Config Deprecations', () => {
expect(migrated.xpack.security.audit.appender.fileName).toEqual('./audit.log');
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.security.audit.appender.path\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.fileName\\"",
+ "Setting \\"xpack.security.audit.appender.path\\" has been replaced by \\"xpack.security.audit.appender.fileName\\"",
]
`);
});
@@ -222,7 +222,7 @@ describe('Config Deprecations', () => {
expect(migrated.xpack.security.audit.appender.fileName).toEqual('./audit.log');
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.security.audit.appender.path\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.fileName\\"",
+ "Setting \\"xpack.security.audit.appender.path\\" has been replaced by \\"xpack.security.audit.appender.fileName\\"",
]
`);
});
@@ -242,7 +242,7 @@ describe('Config Deprecations', () => {
const { messages } = applyConfigDeprecations(cloneDeep(config));
expect(messages).toMatchInlineSnapshot(`
Array [
- "xpack.security.authorization.legacyFallback.enabled is deprecated and is no longer used",
+ "You no longer need to configure \\"xpack.security.authorization.legacyFallback.enabled\\".",
]
`);
});
@@ -262,7 +262,7 @@ describe('Config Deprecations', () => {
const { messages } = applyConfigDeprecations(cloneDeep(config));
expect(messages).toMatchInlineSnapshot(`
Array [
- "xpack.security.authc.saml.maxRedirectURLSize is deprecated and is no longer used",
+ "You no longer need to configure \\"xpack.security.authc.saml.maxRedirectURLSize\\".",
]
`);
});
@@ -286,7 +286,7 @@ describe('Config Deprecations', () => {
const { messages } = applyConfigDeprecations(cloneDeep(config));
expect(messages).toMatchInlineSnapshot(`
Array [
- "\`xpack.security.authc.providers.saml..maxRedirectURLSize\` is deprecated and is no longer used",
+ "\\"xpack.security.authc.providers.saml..maxRedirectURLSize\\" is is no longer used.",
]
`);
});
@@ -305,7 +305,7 @@ describe('Config Deprecations', () => {
expect(migrated).toEqual(config);
expect(messages).toMatchInlineSnapshot(`
Array [
- "Defining \\"xpack.security.authc.providers\\" as an array of provider types is deprecated. Use extended \\"object\\" format instead.",
+ "\\"xpack.security.authc.providers\\" accepts an extended \\"object\\" format instead of an array of provider types.",
]
`);
});
@@ -324,8 +324,8 @@ describe('Config Deprecations', () => {
expect(migrated).toEqual(config);
expect(messages).toMatchInlineSnapshot(`
Array [
- "Defining \\"xpack.security.authc.providers\\" as an array of provider types is deprecated. Use extended \\"object\\" format instead.",
- "Enabling both \`basic\` and \`token\` authentication providers in \`xpack.security.authc.providers\` is deprecated. Login page will only use \`token\` provider.",
+ "\\"xpack.security.authc.providers\\" accepts an extended \\"object\\" format instead of an array of provider types.",
+ "Enabling both \\"basic\\" and \\"token\\" authentication providers in \\"xpack.security.authc.providers\\" is deprecated. Login page will only use \\"token\\" provider.",
]
`);
});
@@ -341,9 +341,9 @@ describe('Config Deprecations', () => {
const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
expect(migrated).toEqual(config);
expect(messages).toMatchInlineSnapshot(`
- Array [
- "Disabling the security plugin (\`xpack.security.enabled\`) will not be supported in the next major version (8.0). To turn off security features, disable them in Elasticsearch instead.",
- ]
+ Array [
+ "Disabling the security plugin \\"xpack.security.enabled\\" will only be supported by disable security in Elasticsearch.",
+ ]
`);
});
diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts
index 808a192f83206..f68112760632e 100644
--- a/x-pack/plugins/security/server/config_deprecations.ts
+++ b/x-pack/plugins/security/server/config_deprecations.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { i18n } from '@kbn/i18n';
import type { ConfigDeprecationProvider } from 'src/core/server';
export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
@@ -27,27 +28,45 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
const legacyAuditLoggerEnabled = !settings?.xpack?.security?.audit?.appender;
if (auditLoggingEnabled && legacyAuditLoggerEnabled) {
addDeprecation({
- message: `The legacy audit logger is deprecated in favor of the new ECS-compliant audit logger.`,
+ title: i18n.translate('xpack.security.deprecations.auditLoggerTitle', {
+ defaultMessage: 'The legacy audit logger is deprecated',
+ }),
+ message: i18n.translate('xpack.security.deprecations.auditLoggerMessage', {
+ defaultMessage:
+ 'The legacy audit logger is deprecated in favor of the new ECS-compliant audit logger.',
+ }),
documentationUrl:
'https://www.elastic.co/guide/en/kibana/current/security-settings-kb.html#audit-logging-settings',
correctiveActions: {
manualSteps: [
- `Declare an audit logger "appender" via "xpack.security.audit.appender" to enable the ECS audit logger.`,
+ i18n.translate('xpack.security.deprecations.auditLogger.manualStepOneMessage', {
+ defaultMessage:
+ 'Declare an audit logger "appender" via "xpack.security.audit.appender" to enable the ECS audit logger.',
+ }),
],
},
});
}
},
+
// Deprecation warning for the old array-based format of `xpack.security.authc.providers`.
(settings, fromPath, addDeprecation) => {
if (Array.isArray(settings?.xpack?.security?.authc?.providers)) {
addDeprecation({
- message:
- `Defining "xpack.security.authc.providers" as an array of provider types is deprecated. ` +
- `Use extended "object" format instead.`,
+ title: i18n.translate('xpack.security.deprecations.authcProvidersTitle', {
+ defaultMessage:
+ 'Defining "xpack.security.authc.providers" as an array of provider types is deprecated',
+ }),
+ message: i18n.translate('xpack.security.deprecations.authcProvidersMessage', {
+ defaultMessage:
+ '"xpack.security.authc.providers" accepts an extended "object" format instead of an array of provider types.',
+ }),
correctiveActions: {
manualSteps: [
- `Use the extended object format for "xpack.security.authc.providers" in your Kibana configuration.`,
+ i18n.translate('xpack.security.deprecations.authcProviders.manualStepOneMessage', {
+ defaultMessage:
+ 'Use the extended object format for "xpack.security.authc.providers" in your Kibana configuration.',
+ }),
],
},
});
@@ -67,11 +86,23 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
if (hasProviderType('basic') && hasProviderType('token')) {
addDeprecation({
- message:
- 'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.',
+ title: i18n.translate('xpack.security.deprecations.basicAndTokenProvidersTitle', {
+ defaultMessage:
+ 'Both "basic" and "token" authentication providers are enabled in "xpack.security.authc.providers"',
+ }),
+ message: i18n.translate('xpack.security.deprecations.basicAndTokenProvidersMessage', {
+ defaultMessage:
+ 'Enabling both "basic" and "token" authentication providers in "xpack.security.authc.providers" is deprecated. Login page will only use "token" provider.',
+ }),
correctiveActions: {
manualSteps: [
- 'Remove either the `basic` or `token` auth provider in "xpack.security.authc.providers" from your Kibana configuration.',
+ i18n.translate(
+ 'xpack.security.deprecations.basicAndTokenProviders.manualStepOneMessage',
+ {
+ defaultMessage:
+ 'Remove either the "basic" or "token" auth provider in "xpack.security.authc.providers" from your Kibana configuration.',
+ }
+ ),
],
},
});
@@ -84,11 +115,20 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
>;
if (Object.values(samlProviders).find((provider) => !!provider.maxRedirectURLSize)) {
addDeprecation({
- message:
- '`xpack.security.authc.providers.saml..maxRedirectURLSize` is deprecated and is no longer used',
+ title: i18n.translate('xpack.security.deprecations.maxRedirectURLSizeTitle', {
+ defaultMessage:
+ '"xpack.security.authc.providers.saml..maxRedirectURLSize" is deprecated',
+ }),
+ message: i18n.translate('xpack.security.deprecations.maxRedirectURLSizeMessage', {
+ defaultMessage:
+ '"xpack.security.authc.providers.saml..maxRedirectURLSize" is is no longer used.',
+ }),
correctiveActions: {
manualSteps: [
- `Remove "xpack.security.authc.providers.saml..maxRedirectURLSize" from your Kibana configuration.`,
+ i18n.translate('xpack.security.deprecations.maxRedirectURLSize.manualStepOneMessage', {
+ defaultMessage:
+ 'Remove "xpack.security.authc.providers.saml..maxRedirectURLSize" from your Kibana configuration.',
+ }),
],
},
});
@@ -97,13 +137,21 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
(settings, fromPath, addDeprecation) => {
if (settings?.xpack?.security?.enabled === false) {
addDeprecation({
- message:
- 'Disabling the security plugin (`xpack.security.enabled`) will not be supported in the next major version (8.0). ' +
- 'To turn off security features, disable them in Elasticsearch instead.',
+ title: i18n.translate('xpack.security.deprecations.enabledTitle', {
+ defaultMessage: 'Disabling the security plugin "xpack.security.enabled" is deprecated',
+ }),
+ message: i18n.translate('xpack.security.deprecations.enabledMessage', {
+ defaultMessage:
+ 'Disabling the security plugin "xpack.security.enabled" will only be supported by disable security in Elasticsearch.',
+ }),
correctiveActions: {
manualSteps: [
- `Remove "xpack.security.enabled" from your Kibana configuration.`,
- `To turn off security features, disable them in Elasticsearch instead.`,
+ i18n.translate('xpack.security.deprecations.enabled.manualStepOneMessage', {
+ defaultMessage: `Remove "xpack.security.enabled" from your Kibana configuration.`,
+ }),
+ i18n.translate('xpack.security.deprecations.enabled.manualStepTwoMessage', {
+ defaultMessage: `To turn off security features, disable them in Elasticsearch instead.`,
+ }),
],
},
});
diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts
index 9093d5d2e0db2..f1a9296177d9c 100644
--- a/x-pack/plugins/security/server/session_management/session_index.ts
+++ b/x-pack/plugins/security/server/session_management/session_index.ts
@@ -317,69 +317,73 @@ export class SessionIndex {
const sessionIndexTemplateName = `${this.options.kibanaIndexName}_security_session_index_template_${SESSION_INDEX_TEMPLATE_VERSION}`;
return (this.indexInitialization = new Promise(async (resolve, reject) => {
- // Check if required index template exists.
- let indexTemplateExists = false;
try {
- indexTemplateExists = (
- await this.options.elasticsearchClient.indices.existsTemplate({
- name: sessionIndexTemplateName,
- })
- ).body;
- } catch (err) {
- this.options.logger.error(
- `Failed to check if session index template exists: ${err.message}`
- );
- return reject(err);
- }
-
- // Create index template if it doesn't exist.
- if (indexTemplateExists) {
- this.options.logger.debug('Session index template already exists.');
- } else {
+ // Check if required index template exists.
+ let indexTemplateExists = false;
try {
- await this.options.elasticsearchClient.indices.putTemplate({
- name: sessionIndexTemplateName,
- body: getSessionIndexTemplate(this.indexName),
- });
- this.options.logger.debug('Successfully created session index template.');
+ indexTemplateExists = (
+ await this.options.elasticsearchClient.indices.existsTemplate({
+ name: sessionIndexTemplateName,
+ })
+ ).body;
} catch (err) {
- this.options.logger.error(`Failed to create session index template: ${err.message}`);
+ this.options.logger.error(
+ `Failed to check if session index template exists: ${err.message}`
+ );
return reject(err);
}
- }
- // Check if required index exists. We cannot be sure that automatic creation of indices is
- // always enabled, so we create session index explicitly.
- let indexExists = false;
- try {
- indexExists = (
- await this.options.elasticsearchClient.indices.exists({ index: this.indexName })
- ).body;
- } catch (err) {
- this.options.logger.error(`Failed to check if session index exists: ${err.message}`);
- return reject(err);
- }
+ // Create index template if it doesn't exist.
+ if (indexTemplateExists) {
+ this.options.logger.debug('Session index template already exists.');
+ } else {
+ try {
+ await this.options.elasticsearchClient.indices.putTemplate({
+ name: sessionIndexTemplateName,
+ body: getSessionIndexTemplate(this.indexName),
+ });
+ this.options.logger.debug('Successfully created session index template.');
+ } catch (err) {
+ this.options.logger.error(`Failed to create session index template: ${err.message}`);
+ return reject(err);
+ }
+ }
- // Create index if it doesn't exist.
- if (indexExists) {
- this.options.logger.debug('Session index already exists.');
- } else {
+ // Check if required index exists. We cannot be sure that automatic creation of indices is
+ // always enabled, so we create session index explicitly.
+ let indexExists = false;
try {
- await this.options.elasticsearchClient.indices.create({ index: this.indexName });
- this.options.logger.debug('Successfully created session index.');
+ indexExists = (
+ await this.options.elasticsearchClient.indices.exists({ index: this.indexName })
+ ).body;
} catch (err) {
- // There can be a race condition if index is created by another Kibana instance.
- if (err?.body?.error?.type === 'resource_already_exists_exception') {
- this.options.logger.debug('Session index already exists.');
- } else {
- this.options.logger.error(`Failed to create session index: ${err.message}`);
- return reject(err);
+ this.options.logger.error(`Failed to check if session index exists: ${err.message}`);
+ return reject(err);
+ }
+
+ // Create index if it doesn't exist.
+ if (indexExists) {
+ this.options.logger.debug('Session index already exists.');
+ } else {
+ try {
+ await this.options.elasticsearchClient.indices.create({ index: this.indexName });
+ this.options.logger.debug('Successfully created session index.');
+ } catch (err) {
+ // There can be a race condition if index is created by another Kibana instance.
+ if (err?.body?.error?.type === 'resource_already_exists_exception') {
+ this.options.logger.debug('Session index already exists.');
+ } else {
+ this.options.logger.error(`Failed to create session index: ${err.message}`);
+ return reject(err);
+ }
}
}
- }
- // Notify any consumers that are awaiting on this promise and immediately reset it.
- resolve();
+ // Notify any consumers that are awaiting on this promise and immediately reset it.
+ resolve();
+ } catch (error) {
+ reject(error);
+ }
}).finally(() => {
this.indexInitialization = undefined;
}));
diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts
index 6a1f6662e796e..0515a1e1969bf 100644
--- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts
+++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts
@@ -40,6 +40,17 @@ describe('Security UsageCollector', () => {
};
const collectorFetchContext = createCollectorFetchContextMock();
+ const DEFAULT_USAGE = {
+ auditLoggingEnabled: false,
+ accessAgreementEnabled: false,
+ authProviderCount: 1,
+ enabledAuthProviders: ['basic'],
+ loginSelectorEnabled: false,
+ httpAuthSchemes: ['apikey'],
+ sessionIdleTimeoutInMinutes: 60,
+ sessionLifespanInMinutes: 43200,
+ sessionCleanupInMinutes: 60,
+ };
describe('initialization', () => {
it('handles an undefined usage collector', () => {
@@ -75,14 +86,7 @@ describe('Security UsageCollector', () => {
.getCollectorByType('security')
?.fetch(collectorFetchContext);
- expect(usage).toEqual({
- auditLoggingEnabled: false,
- accessAgreementEnabled: false,
- authProviderCount: 1,
- enabledAuthProviders: ['basic'],
- loginSelectorEnabled: false,
- httpAuthSchemes: ['apikey'],
- });
+ expect(usage).toEqual(DEFAULT_USAGE);
});
it('reports correctly when security is disabled in Elasticsearch', async () => {
@@ -103,6 +107,9 @@ describe('Security UsageCollector', () => {
enabledAuthProviders: [],
loginSelectorEnabled: false,
httpAuthSchemes: [],
+ sessionIdleTimeoutInMinutes: 0,
+ sessionLifespanInMinutes: 0,
+ sessionCleanupInMinutes: 0,
});
});
@@ -140,14 +147,7 @@ describe('Security UsageCollector', () => {
.getCollectorByType('security')
?.fetch(collectorFetchContext);
- expect(usage).toEqual({
- auditLoggingEnabled: false,
- accessAgreementEnabled: false,
- authProviderCount: 1,
- enabledAuthProviders: ['basic'],
- loginSelectorEnabled: false,
- httpAuthSchemes: ['apikey'],
- });
+ expect(usage).toEqual(DEFAULT_USAGE);
});
it('reports the types and count of enabled auth providers', async () => {
@@ -190,12 +190,10 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
- auditLoggingEnabled: false,
- accessAgreementEnabled: false,
+ ...DEFAULT_USAGE,
authProviderCount: 3,
enabledAuthProviders: ['saml', 'pki'],
loginSelectorEnabled: true,
- httpAuthSchemes: ['apikey'],
});
});
});
@@ -228,12 +226,9 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
- auditLoggingEnabled: false,
+ ...DEFAULT_USAGE,
accessAgreementEnabled: true,
- authProviderCount: 1,
enabledAuthProviders: ['saml'],
- loginSelectorEnabled: false,
- httpAuthSchemes: ['apikey'],
});
});
it('does not report the access agreement if the license does not permit it', async () => {
@@ -266,12 +261,9 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
- auditLoggingEnabled: false,
+ ...DEFAULT_USAGE,
accessAgreementEnabled: false,
- authProviderCount: 1,
enabledAuthProviders: ['saml'],
- loginSelectorEnabled: false,
- httpAuthSchemes: ['apikey'],
});
});
@@ -307,12 +299,9 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
- auditLoggingEnabled: false,
+ ...DEFAULT_USAGE,
accessAgreementEnabled: false,
- authProviderCount: 1,
enabledAuthProviders: ['saml'],
- loginSelectorEnabled: false,
- httpAuthSchemes: ['apikey'],
});
});
});
@@ -346,27 +335,29 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
- auditLoggingEnabled: false,
- accessAgreementEnabled: false,
- authProviderCount: 1,
+ ...DEFAULT_USAGE,
enabledAuthProviders: ['saml'],
loginSelectorEnabled: true,
- httpAuthSchemes: ['apikey'],
});
});
});
describe('audit logging', () => {
- it('reports when audit logging is enabled', async () => {
+ it('reports when legacy audit logging is enabled (and ECS audit logging is not enabled)', async () => {
const config = createSecurityConfig(
ConfigSchema.validate({
audit: {
enabled: true,
+ appender: undefined,
},
})
);
const usageCollection = usageCollectionPluginMock.createSetupContract();
- const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: true });
+ const license = createSecurityLicense({
+ isLicenseAvailable: true,
+ allowLegacyAuditLogging: true,
+ allowAuditLogging: true,
+ });
registerSecurityUsageCollector({ usageCollection, config, license });
const usage = await usageCollection
@@ -374,12 +365,37 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
+ ...DEFAULT_USAGE,
auditLoggingEnabled: true,
- accessAgreementEnabled: false,
- authProviderCount: 1,
- enabledAuthProviders: ['basic'],
- loginSelectorEnabled: false,
- httpAuthSchemes: ['apikey'],
+ auditLoggingType: 'legacy',
+ });
+ });
+
+ it('reports when ECS audit logging is enabled (and legacy audit logging is not enabled)', async () => {
+ const config = createSecurityConfig(
+ ConfigSchema.validate({
+ audit: {
+ enabled: true,
+ appender: { type: 'console', layout: { type: 'json' } },
+ },
+ })
+ );
+ const usageCollection = usageCollectionPluginMock.createSetupContract();
+ const license = createSecurityLicense({
+ isLicenseAvailable: true,
+ allowLegacyAuditLogging: true,
+ allowAuditLogging: true,
+ });
+ registerSecurityUsageCollector({ usageCollection, config, license });
+
+ const usage = await usageCollection
+ .getCollectorByType('security')
+ ?.fetch(collectorFetchContext);
+
+ expect(usage).toEqual({
+ ...DEFAULT_USAGE,
+ auditLoggingEnabled: true,
+ auditLoggingType: 'ecs',
});
});
@@ -400,12 +416,9 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
+ ...DEFAULT_USAGE,
auditLoggingEnabled: false,
- accessAgreementEnabled: false,
- authProviderCount: 1,
- enabledAuthProviders: ['basic'],
- loginSelectorEnabled: false,
- httpAuthSchemes: ['apikey'],
+ auditLoggingType: undefined,
});
});
});
@@ -430,11 +443,7 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
- auditLoggingEnabled: false,
- accessAgreementEnabled: false,
- authProviderCount: 1,
- enabledAuthProviders: ['basic'],
- loginSelectorEnabled: false,
+ ...DEFAULT_USAGE,
httpAuthSchemes: ['basic', 'Negotiate'],
});
});
@@ -458,13 +467,34 @@ describe('Security UsageCollector', () => {
?.fetch(collectorFetchContext);
expect(usage).toEqual({
- auditLoggingEnabled: false,
- accessAgreementEnabled: false,
- authProviderCount: 1,
- enabledAuthProviders: ['basic'],
- loginSelectorEnabled: false,
+ ...DEFAULT_USAGE,
httpAuthSchemes: ['basic', 'Negotiate'],
});
});
});
+
+ describe('session', () => {
+ // Note: can't easily test deprecated 'sessionTimeout' value here because of the way that config deprecation renaming works
+ it('reports customized session idleTimeout, lifespan, and cleanupInterval', async () => {
+ const config = createSecurityConfig(
+ ConfigSchema.validate({
+ session: { idleTimeout: '123m', lifespan: '456m', cleanupInterval: '789m' },
+ })
+ );
+ const usageCollection = usageCollectionPluginMock.createSetupContract();
+ const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false });
+ registerSecurityUsageCollector({ usageCollection, config, license });
+
+ const usage = await usageCollection
+ .getCollectorByType('security')
+ ?.fetch(collectorFetchContext);
+
+ expect(usage).toEqual({
+ ...DEFAULT_USAGE,
+ sessionIdleTimeoutInMinutes: 123,
+ sessionLifespanInMinutes: 456,
+ sessionCleanupInMinutes: 789,
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts
index 813e23a13ff37..15177132e0fb1 100644
--- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts
+++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts
@@ -12,11 +12,15 @@ import type { ConfigType } from '../config';
interface Usage {
auditLoggingEnabled: boolean;
+ auditLoggingType?: 'ecs' | 'legacy';
loginSelectorEnabled: boolean;
accessAgreementEnabled: boolean;
authProviderCount: number;
enabledAuthProviders: string[];
httpAuthSchemes: string[];
+ sessionIdleTimeoutInMinutes: number;
+ sessionLifespanInMinutes: number;
+ sessionCleanupInMinutes: number;
}
interface Deps {
@@ -58,6 +62,13 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
'Indicates if audit logging is both enabled and supported by the current license.',
},
},
+ auditLoggingType: {
+ type: 'keyword',
+ _meta: {
+ description:
+ 'If auditLoggingEnabled is true, indicates what type is enabled (ECS or legacy).',
+ },
+ },
loginSelectorEnabled: {
type: 'boolean',
_meta: {
@@ -98,6 +109,27 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
},
},
},
+ sessionIdleTimeoutInMinutes: {
+ type: 'long',
+ _meta: {
+ description:
+ 'The global session idle timeout expiration that is configured, in minutes (0 if disabled).',
+ },
+ },
+ sessionLifespanInMinutes: {
+ type: 'long',
+ _meta: {
+ description:
+ 'The global session lifespan expiration that is configured, in minutes (0 if disabled).',
+ },
+ },
+ sessionCleanupInMinutes: {
+ type: 'long',
+ _meta: {
+ description:
+ 'The session cleanup interval that is configured, in minutes (0 if disabled).',
+ },
+ },
},
fetch: () => {
const {
@@ -114,13 +146,23 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
authProviderCount: 0,
enabledAuthProviders: [],
httpAuthSchemes: [],
+ sessionIdleTimeoutInMinutes: 0,
+ sessionLifespanInMinutes: 0,
+ sessionCleanupInMinutes: 0,
};
}
const legacyAuditLoggingEnabled = allowLegacyAuditLogging && config.audit.enabled;
- const auditLoggingEnabled =
+ const ecsAuditLoggingEnabled =
allowAuditLogging && config.audit.enabled && config.audit.appender != null;
+ let auditLoggingType: Usage['auditLoggingType'];
+ if (ecsAuditLoggingEnabled) {
+ auditLoggingType = 'ecs';
+ } else if (legacyAuditLoggingEnabled) {
+ auditLoggingType = 'legacy';
+ }
+
const loginSelectorEnabled = config.authc.selector.enabled;
const authProviderCount = config.authc.sortedProviders.length;
const enabledAuthProviders = [
@@ -139,13 +181,22 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
WELL_KNOWN_AUTH_SCHEMES.includes(scheme.toLowerCase())
);
+ const sessionExpirations = config.session.getExpirationTimeouts(undefined); // use `undefined` to get global expiration values
+ const sessionIdleTimeoutInMinutes = sessionExpirations.idleTimeout?.asMinutes() ?? 0;
+ const sessionLifespanInMinutes = sessionExpirations.lifespan?.asMinutes() ?? 0;
+ const sessionCleanupInMinutes = config.session.cleanupInterval?.asMinutes() ?? 0;
+
return {
- auditLoggingEnabled: legacyAuditLoggingEnabled || auditLoggingEnabled,
+ auditLoggingEnabled: legacyAuditLoggingEnabled || ecsAuditLoggingEnabled,
+ auditLoggingType,
loginSelectorEnabled,
accessAgreementEnabled,
authProviderCount,
enabledAuthProviders,
httpAuthSchemes,
+ sessionIdleTimeoutInMinutes,
+ sessionLifespanInMinutes,
+ sessionCleanupInMinutes,
};
},
});
diff --git a/x-pack/plugins/security_solution/common/ecs/event/index.ts b/x-pack/plugins/security_solution/common/ecs/event/index.ts
index 14f38480f90c8..9e2ebb059b3b3 100644
--- a/x-pack/plugins/security_solution/common/ecs/event/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/event/index.ts
@@ -53,7 +53,7 @@ export enum EventCode {
// Memory Protection alert
MEMORY_SIGNATURE = 'memory_signature',
// Memory Protection alert
- MALICIOUS_THREAD = 'malicious_thread',
+ SHELLCODE_THREAD = 'shellcode_thread',
// behavior
BEHAVIOR = 'behavior',
}
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index afe85e1abaa53..8f985db732b61 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -678,7 +678,7 @@ export class EndpointDocGenerator extends BaseDataGenerator {
action: 'start',
kind: 'alert',
category: 'malware',
- code: isShellcode ? 'malicious_thread' : 'memory_signature',
+ code: isShellcode ? 'shellcode_thread' : 'memory_signature',
id: this.seededUUIDv4(),
dataset: 'endpoint',
module: 'endpoint',
diff --git a/x-pack/plugins/security_solution/common/types/timeline/store.ts b/x-pack/plugins/security_solution/common/types/timeline/store.ts
index 01fc9db7c8e1d..03cf0c39378e5 100644
--- a/x-pack/plugins/security_solution/common/types/timeline/store.ts
+++ b/x-pack/plugins/security_solution/common/types/timeline/store.ts
@@ -48,6 +48,7 @@ export interface TimelinePersistInput {
expandedDetail?: TimelineExpandedDetail;
filters?: Filter[];
columns: ColumnHeaderOptions[];
+ defaultColumns?: ColumnHeaderOptions[];
itemsPerPage?: number;
indexNames: string[];
kqlQuery?: {
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
index f5cbc65effd85..674114188632b 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
@@ -54,7 +54,7 @@ describe('Alert details with unmapped fields', () => {
it('Displays the unmapped field on the table', () => {
const expectedUnmmappedField = {
- row: 90,
+ row: 86,
field: 'unmapped',
text: 'This is the unmapped field',
};
diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts
index 41027258f0bf0..c3eab5cc2a936 100644
--- a/x-pack/plugins/security_solution/cypress/objects/rule.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts
@@ -164,7 +164,7 @@ const getRunsEvery = (): Interval => ({
});
const getLookBack = (): Interval => ({
- interval: '17520',
+ interval: '50000',
timeType: 'Hours',
type: 'h',
});
@@ -382,5 +382,5 @@ export const getEditedRule = (): CustomRule => ({
export const expectedExportedRule = (ruleResponse: Cypress.Response): string => {
const jsonrule = ruleResponse.body;
- return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"100m","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-17520h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`;
+ return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"100m","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-50000h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`;
};
diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts
index 1520a88ec31bc..5b8ad49f61b0d 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts
@@ -63,7 +63,12 @@ export const closeAlerts = () => {
};
export const expandFirstAlert = () => {
- cy.get(EXPAND_ALERT_BTN).should('exist').first().click({ force: true });
+ cy.get(EXPAND_ALERT_BTN).should('exist');
+
+ cy.get(EXPAND_ALERT_BTN)
+ .first()
+ .pipe(($el) => $el.trigger('click'))
+ .should('exist');
};
export const viewThreatIntelTab = () => cy.get(THREAT_INTEL_TAB).click();
@@ -75,7 +80,7 @@ export const viewThreatDetails = () => {
export const setEnrichmentDates = (from?: string, to?: string) => {
cy.get(ENRICHMENT_QUERY_RANGE_PICKER).within(() => {
if (from) {
- cy.get(ENRICHMENT_QUERY_START_INPUT).type(`{selectall}${from}{enter}`);
+ cy.get(ENRICHMENT_QUERY_START_INPUT).first().type(`{selectall}${from}{enter}`);
}
if (to) {
cy.get(ENRICHMENT_QUERY_END_INPUT).type(`{selectall}${to}{enter}`);
diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
index b4e4941ff7f94..33bd8a06b9985 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
@@ -19,7 +19,7 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing', inte
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'query',
- from: 'now-17520h',
+ from: 'now-50000h',
index: ['exceptions-*'],
query: rule.customQuery,
language: 'kuery',
@@ -59,7 +59,7 @@ export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'r
threat_filters: [],
threat_index: rule.indicatorIndexPattern,
threat_indicator_path: '',
- from: 'now-17520h',
+ from: 'now-50000h',
index: rule.index,
query: rule.customQuery || '*:*',
language: 'kuery',
@@ -86,7 +86,7 @@ export const createCustomRuleActivated = (
name: rule.name,
severity: rule.severity.toLocaleLowerCase(),
type: 'query',
- from: 'now-17520h',
+ from: 'now-50000h',
index: rule.index,
query: rule.customQuery,
language: 'kuery',
diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
index f34c3f598e934..e2d27a11ed717 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
@@ -532,7 +532,6 @@ export const waitForAlertsToPopulate = async (alertCountThreshold = 1) => {
cy.waitUntil(
() => {
refreshPage();
- cy.get(LOADING_INDICATOR).should('exist');
cy.get(LOADING_INDICATOR).should('not.exist');
return cy
.get(SERVER_SIDE_EVENT_COUNT)
diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json
index 3a0eb1a5458a8..5fda8730d5e9f 100644
--- a/x-pack/plugins/security_solution/package.json
+++ b/x-pack/plugins/security_solution/package.json
@@ -14,7 +14,7 @@
"cypress:run": "yarn cypress:run:reporter --browser chrome --headless --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status",
"cypress:run:firefox": "yarn cypress:run:reporter --browser firefox --headless --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status",
"cypress:run:reporter": "yarn cypress run --config-file ./cypress/cypress.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json",
- "cypress:run:ccs": "yarn cypress:run:reporter --browser chrome --headless --config integrationFolder=./cypress/ccs_integration",
+ "cypress:run:ccs": "yarn cypress:run:reporter --browser chrome --headless --config integrationFolder=./cypress/ccs_integration; status=$?; yarn junit:merge && exit $status",
"cypress:run-as-ci": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/cli_config.ts",
"cypress:run-as-ci:firefox": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/config.firefox.ts",
"cypress:run:upgrade": "yarn cypress:run:reporter --browser chrome --headless --config integrationFolder=./cypress/upgrade_integration",
diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts
index 4df49b957ad9c..59af6737e495f 100644
--- a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts
+++ b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts
@@ -101,68 +101,4 @@ describe('public search functions', () => {
});
expect(deepLinks.some((l) => l.id === SecurityPageName.ueba)).toBeTruthy();
});
-
- describe('Detections Alerts deep links', () => {
- it('should return alerts link for basic license with only read_alerts capabilities', () => {
- const basicLicense = 'basic';
- const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, ({
- siem: { read_alerts: true, crud_alerts: false },
- } as unknown) as Capabilities);
-
- const detectionsDeepLinks =
- basicLinks.find((l) => l.id === SecurityPageName.detections)?.deepLinks ?? [];
-
- expect(
- detectionsDeepLinks.length &&
- detectionsDeepLinks.some((l) => l.id === SecurityPageName.alerts)
- ).toBeTruthy();
- });
-
- it('should return alerts link with for basic license with crud_alerts capabilities', () => {
- const basicLicense = 'basic';
- const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, ({
- siem: { read_alerts: true, crud_alerts: true },
- } as unknown) as Capabilities);
-
- const detectionsDeepLinks =
- basicLinks.find((l) => l.id === SecurityPageName.detections)?.deepLinks ?? [];
-
- expect(
- detectionsDeepLinks.length &&
- detectionsDeepLinks.some((l) => l.id === SecurityPageName.alerts)
- ).toBeTruthy();
- });
-
- it('should NOT return alerts link for basic license with NO read_alerts capabilities', () => {
- const basicLicense = 'basic';
- const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, ({
- siem: { read_alerts: false, crud_alerts: false },
- } as unknown) as Capabilities);
-
- const detectionsDeepLinks =
- basicLinks.find((l) => l.id === SecurityPageName.detections)?.deepLinks ?? [];
-
- expect(
- detectionsDeepLinks.length &&
- detectionsDeepLinks.some((l) => l.id === SecurityPageName.alerts)
- ).toBeFalsy();
- });
-
- it('should return alerts link for basic license with undefined capabilities', () => {
- const basicLicense = 'basic';
- const basicLinks = getDeepLinks(
- mockGlobalState.app.enableExperimental,
- basicLicense,
- undefined
- );
-
- const detectionsDeepLinks =
- basicLinks.find((l) => l.id === SecurityPageName.detections)?.deepLinks ?? [];
-
- expect(
- detectionsDeepLinks.length &&
- detectionsDeepLinks.some((l) => l.id === SecurityPageName.alerts)
- ).toBeTruthy();
- });
- });
});
diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts
index bafab2dd659f4..9f13a8be0e13a 100644
--- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts
+++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts
@@ -368,16 +368,7 @@ export function getDeepLinks(
deepLinks: [],
};
}
- if (
- deepLinkId === SecurityPageName.detections &&
- capabilities != null &&
- capabilities.siem.read_alerts === false
- ) {
- return {
- ...deepLink,
- deepLinks: baseDeepLinks.filter(({ id }) => id !== SecurityPageName.alerts),
- };
- }
+
if (isPremiumLicense(licenseType) && subPluginDeepLinks?.premium) {
return {
...deepLink,
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
index 3ec616127f243..7041cc4264504 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
@@ -59,7 +59,7 @@ const TimelineDetailsPanel = () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx
index db5eb2d882c6f..2b399a0571178 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx
@@ -86,8 +86,8 @@ describe('AlertSummaryView', () => {
return {
category: 'event',
field: 'event.code',
- values: ['malicious_thread'],
- originalValue: ['malicious_thread'],
+ values: ['shellcode_thread'],
+ originalValue: ['shellcode_thread'],
};
}
return item;
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx
index d8c1cc7fbfa60..03c420914d170 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx
@@ -36,7 +36,7 @@ import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers';
import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback';
import { MarkdownRenderer } from '../markdown_editor';
import { LineClamp } from '../line_clamp';
-import { endpointAlertCheck } from '../../utils/endpoint_alert_check';
+import { isAlertFromEndpointEvent } from '../../utils/endpoint_alert_check';
import { getEmptyValue } from '../empty_value';
import { ActionCell } from './table/action_cell';
import { FieldValueCell } from './table/field_value_cell';
@@ -157,7 +157,7 @@ function getEventFieldsToDisplay({
}): EventSummaryField[] {
switch (eventCode) {
// memory protection fields
- case EventCode.MALICIOUS_THREAD:
+ case EventCode.SHELLCODE_THREAD:
return memoryShellCodeAlertFields;
case EventCode.MEMORY_SIGNATURE:
return memorySignatureAlertFields;
@@ -244,7 +244,7 @@ export const getSummaryRows = ({
fieldFromBrowserField: browserField,
};
- if (item.id === 'agent.id' && !endpointAlertCheck({ data })) {
+ if (item.id === 'agent.id' && !isAlertFromEndpointEvent({ data })) {
return acc;
}
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.test.tsx
index d03fa66bb1c10..867eaf73ef85e 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.test.tsx
@@ -40,6 +40,7 @@ describe('EnrichmentRangePicker', () => {
wrapper
.find('input.start-picker')
+ .first()
.simulate('change', { target: { value: '08/10/2019 06:29 PM' } });
wrapper.find('[data-test-subj="enrichment-button"]').hostNodes().simulate('click');
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
index 29ba8fc0bd541..7b7a1ead5d702 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
@@ -33,14 +33,6 @@ import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_fe
import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions';
import { mockTimelines } from '../../mock/mock_timelines_plugin';
-jest.mock('@kbn/alerts', () => ({
- useGetUserAlertsPermissions: () => ({
- loading: false,
- crud: true,
- read: true,
- }),
-}));
-
jest.mock('../../lib/kibana', () => ({
useKibana: () => ({
services: {
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
index 70fd80a13555b..c91b646aba967 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
@@ -5,8 +5,8 @@
* 2.0.
*/
-import React, { useMemo, useEffect } from 'react';
-import { connect, ConnectedProps } from 'react-redux';
+import React, { useCallback, useMemo, useEffect } from 'react';
+import { connect, ConnectedProps, useDispatch } from 'react-redux';
import deepEqual from 'fast-deep-equal';
import styled from 'styled-components';
@@ -78,6 +78,7 @@ type Props = OwnProps & PropsFromRedux;
const StatefulEventsViewerComponent: React.FC = ({
createTimeline,
columns,
+ defaultColumns,
dataProviders,
defaultCellActions,
deletedEventIds,
@@ -108,6 +109,7 @@ const StatefulEventsViewerComponent: React.FC = ({
hasAlertsCrud = false,
unit,
}) => {
+ const dispatch = useDispatch();
const { timelines: timelinesUi } = useKibana().services;
const {
browserFields,
@@ -127,6 +129,7 @@ const StatefulEventsViewerComponent: React.FC = ({
createTimeline({
id,
columns,
+ defaultColumns,
excludedRowRendererIds,
indexNames: selectedPatterns,
sort,
@@ -149,6 +152,13 @@ const StatefulEventsViewerComponent: React.FC = ({
) : null,
[graphEventId, id]
);
+ const setQuery = useCallback(
+ (inspect, loading, refetch) => {
+ dispatch(inputsActions.setQuery({ id, inputId: 'global', inspect, loading, refetch }));
+ },
+ [dispatch, id]
+ );
+
return (
<>
@@ -180,6 +190,7 @@ const StatefulEventsViewerComponent: React.FC = ({
onRuleChange,
renderCellValue,
rowRenderers,
+ setQuery,
start,
sort,
additionalFilters,
@@ -241,6 +252,7 @@ const makeMapStateToProps = () => {
const timeline: TimelineModel = getTimeline(state, id) ?? defaultModel;
const {
columns,
+ defaultColumns,
dataProviders,
deletedEventIds,
excludedRowRendererIds,
@@ -254,6 +266,7 @@ const makeMapStateToProps = () => {
return {
columns,
+ defaultColumns,
dataProviders,
deletedEventIds,
excludedRowRendererIds,
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json
index d46b39b90fe5a..043ea11a51fd1 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json
@@ -19,13 +19,13 @@
"Target.process.pe.original_file_name",
"Target.process.pe.product",
"Target.process.pgid",
- "Target.process.thread.Ext.start_address_details.allocation_type",
+ "Target.process.Ext.memory_region.allocation_type",
"Target.process.thread.Ext.start_address_bytes_disasm_hash",
"Target.process.thread.Ext.start_address_allocation_offset",
- "Target.process.thread.Ext.start_address_details.allocation_size",
- "Target.process.thread.Ext.start_address_details.region_size",
- "Target.process.thread.Ext.start_address_details.region_protection",
- "Target.process.thread.Ext.start_address_details.memory_pe.imphash",
+ "Target.process.Ext.memory_region.allocation_size",
+ "Target.process.Ext.memory_region.region_size",
+ "Target.process.Ext.memory_region.region_protection",
+ "Target.process.Ext.memory_region.memory_pe.imphash",
"Target.process.thread.Ext.start_address_bytes",
"agent.id",
"agent.type",
@@ -82,6 +82,8 @@
"process.Ext.services",
"process.Ext.user",
"process.Ext.code_signature",
+ "process.Ext.token.integrity_level_name",
+ "process.Ext.memory_region.malware_signature.all_names",
"process.executable",
"process.hash.md5",
"process.hash.sha1",
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
index 9696604ddf222..209d7d8fa273b 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
@@ -1031,7 +1031,7 @@ describe('Exception helpers', () => {
]);
});
- test('it should return pre-populated memory shellcode items for event code `malicious_thread`', () => {
+ test('it should return pre-populated memory shellcode items for event code `shellcode_thread`', () => {
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
_id: '123',
process: {
@@ -1049,7 +1049,7 @@ describe('Exception helpers', () => {
self_injection: true,
},
event: {
- code: 'malicious_thread',
+ code: 'shellcode_thread',
},
Target: {
process: {
@@ -1108,52 +1108,10 @@ describe('Exception helpers', () => {
value: 'high',
id: '123',
},
- {
- field: 'Target.process.thread.Ext.start_address_details',
- type: 'nested',
- entries: [
- {
- field: 'allocation_type',
- operator: 'included',
- type: 'match',
- value: 'PRIVATE',
- id: '123',
- },
- {
- field: 'allocation_size',
- operator: 'included',
- type: 'match',
- value: '4000',
- id: '123',
- },
- {
- field: 'region_size',
- operator: 'included',
- type: 'match',
- value: '4000',
- id: '123',
- },
- {
- field: 'region_protection',
- operator: 'included',
- type: 'match',
- value: 'RWX',
- id: '123',
- },
- {
- field: 'memory_pe.imphash',
- operator: 'included',
- type: 'match',
- value: 'a hash',
- id: '123',
- },
- ],
- id: '123',
- },
]);
});
- test('it should return pre-populated memory shellcode items for event code `malicious_thread` and skip empty', () => {
+ test('it should return pre-populated memory shellcode items for event code `shellcode_thread` and skip empty', () => {
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
_id: '123',
process: {
@@ -1171,7 +1129,7 @@ describe('Exception helpers', () => {
self_injection: true,
},
event: {
- code: 'malicious_thread',
+ code: 'shellcode_thread',
},
Target: {
process: {
@@ -1217,41 +1175,6 @@ describe('Exception helpers', () => {
value: 'high',
id: '123',
},
- {
- field: 'Target.process.thread.Ext.start_address_details',
- type: 'nested',
- entries: [
- {
- field: 'allocation_size',
- operator: 'included',
- type: 'match',
- value: '4000',
- id: '123',
- },
- {
- field: 'region_size',
- operator: 'included',
- type: 'match',
- value: '4000',
- id: '123',
- },
- {
- field: 'region_protection',
- operator: 'included',
- type: 'match',
- value: 'RWX',
- id: '123',
- },
- {
- field: 'memory_pe.imphash',
- operator: 'included',
- type: 'match',
- value: 'a hash',
- id: '123',
- },
- ],
- id: '123',
- },
]);
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
index 3d219b90a2fc8..58da977fcb8f0 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
@@ -577,7 +577,7 @@ export const getPrepopulatedMemoryShellcodeException = ({
eventCode: string;
alertEcsData: Flattened;
}): ExceptionsBuilderExceptionItem => {
- const { process, Target } = alertEcsData;
+ const { process } = alertEcsData;
const entries = filterEmptyExceptionEntries([
{
field: 'Memory_protection.feature',
@@ -609,44 +609,6 @@ export const getPrepopulatedMemoryShellcodeException = ({
type: 'match' as const,
value: process?.Ext?.token?.integrity_level_name ?? '',
},
- {
- field: 'Target.process.thread.Ext.start_address_details',
- type: 'nested' as const,
- entries: [
- {
- field: 'allocation_type',
- operator: 'included' as const,
- type: 'match' as const,
- value: Target?.process?.thread?.Ext?.start_address_details?.allocation_type ?? '',
- },
- {
- field: 'allocation_size',
- operator: 'included' as const,
- type: 'match' as const,
- value: String(Target?.process?.thread?.Ext?.start_address_details?.allocation_size) ?? '',
- },
- {
- field: 'region_size',
- operator: 'included' as const,
- type: 'match' as const,
- value: String(Target?.process?.thread?.Ext?.start_address_details?.region_size) ?? '',
- },
- {
- field: 'region_protection',
- operator: 'included' as const,
- type: 'match' as const,
- value:
- String(Target?.process?.thread?.Ext?.start_address_details?.region_protection) ?? '',
- },
- {
- field: 'memory_pe.imphash',
- operator: 'included' as const,
- type: 'match' as const,
- value:
- String(Target?.process?.thread?.Ext?.start_address_details?.memory_pe?.imphash) ?? '',
- },
- ],
- },
]);
return {
@@ -845,7 +807,7 @@ export const defaultEndpointExceptionItems = (
alertEcsData,
}),
];
- case 'malicious_thread':
+ case 'shellcode_thread':
return [
getPrepopulatedMemoryShellcodeException({
listId,
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
index 1f98d3b826129..b488000ac8736 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { useGetUserAlertsPermissions } from '@kbn/alerts';
import { renderHook } from '@testing-library/react-hooks';
import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public';
@@ -24,7 +23,6 @@ jest.mock('../../../lib/kibana');
jest.mock('../../../hooks/use_selector');
jest.mock('../../../hooks/use_experimental_features');
jest.mock('../../../utils/route/use_route_spy');
-jest.mock('@kbn/alerts');
describe('useSecuritySolutionNavigation', () => {
const mockUrlState = {
[CONSTANTS.appQuery]: { query: 'host.name:"security-solution-es"', language: 'kuery' },
@@ -76,11 +74,6 @@ describe('useSecuritySolutionNavigation', () => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
(useDeepEqualSelector as jest.Mock).mockReturnValue({ urlState: mockUrlState });
(useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy);
- (useGetUserAlertsPermissions as jest.Mock).mockReturnValue({
- loading: false,
- crud: true,
- read: true,
- });
(useKibana as jest.Mock).mockReturnValue({
services: {
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
index ca574a5872761..1630bc47fd0c3 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
@@ -7,15 +7,13 @@
import React, { useCallback, useMemo } from 'react';
import { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_nav_types';
-import { useGetUserAlertsPermissions } from '@kbn/alerts';
import { securityNavGroup } from '../../../../app/home/home_navigations';
import { getSearch } from '../helpers';
import { PrimaryNavigationItemsProps } from './types';
-import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana';
+import { useGetUserCasesPermissions } from '../../../lib/kibana';
import { useNavigation } from '../../../lib/kibana/hooks';
import { NavTab } from '../types';
-import { SERVER_APP_ID } from '../../../../../common/constants';
export const usePrimaryNavigationItems = ({
navTabs,
@@ -63,9 +61,7 @@ export const usePrimaryNavigationItems = ({
};
function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
- const uiCapabilities = useKibana().services.application.capabilities;
const hasCasesReadPermissions = useGetUserCasesPermissions()?.read;
- const hasAlertsReadPermissions = useGetUserAlertsPermissions(uiCapabilities, SERVER_APP_ID);
return useMemo(
() => [
{
@@ -75,9 +71,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
},
{
...securityNavGroup.detect,
- items: hasAlertsReadPermissions.read
- ? [navTabs.alerts, navTabs.rules, navTabs.exceptions]
- : [navTabs.rules, navTabs.exceptions],
+ items: [navTabs.alerts, navTabs.rules, navTabs.exceptions],
},
{
...securityNavGroup.explore,
@@ -92,6 +86,6 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
items: [navTabs.endpoints, navTabs.trusted_apps, navTabs.event_filters],
},
],
- [navTabs, hasCasesReadPermissions, hasAlertsReadPermissions]
+ [navTabs, hasCasesReadPermissions]
);
}
diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx
index 051c1bd8ae5cb..9cfd0771425ee 100644
--- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx
@@ -31,6 +31,15 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar
z-index: 9950 !important;
}
+ /*
+ overrides the default styling of EuiDataGrid expand popover footer to
+ make it a column of actions instead of the default actions row
+ */
+ .euiDataGridRowCell__popover .euiPopoverFooter .euiFlexGroup {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
/*
overrides the default styling of euiComboBoxOptionsList because it's implemented
as a popover, so it's not selectable as a child of the styled component
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx
index fa9de895f7d03..028473f5c2001 100644
--- a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx
@@ -5,9 +5,8 @@
* 2.0.
*/
-import React, { createContext, useContext } from 'react';
+import React, { createContext, useContext, useEffect, useState } from 'react';
import { DeepReadonly } from 'utility-types';
-import { useGetUserAlertsPermissions } from '@kbn/alerts';
import { Capabilities } from '../../../../../../../src/core/public';
import { useFetchDetectionEnginePrivileges } from '../../../detections/components/user_privileges/use_fetch_detection_engine_privileges';
@@ -19,14 +18,14 @@ export interface UserPrivilegesState {
listPrivileges: ReturnType;
detectionEnginePrivileges: ReturnType;
endpointPrivileges: EndpointPrivileges;
- alertsPrivileges: ReturnType;
+ kibanaSecuritySolutionsPrivileges: { crud: boolean; read: boolean };
}
export const initialUserPrivilegesState = (): UserPrivilegesState => ({
listPrivileges: { loading: false, error: undefined, result: undefined },
detectionEnginePrivileges: { loading: false, error: undefined, result: undefined },
endpointPrivileges: { loading: true, canAccessEndpointManagement: false, canAccessFleet: false },
- alertsPrivileges: { loading: false, read: false, crud: false },
+ kibanaSecuritySolutionsPrivileges: { crud: false, read: false },
});
const UserPrivilegesContext = createContext(initialUserPrivilegesState());
@@ -43,14 +42,29 @@ export const UserPrivilegesProvider = ({
const listPrivileges = useFetchListPrivileges();
const detectionEnginePrivileges = useFetchDetectionEnginePrivileges();
const endpointPrivileges = useEndpointPrivileges();
- const alertsPrivileges = useGetUserAlertsPermissions(kibanaCapabilities, SERVER_APP_ID);
+ const [kibanaSecuritySolutionsPrivileges, setKibanaSecuritySolutionsPrivileges] = useState({
+ crud: false,
+ read: false,
+ });
+ const crud: boolean = kibanaCapabilities[SERVER_APP_ID].crud === true;
+ const read: boolean = kibanaCapabilities[SERVER_APP_ID].show === true;
+
+ useEffect(() => {
+ setKibanaSecuritySolutionsPrivileges((currPrivileges) => {
+ if (currPrivileges.read !== read || currPrivileges.crud !== crud) {
+ return { read, crud };
+ }
+ return currPrivileges;
+ });
+ }, [crud, read]);
+
return (
{children}
diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx
index 1481ae3e4248c..ae62c214d7b7a 100644
--- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx
+++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx
@@ -116,6 +116,36 @@ export const defaultCellActions: TGridCellAction[] = [
fieldName: columnId,
});
+ return (
+ <>
+ {timelines.getHoverActions().getCopyButton({
+ Component,
+ field: columnId,
+ isHoverAction: false,
+ ownFocus: false,
+ showTooltip: false,
+ value,
+ })}
+ >
+ );
+ },
+ ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({
+ rowIndex,
+ columnId,
+ Component,
+ }) => {
+ const { timelines } = useKibanaServices();
+
+ const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
+ if (pageRowIndex >= data.length) {
+ return null;
+ }
+
+ const value = getMappedNonEcsValue({
+ data: data[pageRowIndex],
+ fieldName: columnId,
+ });
+
const dataProvider: DataProvider[] = useMemo(
() =>
value?.map((x) => ({
@@ -170,58 +200,30 @@ export const defaultCellActions: TGridCellAction[] = [
fieldName: columnId,
});
- return (
- <>
- {allowTopN({
+ const showButton = useMemo(
+ () =>
+ allowTopN({
browserField: getAllFieldsByName(browserFields)[columnId],
fieldName: columnId,
hideTopN: false,
- }) && (
-
- )}
- >
+ }),
+ [columnId]
);
- },
- ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({
- rowIndex,
- columnId,
- Component,
- }) => {
- const { timelines } = useKibanaServices();
-
- const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
- if (pageRowIndex >= data.length) {
- return null;
- }
- const value = getMappedNonEcsValue({
- data: data[pageRowIndex],
- fieldName: columnId,
- });
-
- return (
- <>
- {timelines.getHoverActions().getCopyButton({
- Component,
- field: columnId,
- isHoverAction: false,
- ownFocus: false,
- showTooltip: false,
- value,
- })}
- >
- );
+ return showButton ? (
+
+ ) : null;
},
];
diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts
index e95f5c15d4ecb..d0a03d62a682b 100644
--- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts
+++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts
@@ -6,10 +6,11 @@
*/
import _ from 'lodash';
+import { Ecs } from '../../../common/ecs';
import { generateMockDetailItemData } from '../mock';
-import { endpointAlertCheck } from './endpoint_alert_check';
+import { isAlertFromEndpointAlert, isAlertFromEndpointEvent } from './endpoint_alert_check';
-describe('Endpoint Alert Check Utility', () => {
+describe('isAlertFromEndpointEvent', () => {
let mockDetailItemData: ReturnType;
beforeEach(() => {
@@ -38,16 +39,47 @@ describe('Endpoint Alert Check Utility', () => {
});
it('should return true if detections data comes from an endpoint rule', () => {
- expect(endpointAlertCheck({ data: mockDetailItemData })).toBe(true);
+ expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBe(true);
});
it('should return false if it is not an Alert (ex. maybe an event)', () => {
_.remove(mockDetailItemData, { field: 'signal.rule.id' });
- expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy();
+ expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBeFalsy();
});
it('should return false if it is not an endpoint agent', () => {
_.remove(mockDetailItemData, { field: 'agent.type' });
- expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy();
+ expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBeFalsy();
+ });
+});
+
+describe('isAlertFromEndpointAlert', () => {
+ it('should return true if detections data comes from an endpoint rule', () => {
+ const mockEcsData = {
+ _id: 'mockId',
+ 'signal.original_event.module': ['endpoint'],
+ 'signal.original_event.kind': ['alert'],
+ } as Ecs;
+ expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBe(true);
+ });
+
+ it('should return false if ecsData is undefined', () => {
+ expect(isAlertFromEndpointAlert({ ecsData: undefined })).toBeFalsy();
+ });
+
+ it('should return false if it is not an Alert', () => {
+ const mockEcsData = {
+ _id: 'mockId',
+ 'signal.original_event.module': ['endpoint'],
+ } as Ecs;
+ expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBeFalsy();
+ });
+
+ it('should return false if it is not an endpoint module', () => {
+ const mockEcsData = {
+ _id: 'mockId',
+ 'signal.original_event.kind': ['alert'],
+ } as Ecs;
+ expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBeFalsy();
});
});
diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts
index 30c6e3fdeb672..7e7e7a6bcdd1f 100644
--- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts
+++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts
@@ -5,15 +5,20 @@
* 2.0.
*/
-import { find, some } from 'lodash/fp';
+import { find, getOr, some } from 'lodash/fp';
import { TimelineEventsDetailsItem } from '../../../../timelines/common';
+import { Ecs } from '../../../common/ecs';
/**
* Checks to see if the given set of Timeline event detail items includes data that indicates its
* an endpoint Alert. Note that it will NOT match on Events - only alerts
* @param data
*/
-export const endpointAlertCheck = ({ data }: { data: TimelineEventsDetailsItem[] }): boolean => {
+export const isAlertFromEndpointEvent = ({
+ data,
+}: {
+ data: TimelineEventsDetailsItem[];
+}): boolean => {
const isAlert = some({ category: 'signal', field: 'signal.rule.id' }, data);
if (!isAlert) {
@@ -23,3 +28,18 @@ export const endpointAlertCheck = ({ data }: { data: TimelineEventsDetailsItem[]
const findEndpointAlert = find({ field: 'agent.type' }, data)?.values;
return findEndpointAlert ? findEndpointAlert[0] === 'endpoint' : false;
};
+
+export const isAlertFromEndpointAlert = ({
+ ecsData,
+}: {
+ ecsData: Ecs | null | undefined;
+}): boolean => {
+ if (ecsData == null) {
+ return false;
+ }
+
+ const eventModules = getOr([], 'signal.original_event.module', ecsData);
+ const kinds = getOr([], 'signal.original_event.kind', ecsData);
+
+ return eventModules.includes('endpoint') && kinds.includes('alert');
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx
index 9cc844a80b031..6bd902658c8e4 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx
@@ -14,8 +14,6 @@ import { HeaderSection } from '../../../../common/components/header_section';
import { useQueryAlerts } from '../../../containers/detection_engine/alerts/use_query';
import { InspectButtonContainer } from '../../../../common/components/inspect';
-import { fetchQueryRuleRegistryAlerts } from '../../../containers/detection_engine/alerts/api';
-
import { getAlertsCountQuery } from './helpers';
import * as i18n from './translations';
import { AlertsCount } from './alerts_count';
@@ -49,7 +47,8 @@ export const AlertsCountPanel = memo(
// ? fetchQueryRuleRegistryAlerts
// : fetchQueryAlerts;
- const fetchMethod = fetchQueryRuleRegistryAlerts;
+ // Disabling the fecth method in useQueryAlerts since it is defaulted to the old one
+ // const fetchMethod = fetchQueryRuleRegistryAlerts;
const additionalFilters = useMemo(() => {
try {
@@ -73,7 +72,6 @@ export const AlertsCountPanel = memo(
request,
refetch,
} = useQueryAlerts<{}, AlertsCountAggregation>({
- fetchMethod,
query: getAlertsCountQuery(selectedStackByOption, from, to, additionalFilters),
indexName: signalIndexName,
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx
index b296371bae58d..2182ed7da0c4f 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx
@@ -43,7 +43,6 @@ import type { AlertsStackByField } from '../common/types';
import { KpiPanel, StackBySelect } from '../common/components';
import { useInspectButton } from '../common/hooks';
-import { fetchQueryRuleRegistryAlerts } from '../../../containers/detection_engine/alerts/api';
const defaultTotalAlertsObj: AlertsTotal = {
value: 0,
@@ -117,16 +116,12 @@ export const AlertsHistogramPanel = memo(
request,
refetch,
} = useQueryAlerts<{}, AlertsAggregation>({
- fetchMethod: fetchQueryRuleRegistryAlerts,
- query: {
- index: signalIndexName,
- ...getAlertsHistogramQuery(
- selectedStackByOption,
- from,
- to,
- buildCombinedQueries(combinedQueries)
- ),
- },
+ query: getAlertsHistogramQuery(
+ selectedStackByOption,
+ from,
+ to,
+ buildCombinedQueries(combinedQueries)
+ ),
indexName: signalIndexName,
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
index 7e755ad654685..3bc229273bc83 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
@@ -7,7 +7,7 @@
import {
ALERT_DURATION,
- ALERT_ID,
+ ALERT_INSTANCE_ID,
ALERT_RULE_PRODUCER,
ALERT_START,
ALERT_WORKFLOW_STATUS,
@@ -275,7 +275,7 @@ export const buildShowBuildingBlockFilterRuleRegistry = (
export const requiredFieldMappingsForActionsRuleRegistry = {
'@timestamp': '@timestamp',
- 'alert.id': ALERT_ID,
+ 'alert.instance.id': ALERT_INSTANCE_ID,
'event.kind': 'event.kind',
'alert.start': ALERT_START,
'alert.uuid': ALERT_UUID,
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
index 4b3c792319cd1..e179c02987462 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
@@ -381,7 +381,7 @@ export const AlertsTableComponent: React.FC = ({
pageFilters={defaultFiltersMemo}
defaultCellActions={defaultCellActions}
defaultModel={defaultTimelineModel}
- entityType="alerts"
+ entityType="events"
end={to}
currentFilter={filterGroup}
id={timelineId}
@@ -392,7 +392,7 @@ export const AlertsTableComponent: React.FC = ({
start={from}
utilityBar={utilityBarCallback}
additionalFilters={additionalFiltersComponent}
- hasAlertsCrud={hasIndexWrite}
+ hasAlertsCrud={hasIndexWrite && hasIndexMaintenance}
/>
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx
index eb31a59f0ca87..9568f9c894e24 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx
@@ -13,14 +13,6 @@ import React from 'react';
import { Ecs } from '../../../../../common/ecs';
import { mockTimelines } from '../../../../common/mock/mock_timelines_plugin';
-jest.mock('@kbn/alerts', () => ({
- useGetUserAlertsPermissions: () => ({
- loading: false,
- crud: true,
- read: true,
- }),
-}));
-
const ecsRowData: Ecs = { _id: '1', agent: { type: ['blah'] } };
const props = {
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
index cc719a9999383..f2297b7d567bc 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
@@ -11,7 +11,7 @@ import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover, EuiToolTip } from '@ela
import { indexOf } from 'lodash';
import { ExceptionListType } from '@kbn/securitysolution-io-ts-list-types';
-import { get, getOr } from 'lodash/fp';
+import { get } from 'lodash/fp';
import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers';
import { EventsTdContent } from '../../../../timelines/components/timeline/styles';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../../../timelines/components/timeline/helpers';
@@ -36,6 +36,7 @@ import { useInvestigateInResolverContextItem } from './investigate_in_resolver';
import { ATTACH_ALERT_TO_CASE_FOR_ROW } from '../../../../timelines/components/timeline/body/translations';
import { useEventFilterAction } from './use_event_filter_action';
import { useAddToCaseActions } from './use_add_to_case_actions';
+import { isAlertFromEndpointAlert } from '../../../../common/utils/endpoint_alert_check';
interface AlertContextMenuProps {
ariaLabel?: string;
@@ -78,17 +79,6 @@ const AlertContextMenuComponent: React.FC = ({
const isEvent = useMemo(() => indexOf(ecsRowData.event?.kind, 'event') !== -1, [ecsRowData]);
- const isEndpointAlert = useMemo((): boolean => {
- if (ecsRowData == null) {
- return false;
- }
-
- const eventModules = getOr([], 'signal.original_event.module', ecsRowData);
- const kinds = getOr([], 'signal.original_event.kind', ecsRowData);
-
- return eventModules.includes('endpoint') && kinds.includes('alert');
- }, [ecsRowData]);
-
const onButtonClick = useCallback(() => {
setPopover(!isPopoverOpen);
}, [isPopoverOpen]);
@@ -153,7 +143,7 @@ const AlertContextMenuComponent: React.FC = ({
}, [closePopover, onAddEventFilterClick]);
const { exceptionActionItems } = useExceptionActions({
- isEndpointAlert,
+ isEndpointAlert: isAlertFromEndpointAlert({ ecsData: ecsRowData }),
onAddExceptionTypeClick: handleOnAddExceptionTypeClick,
});
const investigateInResolverActionItems = useInvestigateInResolverContextItem({
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx
index 3568972aef2e9..8da4ce1c3ed7f 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx
@@ -7,15 +7,12 @@
import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
-import { useGetUserAlertsPermissions } from '@kbn/alerts';
import { useStatusBulkActionItems } from '../../../../../../timelines/public';
import { Status } from '../../../../../common/detection_engine/schemas/common/schemas';
import { timelineActions } from '../../../../timelines/store/timeline';
+import { useAlertsPrivileges } from '../../../containers/detection_engine/alerts/use_alerts_privileges';
import { SetEventsDeletedProps, SetEventsLoadingProps } from '../types';
-
-import { useKibana } from '../../../../common/lib/kibana';
-import { SERVER_APP_ID } from '../../../../../common/constants';
interface Props {
alertStatus?: Status;
closePopover: () => void;
@@ -34,8 +31,7 @@ export const useAlertsActions = ({
refetch,
}: Props) => {
const dispatch = useDispatch();
- const uiCapabilities = useKibana().services.application.capabilities;
- const alertsPrivileges = useGetUserAlertsPermissions(uiCapabilities, SERVER_APP_ID);
+ const { hasIndexWrite, hasKibanaCRUD } = useAlertsPrivileges();
const onStatusUpdate = useCallback(() => {
closePopover();
@@ -66,9 +62,10 @@ export const useAlertsActions = ({
setEventsDeleted,
onUpdateSuccess: onStatusUpdate,
onUpdateFailure: onStatusUpdate,
+ timelineId,
});
return {
- actionItems: alertsPrivileges.crud ? actionItems : [],
+ actionItems: hasIndexWrite && hasKibanaCRUD ? actionItems : [],
};
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/translations.tsx
index 3509ad73001ec..0d628d89c0925 100644
--- a/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/translations.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/translations.tsx
@@ -9,10 +9,6 @@ import { EuiCode } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
-import {
- DetectionsRequirementsLink,
- SecuritySolutionRequirementsLink,
-} from '../../../../common/components/links_to_docs';
import {
DEFAULT_ITEMS_INDEX,
DEFAULT_LISTS_INDEX,
@@ -21,6 +17,10 @@ import {
} from '../../../../../common/constants';
import { CommaSeparatedValues } from './comma_separated_values';
import { MissingPrivileges } from './use_missing_privileges';
+import {
+ DetectionsRequirementsLink,
+ SecuritySolutionRequirementsLink,
+} from '../../../../common/components/links_to_docs';
export const MISSING_PRIVILEGES_CALLOUT_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageTitle',
@@ -46,17 +46,17 @@ const CANNOT_EDIT_LISTS = i18n.translate(
const CANNOT_EDIT_ALERTS = i18n.translate(
'xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.cannotEditAlerts',
{
- defaultMessage: 'Without these privileges, you cannot open or close alerts.',
+ defaultMessage: 'Without these privileges, you cannot view or change status of alerts.',
}
);
export const missingPrivilegesCallOutBody = ({
indexPrivileges,
- featurePrivileges,
+ featurePrivileges = [],
}: MissingPrivileges) => (
@@ -77,23 +77,30 @@ export const missingPrivilegesCallOutBody = ({
{indexPrivileges.map(([index, missingPrivileges]) => (
{missingIndexPrivileges(index, missingPrivileges)}
))}
-
- >
- ) : null,
- featurePrivileges:
- featurePrivileges.length > 0 ? (
- <>
-
-
- {featurePrivileges.map(([feature, missingPrivileges]) => (
+ {
+ // TODO: Uncomment once RBAC for alerts is reenabled
+ /* {featurePrivileges.map(([feature, missingPrivileges]) => (
{missingFeaturePrivileges(feature, missingPrivileges)}
- ))}
+ ))} */
+ }
>
) : null,
+ // TODO: Uncomment once RBAC for alerts is reenabled
+ // featurePrivileges:
+ // featurePrivileges.length > 0 ? (
+ // <>
+ //
+ //
+ // {featurePrivileges.map(([feature, missingPrivileges]) => (
+ // {missingFeaturePrivileges(feature, missingPrivileges)}
+ // ))}
+ //
+ // >
+ // ) : null,
docs: (
@@ -152,14 +159,15 @@ const missingIndexPrivileges = (index: string, privileges: string[]) => (
/>
);
-const missingFeaturePrivileges = (feature: string, privileges: string[]) => (
- ,
- index: {feature} ,
- explanation: getPrivilegesExplanation(privileges, feature),
- }}
- />
-);
+// TODO: Uncomment once RBAC for alerts is reenabled
+// const missingFeaturePrivileges = (feature: string, privileges: string[]) => (
+// ,
+// index: {feature} ,
+// explanation: getPrivilegesExplanation(privileges, feature),
+// }}
+// />
+// );
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts b/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts
index ea2b081239fda..eec9bd1f09053 100644
--- a/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts
@@ -40,14 +40,18 @@ export interface MissingPrivileges {
}
export const useMissingPrivileges = (): MissingPrivileges => {
- const { listPrivileges } = useUserPrivileges();
+ const { detectionEnginePrivileges, listPrivileges } = useUserPrivileges();
const [{ canUserCRUD }] = useUserData();
return useMemo(() => {
const featurePrivileges: MissingFeaturePrivileges[] = [];
const indexPrivileges: MissingIndexPrivileges[] = [];
- if (canUserCRUD == null || listPrivileges.result == null) {
+ if (
+ canUserCRUD == null ||
+ listPrivileges.result == null ||
+ detectionEnginePrivileges.result == null
+ ) {
/**
* Do not check privileges till we get all the data. That helps to reduce
* subsequent layout shift while loading and skip unneeded re-renders.
@@ -72,9 +76,16 @@ export const useMissingPrivileges = (): MissingPrivileges => {
indexPrivileges.push(missingListsPrivileges);
}
+ const missingDetectionPrivileges = getMissingIndexPrivileges(
+ detectionEnginePrivileges.result.index
+ );
+ if (missingDetectionPrivileges) {
+ indexPrivileges.push(missingDetectionPrivileges);
+ }
+
return {
featurePrivileges,
indexPrivileges,
};
- }, [canUserCRUD, listPrivileges]);
+ }, [canUserCRUD, listPrivileges, detectionEnginePrivileges]);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx
index e670c000d789a..24bc670a13ec4 100644
--- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx
@@ -10,7 +10,7 @@ import type { TimelineEventsDetailsItem } from '../../../../common';
import { isIsolationSupported } from '../../../../common/endpoint/service/host_isolation/utils';
import { HostStatus } from '../../../../common/endpoint/types';
import { useIsolationPrivileges } from '../../../common/hooks/endpoint/use_isolate_privileges';
-import { endpointAlertCheck } from '../../../common/utils/endpoint_alert_check';
+import { isAlertFromEndpointEvent } from '../../../common/utils/endpoint_alert_check';
import { useHostIsolationStatus } from '../../containers/detection_engine/alerts/use_host_isolation_status';
import { ISOLATE_HOST, UNISOLATE_HOST } from './translations';
import { getFieldValue } from './helpers';
@@ -29,7 +29,7 @@ export const useHostIsolationAction = ({
onAddIsolationStatusClick,
}: UseHostIsolationActionProps) => {
const isEndpointAlert = useMemo(() => {
- return endpointAlertCheck({ data: detailsData || [] });
+ return isAlertFromEndpointEvent({ data: detailsData || [] });
}, [detailsData]);
const agentId = useMemo(
diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx
index 76c0017f6fa9c..d98b168f209da 100644
--- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx
@@ -24,17 +24,20 @@ jest.mock('../../../common/lib/kibana', () => ({
useKibana: jest.fn(),
useGetUserCasesPermissions: jest.fn().mockReturnValue({ crud: true }),
}));
+jest.mock('../../containers/detection_engine/alerts/use_alerts_privileges', () => ({
+ useAlertsPrivileges: jest.fn().mockReturnValue({ hasIndexWrite: true, hasKibanaCRUD: true }),
+}));
jest.mock('../../../cases/components/use_insert_timeline');
jest.mock('../../../common/hooks/use_experimental_features', () => ({
useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true),
}));
-jest.mock('@kbn/alerts', () => {
- return { useGetUserAlertsPermissions: jest.fn().mockReturnValue({ crud: true }) };
-});
jest.mock('../../../common/utils/endpoint_alert_check', () => {
- return { endpointAlertCheck: jest.fn().mockReturnValue(true) };
+ return {
+ isAlertFromEndpointAlert: jest.fn().mockReturnValue(true),
+ isAlertFromEndpointEvent: jest.fn().mockReturnValue(true),
+ };
});
jest.mock('../../../../common/endpoint/service/host_isolation/utils', () => {
diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
index a6114884b955d..0432e7d353086 100644
--- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
@@ -20,7 +20,7 @@ import { useHostIsolationAction } from '../host_isolation/use_host_isolation_act
import { getFieldValue } from '../host_isolation/helpers';
import type { Ecs } from '../../../../common/ecs';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
-import { endpointAlertCheck } from '../../../common/utils/endpoint_alert_check';
+import { isAlertFromEndpointAlert } from '../../../common/utils/endpoint_alert_check';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { useAddToCaseActions } from '../alerts_table/timeline_actions/use_add_to_case_actions';
@@ -87,13 +87,6 @@ export const TakeActionDropdown = React.memo(
]);
const isEvent = actionsData.eventKind === 'event';
- const isEndpointAlert = useMemo((): boolean => {
- if (detailsData == null) {
- return false;
- }
- return endpointAlertCheck({ data: detailsData });
- }, [detailsData]);
-
const togglePopoverHandler = useCallback(() => {
setIsPopoverOpen(!isPopoverOpen);
}, [isPopoverOpen]);
@@ -131,7 +124,7 @@ export const TakeActionDropdown = React.memo(
);
const { exceptionActionItems } = useExceptionActions({
- isEndpointAlert,
+ isEndpointAlert: isAlertFromEndpointAlert({ ecsData }),
onAddExceptionTypeClick: handleOnAddExceptionTypeClick,
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx
index 9972233dce351..67863f05c7d83 100644
--- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx
@@ -43,6 +43,7 @@ describe('useUserInfo', () => {
expect(result.all).toHaveLength(1);
expect(result.current).toEqual({
canUserCRUD: null,
+ canUserREAD: null,
hasEncryptionKey: null,
hasIndexManage: null,
hasIndexMaintenance: null,
diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx
index da6df631d951e..9c81b51445f60 100644
--- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx
@@ -10,11 +10,11 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro
import { useAlertsPrivileges } from '../../containers/detection_engine/alerts/use_alerts_privileges';
import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index';
-import { useKibana } from '../../../common/lib/kibana';
import { useCreateTransforms } from '../../../transforms/containers/use_create_transforms';
export interface State {
canUserCRUD: boolean | null;
+ canUserREAD: boolean | null;
hasIndexManage: boolean | null;
hasIndexMaintenance: boolean | null;
hasIndexWrite: boolean | null;
@@ -30,6 +30,7 @@ export interface State {
export const initialState: State = {
canUserCRUD: null,
+ canUserREAD: null,
hasIndexManage: null,
hasIndexMaintenance: null,
hasIndexWrite: null,
@@ -77,10 +78,6 @@ export type Action =
type: 'updateHasEncryptionKey';
hasEncryptionKey: boolean | null;
}
- | {
- type: 'updateCanUserCRUD';
- canUserCRUD: boolean | null;
- }
| {
type: 'updateSignalIndexName';
signalIndexName: string | null;
@@ -88,6 +85,14 @@ export type Action =
| {
type: 'updateSignalIndexMappingOutdated';
signalIndexMappingOutdated: boolean | null;
+ }
+ | {
+ type: 'updateCanUserCRUD';
+ canUserCRUD: boolean | null;
+ }
+ | {
+ type: 'updateCanUserREAD';
+ canUserREAD: boolean | null;
};
export const userInfoReducer = (state: State, action: Action): State => {
@@ -146,12 +151,6 @@ export const userInfoReducer = (state: State, action: Action): State => {
hasEncryptionKey: action.hasEncryptionKey,
};
}
- case 'updateCanUserCRUD': {
- return {
- ...state,
- canUserCRUD: action.canUserCRUD,
- };
- }
case 'updateSignalIndexName': {
return {
...state,
@@ -164,6 +163,18 @@ export const userInfoReducer = (state: State, action: Action): State => {
signalIndexMappingOutdated: action.signalIndexMappingOutdated,
};
}
+ case 'updateCanUserCRUD': {
+ return {
+ ...state,
+ canUserCRUD: action.canUserCRUD,
+ };
+ }
+ case 'updateCanUserREAD': {
+ return {
+ ...state,
+ canUserREAD: action.canUserREAD,
+ };
+ }
default:
return state;
}
@@ -187,6 +198,7 @@ export const useUserInfo = (): State => {
const [
{
canUserCRUD,
+ canUserREAD,
hasIndexManage,
hasIndexMaintenance,
hasIndexWrite,
@@ -210,6 +222,8 @@ export const useUserInfo = (): State => {
hasIndexUpdateDelete: hasApiIndexUpdateDelete,
hasIndexWrite: hasApiIndexWrite,
hasIndexRead: hasApiIndexRead,
+ hasKibanaCRUD,
+ hasKibanaREAD,
} = useAlertsPrivileges();
const {
loading: indexNameLoading,
@@ -221,8 +235,17 @@ export const useUserInfo = (): State => {
const { createTransforms } = useCreateTransforms();
- const uiCapabilities = useKibana().services.application.capabilities;
- const capabilitiesCanUserCRUD: boolean = uiCapabilities.siem.crud === true;
+ useEffect(() => {
+ if (!loading && canUserCRUD !== hasKibanaCRUD) {
+ dispatch({ type: 'updateCanUserCRUD', canUserCRUD: hasKibanaCRUD });
+ }
+ }, [dispatch, loading, canUserCRUD, hasKibanaCRUD]);
+
+ useEffect(() => {
+ if (!loading && canUserREAD !== hasKibanaREAD) {
+ dispatch({ type: 'updateCanUserREAD', canUserREAD: hasKibanaREAD });
+ }
+ }, [dispatch, loading, canUserREAD, hasKibanaREAD]);
useEffect(() => {
if (loading !== (privilegeLoading || indexNameLoading)) {
@@ -293,12 +316,6 @@ export const useUserInfo = (): State => {
}
}, [dispatch, loading, hasEncryptionKey, isApiEncryptionKey]);
- useEffect(() => {
- if (!loading && canUserCRUD !== capabilitiesCanUserCRUD) {
- dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD });
- }
- }, [dispatch, loading, canUserCRUD, capabilitiesCanUserCRUD]);
-
useEffect(() => {
if (!loading && signalIndexName !== apiSignalIndexName && apiSignalIndexName != null) {
dispatch({ type: 'updateSignalIndexName', signalIndexName: apiSignalIndexName });
@@ -351,6 +368,7 @@ export const useUserInfo = (): State => {
isAuthenticated,
hasEncryptionKey,
canUserCRUD,
+ canUserREAD,
hasIndexManage,
hasIndexMaintenance,
hasIndexWrite,
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx
index 64d9db80316a9..cbab24835c1ac 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx
@@ -87,10 +87,10 @@ const userPrivilegesInitial: ReturnType = {
error: undefined,
},
endpointPrivileges: { loading: true, canAccessEndpointManagement: false, canAccessFleet: false },
- alertsPrivileges: { loading: true, crud: false, read: false },
+ kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
};
-describe('usePrivilegeUser', () => {
+describe('useAlertsPrivileges', () => {
let appToastsMock: jest.Mocked>;
beforeEach(() => {
@@ -113,13 +113,15 @@ describe('usePrivilegeUser', () => {
hasIndexMaintenance: null,
hasIndexWrite: null,
hasIndexUpdateDelete: null,
+ hasKibanaCRUD: false,
+ hasKibanaREAD: false,
isAuthenticated: null,
loading: false,
});
});
});
- test('if there is an error when fetching user privilege, we should get back false for every properties', async () => {
+ test('if there is an error when fetching user privilege, we should get back false for all index related properties', async () => {
const userPrivileges = produce(userPrivilegesInitial, (draft) => {
draft.detectionEnginePrivileges.error = new Error('Something went wrong');
});
@@ -137,6 +139,8 @@ describe('usePrivilegeUser', () => {
hasIndexRead: false,
hasIndexWrite: false,
hasIndexUpdateDelete: false,
+ hasKibanaCRUD: true,
+ hasKibanaREAD: true,
isAuthenticated: false,
loading: false,
});
@@ -162,9 +166,11 @@ describe('usePrivilegeUser', () => {
hasEncryptionKey: true,
hasIndexManage: false,
hasIndexMaintenance: true,
- hasIndexRead: false,
- hasIndexWrite: false,
+ hasIndexRead: true,
+ hasIndexWrite: true,
hasIndexUpdateDelete: true,
+ hasKibanaCRUD: true,
+ hasKibanaREAD: true,
isAuthenticated: true,
loading: false,
});
@@ -187,9 +193,67 @@ describe('usePrivilegeUser', () => {
hasEncryptionKey: true,
hasIndexManage: true,
hasIndexMaintenance: true,
- hasIndexRead: false,
- hasIndexWrite: false,
+ hasIndexRead: true,
+ hasIndexWrite: true,
+ hasIndexUpdateDelete: true,
+ hasKibanaCRUD: true,
+ hasKibanaREAD: true,
+ isAuthenticated: true,
+ loading: false,
+ });
+ });
+ });
+
+ test('returns "hasKibanaCRUD" as false if user does not have SIEM Kibana "all" privileges', async () => {
+ const userPrivileges = produce(userPrivilegesInitial, (draft) => {
+ draft.detectionEnginePrivileges.result = privilege;
+ draft.kibanaSecuritySolutionsPrivileges = { crud: false, read: true };
+ });
+ useUserPrivilegesMock.mockReturnValue(userPrivileges);
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useAlertsPrivileges()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ hasEncryptionKey: true,
+ hasIndexManage: true,
+ hasIndexMaintenance: true,
+ hasIndexRead: true,
+ hasIndexWrite: true,
+ hasIndexUpdateDelete: true,
+ hasKibanaCRUD: false,
+ hasKibanaREAD: true,
+ isAuthenticated: true,
+ loading: false,
+ });
+ });
+ });
+
+ test('returns "hasKibanaREAD" as false if user does not have at least SIEM Kibana "read" privileges', async () => {
+ const userPrivileges = produce(userPrivilegesInitial, (draft) => {
+ draft.detectionEnginePrivileges.result = privilege;
+ draft.kibanaSecuritySolutionsPrivileges = { crud: false, read: false };
+ });
+ useUserPrivilegesMock.mockReturnValue(userPrivileges);
+
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useAlertsPrivileges()
+ );
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+ expect(result.current).toEqual({
+ hasEncryptionKey: true,
+ hasIndexManage: true,
+ hasIndexMaintenance: true,
+ hasIndexRead: true,
+ hasIndexWrite: true,
hasIndexUpdateDelete: true,
+ hasKibanaCRUD: false,
+ hasKibanaREAD: false,
isAuthenticated: true,
loading: false,
});
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx
index 1d9b8228b5070..b377eda49d0cd 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx
@@ -20,6 +20,8 @@ export interface AlertsPrivelegesState {
hasIndexUpdateDelete: boolean | null;
hasIndexMaintenance: boolean | null;
hasIndexRead: boolean | null;
+ hasKibanaCRUD: boolean;
+ hasKibanaREAD: boolean;
}
/**
* Hook to get user privilege from
@@ -34,8 +36,13 @@ export const useAlertsPrivileges = (): UseAlertsPrivelegesReturn => {
hasIndexWrite: null,
hasIndexUpdateDelete: null,
hasIndexMaintenance: null,
+ hasKibanaCRUD: false,
+ hasKibanaREAD: false,
});
- const { detectionEnginePrivileges, alertsPrivileges } = useUserPrivileges();
+ const {
+ detectionEnginePrivileges,
+ kibanaSecuritySolutionsPrivileges: { crud: hasKibanaCRUD, read: hasKibanaREAD },
+ } = useUserPrivileges();
useEffect(() => {
if (detectionEnginePrivileges.error != null) {
@@ -47,9 +54,11 @@ export const useAlertsPrivileges = (): UseAlertsPrivelegesReturn => {
hasIndexWrite: false,
hasIndexUpdateDelete: false,
hasIndexMaintenance: false,
+ hasKibanaCRUD,
+ hasKibanaREAD,
});
}
- }, [detectionEnginePrivileges.error]);
+ }, [detectionEnginePrivileges.error, hasKibanaCRUD, hasKibanaREAD]);
useEffect(() => {
if (detectionEnginePrivileges.result != null) {
@@ -62,13 +71,19 @@ export const useAlertsPrivileges = (): UseAlertsPrivelegesReturn => {
hasEncryptionKey: privilege.has_encryption_key,
hasIndexManage: privilege.index[indexName].manage && privilege.cluster.manage,
hasIndexMaintenance: privilege.index[indexName].maintenance,
- hasIndexRead: alertsPrivileges.read,
- hasIndexWrite: alertsPrivileges.crud,
+ hasIndexRead: privilege.index[indexName].read,
+ hasIndexWrite:
+ privilege.index[indexName].create ||
+ privilege.index[indexName].create_doc ||
+ privilege.index[indexName].index ||
+ privilege.index[indexName].write,
hasIndexUpdateDelete: privilege.index[indexName].write,
+ hasKibanaCRUD,
+ hasKibanaREAD,
});
}
}
- }, [detectionEnginePrivileges.result, alertsPrivileges]);
+ }, [detectionEnginePrivileges.result, hasKibanaCRUD, hasKibanaREAD]);
return { loading: detectionEnginePrivileges.loading, ...privileges };
};
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx
index 6d68dae375866..ade83fed4fd6b 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx
@@ -14,13 +14,6 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
jest.mock('./api');
jest.mock('../../../../common/hooks/use_app_toasts');
jest.mock('../../../../common/components/user_privileges/use_endpoint_privileges');
-jest.mock('@kbn/alerts', () => ({
- useGetUserAlertsPermissions: () => ({
- loading: false,
- crud: true,
- read: true,
- }),
-}));
describe('useSignalIndex', () => {
let appToastsMock: jest.Mocked>;
diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx
index dbd59d2510238..18952feee528b 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx
@@ -7,15 +7,15 @@
import React, { useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
-import { useGetUserAlertsPermissions } from '@kbn/alerts';
-import { ALERTS_PATH, SecurityPageName, SERVER_APP_ID } from '../../../../common/constants';
+import { ALERTS_PATH, SecurityPageName } from '../../../../common/constants';
import { NotFoundPage } from '../../../app/404';
import * as i18n from './translations';
import { TrackApplicationView } from '../../../../../../../src/plugins/usage_collection/public';
import { DetectionEnginePage } from '../../pages/detection_engine/detection_engine';
import { useKibana } from '../../../common/lib/kibana';
import { SpyRoute } from '../../../common/utils/route/spy_routes';
+import { useAlertsPrivileges } from '../../containers/detection_engine/alerts/use_alerts_privileges';
const AlertsRoute = () => (
@@ -25,15 +25,12 @@ const AlertsRoute = () => (
);
const AlertsContainerComponent: React.FC = () => {
- const {
- chrome,
- application: { capabilities },
- } = useKibana().services;
- const userPermissions = useGetUserAlertsPermissions(capabilities, SERVER_APP_ID);
+ const { chrome } = useKibana().services;
+ const { hasIndexRead, hasIndexWrite } = useAlertsPrivileges();
useEffect(() => {
// if the user is read only then display the glasses badge in the global navigation header
- if (userPermissions != null && !userPermissions.crud && userPermissions.read) {
+ if (!hasIndexWrite && hasIndexRead) {
chrome.setBadge({
text: i18n.READ_ONLY_BADGE_TEXT,
tooltip: i18n.READ_ONLY_BADGE_TOOLTIP,
@@ -45,7 +42,7 @@ const AlertsContainerComponent: React.FC = () => {
return () => {
chrome.setBadge();
};
- }, [userPermissions, chrome]);
+ }, [chrome, hasIndexRead, hasIndexWrite]);
return (
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx
index a92f4d706dc7c..0d0c51bc540b4 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx
@@ -80,7 +80,7 @@ jest.mock('../../../common/lib/kibana', () => {
docLinks: {
links: {
siem: {
- gettingStarted: 'link',
+ privileges: 'link',
},
},
},
@@ -107,6 +107,7 @@ describe('DetectionEnginePageComponent', () => {
(useUserData as jest.Mock).mockReturnValue([
{
hasIndexRead: true,
+ canUserREAD: true,
},
]);
(useSourcererScope as jest.Mock).mockReturnValue({
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
index d6531198c1884..71542e6931489 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
@@ -5,6 +5,10 @@
* 2.0.
*/
+// No bueno, I know! Encountered when reverting RBAC work post initial BCs
+// Don't want to include large amounts of refactor in this temporary workaround
+// TODO: Refactor code - component can be broken apart
+/* eslint-disable complexity */
import {
EuiFlexGroup,
EuiFlexItem,
@@ -17,7 +21,6 @@ import { noop } from 'lodash/fp';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { connect, ConnectedProps, useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
-import { AlertsFeatureNoPermissions } from '@kbn/alerts';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
@@ -73,6 +76,7 @@ import {
AlertsTableFilterGroup,
FILTER_OPEN,
} from '../../components/alerts_table/alerts_filter_group';
+import { EmptyPage } from '../../../common/components/empty_page';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -117,7 +121,10 @@ const DetectionEnginePageComponent: React.FC = ({
isAuthenticated: isUserAuthenticated,
hasEncryptionKey,
signalIndexName,
- hasIndexWrite,
+ hasIndexWrite = false,
+ hasIndexMaintenance = false,
+ canUserCRUD = false,
+ canUserREAD,
hasIndexRead,
},
] = useUserData();
@@ -249,6 +256,18 @@ const DetectionEnginePageComponent: React.FC = ({
[containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable]
);
+ const emptyPageActions = useMemo(
+ () => ({
+ feature: {
+ icon: 'documents',
+ label: i18n.GO_TO_DOCUMENTATION,
+ url: `${docLinks.links.siem.privileges}`,
+ target: '_blank',
+ },
+ }),
+ [docLinks]
+ );
+
if (isUserAuthenticated != null && !isUserAuthenticated && !loading) {
return (
@@ -275,92 +294,89 @@ const DetectionEnginePageComponent: React.FC = ({
{hasEncryptionKey != null && !hasEncryptionKey && }
- {indicesExist ? (
+ {indicesExist && (hasIndexRead === false || canUserREAD === false) ? (
+
+ ) : indicesExist && hasIndexRead && canUserREAD ? (
- {hasIndexRead ? (
- <>
-
-
-
-
-
-
-
- {i18n.BUTTON_MANAGE_RULES}
-
-
-
-
-
-
-
-
- {timelinesUi.getLastUpdated({
- updatedAt: updatedAt || 0,
- showUpdating: loading,
- })}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {i18n.BUTTON_MANAGE_RULES}
+
+
+
+
+
+
+
+
+ {timelinesUi.getLastUpdated({
+ updatedAt: updatedAt || 0,
+ showUpdating: loading,
+ })}
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
- >
- ) : (
-
- )}
+
) : (
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
index c1d674ce456ff..0c67a19e59e32 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
@@ -45,13 +45,6 @@ jest.mock('../../../../../common/containers/use_global_time', () => ({
setQuery: jest.fn(),
}),
}));
-jest.mock('@kbn/alerts', () => ({
- useGetUserAlertsPermissions: () => ({
- loading: false,
- crud: true,
- read: true,
- }),
-}));
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts
index 96e423aff1658..fedf119025304 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts
@@ -143,3 +143,18 @@ export const ML_RULES_UNAVAILABLE = (totalRules: number) =>
defaultMessage:
'{totalRules} {totalRules, plural, =1 {rule requires} other {rules require}} Machine Learning to enable.',
});
+
+export const FEATURE_NO_PERMISSIONS_TITLE = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.noPermissionsTitle',
+ {
+ defaultMessage: 'Privileges required',
+ }
+);
+
+export const ALERTS_FEATURE_NO_PERMISSIONS_MSG = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.noPermissionsMessage',
+ {
+ defaultMessage:
+ 'To view alerts, you must update privileges. For more information, contact your Kibana administrator.',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap
index 1bc2581a520ae..1355594831a24 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap
@@ -205,7 +205,7 @@ exports[`TrustedAppsGrid renders correctly when failed loading data for the seco
>
{
}, [addMessage]);
const {
endpointPrivileges: { canAccessFleet },
- alertsPrivileges,
} = useUserPrivileges();
+ const { hasIndexRead, hasKibanaREAD } = useAlertsPrivileges();
const isThreatIntelModuleEnabled = useIsThreatIntelModuleEnabled();
return (
<>
@@ -98,7 +99,7 @@ const OverviewComponent = () => {
- {alertsPrivileges?.read && (
+ {hasIndexRead && hasKibanaREAD && (
<>
diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx
index 4a951dfff45d7..93fa70ddd9bfb 100644
--- a/x-pack/plugins/security_solution/public/plugin.tsx
+++ b/x-pack/plugins/security_solution/public/plugin.tsx
@@ -33,7 +33,6 @@ import {
import { Storage } from '../../../../src/plugins/kibana_utils/public';
import { initTelemetry } from './common/lib/telemetry';
import { KibanaServices } from './common/lib/kibana/services';
-import { BASE_RAC_ALERTS_API_PATH } from '../../rule_registry/common/constants';
import {
APP_ID,
@@ -42,7 +41,7 @@ import {
APP_PATH,
DEFAULT_INDEX_KEY,
APP_ICON_SOLUTION,
- SERVER_APP_ID,
+ DETECTION_ENGINE_INDEX_URL,
} from '../common/constants';
import { getDeepLinks, updateGlobalNavigation } from './app/deep_links';
@@ -354,14 +353,17 @@ export class Plugin implements IPlugin ({
- useGetUserAlertsPermissions: () => ({
- loading: false,
- crud: true,
- read: true,
- }),
-}));
-
describe('Details Panel Component', () => {
const state: State = { ...mockGlobalState };
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
index cad6648cd1f38..5ed9398a621e8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
@@ -29,14 +29,6 @@ jest.mock(
})
);
-jest.mock('@kbn/alerts', () => ({
- useGetUserAlertsPermissions: () => ({
- loading: false,
- crud: true,
- read: true,
- }),
-}));
-
jest.mock('../../../../../common/lib/kibana', () => ({
useKibana: () => ({
services: {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx
index d20c62348f07f..404127893b11c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx
@@ -24,13 +24,6 @@ import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin'
jest.mock('../../../../../common/hooks/use_experimental_features');
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
-jest.mock('@kbn/alerts', () => ({
- useGetUserAlertsPermissions: () => ({
- loading: false,
- crud: true,
- read: true,
- }),
-}));
jest.mock('../../../../../common/hooks/use_selector');
jest.mock('../../../../../common/lib/kibana', () => ({
useKibana: () => ({
diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts
index 5494270d9ad81..cff1e2482a1ee 100644
--- a/x-pack/plugins/security_solution/server/features.ts
+++ b/x-pack/plugins/security_solution/server/features.ts
@@ -114,7 +114,7 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
},
alerting: ruleTypes,
cases: [APP_ID],
- subFeatures: [{ ...CASES_SUB_FEATURE }, { ...getAlertsSubFeature(ruleTypes) }],
+ subFeatures: [{ ...CASES_SUB_FEATURE } /* , { ...getAlertsSubFeature(ruleTypes) } */],
privileges: {
all: {
app: [APP_ID, 'kibana'],
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap
index 833a9084fdac6..3c065ab0ac109 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap
@@ -6,11 +6,6 @@ Object {
"test-index-*",
],
"template": Object {
- "aliases": Object {
- ".alerts-security.alerts-space-id": Object {
- "is_write_index": false,
- },
- },
"mappings": Object {
"_meta": Object {
"aliases_version": 1,
@@ -1810,10 +1805,6 @@ Object {
"path": "signal.rule.building_block_type",
"type": "alias",
},
- "kibana.alert.rule.consumer": Object {
- "type": "constant_keyword",
- "value": "siem",
- },
"kibana.alert.rule.created_at": Object {
"path": "signal.rule.created_at",
"type": "alias",
@@ -1870,10 +1861,6 @@ Object {
"path": "signal.rule.note",
"type": "alias",
},
- "kibana.alert.rule.producer": Object {
- "type": "constant_keyword",
- "value": "siem",
- },
"kibana.alert.rule.query": Object {
"path": "signal.rule.query",
"type": "alias",
@@ -1902,10 +1889,6 @@ Object {
"path": "signal.rule.rule_name_override",
"type": "alias",
},
- "kibana.alert.rule.rule_type_id": Object {
- "type": "constant_keyword",
- "value": "siem.signals",
- },
"kibana.alert.rule.saved_id": Object {
"path": "signal.rule.saved_id",
"type": "alias",
@@ -2070,10 +2053,6 @@ Object {
"path": "signal.status",
"type": "alias",
},
- "kibana.space_ids": Object {
- "type": "constant_keyword",
- "value": "space-id",
- },
"labels": Object {
"type": "object",
},
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts
index ab7ff26d9d875..d65a1ad87b41a 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts
@@ -25,7 +25,6 @@ import { buildSiemResponse } from '../utils';
import {
createSignalsFieldAliases,
getSignalsTemplate,
- getRbacRequiredFields,
SIGNALS_TEMPLATE_VERSION,
SIGNALS_FIELD_ALIASES_VERSION,
ALIAS_VERSION_FIELD,
@@ -89,7 +88,7 @@ export const createDetectionIndex = async (
ruleDataService: RuleDataPluginService,
ruleRegistryEnabled: boolean
): Promise => {
- const esClient = context.core.elasticsearch.client.asInternalUser;
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
const spaceId = siemClient.getSpaceId();
if (!siemClient) {
@@ -132,11 +131,11 @@ export const createDetectionIndex = async (
// for BOTH the index AND alias name. However, through 7.14 admins only needed permissions for .siem-signals (the index)
// and not .alerts-security.alerts (the alias). From the security solution perspective, all .siem-signals--*
// indices should have an alias to .alerts-security.alerts- so it's safe to add those aliases as the internal user.
- await addIndexAliases({
- esClient: context.core.elasticsearch.client.asInternalUser,
- index,
- aadIndexAliasName,
- });
+ // await addIndexAliases({
+ // esClient: context.core.elasticsearch.client.asInternalUser,
+ // index,
+ // aadIndexAliasName,
+ // });
const indexVersion = await getIndexVersion(esClient, index);
if (isOutdated({ current: indexVersion, target: SIGNALS_TEMPLATE_VERSION })) {
await esClient.indices.rollover({ alias: index });
@@ -166,7 +165,7 @@ const addFieldAliasesToIndices = async ({
properties: {
...signalExtraFields,
...fieldAliases,
- ...getRbacRequiredFields(spaceId),
+ // ...getRbacRequiredFields(spaceId),
},
_meta: {
version: currentVersion,
@@ -181,26 +180,26 @@ const addFieldAliasesToIndices = async ({
}
};
-const addIndexAliases = async ({
- esClient,
- index,
- aadIndexAliasName,
-}: {
- esClient: ElasticsearchClient;
- index: string;
- aadIndexAliasName: string;
-}) => {
- const { body: indices } = await esClient.indices.getAlias({ name: index });
- const aliasActions = {
- actions: Object.keys(indices).map((concreteIndexName) => {
- return {
- add: {
- index: concreteIndexName,
- alias: aadIndexAliasName,
- is_write_index: false,
- },
- };
- }),
- };
- await esClient.indices.updateAliases({ body: aliasActions });
-};
+// const addIndexAliases = async ({
+// esClient,
+// index,
+// aadIndexAliasName,
+// }: {
+// esClient: ElasticsearchClient;
+// index: string;
+// aadIndexAliasName: string;
+// }) => {
+// const { body: indices } = await esClient.indices.getAlias({ name: index });
+// const aliasActions = {
+// actions: Object.keys(indices).map((concreteIndexName) => {
+// return {
+// add: {
+// index: concreteIndexName,
+// alias: aadIndexAliasName,
+// is_write_index: false,
+// },
+// };
+// }),
+// };
+// await esClient.indices.updateAliases({ body: aliasActions });
+// };
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts
index 3355b0659f284..bb67dd1fca6df 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts
@@ -107,12 +107,13 @@ describe('get_signals_template', () => {
}
}, []);
const constantKeywordsFound = recursiveConstantKeywordFound('', template);
- expect(constantKeywordsFound).toEqual([
- 'template.mappings.properties.kibana.space_ids',
- 'template.mappings.properties.kibana.alert.rule.consumer',
- 'template.mappings.properties.kibana.alert.rule.producer',
- 'template.mappings.properties.kibana.alert.rule.rule_type_id',
- ]);
+ expect(constantKeywordsFound).toEqual([]);
+ // expect(constantKeywordsFound).toEqual([
+ // 'template.mappings.properties.kibana.space_ids',
+ // 'template.mappings.properties.kibana.alert.rule.consumer',
+ // 'template.mappings.properties.kibana.alert.rule.producer',
+ // 'template.mappings.properties.kibana.alert.rule.rule_type_id',
+ // ]);
});
test('it should match snapshot', () => {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts
index 38a3612e5861d..3470f955dbdba 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts
@@ -48,11 +48,11 @@ export const getSignalsTemplate = (index: string, spaceId: string, aadIndexAlias
const template = {
index_patterns: [`${index}-*`],
template: {
- aliases: {
- [aadIndexAliasName]: {
- is_write_index: false,
- },
- },
+ // aliases: {
+ // [aadIndexAliasName]: {
+ // is_write_index: false,
+ // },
+ // },
settings: {
index: {
lifecycle: {
@@ -72,7 +72,7 @@ export const getSignalsTemplate = (index: string, spaceId: string, aadIndexAlias
...ecsMapping.mappings.properties,
...otherMapping.mappings.properties,
...fieldAliases,
- ...getRbacRequiredFields(spaceId),
+ // ...getRbacRequiredFields(spaceId),
signal: signalsMapping.mappings.properties.signal,
},
_meta: {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts
index c36dade4bb9d0..4cfedd5dcaa01 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts
@@ -30,7 +30,7 @@ export const readIndexRoute = (router: SecuritySolutionPluginRouter, config: Con
const siemResponse = buildSiemResponse(response);
try {
- const esClient = context.core.elasticsearch.client.asInternalUser;
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
const siemClient = context.securitySolution?.getAppClient();
if (!siemClient) {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
index 53bebf340c267..d3193900859fa 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
@@ -128,208 +128,225 @@ export const importRulesRoute = (
const batchParseObjects = chunkParseObjects.shift() ?? [];
const newImportRuleResponse = await Promise.all(
batchParseObjects.reduce>>((accum, parsedRule) => {
- const importsWorkerPromise = new Promise(async (resolve) => {
- if (parsedRule instanceof Error) {
- // If the JSON object had a validation or parse error then we return
- // early with the error and an (unknown) for the ruleId
- resolve(
- createBulkErrorObject({
- statusCode: 400,
- message: parsedRule.message,
- })
- );
- return null;
- }
- const {
- anomaly_threshold: anomalyThreshold,
- author,
- building_block_type: buildingBlockType,
- description,
- enabled,
- event_category_override: eventCategoryOverride,
- false_positives: falsePositives,
- from,
- immutable,
- query: queryOrUndefined,
- language: languageOrUndefined,
- license,
- machine_learning_job_id: machineLearningJobId,
- output_index: outputIndex,
- saved_id: savedId,
- meta,
- filters: filtersRest,
- rule_id: ruleId,
- index,
- interval,
- max_signals: maxSignals,
- risk_score: riskScore,
- risk_score_mapping: riskScoreMapping,
- rule_name_override: ruleNameOverride,
- name,
- severity,
- severity_mapping: severityMapping,
- tags,
- threat,
- threat_filters: threatFilters,
- threat_index: threatIndex,
- threat_query: threatQuery,
- threat_mapping: threatMapping,
- threat_language: threatLanguage,
- threat_indicator_path: threatIndicatorPath,
- concurrent_searches: concurrentSearches,
- items_per_search: itemsPerSearch,
- threshold,
- timestamp_override: timestampOverride,
- to,
- type,
- references,
- note,
- timeline_id: timelineId,
- timeline_title: timelineTitle,
- throttle,
- version,
- exceptions_list: exceptionsList,
- } = parsedRule;
-
- try {
- const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined;
-
- const language =
- !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined;
-
- // TODO: Fix these either with an is conversion or by better typing them within io-ts
- const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[];
+ const importsWorkerPromise = new Promise(
+ async (resolve, reject) => {
+ try {
+ if (parsedRule instanceof Error) {
+ // If the JSON object had a validation or parse error then we return
+ // early with the error and an (unknown) for the ruleId
+ resolve(
+ createBulkErrorObject({
+ statusCode: 400,
+ message: parsedRule.message,
+ })
+ );
+ return null;
+ }
- throwHttpError(await mlAuthz.validateRuleType(type));
-
- const rule = await readRules({ rulesClient, ruleId, id: undefined });
- if (rule == null) {
- await createRules({
- rulesClient,
- anomalyThreshold,
+ const {
+ anomaly_threshold: anomalyThreshold,
author,
- buildingBlockType,
+ building_block_type: buildingBlockType,
description,
enabled,
- eventCategoryOverride,
- falsePositives,
+ event_category_override: eventCategoryOverride,
+ false_positives: falsePositives,
from,
immutable,
- query,
- language,
+ query: queryOrUndefined,
+ language: languageOrUndefined,
license,
- machineLearningJobId,
- outputIndex: signalsIndex,
- savedId,
- timelineId,
- timelineTitle,
+ machine_learning_job_id: machineLearningJobId,
+ output_index: outputIndex,
+ saved_id: savedId,
meta,
- filters,
- ruleId,
+ filters: filtersRest,
+ rule_id: ruleId,
index,
interval,
- maxSignals,
+ max_signals: maxSignals,
+ risk_score: riskScore,
+ risk_score_mapping: riskScoreMapping,
+ rule_name_override: ruleNameOverride,
name,
- riskScore,
- riskScoreMapping,
- ruleNameOverride,
severity,
- severityMapping,
+ severity_mapping: severityMapping,
tags,
- throttle,
- to,
- type,
threat,
+ threat_filters: threatFilters,
+ threat_index: threatIndex,
+ threat_query: threatQuery,
+ threat_mapping: threatMapping,
+ threat_language: threatLanguage,
+ threat_indicator_path: threatIndicatorPath,
+ concurrent_searches: concurrentSearches,
+ items_per_search: itemsPerSearch,
threshold,
- threatFilters,
- threatIndex,
- threatIndicatorPath,
- threatQuery,
- threatMapping,
- threatLanguage,
- concurrentSearches,
- itemsPerSearch,
- timestampOverride,
- references,
- note,
- version,
- exceptionsList,
- actions: [], // Actions are not imported nor exported at this time
- });
- resolve({ rule_id: ruleId, status_code: 200 });
- } else if (rule != null && request.query.overwrite) {
- await patchRules({
- rulesClient,
- author,
- buildingBlockType,
- spaceId: context.securitySolution.getSpaceId(),
- ruleStatusClient,
- description,
- enabled,
- eventCategoryOverride,
- falsePositives,
- from,
- query,
- language,
- license,
- outputIndex,
- savedId,
- timelineId,
- timelineTitle,
- meta,
- filters,
- rule,
- index,
- interval,
- maxSignals,
- riskScore,
- riskScoreMapping,
- ruleNameOverride,
- name,
- severity,
- severityMapping,
- tags,
- timestampOverride,
- throttle,
+ timestamp_override: timestampOverride,
to,
type,
- threat,
- threshold,
- threatFilters,
- threatIndex,
- threatQuery,
- threatMapping,
- threatLanguage,
- concurrentSearches,
- itemsPerSearch,
references,
note,
+ timeline_id: timelineId,
+ timeline_title: timelineTitle,
+ throttle,
version,
- exceptionsList,
- anomalyThreshold,
- machineLearningJobId,
- actions: undefined,
- });
- resolve({ rule_id: ruleId, status_code: 200 });
- } else if (rule != null) {
- resolve(
- createBulkErrorObject({
+ exceptions_list: exceptionsList,
+ } = parsedRule;
+
+ try {
+ const query =
+ !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined;
+ const language =
+ !isMlRule(type) && languageOrUndefined == null
+ ? 'kuery'
+ : languageOrUndefined; // TODO: Fix these either with an is conversion or by better typing them within io-ts
+
+ const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[];
+ throwHttpError(await mlAuthz.validateRuleType(type));
+ const rule = await readRules({
+ rulesClient,
ruleId,
- statusCode: 409,
- message: `rule_id: "${ruleId}" already exists`,
- })
- );
+ id: undefined,
+ });
+
+ if (rule == null) {
+ await createRules({
+ rulesClient,
+ anomalyThreshold,
+ author,
+ buildingBlockType,
+ description,
+ enabled,
+ eventCategoryOverride,
+ falsePositives,
+ from,
+ immutable,
+ query,
+ language,
+ license,
+ machineLearningJobId,
+ outputIndex: signalsIndex,
+ savedId,
+ timelineId,
+ timelineTitle,
+ meta,
+ filters,
+ ruleId,
+ index,
+ interval,
+ maxSignals,
+ name,
+ riskScore,
+ riskScoreMapping,
+ ruleNameOverride,
+ severity,
+ severityMapping,
+ tags,
+ throttle,
+ to,
+ type,
+ threat,
+ threshold,
+ threatFilters,
+ threatIndex,
+ threatIndicatorPath,
+ threatQuery,
+ threatMapping,
+ threatLanguage,
+ concurrentSearches,
+ itemsPerSearch,
+ timestampOverride,
+ references,
+ note,
+ version,
+ exceptionsList,
+ actions: [], // Actions are not imported nor exported at this time
+ });
+ resolve({
+ rule_id: ruleId,
+ status_code: 200,
+ });
+ } else if (rule != null && request.query.overwrite) {
+ await patchRules({
+ rulesClient,
+ author,
+ buildingBlockType,
+ spaceId: context.securitySolution.getSpaceId(),
+ ruleStatusClient,
+ description,
+ enabled,
+ eventCategoryOverride,
+ falsePositives,
+ from,
+ query,
+ language,
+ license,
+ outputIndex,
+ savedId,
+ timelineId,
+ timelineTitle,
+ meta,
+ filters,
+ rule,
+ index,
+ interval,
+ maxSignals,
+ riskScore,
+ riskScoreMapping,
+ ruleNameOverride,
+ name,
+ severity,
+ severityMapping,
+ tags,
+ timestampOverride,
+ throttle,
+ to,
+ type,
+ threat,
+ threshold,
+ threatFilters,
+ threatIndex,
+ threatQuery,
+ threatMapping,
+ threatLanguage,
+ concurrentSearches,
+ itemsPerSearch,
+ references,
+ note,
+ version,
+ exceptionsList,
+ anomalyThreshold,
+ machineLearningJobId,
+ actions: undefined,
+ });
+ resolve({
+ rule_id: ruleId,
+ status_code: 200,
+ });
+ } else if (rule != null) {
+ resolve(
+ createBulkErrorObject({
+ ruleId,
+ statusCode: 409,
+ message: `rule_id: "${ruleId}" already exists`,
+ })
+ );
+ }
+ } catch (err) {
+ resolve(
+ createBulkErrorObject({
+ ruleId,
+ statusCode: err.statusCode ?? 400,
+ message: err.message,
+ })
+ );
+ }
+ } catch (error) {
+ reject(error);
}
- } catch (err) {
- resolve(
- createBulkErrorObject({
- ruleId,
- statusCode: err.statusCode ?? 400,
- message: err.message,
- })
- );
}
- });
+ );
return [...accum, importsWorkerPromise];
}, [])
);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts
index bf21f9de037f4..e54cc94b886f6 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts
@@ -6,6 +6,7 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
+import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils';
import { setSignalStatusValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/set_signal_status_type_dependents';
import {
SetSignalsStatusSchemaDecoded,
@@ -66,7 +67,12 @@ export const setSignalsStatusRoute = (router: SecuritySolutionPluginRouter) => {
refresh: true,
body: {
script: {
- source: `ctx._source.signal.status = '${status}'`,
+ source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) {
+ ctx._source['${ALERT_WORKFLOW_STATUS}'] = '${status}'
+ }
+ if (ctx._source.signal != null && ctx._source.signal.status != null) {
+ ctx._source.signal.status = '${status}'
+ }`,
lang: 'painless',
},
query: queryObject,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
index 5c2d1fa061221..dc929ed0fdeb1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
@@ -12,7 +12,7 @@ import uuidv5 from 'uuid/v5';
import dateMath from '@elastic/datemath';
import type { estypes } from '@elastic/elasticsearch';
import { ApiResponse, Context } from '@elastic/elasticsearch/lib/Transport';
-import { ALERT_ID } from '@kbn/rule-data-utils';
+import { ALERT_INSTANCE_ID } from '@kbn/rule-data-utils';
import type { ListArray, ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { MAX_EXCEPTION_LIST_SIZE } from '@kbn/securitysolution-list-constants';
import { hasLargeValueList } from '@kbn/securitysolution-list-utils';
@@ -987,7 +987,7 @@ export const isWrappedSignalHit = (event: SimpleHit): event is WrappedSignalHit
};
export const isWrappedRACAlert = (event: SimpleHit): event is WrappedRACAlert => {
- return (event as WrappedRACAlert)?._source?.[ALERT_ID] != null;
+ return (event as WrappedRACAlert)?._source?.[ALERT_INSTANCE_ID] != null;
};
export const getField = (event: SimpleHit, field: string): T | undefined => {
diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts
index 70d93d7552b1c..7e35c2163df70 100644
--- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts
+++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts
@@ -106,133 +106,132 @@ export const importTimelines = async (
batchParseObjects.reduce>>((accum, parsedTimeline) => {
const importsWorkerPromise = new Promise(
async (resolve, reject) => {
- if (parsedTimeline instanceof Error) {
- // If the JSON object had a validation or parse error then we return
- // early with the error and an (unknown) for the ruleId
- resolve(
- createBulkErrorObject({
- statusCode: 400,
- message: parsedTimeline.message,
- })
- );
-
- return null;
- }
-
- const {
- savedObjectId,
- pinnedEventIds,
- globalNotes,
- eventNotes,
- status,
- templateTimelineId,
- templateTimelineVersion,
- title,
- timelineType,
- version,
- } = parsedTimeline;
-
- const parsedTimelineObject = omit(timelineSavedObjectOmittedFields, parsedTimeline);
- let newTimeline = null;
try {
- const compareTimelinesStatus = new CompareTimelinesStatus({
+ if (parsedTimeline instanceof Error) {
+ // If the JSON object had a validation or parse error then we return
+ // early with the error and an (unknown) for the ruleId
+ resolve(
+ createBulkErrorObject({
+ statusCode: 400,
+ message: parsedTimeline.message,
+ })
+ );
+ return null;
+ }
+
+ const {
+ savedObjectId,
+ pinnedEventIds,
+ globalNotes,
+ eventNotes,
status,
- timelineType,
+ templateTimelineId,
+ templateTimelineVersion,
title,
- timelineInput: {
- id: savedObjectId,
- version,
- },
- templateTimelineInput: {
- id: templateTimelineId,
- version: templateTimelineVersion,
- },
- frameworkRequest,
- });
- await compareTimelinesStatus.init();
- const isTemplateTimeline = compareTimelinesStatus.isHandlingTemplateTimeline;
- if (compareTimelinesStatus.isCreatableViaImport) {
- // create timeline / timeline template
- newTimeline = await createTimelines({
- frameworkRequest,
- timeline: setTimeline(parsedTimelineObject, parsedTimeline, isTemplateTimeline),
- pinnedEventIds: isTemplateTimeline ? null : pinnedEventIds,
- notes: isTemplateTimeline ? globalNotes : [...globalNotes, ...eventNotes],
- isImmutable,
- overrideNotesOwner: false,
- });
+ timelineType,
+ version,
+ } = parsedTimeline;
+ const parsedTimelineObject = omit(timelineSavedObjectOmittedFields, parsedTimeline);
+ let newTimeline = null;
- resolve({
- timeline_id: newTimeline.timeline.savedObjectId,
- status_code: 200,
- action: TimelineStatusActions.createViaImport,
+ try {
+ const compareTimelinesStatus = new CompareTimelinesStatus({
+ status,
+ timelineType,
+ title,
+ timelineInput: {
+ id: savedObjectId,
+ version,
+ },
+ templateTimelineInput: {
+ id: templateTimelineId,
+ version: templateTimelineVersion,
+ },
+ frameworkRequest,
});
- }
+ await compareTimelinesStatus.init();
+ const isTemplateTimeline = compareTimelinesStatus.isHandlingTemplateTimeline;
- if (!compareTimelinesStatus.isHandlingTemplateTimeline) {
- const errorMessage = compareTimelinesStatus.checkIsFailureCases(
- TimelineStatusActions.createViaImport
- );
- const message = errorMessage?.body ?? DEFAULT_ERROR;
-
- resolve(
- createBulkErrorObject({
- id: savedObjectId ?? 'unknown',
- statusCode: 409,
- message,
- })
- );
- } else {
- if (compareTimelinesStatus.isUpdatableViaImport) {
- // update timeline template
+ if (compareTimelinesStatus.isCreatableViaImport) {
+ // create timeline / timeline template
newTimeline = await createTimelines({
frameworkRequest,
- timeline: parsedTimelineObject,
- timelineSavedObjectId: compareTimelinesStatus.timelineId,
- timelineVersion: compareTimelinesStatus.timelineVersion,
- notes: globalNotes,
- existingNoteIds: compareTimelinesStatus.timelineInput.data?.noteIds,
+ timeline: setTimeline(parsedTimelineObject, parsedTimeline, isTemplateTimeline),
+ pinnedEventIds: isTemplateTimeline ? null : pinnedEventIds,
+ notes: isTemplateTimeline ? globalNotes : [...globalNotes, ...eventNotes],
isImmutable,
overrideNotesOwner: false,
});
-
resolve({
timeline_id: newTimeline.timeline.savedObjectId,
status_code: 200,
- action: TimelineStatusActions.updateViaImport,
+ action: TimelineStatusActions.createViaImport,
});
- } else {
+ }
+
+ if (!compareTimelinesStatus.isHandlingTemplateTimeline) {
const errorMessage = compareTimelinesStatus.checkIsFailureCases(
- TimelineStatusActions.updateViaImport
+ TimelineStatusActions.createViaImport
);
-
const message = errorMessage?.body ?? DEFAULT_ERROR;
-
resolve(
createBulkErrorObject({
- id:
- savedObjectId ??
- (templateTimelineId
- ? `(template_timeline_id) ${templateTimelineId}`
- : 'unknown'),
+ id: savedObjectId ?? 'unknown',
statusCode: 409,
message,
})
);
+ } else {
+ if (compareTimelinesStatus.isUpdatableViaImport) {
+ // update timeline template
+ newTimeline = await createTimelines({
+ frameworkRequest,
+ timeline: parsedTimelineObject,
+ timelineSavedObjectId: compareTimelinesStatus.timelineId,
+ timelineVersion: compareTimelinesStatus.timelineVersion,
+ notes: globalNotes,
+ existingNoteIds: compareTimelinesStatus.timelineInput.data?.noteIds,
+ isImmutable,
+ overrideNotesOwner: false,
+ });
+ resolve({
+ timeline_id: newTimeline.timeline.savedObjectId,
+ status_code: 200,
+ action: TimelineStatusActions.updateViaImport,
+ });
+ } else {
+ const errorMessage = compareTimelinesStatus.checkIsFailureCases(
+ TimelineStatusActions.updateViaImport
+ );
+ const message = errorMessage?.body ?? DEFAULT_ERROR;
+ resolve(
+ createBulkErrorObject({
+ id:
+ savedObjectId ??
+ (templateTimelineId
+ ? `(template_timeline_id) ${templateTimelineId}`
+ : 'unknown'),
+ statusCode: 409,
+ message,
+ })
+ );
+ }
}
+ } catch (err) {
+ resolve(
+ createBulkErrorObject({
+ id:
+ savedObjectId ??
+ (templateTimelineId
+ ? `(template_timeline_id) ${templateTimelineId}`
+ : 'unknown'),
+ statusCode: 400,
+ message: err.message,
+ })
+ );
}
- } catch (err) {
- resolve(
- createBulkErrorObject({
- id:
- savedObjectId ??
- (templateTimelineId
- ? `(template_timeline_id) ${templateTimelineId}`
- : 'unknown'),
- statusCode: 400,
- message: err.message,
- })
- );
+ } catch (error) {
+ reject(error);
}
}
);
diff --git a/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts b/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts
index d0e99690066dd..b544674d61c07 100644
--- a/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts
+++ b/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts
@@ -9,32 +9,42 @@ import { isString } from 'lodash';
import { JsonValue } from '@kbn/utility-types';
import { HealthStatus, RawMonitoringStats } from '../monitoring';
import { TaskManagerConfig } from '../config';
+import { Logger } from '../../../../../src/core/server';
export function calculateHealthStatus(
summarizedStats: RawMonitoringStats,
- config: TaskManagerConfig
+ config: TaskManagerConfig,
+ logger: Logger
): HealthStatus {
const now = Date.now();
- // if "hot" health stats are any more stale than monitored_stats_required_freshness (pollInterval +1s buffer by default)
- // consider the system unhealthy
- const requiredHotStatsFreshness: number = config.monitored_stats_required_freshness;
+ // if "hot" health stats are any more stale than monitored_stats_required_freshness
+ // times a multiplier, consider the system unhealthy
+ const requiredHotStatsFreshness: number = config.monitored_stats_required_freshness * 3;
- // if "cold" health stats are any more stale than the configured refresh (+ a buffer), consider the system unhealthy
+ // if "cold" health stats are any more stale than the configured refresh
+ // times a multiplier, consider the system unhealthy
const requiredColdStatsFreshness: number = config.monitored_aggregated_stats_refresh_rate * 1.5;
- /**
- * If the monitored stats aren't fresh, return a red status
- */
- const healthStatus =
- hasStatus(summarizedStats.stats, HealthStatus.Error) ||
- hasExpiredHotTimestamps(summarizedStats, now, requiredHotStatsFreshness) ||
- hasExpiredColdTimestamps(summarizedStats, now, requiredColdStatsFreshness)
- ? HealthStatus.Error
- : hasStatus(summarizedStats.stats, HealthStatus.Warning)
- ? HealthStatus.Warning
- : HealthStatus.OK;
- return healthStatus;
+ if (hasStatus(summarizedStats.stats, HealthStatus.Error)) {
+ return HealthStatus.Error;
+ }
+
+ if (hasExpiredHotTimestamps(summarizedStats, now, requiredHotStatsFreshness)) {
+ logger.debug('setting HealthStatus.Error because of expired hot timestamps');
+ return HealthStatus.Error;
+ }
+
+ if (hasExpiredColdTimestamps(summarizedStats, now, requiredColdStatsFreshness)) {
+ logger.debug('setting HealthStatus.Error because of expired cold timestamps');
+ return HealthStatus.Error;
+ }
+
+ if (hasStatus(summarizedStats.stats, HealthStatus.Warning)) {
+ return HealthStatus.Warning;
+ }
+
+ return HealthStatus.OK;
}
function hasStatus(stats: RawMonitoringStats['stats'], status: HealthStatus): boolean {
diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
index b8e3e78925df5..4e17d64870a39 100644
--- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
+++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
@@ -50,12 +50,15 @@ describe('logHealthMetrics', () => {
(calculateHealthStatus as jest.Mock).mockImplementation(() => HealthStatus.Error);
logHealthMetrics(health, logger, config);
- expect((logger as jest.Mocked).warn.mock.calls[0][0] as string).toBe(
- `Detected potential performance issue with Task Manager. Set 'xpack.task_manager.monitored_stats_health_verbose_log.enabled: true' in your Kibana.yml to enable debug logging`
- );
- expect((logger as jest.Mocked).warn.mock.calls[1][0] as string).toBe(
- `Detected potential performance issue with Task Manager. Set 'xpack.task_manager.monitored_stats_health_verbose_log.enabled: true' in your Kibana.yml to enable debug logging`
- );
+ const debugCalls = (logger as jest.Mocked).debug.mock.calls;
+ const performanceMessage = /^Task Manager detected a degradation in performance/;
+ const lastStatsMessage = /^Latest Monitored Stats: \{.*\}$/;
+ expect(debugCalls[0][0] as string).toMatch(lastStatsMessage);
+ expect(debugCalls[1][0] as string).toMatch(lastStatsMessage);
+ expect(debugCalls[2][0] as string).toMatch(performanceMessage);
+ expect(debugCalls[3][0] as string).toMatch(lastStatsMessage);
+ expect(debugCalls[4][0] as string).toMatch(lastStatsMessage);
+ expect(debugCalls[5][0] as string).toMatch(performanceMessage);
});
it('should not log a warning message to enable verbose logging when the status goes from Warning to OK', () => {
diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
index e8511b1e8c71d..d541ffb5684da 100644
--- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
+++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { kibanaPackageJson } from '@kbn/utils';
+
import { isEmpty } from 'lodash';
import { Logger } from '../../../../../src/core/server';
import { HealthStatus } from '../monitoring';
@@ -36,7 +38,7 @@ export function logHealthMetrics(
capacity_estimation: undefined,
},
};
- const statusWithoutCapacity = calculateHealthStatus(healthWithoutCapacity, config);
+ const statusWithoutCapacity = calculateHealthStatus(healthWithoutCapacity, config, logger);
if (statusWithoutCapacity === HealthStatus.Warning) {
logLevel = LogLevel.Warn;
} else if (statusWithoutCapacity === HealthStatus.Error && !isEmpty(monitoredHealth.stats)) {
@@ -44,6 +46,8 @@ export function logHealthMetrics(
}
const message = `Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`;
+ const docLink = `https://www.elastic.co/guide/en/kibana/${kibanaPackageJson.branch}/task-manager-health-monitoring.html`;
+ const detectedProblemMessage = `Task Manager detected a degradation in performance. This is usually temporary, and Kibana can recover automatically. If the problem persists, check the docs for troubleshooting information: ${docLink} .`;
if (enabled) {
const driftInSeconds = (monitoredHealth.stats.runtime?.value.drift.p99 ?? 0) / 1000;
if (
@@ -80,9 +84,7 @@ export function logHealthMetrics(
// This is legacy support - we used to always show this
logger.debug(message);
if (logLevel !== LogLevel.Debug && lastLogLevel === LogLevel.Debug) {
- logger.warn(
- `Detected potential performance issue with Task Manager. Set 'xpack.task_manager.monitored_stats_health_verbose_log.enabled: true' in your Kibana.yml to enable debug logging`
- );
+ logger.debug(detectedProblemMessage);
}
}
diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts
index 5e2b075415a10..d6ae8024b92f7 100644
--- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts
@@ -7,11 +7,19 @@
import { CapacityEstimationParams, estimateCapacity } from './capacity_estimation';
import { HealthStatus, RawMonitoringStats } from './monitoring_stats_stream';
+import { mockLogger } from '../test_utils';
describe('estimateCapacity', () => {
+ const logger = mockLogger();
+
+ beforeAll(() => {
+ jest.resetAllMocks();
+ });
+
test('estimates the max throughput per minute based on the workload and the assumed kibana instances', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -67,6 +75,7 @@ describe('estimateCapacity', () => {
test('reduces the available capacity per kibana when average task duration exceeds the poll interval', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -124,6 +133,7 @@ describe('estimateCapacity', () => {
test('estimates the max throughput per minute when duration by persistence is empty', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -160,6 +170,7 @@ describe('estimateCapacity', () => {
test('estimates the max throughput per minute based on the workload and the assumed kibana instances when there are tasks that repeat each hour or day', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -215,6 +226,7 @@ describe('estimateCapacity', () => {
test('estimates the max throughput available when there are no active Kibana', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -271,6 +283,7 @@ describe('estimateCapacity', () => {
test('estimates the max throughput available to handle the workload when there are multiple active kibana instances', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -332,6 +345,7 @@ describe('estimateCapacity', () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -412,6 +426,7 @@ describe('estimateCapacity', () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -493,6 +508,7 @@ describe('estimateCapacity', () => {
test('marks estimated capacity as OK state when workload and load suggest capacity is sufficient', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -557,6 +573,7 @@ describe('estimateCapacity', () => {
test('marks estimated capacity as Warning state when capacity is insufficient for recent spikes of non-recurring workload, but sufficient for the recurring workload', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -618,6 +635,7 @@ describe('estimateCapacity', () => {
test('marks estimated capacity as Error state when workload and load suggest capacity is insufficient', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -679,6 +697,7 @@ describe('estimateCapacity', () => {
test('recommmends a 20% increase in kibana when a spike in non-recurring tasks forces recurring task capacity to zero', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
@@ -754,6 +773,7 @@ describe('estimateCapacity', () => {
test('recommmends a 20% increase in kibana when a spike in non-recurring tasks in a system with insufficient capacity even for recurring tasks', async () => {
expect(
estimateCapacity(
+ logger,
mockStats(
{ max_workers: 10, poll_interval: 3000 },
{
diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
index 9cc223f63b196..49c593d77acec 100644
--- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
@@ -12,6 +12,7 @@ import { RawMonitoringStats, RawMonitoredStat, HealthStatus } from './monitoring
import { AveragedStat } from './task_run_calcultors';
import { TaskPersistenceTypes } from './task_run_statistics';
import { asErr, asOk, map, Result } from '../lib/result_type';
+import { Logger } from '../../../../../src/core/server';
export interface CapacityEstimationStat extends JsonObject {
observed: {
@@ -44,6 +45,7 @@ function isCapacityEstimationParams(
}
export function estimateCapacity(
+ logger: Logger,
capacityStats: CapacityEstimationParams
): RawMonitoredStat {
const workload = capacityStats.workload.value;
@@ -183,13 +185,14 @@ export function estimateCapacity(
const assumedRequiredThroughputPerMinutePerKibana =
averageCapacityUsedByNonRecurringAndEphemeralTasksPerKibana +
averageRecurringRequiredPerMinute / assumedKibanaInstances;
+
+ const status = getHealthStatus(logger, {
+ assumedRequiredThroughputPerMinutePerKibana,
+ assumedAverageRecurringRequiredThroughputPerMinutePerKibana,
+ capacityPerMinutePerKibana,
+ });
return {
- status:
- assumedRequiredThroughputPerMinutePerKibana < capacityPerMinutePerKibana
- ? HealthStatus.OK
- : assumedAverageRecurringRequiredThroughputPerMinutePerKibana < capacityPerMinutePerKibana
- ? HealthStatus.Warning
- : HealthStatus.Error,
+ status,
timestamp: new Date().toISOString(),
value: {
observed: mapValues(
@@ -220,13 +223,43 @@ export function estimateCapacity(
};
}
+interface GetHealthStatusParams {
+ assumedRequiredThroughputPerMinutePerKibana: number;
+ assumedAverageRecurringRequiredThroughputPerMinutePerKibana: number;
+ capacityPerMinutePerKibana: number;
+}
+
+function getHealthStatus(logger: Logger, params: GetHealthStatusParams): HealthStatus {
+ const {
+ assumedRequiredThroughputPerMinutePerKibana,
+ assumedAverageRecurringRequiredThroughputPerMinutePerKibana,
+ capacityPerMinutePerKibana,
+ } = params;
+ if (assumedRequiredThroughputPerMinutePerKibana < capacityPerMinutePerKibana) {
+ return HealthStatus.OK;
+ }
+
+ if (assumedAverageRecurringRequiredThroughputPerMinutePerKibana < capacityPerMinutePerKibana) {
+ logger.debug(
+ `setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) < capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`
+ );
+ return HealthStatus.Warning;
+ }
+
+ logger.debug(
+ `setting HealthStatus.Error because assumedRequiredThroughputPerMinutePerKibana (${assumedRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana}) AND assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`
+ );
+ return HealthStatus.Error;
+}
+
export function withCapacityEstimate(
+ logger: Logger,
monitoredStats: RawMonitoringStats['stats']
): RawMonitoringStats['stats'] {
if (isCapacityEstimationParams(monitoredStats)) {
return {
...monitoredStats,
- capacity_estimation: estimateCapacity(monitoredStats),
+ capacity_estimation: estimateCapacity(logger, monitoredStats),
};
}
return monitoredStats;
diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
index 82a111305927f..e477edf3b9aed 100644
--- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
@@ -47,62 +47,62 @@ describe('Configuration Statistics Aggregator', () => {
};
return new Promise(async (resolve, reject) => {
- createConfigurationAggregator(configuration, managedConfig)
- .pipe(take(3), bufferCount(3))
- .subscribe(([initial, updatedWorkers, updatedInterval]) => {
- expect(initial.value).toEqual({
- max_workers: 10,
- poll_interval: 6000000,
- max_poll_inactivity_cycles: 10,
- request_capacity: 1000,
- monitored_aggregated_stats_refresh_rate: 5000,
- monitored_stats_running_average_window: 50,
- monitored_task_execution_thresholds: {
- default: {
- error_threshold: 90,
- warn_threshold: 80,
+ try {
+ createConfigurationAggregator(configuration, managedConfig)
+ .pipe(take(3), bufferCount(3))
+ .subscribe(([initial, updatedWorkers, updatedInterval]) => {
+ expect(initial.value).toEqual({
+ max_workers: 10,
+ poll_interval: 6000000,
+ max_poll_inactivity_cycles: 10,
+ request_capacity: 1000,
+ monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_running_average_window: 50,
+ monitored_task_execution_thresholds: {
+ default: {
+ error_threshold: 90,
+ warn_threshold: 80,
+ },
+ custom: {},
},
- custom: {},
- },
- });
-
- expect(updatedWorkers.value).toEqual({
- max_workers: 8,
- poll_interval: 6000000,
- max_poll_inactivity_cycles: 10,
- request_capacity: 1000,
- monitored_aggregated_stats_refresh_rate: 5000,
- monitored_stats_running_average_window: 50,
- monitored_task_execution_thresholds: {
- default: {
- error_threshold: 90,
- warn_threshold: 80,
+ });
+ expect(updatedWorkers.value).toEqual({
+ max_workers: 8,
+ poll_interval: 6000000,
+ max_poll_inactivity_cycles: 10,
+ request_capacity: 1000,
+ monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_running_average_window: 50,
+ monitored_task_execution_thresholds: {
+ default: {
+ error_threshold: 90,
+ warn_threshold: 80,
+ },
+ custom: {},
},
- custom: {},
- },
- });
-
- expect(updatedInterval.value).toEqual({
- max_workers: 8,
- poll_interval: 3000,
- max_poll_inactivity_cycles: 10,
- request_capacity: 1000,
- monitored_aggregated_stats_refresh_rate: 5000,
- monitored_stats_running_average_window: 50,
- monitored_task_execution_thresholds: {
- default: {
- error_threshold: 90,
- warn_threshold: 80,
+ });
+ expect(updatedInterval.value).toEqual({
+ max_workers: 8,
+ poll_interval: 3000,
+ max_poll_inactivity_cycles: 10,
+ request_capacity: 1000,
+ monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_running_average_window: 50,
+ monitored_task_execution_thresholds: {
+ default: {
+ error_threshold: 90,
+ warn_threshold: 80,
+ },
+ custom: {},
},
- custom: {},
- },
- });
- resolve();
- }, reject);
-
- managedConfig.maxWorkersConfiguration$.next(8);
-
- managedConfig.pollIntervalConfiguration$.next(3000);
+ });
+ resolve();
+ }, reject);
+ managedConfig.maxWorkersConfiguration$.next(8);
+ managedConfig.pollIntervalConfiguration$.next(3000);
+ } catch (error) {
+ reject(error);
+ }
});
});
});
diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
index fdddfc41e590a..08badf8fe1c9d 100644
--- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
@@ -136,6 +136,7 @@ export function createMonitoringStatsStream(
}
export function summarizeMonitoringStats(
+ logger: Logger,
{
// eslint-disable-next-line @typescript-eslint/naming-convention
last_update,
@@ -143,7 +144,7 @@ export function summarizeMonitoringStats(
}: MonitoringStats,
config: TaskManagerConfig
): RawMonitoringStats {
- const summarizedStats = withCapacityEstimate({
+ const summarizedStats = withCapacityEstimate(logger, {
...(configuration
? {
configuration: {
@@ -156,7 +157,7 @@ export function summarizeMonitoringStats(
? {
runtime: {
timestamp: runtime.timestamp,
- ...summarizeTaskRunStat(runtime.value, config),
+ ...summarizeTaskRunStat(logger, runtime.value, config),
},
}
: {}),
diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts
index 46dc56b2bac4d..32fe9bf60466f 100644
--- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts
@@ -10,6 +10,7 @@ import { Subject, Observable } from 'rxjs';
import stats from 'stats-lite';
import sinon from 'sinon';
import { take, tap, bufferCount, skip, map } from 'rxjs/operators';
+import { mockLogger } from '../test_utils';
import { ConcreteTaskInstance, TaskStatus } from '../task';
import {
@@ -36,9 +37,11 @@ import { configSchema } from '../config';
describe('Task Run Statistics', () => {
let fakeTimer: sinon.SinonFakeTimers;
+ const logger = mockLogger();
beforeAll(() => {
fakeTimer = sinon.useFakeTimers();
+ jest.resetAllMocks();
});
afterAll(() => fakeTimer.restore());
@@ -77,7 +80,7 @@ describe('Task Run Statistics', () => {
// Use 'summarizeTaskRunStat' to receive summarize stats
map(({ key, value }: AggregatedStat) => ({
key,
- value: summarizeTaskRunStat(value, getTaskManagerConfig()).value,
+ value: summarizeTaskRunStat(logger, value, getTaskManagerConfig()).value,
})),
take(runAtDrift.length),
bufferCount(runAtDrift.length)
@@ -145,7 +148,7 @@ describe('Task Run Statistics', () => {
// Use 'summarizeTaskRunStat' to receive summarize stats
map(({ key, value }: AggregatedStat) => ({
key,
- value: summarizeTaskRunStat(value, getTaskManagerConfig()).value,
+ value: summarizeTaskRunStat(logger, value, getTaskManagerConfig()).value,
})),
take(runDurations.length * 2),
bufferCount(runDurations.length * 2)
@@ -241,7 +244,7 @@ describe('Task Run Statistics', () => {
// Use 'summarizeTaskRunStat' to receive summarize stats
map(({ key, value }: AggregatedStat) => ({
key,
- value: summarizeTaskRunStat(value, getTaskManagerConfig()).value,
+ value: summarizeTaskRunStat(logger, value, getTaskManagerConfig()).value,
})),
take(10),
bufferCount(10)
@@ -321,6 +324,7 @@ describe('Task Run Statistics', () => {
map(({ key, value }: AggregatedStat) => ({
key,
value: summarizeTaskRunStat(
+ logger,
value,
getTaskManagerConfig({
monitored_task_execution_thresholds: {
@@ -449,7 +453,7 @@ describe('Task Run Statistics', () => {
// Use 'summarizeTaskRunStat' to receive summarize stats
map(({ key, value }: AggregatedStat) => ({
key,
- value: summarizeTaskRunStat(value, getTaskManagerConfig({})).value,
+ value: summarizeTaskRunStat(logger, value, getTaskManagerConfig({})).value,
})),
take(taskEvents.length),
bufferCount(taskEvents.length)
@@ -590,7 +594,7 @@ describe('Task Run Statistics', () => {
// Use 'summarizeTaskRunStat' to receive summarize stats
map(({ key, value }: AggregatedStat) => ({
key,
- value: summarizeTaskRunStat(value, getTaskManagerConfig({})).value,
+ value: summarizeTaskRunStat(logger, value, getTaskManagerConfig({})).value,
})),
take(taskEvents.length),
bufferCount(taskEvents.length)
@@ -707,7 +711,7 @@ describe('Task Run Statistics', () => {
// Use 'summarizeTaskRunStat' to receive summarize stats
map(({ key, value }: AggregatedStat) => ({
key,
- value: summarizeTaskRunStat(value, getTaskManagerConfig()).value,
+ value: summarizeTaskRunStat(logger, value, getTaskManagerConfig()).value,
})),
tap(() => {
expectedTimestamp.push(new Date().toISOString());
diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts
index 3946827827fee..44908706aa6ec 100644
--- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts
@@ -38,6 +38,7 @@ import {
import { HealthStatus } from './monitoring_stats_stream';
import { TaskPollingLifecycle } from '../polling_lifecycle';
import { TaskExecutionFailureThreshold, TaskManagerConfig } from '../config';
+import { Logger } from '../../../../../src/core/server';
interface FillPoolStat extends JsonObject {
duration: number[];
@@ -337,6 +338,7 @@ const DEFAULT_POLLING_FREQUENCIES = {
};
export function summarizeTaskRunStat(
+ logger: Logger,
{
polling: {
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -403,6 +405,7 @@ export function summarizeTaskRunStat(
executionResultFrequency,
(typedResultFrequencies, taskType) =>
summarizeTaskExecutionResultFrequencyStat(
+ logger,
{
...DEFAULT_TASK_RUN_FREQUENCIES,
...calculateFrequency(typedResultFrequencies),
@@ -418,16 +421,35 @@ export function summarizeTaskRunStat(
}
function summarizeTaskExecutionResultFrequencyStat(
+ logger: Logger,
resultFrequencySummary: ResultFrequency,
executionErrorThreshold: TaskExecutionFailureThreshold
): ResultFrequencySummary {
+ const status = getHealthStatus(logger, resultFrequencySummary, executionErrorThreshold);
return {
...resultFrequencySummary,
- status:
- resultFrequencySummary.Failed > executionErrorThreshold.warn_threshold
- ? resultFrequencySummary.Failed > executionErrorThreshold.error_threshold
- ? HealthStatus.Error
- : HealthStatus.Warning
- : HealthStatus.OK,
+ status,
};
}
+
+function getHealthStatus(
+ logger: Logger,
+ resultFrequencySummary: ResultFrequency,
+ executionErrorThreshold: TaskExecutionFailureThreshold
+): HealthStatus {
+ if (resultFrequencySummary.Failed > executionErrorThreshold.warn_threshold) {
+ if (resultFrequencySummary.Failed > executionErrorThreshold.error_threshold) {
+ logger.debug(
+ `setting HealthStatus.Error because resultFrequencySummary.Failed (${resultFrequencySummary.Failed}) > error_threshold (${executionErrorThreshold.error_threshold})`
+ );
+ return HealthStatus.Error;
+ } else {
+ logger.debug(
+ `setting HealthStatus.Warning because resultFrequencySummary.Failed (${resultFrequencySummary.Failed}) > warn_threshold (${executionErrorThreshold.warn_threshold})`
+ );
+ return HealthStatus.Warning;
+ }
+ }
+
+ return HealthStatus.OK;
+}
diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts
index 9125bca8f5b05..d24931646128a 100644
--- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts
@@ -328,27 +328,44 @@ describe('Workload Statistics Aggregator', () => {
loggingSystemMock.create().get()
);
- return new Promise(async (resolve) => {
- workloadAggregator.pipe(first()).subscribe((result) => {
- expect(result.key).toEqual('workload');
- expect(result.value).toMatchObject({
- count: 4,
- task_types: {
- actions_telemetry: { count: 2, status: { idle: 2 } },
- alerting_telemetry: { count: 1, status: { idle: 1 } },
- session_cleanup: { count: 1, status: { idle: 1 } },
- },
+ return new Promise(async (resolve, reject) => {
+ try {
+ workloadAggregator.pipe(first()).subscribe((result) => {
+ expect(result.key).toEqual('workload');
+ expect(result.value).toMatchObject({
+ count: 4,
+ task_types: {
+ actions_telemetry: {
+ count: 2,
+ status: {
+ idle: 2,
+ },
+ },
+ alerting_telemetry: {
+ count: 1,
+ status: {
+ idle: 1,
+ },
+ },
+ session_cleanup: {
+ count: 1,
+ status: {
+ idle: 1,
+ },
+ },
+ },
+ });
+ resolve();
});
- resolve();
- });
-
- availability$.next(false);
-
- await sleep(10);
- expect(taskStore.aggregate).not.toHaveBeenCalled();
- await sleep(10);
- expect(taskStore.aggregate).not.toHaveBeenCalled();
- availability$.next(true);
+ availability$.next(false);
+ await sleep(10);
+ expect(taskStore.aggregate).not.toHaveBeenCalled();
+ await sleep(10);
+ expect(taskStore.aggregate).not.toHaveBeenCalled();
+ availability$.next(true);
+ } catch (error) {
+ reject(error);
+ }
});
});
diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts
index 11746e2da2847..35dec38b43df5 100644
--- a/x-pack/plugins/task_manager/server/plugin.ts
+++ b/x-pack/plugins/task_manager/server/plugin.ts
@@ -95,6 +95,14 @@ export class TaskManagerPlugin
this.config!
);
+ core.status.derivedStatus$.subscribe((status) =>
+ this.logger.debug(`status core.status.derivedStatus now set to ${status.level}`)
+ );
+ serviceStatus$.subscribe((status) =>
+ this.logger.debug(`status serviceStatus now set to ${status.level}`)
+ );
+
+ // here is where the system status is updated
core.status.set(
combineLatest([core.status.derivedStatus$, serviceStatus$]).pipe(
map(([derivedStatus, serviceStatus]) =>
diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts
index 01c1d6a5f3983..f34728cd8ff3c 100644
--- a/x-pack/plugins/task_manager/server/routes/health.test.ts
+++ b/x-pack/plugins/task_manager/server/routes/health.test.ts
@@ -20,7 +20,7 @@ import {
RawMonitoringStats,
summarizeMonitoringStats,
} from '../monitoring';
-import { ServiceStatusLevels } from 'src/core/server';
+import { ServiceStatusLevels, Logger } from 'src/core/server';
import { configSchema, TaskManagerConfig } from '../config';
import { calculateHealthStatusMock } from '../lib/calculate_health_status.mock';
import { FillPoolResult } from '../lib/fill_pool';
@@ -30,14 +30,14 @@ jest.mock('../lib/log_health_metrics', () => ({
}));
describe('healthRoute', () => {
+ const logger = loggingSystemMock.create().get();
+
beforeEach(() => {
jest.resetAllMocks();
});
it('registers the route', async () => {
const router = httpServiceMock.createRouter();
-
- const logger = loggingSystemMock.create().get();
healthRoute(router, of(), logger, uuid.v4(), getTaskManagerConfig());
const [config] = router.get.mock.calls[0];
@@ -47,7 +47,6 @@ describe('healthRoute', () => {
it('logs the Task Manager stats at a fixed interval', async () => {
const router = httpServiceMock.createRouter();
- const logger = loggingSystemMock.create().get();
const calculateHealthStatus = calculateHealthStatusMock.create();
calculateHealthStatus.mockImplementation(() => HealthStatus.OK);
const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
@@ -87,19 +86,22 @@ describe('healthRoute', () => {
id,
timestamp: expect.any(String),
status: expect.any(String),
- ...ignoreCapacityEstimation(summarizeMonitoringStats(mockStat, getTaskManagerConfig({}))),
+ ...ignoreCapacityEstimation(
+ summarizeMonitoringStats(logger, mockStat, getTaskManagerConfig({}))
+ ),
});
expect(logHealthMetrics.mock.calls[1][0]).toMatchObject({
id,
timestamp: expect.any(String),
status: expect.any(String),
- ...ignoreCapacityEstimation(summarizeMonitoringStats(nextMockStat, getTaskManagerConfig({}))),
+ ...ignoreCapacityEstimation(
+ summarizeMonitoringStats(logger, nextMockStat, getTaskManagerConfig({}))
+ ),
});
});
it(`logs at a warn level if the status is warning`, async () => {
const router = httpServiceMock.createRouter();
- const logger = loggingSystemMock.create().get();
const calculateHealthStatus = calculateHealthStatusMock.create();
calculateHealthStatus.mockImplementation(() => HealthStatus.Warning);
const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
@@ -141,7 +143,7 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(warnRuntimeStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, warnRuntimeStat, getTaskManagerConfig({}))
),
});
expect(logHealthMetrics.mock.calls[1][0]).toMatchObject({
@@ -149,7 +151,7 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(warnConfigurationStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, warnConfigurationStat, getTaskManagerConfig({}))
),
});
expect(logHealthMetrics.mock.calls[2][0]).toMatchObject({
@@ -157,7 +159,7 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(warnWorkloadStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, warnWorkloadStat, getTaskManagerConfig({}))
),
});
expect(logHealthMetrics.mock.calls[3][0]).toMatchObject({
@@ -165,14 +167,13 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(warnEphemeralStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, warnEphemeralStat, getTaskManagerConfig({}))
),
});
});
it(`logs at an error level if the status is error`, async () => {
const router = httpServiceMock.createRouter();
- const logger = loggingSystemMock.create().get();
const calculateHealthStatus = calculateHealthStatusMock.create();
calculateHealthStatus.mockImplementation(() => HealthStatus.Error);
const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
@@ -214,7 +215,7 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(errorRuntimeStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, errorRuntimeStat, getTaskManagerConfig({}))
),
});
expect(logHealthMetrics.mock.calls[1][0]).toMatchObject({
@@ -222,7 +223,7 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(errorConfigurationStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, errorConfigurationStat, getTaskManagerConfig({}))
),
});
expect(logHealthMetrics.mock.calls[2][0]).toMatchObject({
@@ -230,7 +231,7 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(errorWorkloadStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, errorWorkloadStat, getTaskManagerConfig({}))
),
});
expect(logHealthMetrics.mock.calls[3][0]).toMatchObject({
@@ -238,7 +239,7 @@ describe('healthRoute', () => {
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(errorEphemeralStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(logger, errorEphemeralStat, getTaskManagerConfig({}))
),
});
});
@@ -251,7 +252,7 @@ describe('healthRoute', () => {
const { serviceStatus$ } = healthRoute(
router,
stats$,
- loggingSystemMock.create().get(),
+ logger,
uuid.v4(),
getTaskManagerConfig({
monitored_stats_required_freshness: 1000,
@@ -269,7 +270,7 @@ describe('healthRoute', () => {
stats$.next(
mockHealthStats({
- last_update: new Date(Date.now() - 1500).toISOString(),
+ last_update: new Date(Date.now() - 3001).toISOString(),
})
);
@@ -278,6 +279,7 @@ describe('healthRoute', () => {
status: 'error',
...ignoreCapacityEstimation(
summarizeMonitoringStats(
+ logger,
mockHealthStats({
last_update: expect.any(String),
stats: {
@@ -307,9 +309,15 @@ describe('healthRoute', () => {
});
expect(await serviceStatus).toMatchObject({
- level: ServiceStatusLevels.unavailable,
- summary: 'Task Manager is unavailable',
+ level: ServiceStatusLevels.degraded,
+ summary: 'Task Manager is unhealthy',
});
+ const debugCalls = (logger as jest.Mocked).debug.mock.calls as string[][];
+ const warnMessage = /^setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana/;
+ const found = debugCalls
+ .map((arr) => arr[0])
+ .find((message) => message.match(warnMessage) != null);
+ expect(found).toMatch(warnMessage);
});
it('returns a error status if the workload stats have not been updated within the required cold freshness', async () => {
@@ -320,7 +328,7 @@ describe('healthRoute', () => {
healthRoute(
router,
stats$,
- loggingSystemMock.create().get(),
+ logger,
uuid.v4(),
getTaskManagerConfig({
monitored_stats_required_freshness: 5000,
@@ -352,6 +360,7 @@ describe('healthRoute', () => {
status: 'error',
...ignoreCapacityEstimation(
summarizeMonitoringStats(
+ logger,
mockHealthStats({
last_update: expect.any(String),
stats: {
@@ -388,7 +397,7 @@ describe('healthRoute', () => {
healthRoute(
router,
stats$,
- loggingSystemMock.create().get(),
+ logger,
uuid.v4(),
getTaskManagerConfig({
monitored_stats_required_freshness: 1000,
@@ -399,7 +408,7 @@ describe('healthRoute', () => {
await sleep(0);
// eslint-disable-next-line @typescript-eslint/naming-convention
- const last_successful_poll = new Date(Date.now() - 2000).toISOString();
+ const last_successful_poll = new Date(Date.now() - 3001).toISOString();
stats$.next(
mockHealthStats({
stats: {
@@ -423,6 +432,7 @@ describe('healthRoute', () => {
status: 'error',
...ignoreCapacityEstimation(
summarizeMonitoringStats(
+ logger,
mockHealthStats({
last_update: expect.any(String),
stats: {
diff --git a/x-pack/plugins/task_manager/server/routes/health.ts b/x-pack/plugins/task_manager/server/routes/health.ts
index fe58ee3490aff..4101662184430 100644
--- a/x-pack/plugins/task_manager/server/routes/health.ts
+++ b/x-pack/plugins/task_manager/server/routes/health.ts
@@ -62,8 +62,8 @@ export function healthRoute(
const requiredHotStatsFreshness: number = config.monitored_stats_required_freshness;
function getHealthStatus(monitoredStats: MonitoringStats) {
- const summarizedStats = summarizeMonitoringStats(monitoredStats, config);
- const status = calculateHealthStatus(summarizedStats, config);
+ const summarizedStats = summarizeMonitoringStats(logger, monitoredStats, config);
+ const status = calculateHealthStatus(summarizedStats, config, logger);
const now = Date.now();
const timestamp = new Date(now).toISOString();
return { id: taskManagerId, timestamp, status, ...summarizedStats };
@@ -118,9 +118,7 @@ export function withServiceStatus(
const level =
monitoredHealth.status === HealthStatus.OK
? ServiceStatusLevels.available
- : monitoredHealth.status === HealthStatus.Warning
- ? ServiceStatusLevels.degraded
- : ServiceStatusLevels.unavailable;
+ : ServiceStatusLevels.degraded;
return [
monitoredHealth,
{
diff --git a/x-pack/plugins/task_manager/server/saved_objects/index.ts b/x-pack/plugins/task_manager/server/saved_objects/index.ts
index d2d079c7747b1..abbd1af73b55a 100644
--- a/x-pack/plugins/task_manager/server/saved_objects/index.ts
+++ b/x-pack/plugins/task_manager/server/saved_objects/index.ts
@@ -8,7 +8,7 @@
import type { SavedObjectsServiceSetup, SavedObjectsTypeMappingDefinition } from 'kibana/server';
import { estypes } from '@elastic/elasticsearch';
import mappings from './mappings.json';
-import { migrations } from './migrations';
+import { getMigrations } from './migrations';
import { TaskManagerConfig } from '../config.js';
import { getOldestIdleActionTask } from '../queries/oldest_idle_action_task';
@@ -22,7 +22,7 @@ export function setupSavedObjects(
hidden: true,
convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id; ctx._source.remove("kibana")`,
mappings: mappings.task as SavedObjectsTypeMappingDefinition,
- migrations,
+ migrations: getMigrations(),
indexPattern: config.index,
excludeOnUpgrade: async ({ readonlyEsClient }) => {
const oldestNeededActionParams = await getOldestIdleActionTask(
diff --git a/x-pack/plugins/task_manager/server/saved_objects/migrations.test.ts b/x-pack/plugins/task_manager/server/saved_objects/migrations.test.ts
new file mode 100644
index 0000000000000..73141479d9081
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/saved_objects/migrations.test.ts
@@ -0,0 +1,169 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import uuid from 'uuid';
+import { getMigrations } from './migrations';
+import { SavedObjectUnsanitizedDoc } from 'kibana/server';
+import { migrationMocks } from 'src/core/server/mocks';
+import { TaskInstanceWithDeprecatedFields } from '../task';
+
+const migrationContext = migrationMocks.createContext();
+
+describe('successful migrations', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+ describe('7.4.0', () => {
+ test('extend task instance with updated_at', () => {
+ const migration740 = getMigrations()['7.4.0'];
+ const taskInstance = getMockData({});
+ expect(migration740(taskInstance, migrationContext).attributes.updated_at).not.toBeNull();
+ });
+ });
+
+ describe('7.6.0', () => {
+ test('rename property Internal to Schedule', () => {
+ const migration760 = getMigrations()['7.6.0'];
+ const taskInstance = getMockData({});
+ expect(migration760(taskInstance, migrationContext)).toEqual({
+ ...taskInstance,
+ attributes: {
+ ...taskInstance.attributes,
+ schedule: taskInstance.attributes.schedule,
+ },
+ });
+ });
+ });
+
+ describe('8.0.0', () => {
+ test('transforms actionsTasksLegacyIdToSavedObjectIds', () => {
+ const migration800 = getMigrations()['8.0.0'];
+ const taskInstance = getMockData({
+ taskType: 'actions:123456',
+ params: JSON.stringify({ spaceId: 'user1', actionTaskParamsId: '123456' }),
+ });
+
+ expect(migration800(taskInstance, migrationContext)).toEqual({
+ ...taskInstance,
+ attributes: {
+ ...taskInstance.attributes,
+ params: '{"spaceId":"user1","actionTaskParamsId":"800f81f8-980e-58ca-b710-d1b0644adea2"}',
+ },
+ });
+ });
+
+ test('it is only applicable for saved objects that live in a custom space', () => {
+ const migration800 = getMigrations()['8.0.0'];
+ const taskInstance = getMockData({
+ taskType: 'actions:123456',
+ params: JSON.stringify({ spaceId: 'default', actionTaskParamsId: '123456' }),
+ });
+
+ expect(migration800(taskInstance, migrationContext)).toEqual(taskInstance);
+ });
+
+ test('it is only applicable for saved objects that live in a custom space even if spaces are disabled', () => {
+ const migration800 = getMigrations()['8.0.0'];
+ const taskInstance = getMockData({
+ taskType: 'actions:123456',
+ params: JSON.stringify({ actionTaskParamsId: '123456' }),
+ });
+
+ expect(migration800(taskInstance, migrationContext)).toEqual(taskInstance);
+ });
+
+ test('transforms alertingTaskLegacyIdToSavedObjectIds', () => {
+ const migration800 = getMigrations()['8.0.0'];
+ const taskInstance = getMockData({
+ taskType: 'alerting:123456',
+ params: JSON.stringify({ spaceId: 'user1', alertId: '123456' }),
+ });
+
+ expect(migration800(taskInstance, migrationContext)).toEqual({
+ ...taskInstance,
+ attributes: {
+ ...taskInstance.attributes,
+ params: '{"spaceId":"user1","alertId":"1a4f9206-e25f-58e6-bad5-3ff21e90648e"}',
+ },
+ });
+ });
+
+ test('skip transformation for defult space scenario', () => {
+ const migration800 = getMigrations()['8.0.0'];
+ const taskInstance = getMockData({
+ taskType: 'alerting:123456',
+ params: JSON.stringify({ spaceId: 'default', alertId: '123456' }),
+ });
+
+ expect(migration800(taskInstance, migrationContext)).toEqual({
+ ...taskInstance,
+ attributes: {
+ ...taskInstance.attributes,
+ params: '{"spaceId":"default","alertId":"123456"}',
+ },
+ });
+ });
+ });
+});
+
+describe('handles errors during migrations', () => {
+ describe('8.0.0 throws if migration fails', () => {
+ test('should throw the exception if task instance params format is wrong', () => {
+ const migration800 = getMigrations()['8.0.0'];
+ const taskInstance = getMockData({
+ taskType: 'alerting:123456',
+ params: `{ spaceId: 'user1', customId: '123456' }`,
+ });
+ expect(() => {
+ migration800(taskInstance, migrationContext);
+ }).toThrowError();
+ expect(migrationContext.log.error).toHaveBeenCalledWith(
+ `savedObject 8.0.0 migration failed for task instance ${taskInstance.id} with error: Unexpected token s in JSON at position 2`,
+ {
+ migrations: {
+ taskInstanceDocument: {
+ ...taskInstance,
+ attributes: {
+ ...taskInstance.attributes,
+ },
+ },
+ },
+ }
+ );
+ });
+ });
+});
+
+function getUpdatedAt(): string {
+ const updatedAt = new Date();
+ updatedAt.setHours(updatedAt.getHours() + 2);
+ return updatedAt.toISOString();
+}
+
+function getMockData(
+ overwrites: Record = {}
+): SavedObjectUnsanitizedDoc> {
+ return {
+ attributes: {
+ scheduledAt: new Date(),
+ state: { runs: 0, total_cleaned_up: 0 },
+ runAt: new Date(),
+ startedAt: new Date(),
+ retryAt: new Date(),
+ ownerId: '234',
+ taskType: 'foo',
+ schedule: { interval: '10s' },
+ params: {
+ bar: true,
+ },
+ ...overwrites,
+ },
+ updated_at: getUpdatedAt(),
+ id: uuid.v4(),
+ type: 'task',
+ };
+}
diff --git a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts
index 879fca2ae4f6f..a2ed91dba2737 100644
--- a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts
+++ b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts
@@ -5,16 +5,123 @@
* 2.0.
*/
-import { SavedObjectMigrationMap, SavedObjectUnsanitizedDoc } from '../../../../../src/core/server';
+import {
+ LogMeta,
+ SavedObjectMigrationContext,
+ SavedObjectMigrationFn,
+ SavedObjectMigrationMap,
+ SavedObjectsUtils,
+ SavedObjectUnsanitizedDoc,
+} from '../../../../../src/core/server';
import { TaskInstance, TaskInstanceWithDeprecatedFields } from '../task';
-export const migrations: SavedObjectMigrationMap = {
- '7.4.0': (doc) => ({
- ...doc,
- updated_at: new Date().toISOString(),
- }),
- '7.6.0': moveIntervalIntoSchedule,
-};
+interface TaskInstanceLogMeta extends LogMeta {
+ migrations: { taskInstanceDocument: SavedObjectUnsanitizedDoc };
+}
+
+type TaskInstanceMigration = (
+ doc: SavedObjectUnsanitizedDoc
+) => SavedObjectUnsanitizedDoc;
+
+export function getMigrations(): SavedObjectMigrationMap {
+ return {
+ '7.4.0': executeMigrationWithErrorHandling(
+ (doc) => ({
+ ...doc,
+ updated_at: new Date().toISOString(),
+ }),
+ '7.4.0'
+ ),
+ '7.6.0': executeMigrationWithErrorHandling(moveIntervalIntoSchedule, '7.6.0'),
+ '8.0.0': executeMigrationWithErrorHandling(
+ pipeMigrations(alertingTaskLegacyIdToSavedObjectIds, actionsTasksLegacyIdToSavedObjectIds),
+ '8.0.0'
+ ),
+ };
+}
+
+function executeMigrationWithErrorHandling(
+ migrationFunc: SavedObjectMigrationFn<
+ TaskInstanceWithDeprecatedFields,
+ TaskInstanceWithDeprecatedFields
+ >,
+ version: string
+) {
+ return (
+ doc: SavedObjectUnsanitizedDoc,
+ context: SavedObjectMigrationContext
+ ) => {
+ try {
+ return migrationFunc(doc, context);
+ } catch (ex) {
+ context.log.error(
+ `savedObject ${version} migration failed for task instance ${doc.id} with error: ${ex.message}`,
+ {
+ migrations: {
+ taskInstanceDocument: doc,
+ },
+ }
+ );
+ throw ex;
+ }
+ };
+}
+
+function alertingTaskLegacyIdToSavedObjectIds(
+ doc: SavedObjectUnsanitizedDoc
+): SavedObjectUnsanitizedDoc {
+ if (doc.attributes.taskType.startsWith('alerting:')) {
+ let params: { spaceId?: string; alertId?: string } = {};
+ params = JSON.parse((doc.attributes.params as unknown) as string);
+
+ if (params.alertId && params.spaceId && params.spaceId !== 'default') {
+ const newId = SavedObjectsUtils.getConvertedObjectId(params.spaceId, 'alert', params.alertId);
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ params: JSON.stringify({
+ ...params,
+ alertId: newId,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ }) as any,
+ },
+ };
+ }
+ }
+
+ return doc;
+}
+
+function actionsTasksLegacyIdToSavedObjectIds(
+ doc: SavedObjectUnsanitizedDoc
+): SavedObjectUnsanitizedDoc {
+ if (doc.attributes.taskType.startsWith('actions:')) {
+ let params: { spaceId?: string; actionTaskParamsId?: string } = {};
+ params = JSON.parse((doc.attributes.params as unknown) as string);
+
+ if (params.actionTaskParamsId && params.spaceId && params.spaceId !== 'default') {
+ const newId = SavedObjectsUtils.getConvertedObjectId(
+ params.spaceId,
+ 'action_task_params',
+ params.actionTaskParamsId
+ );
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ params: JSON.stringify({
+ ...params,
+ actionTaskParamsId: newId,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ }) as any,
+ },
+ };
+ }
+ }
+
+ return doc;
+}
function moveIntervalIntoSchedule({
attributes: { interval, ...attributes },
@@ -34,3 +141,8 @@ function moveIntervalIntoSchedule({
},
};
}
+
+function pipeMigrations(...migrations: TaskInstanceMigration[]): TaskInstanceMigration {
+ return (doc: SavedObjectUnsanitizedDoc) =>
+ migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc);
+}
diff --git a/x-pack/plugins/task_manager/server/task_scheduling.ts b/x-pack/plugins/task_manager/server/task_scheduling.ts
index 88176b25680ca..a89f66d9c772b 100644
--- a/x-pack/plugins/task_manager/server/task_scheduling.ts
+++ b/x-pack/plugins/task_manager/server/task_scheduling.ts
@@ -113,11 +113,18 @@ export class TaskScheduling {
*/
public async runNow(taskId: string): Promise {
return new Promise(async (resolve, reject) => {
- this.awaitTaskRunResult(taskId)
- // don't expose state on runNow
- .then(({ id }) => resolve({ id }))
- .catch(reject);
- this.taskPollingLifecycle.attemptToRun(taskId);
+ try {
+ this.awaitTaskRunResult(taskId) // don't expose state on runNow
+ .then(({ id }) =>
+ resolve({
+ id,
+ })
+ )
+ .catch(reject);
+ this.taskPollingLifecycle.attemptToRun(taskId);
+ } catch (error) {
+ reject(error);
+ }
});
}
@@ -137,39 +144,42 @@ export class TaskScheduling {
taskInstance: task,
});
return new Promise(async (resolve, reject) => {
- // The actual promise returned from this function is resolved after the awaitTaskRunResult promise resolves.
- // However, we do not wait to await this promise, as we want later execution to happen in parallel.
- // The awaitTaskRunResult promise is resolved once the ephemeral task is successfully executed (technically, when a TaskEventType.TASK_RUN is emitted with the same id).
- // However, the ephemeral task won't even get into the queue until the subsequent this.ephemeralTaskLifecycle.attemptToRun is called (which puts it in the queue).
-
- // The reason for all this confusion? Timing.
-
- // In the this.ephemeralTaskLifecycle.attemptToRun, it's possible that the ephemeral task is put into the queue and processed before this function call returns anything.
- // If that happens, putting the awaitTaskRunResult after would just hang because the task already completed. We need to listen for the completion before we add it to the queue to avoid this possibility.
- const { cancel, resolveOnCancel } = cancellablePromise();
- this.awaitTaskRunResult(id, resolveOnCancel)
- .then((arg: RunNowResult) => {
- resolve(arg);
- })
- .catch((err: Error) => {
- reject(err);
+ try {
+ // The actual promise returned from this function is resolved after the awaitTaskRunResult promise resolves.
+ // However, we do not wait to await this promise, as we want later execution to happen in parallel.
+ // The awaitTaskRunResult promise is resolved once the ephemeral task is successfully executed (technically, when a TaskEventType.TASK_RUN is emitted with the same id).
+ // However, the ephemeral task won't even get into the queue until the subsequent this.ephemeralTaskLifecycle.attemptToRun is called (which puts it in the queue).
+ // The reason for all this confusion? Timing.
+ // In the this.ephemeralTaskLifecycle.attemptToRun, it's possible that the ephemeral task is put into the queue and processed before this function call returns anything.
+ // If that happens, putting the awaitTaskRunResult after would just hang because the task already completed. We need to listen for the completion before we add it to the queue to avoid this possibility.
+ const { cancel, resolveOnCancel } = cancellablePromise();
+ this.awaitTaskRunResult(id, resolveOnCancel)
+ .then((arg: RunNowResult) => {
+ resolve(arg);
+ })
+ .catch((err: Error) => {
+ reject(err);
+ });
+ const attemptToRunResult = this.ephemeralTaskLifecycle.attemptToRun({
+ id,
+ scheduledAt: new Date(),
+ runAt: new Date(),
+ status: TaskStatus.Idle,
+ ownerId: this.taskManagerId,
+ ...modifiedTask,
});
- const attemptToRunResult = this.ephemeralTaskLifecycle.attemptToRun({
- id,
- scheduledAt: new Date(),
- runAt: new Date(),
- status: TaskStatus.Idle,
- ownerId: this.taskManagerId,
- ...modifiedTask,
- });
- if (isErr(attemptToRunResult)) {
- cancel();
- reject(
- new EphemeralTaskRejectedDueToCapacityError(
- `Ephemeral Task of type ${task.taskType} was rejected`,
- task
- )
- );
+
+ if (isErr(attemptToRunResult)) {
+ cancel();
+ reject(
+ new EphemeralTaskRejectedDueToCapacityError(
+ `Ephemeral Task of type ${task.taskType} was rejected`,
+ task
+ )
+ );
+ }
+ } catch (error) {
+ reject(error);
}
});
}
diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
index 41af9b0754841..38e74e15f7ae7 100644
--- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
+++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
@@ -5455,6 +5455,12 @@
"description": "Indicates if audit logging is both enabled and supported by the current license."
}
},
+ "auditLoggingType": {
+ "type": "keyword",
+ "_meta": {
+ "description": "If auditLoggingEnabled is true, indicates what type is enabled (ECS or legacy)."
+ }
+ },
"loginSelectorEnabled": {
"type": "boolean",
"_meta": {
@@ -5490,6 +5496,24 @@
"description": "The set of enabled http auth schemes. Used for api-based usage, and when credentials are provided via reverse-proxy."
}
}
+ },
+ "sessionIdleTimeoutInMinutes": {
+ "type": "long",
+ "_meta": {
+ "description": "The global session idle timeout expiration that is configured, in minutes (0 if disabled)."
+ }
+ },
+ "sessionLifespanInMinutes": {
+ "type": "long",
+ "_meta": {
+ "description": "The global session lifespan expiration that is configured, in minutes (0 if disabled)."
+ }
+ },
+ "sessionCleanupInMinutes": {
+ "type": "long",
+ "_meta": {
+ "description": "The session cleanup interval that is configured, in minutes (0 if disabled)."
+ }
}
}
},
diff --git a/x-pack/plugins/timelines/common/constants.ts b/x-pack/plugins/timelines/common/constants.ts
index 0c03682cc8332..262ab841492e3 100644
--- a/x-pack/plugins/timelines/common/constants.ts
+++ b/x-pack/plugins/timelines/common/constants.ts
@@ -21,3 +21,4 @@ export const FILTER_IN_PROGRESS: AlertStatus = 'in-progress';
export const FILTER_ACKNOWLEDGED: AlertStatus = 'acknowledged';
export const RAC_ALERTS_BULK_UPDATE_URL = '/internal/rac/alerts/bulk_update';
+export const DETECTION_ENGINE_SIGNALS_STATUS_URL = '/api/detection_engine/signals/status';
diff --git a/x-pack/plugins/timelines/common/types/timeline/actions/index.ts b/x-pack/plugins/timelines/common/types/timeline/actions/index.ts
index 281a1fcc91799..e85f2eaa12d72 100644
--- a/x-pack/plugins/timelines/common/types/timeline/actions/index.ts
+++ b/x-pack/plugins/timelines/common/types/timeline/actions/index.ts
@@ -61,6 +61,7 @@ export interface StatusBulkActionsProps {
setEventsDeleted: SetEventsDeleted;
onUpdateSuccess?: OnUpdateAlertStatusSuccess;
onUpdateFailure?: OnUpdateAlertStatusError;
+ timelineId?: string;
}
export interface HeaderActionProps {
width: number;
diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx
index b6d581f52cbe5..73be0c13faf51 100644
--- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx
+++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx
@@ -26,6 +26,7 @@ export interface AddToCaseActionProps {
} | null;
appId: string;
onClose?: Function;
+ disableAlerts?: boolean;
}
const AddToCaseActionComponent: React.FC = ({
@@ -35,6 +36,7 @@ const AddToCaseActionComponent: React.FC = ({
casePermissions,
appId,
onClose,
+ disableAlerts,
}) => {
const eventId = event?.ecs._id ?? '';
const eventIndex = event?.ecs._index ?? '';
@@ -104,6 +106,7 @@ const AddToCaseActionComponent: React.FC = ({
onSuccess={onCaseSuccess}
useInsertTimeline={useInsertTimeline}
appId={appId}
+ disableAlerts={disableAlerts}
/>
)}
{isAllCaseModalOpen && cases.getAllCasesSelectorModal(getAllCasesSelectorModalProps)}
diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx
index 826b9cd8dc4a6..4f189648634d0 100644
--- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx
+++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx
@@ -6,7 +6,7 @@
*/
import React, { memo, useMemo } from 'react';
-import styled from 'styled-components';
+import styled, { createGlobalStyle } from 'styled-components';
import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
import * as i18n from '../translations';
@@ -20,6 +20,7 @@ export interface CreateCaseModalProps {
onSuccess: (theCase: Case) => Promise;
useInsertTimeline?: Function;
appId: string;
+ disableAlerts?: boolean;
}
const StyledFlyout = styled(EuiFlyout)`
@@ -27,6 +28,23 @@ const StyledFlyout = styled(EuiFlyout)`
z-index: ${theme.eui.euiZModal};
`}
`;
+
+const maskOverlayClassName = 'create-case-flyout-mask-overlay';
+
+/**
+ * We need to target the mask overlay which is a parent element
+ * of the flyout.
+ * A global style is needed to target a parent element.
+ */
+
+const GlobalStyle = createGlobalStyle<{ theme: { eui: { euiZModal: number } } }>`
+ .${maskOverlayClassName} {
+ ${({ theme }) => `
+ z-index: ${theme.eui.euiZModal};
+ `}
+ }
+`;
+
// Adding bottom padding because timeline's
// bottom bar gonna hide the submit button.
const StyledEuiFlyoutBody = styled(EuiFlyoutBody)`
@@ -53,6 +71,7 @@ const CreateCaseFlyoutComponent: React.FC = ({
onCloseFlyout,
onSuccess,
appId,
+ disableAlerts,
}) => {
const { cases } = useKibana().services;
const createCaseProps = useMemo(() => {
@@ -62,19 +81,27 @@ const CreateCaseFlyoutComponent: React.FC = ({
onSuccess,
withSteps: false,
owner: [appId],
+ disableAlerts,
};
- }, [afterCaseCreated, onCloseFlyout, onSuccess, appId]);
+ }, [afterCaseCreated, onCloseFlyout, onSuccess, appId, disableAlerts]);
return (
-
-
-
- {i18n.CREATE_TITLE}
-
-
-
- {cases.getCreateCase(createCaseProps)}
-
-
+ <>
+
+
+
+
+ {i18n.CREATE_TITLE}
+
+
+
+ {cases.getCreateCase(createCaseProps)}
+
+
+ >
);
};
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
index 1c7fe5c82df85..0c1f8fbacd221 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
@@ -69,8 +69,8 @@ const CopyButton: React.FC = React.memo(
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.test.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.test.tsx
new file mode 100644
index 0000000000000..e5d53df638829
--- /dev/null
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.test.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import OverflowButton from './overflow';
+
+describe('OverflowButton', () => {
+ const props = {
+ field: 'host.name',
+ ownFocus: false,
+ showTooltip: true,
+ value: 'mac',
+ closePopOver: jest.fn(),
+ items: [
],
+ isOverflowPopoverOpen: false,
+ };
+ test('should render a popover', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find('EuiPopover').exists()).toBeTruthy();
+ });
+
+ test('the popover always contains a class that hides it when an overlay (e.g. the inspect modal) is displayed', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find('EuiPopover').prop('panelClassName')).toEqual('withHoverActions__popover');
+ });
+
+ test('should enable repositionOnScroll', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find('EuiPopover').prop('repositionOnScroll')).toEqual(true);
+ });
+
+ test('should render a tooltip if showTooltip is true', () => {
+ const testProps = {
+ ...props,
+ showTooltip: true,
+ };
+ const wrapper = shallow( );
+ expect(wrapper.find('EuiToolTip').exists()).toBeTruthy();
+ });
+});
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.tsx
index a10c96f3aa0ae..aa582504f4f71 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.tsx
@@ -97,6 +97,7 @@ const OverflowButton: React.FC = React.memo(
closePopover={closePopOver}
panelPaddingSize="none"
panelClassName="withHoverActions__popover"
+ repositionOnScroll={true}
anchorPosition="downLeft"
>
diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts
index 542be06578d6b..47cd1ed92d661 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts
+++ b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts
@@ -40,15 +40,17 @@ export const useDataGridHeightHack = (pageSize: number, rowCount: number) => {
gridVirtualized &&
gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll
) {
- setHeight(
- height +
+ setHeight((currHeight) => {
+ return (
+ currHeight +
gridVirtualized.children[0].clientHeight -
gridVirtualized.clientHeight +
MAGIC_GAP
- );
+ );
+ });
}
}, TIME_INTERVAL);
- }, [pageSize, rowCount, height]);
+ }, [pageSize, rowCount]);
return height;
};
diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx
index 779fddcad2562..e98d9fff04a0c 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx
@@ -41,7 +41,7 @@ import { useDeepEqualSelector } from '../../../hooks/use_selector';
import { defaultHeaders } from '../body/column_headers/default_headers';
import { buildCombinedQuery, getCombinedFilterQuery, resolverIsShowing } from '../helpers';
import { tGridActions, tGridSelectors } from '../../../store/t_grid';
-import { useTimelineEvents } from '../../../container';
+import { useTimelineEvents, InspectResponse, Refetch } from '../../../container';
import { StatefulBody } from '../body';
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexGroup, UpdatedFlexItem } from '../styles';
import { Sort } from '../body/sort';
@@ -114,6 +114,7 @@ export interface TGridIntegratedProps {
query: Query;
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
rowRenderers: RowRenderer[];
+ setQuery: (inspect: InspectResponse, loading: boolean, refetch: Refetch) => void;
sort: Sort[];
start: string;
tGridEventRenderedViewEnabled: boolean;
@@ -150,6 +151,7 @@ const TGridIntegratedComponent: React.FC = ({
query,
renderCellValue,
rowRenderers,
+ setQuery,
sort,
start,
tGridEventRenderedViewEnabled,
@@ -269,6 +271,10 @@ const TGridIntegratedComponent: React.FC = ({
}
}, [loading]);
+ useEffect(() => {
+ setQuery(inspect, loading, refetch);
+ }, [inspect, loading, refetch, setQuery]);
+
return (
= ({
{!resolverIsShowing(graphEventId) && additionalFilters}
- {tGridEventRenderedViewEnabled && entityType === 'alerts' && (
-
-
-
- )}
+ {tGridEventRenderedViewEnabled &&
+ ['detections-page', 'detections-rules-details-page'].includes(id) && (
+
+
+
+ )}
{!graphEventId && graphOverlay == null && (
diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx
index d1d8662c567cc..ee9b7be48df63 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx
@@ -411,7 +411,7 @@ const TGridStandaloneComponent: React.FC = ({
>
) : null}
-
+
);
diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/alert_status_bulk_actions.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/alert_status_bulk_actions.tsx
index e4ccf1b72529f..be4a75e443494 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/alert_status_bulk_actions.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/alert_status_bulk_actions.tsx
@@ -120,6 +120,7 @@ export const AlertStatusBulkActionsComponent = React.memo void;
+export type Refetch = () => void;
export interface TimelineArgs {
consumers: Record;
diff --git a/x-pack/plugins/timelines/public/container/use_update_alerts.ts b/x-pack/plugins/timelines/public/container/use_update_alerts.ts
index 7cce40b59632d..7f42ddc6e8211 100644
--- a/x-pack/plugins/timelines/public/container/use_update_alerts.ts
+++ b/x-pack/plugins/timelines/public/container/use_update_alerts.ts
@@ -10,7 +10,10 @@ import { CoreStart } from '../../../../../src/core/public';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
import { AlertStatus } from '../../../timelines/common';
-import { RAC_ALERTS_BULK_UPDATE_URL } from '../../common/constants';
+import {
+ DETECTION_ENGINE_SIGNALS_STATUS_URL,
+ RAC_ALERTS_BULK_UPDATE_URL,
+} from '../../common/constants';
/**
* Update alert status by query
@@ -18,25 +21,35 @@ import { RAC_ALERTS_BULK_UPDATE_URL } from '../../common/constants';
* @param status to update to('open' / 'closed' / 'acknowledged')
* @param index index to be updated
* @param query optional query object to update alerts by query.
- * @param ids optional array of alert ids to update. Ignored if query passed.
+
*
* @throws An error if response is not OK
*/
-export const useUpdateAlertsStatus = (): {
+export const useUpdateAlertsStatus = (
+ timelineId: string
+): {
updateAlertStatus: (params: {
status: AlertStatus;
index: string;
- ids?: string[];
- query?: object;
+ query: object;
}) => Promise;
} => {
const { http } = useKibana().services;
return {
- updateAlertStatus: async ({ status, index, ids, query }) => {
- const { body } = await http.post(RAC_ALERTS_BULK_UPDATE_URL, {
- body: JSON.stringify({ index, status, ...(query ? { query } : { ids }) }),
- });
- return body;
+ updateAlertStatus: async ({ status, index, query }) => {
+ if (['detections-page', 'detections-rules-details-page'].includes(timelineId)) {
+ return http!.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, {
+ method: 'POST',
+ body: JSON.stringify({ status, query }),
+ });
+ } else {
+ const { body } = await http.post(RAC_ALERTS_BULK_UPDATE_URL, {
+ body: JSON.stringify({ index, status, query }),
+ });
+ return body;
+ }
},
};
};
+
+//
diff --git a/x-pack/plugins/timelines/public/hooks/use_status_bulk_action_items.tsx b/x-pack/plugins/timelines/public/hooks/use_status_bulk_action_items.tsx
index 8fd637767a387..c9269436646ea 100644
--- a/x-pack/plugins/timelines/public/hooks/use_status_bulk_action_items.tsx
+++ b/x-pack/plugins/timelines/public/hooks/use_status_bulk_action_items.tsx
@@ -26,8 +26,9 @@ export const useStatusBulkActionItems = ({
setEventsDeleted,
onUpdateSuccess,
onUpdateFailure,
+ timelineId,
}: StatusBulkActionsProps) => {
- const { updateAlertStatus } = useUpdateAlertsStatus();
+ const { updateAlertStatus } = useUpdateAlertsStatus(timelineId ?? '');
const { addSuccess, addError, addWarning } = useAppToasts();
const onAlertStatusUpdateSuccess = useCallback(
diff --git a/x-pack/plugins/timelines/public/mock/t_grid.tsx b/x-pack/plugins/timelines/public/mock/t_grid.tsx
index 6e0b9747186d1..3ae1a1d53c207 100644
--- a/x-pack/plugins/timelines/public/mock/t_grid.tsx
+++ b/x-pack/plugins/timelines/public/mock/t_grid.tsx
@@ -114,6 +114,7 @@ export const tGridIntegratedProps: TGridIntegratedProps = {
},
renderCellValue: () => null,
rowRenderers: [],
+ setQuery: () => null,
sort: [
{
columnId: '@timestamp',
diff --git a/x-pack/plugins/timelines/public/plugin.ts b/x-pack/plugins/timelines/public/plugin.ts
index 74e1f2b32844a..4b383ce392147 100644
--- a/x-pack/plugins/timelines/public/plugin.ts
+++ b/x-pack/plugins/timelines/public/plugin.ts
@@ -48,6 +48,13 @@ export class TimelinesPlugin implements Plugin {
return getHoverActions(this._store!);
},
getTGrid: (props: TGridProps) => {
+ if (props.type === 'standalone' && this._store) {
+ const { getState } = this._store;
+ const state = getState();
+ if (state && state.app) {
+ this._store = undefined;
+ }
+ }
return getTGridLazy(props, {
store: this._store,
storage: this._storage,
diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts
index e20d76bdaf625..907907e978123 100644
--- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts
+++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts
@@ -65,7 +65,7 @@ export const requestIndexFieldSearch = async (
});
return get(searchResponse, 'body.hits.total.value', 0) > 0;
} else {
- if (index.startsWith('.alerts-security.alerts')) {
+ if (index.startsWith('.alerts-observability')) {
return indexPatternsFetcherAsInternalUser.getFieldsForWildcard({
pattern: index,
});
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx
index 6249e77ce31dc..55576c3f3ee7d 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx
@@ -42,8 +42,8 @@ export const useCloneAction = (forceDisable: boolean, transformNodes: number) =>
toastNotifications.addDanger(
i18n.translate('xpack.transform.clone.noIndexPatternErrorPromptText', {
defaultMessage:
- 'Unable to clone the transform . No index pattern exists for {indexPattern}.',
- values: { indexPattern: indexPatternTitle },
+ 'Unable to clone the transform {transformId}. No index pattern exists for {indexPattern}.',
+ values: { indexPattern: indexPatternTitle, transformId: item.id },
})
);
} else {
@@ -52,11 +52,11 @@ export const useCloneAction = (forceDisable: boolean, transformNodes: number) =>
);
}
} catch (e) {
- toastNotifications.addDanger(
- i18n.translate('xpack.transform.clone.errorPromptText', {
+ toastNotifications.addError(e, {
+ title: i18n.translate('xpack.transform.clone.errorPromptText', {
defaultMessage: 'An error occurred checking if source index pattern exists',
- })
- );
+ }),
+ });
}
},
[
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx
index b84b309c478fd..03e45b8271952 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx
@@ -5,25 +5,63 @@
* 2.0.
*/
-import React, { useContext, useMemo, useState } from 'react';
+import React, { useCallback, useContext, useMemo, useState } from 'react';
-import { TransformConfigUnion } from '../../../../../../common/types/transform';
+import { i18n } from '@kbn/i18n';
import { TransformListAction, TransformListRow } from '../../../../common';
import { AuthorizationContext } from '../../../../lib/authorization';
import { editActionNameText, EditActionName } from './edit_action_name';
+import { useSearchItems } from '../../../../hooks/use_search_items';
+import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies';
+import { TransformConfigUnion } from '../../../../../../common/types/transform';
export const useEditAction = (forceDisable: boolean, transformNodes: number) => {
const { canCreateTransform } = useContext(AuthorizationContext).capabilities;
const [config, setConfig] = useState();
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const [indexPatternId, setIndexPatternId] = useState();
+
const closeFlyout = () => setIsFlyoutVisible(false);
- const showFlyout = (newConfig: TransformConfigUnion) => {
- setConfig(newConfig);
- setIsFlyoutVisible(true);
- };
+
+ const { getIndexPatternIdByTitle } = useSearchItems(undefined);
+ const toastNotifications = useToastNotifications();
+ const appDeps = useAppDependencies();
+ const indexPatterns = appDeps.data.indexPatterns;
+
+ const clickHandler = useCallback(
+ async (item: TransformListRow) => {
+ try {
+ const indexPatternTitle = Array.isArray(item.config.source.index)
+ ? item.config.source.index.join(',')
+ : item.config.source.index;
+ const currentIndexPatternId = getIndexPatternIdByTitle(indexPatternTitle);
+
+ if (currentIndexPatternId === undefined) {
+ toastNotifications.addWarning(
+ i18n.translate('xpack.transform.edit.noIndexPatternErrorPromptText', {
+ defaultMessage:
+ 'Unable to get index pattern the transform {transformId}. No index pattern exists for {indexPattern}.',
+ values: { indexPattern: indexPatternTitle, transformId: item.id },
+ })
+ );
+ }
+ setIndexPatternId(currentIndexPatternId);
+ setConfig(item.config);
+ setIsFlyoutVisible(true);
+ } catch (e) {
+ toastNotifications.addError(e, {
+ title: i18n.translate('xpack.transform.edit.errorPromptText', {
+ defaultMessage: 'An error occurred checking if source index pattern exists',
+ }),
+ });
+ }
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [indexPatterns, toastNotifications, getIndexPatternIdByTitle]
+ );
const action: TransformListAction = useMemo(
() => ({
@@ -32,10 +70,10 @@ export const useEditAction = (forceDisable: boolean, transformNodes: number) =>
description: editActionNameText,
icon: 'pencil',
type: 'icon',
- onClick: (item: TransformListRow) => showFlyout(item.config),
+ onClick: (item: TransformListRow) => clickHandler(item),
'data-test-subj': 'transformActionEdit',
}),
- [canCreateTransform, forceDisable, transformNodes]
+ [canCreateTransform, clickHandler, forceDisable, transformNodes]
);
return {
@@ -43,6 +81,6 @@ export const useEditAction = (forceDisable: boolean, transformNodes: number) =>
config,
closeFlyout,
isFlyoutVisible,
- showFlyout,
+ indexPatternId,
};
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
index faa304678c0fa..55225e0ff45c0 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
@@ -30,7 +30,6 @@ import { getErrorMessage } from '../../../../../../common/utils/errors';
import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common';
import { useToastNotifications } from '../../../../app_dependencies';
-
import { useApi } from '../../../../hooks/use_api';
import { EditTransformFlyoutCallout } from './edit_transform_flyout_callout';
@@ -43,9 +42,14 @@ import {
interface EditTransformFlyoutProps {
closeFlyout: () => void;
config: TransformConfigUnion;
+ indexPatternId?: string;
}
-export const EditTransformFlyout: FC = ({ closeFlyout, config }) => {
+export const EditTransformFlyout: FC = ({
+ closeFlyout,
+ config,
+ indexPatternId,
+}) => {
const api = useApi();
const toastNotifications = useToastNotifications();
@@ -96,7 +100,10 @@ export const EditTransformFlyout: FC = ({ closeFlyout,
}>
-
+
{errorMessage !== undefined && (
<>
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx
index d434e2e719f5e..40ccd68724400 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx
@@ -5,23 +5,58 @@
* 2.0.
*/
-import React, { FC } from 'react';
+import React, { FC, useEffect, useMemo, useState } from 'react';
-import { EuiForm, EuiAccordion, EuiSpacer } from '@elastic/eui';
+import { EuiForm, EuiAccordion, EuiSpacer, EuiSelect, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EditTransformFlyoutFormTextInput } from './edit_transform_flyout_form_text_input';
import { UseEditTransformFlyoutReturnType } from './use_edit_transform_flyout';
+import { useAppDependencies } from '../../../../app_dependencies';
+import { KBN_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/common';
interface EditTransformFlyoutFormProps {
editTransformFlyout: UseEditTransformFlyoutReturnType;
+ indexPatternId?: string;
}
export const EditTransformFlyoutForm: FC = ({
editTransformFlyout: [state, dispatch],
+ indexPatternId,
}) => {
const formFields = state.formFields;
+ const [dateFieldNames, setDateFieldNames] = useState([]);
+
+ const appDeps = useAppDependencies();
+ const indexPatternsClient = appDeps.data.indexPatterns;
+
+ useEffect(
+ function getDateFields() {
+ let unmounted = false;
+ if (indexPatternId !== undefined) {
+ indexPatternsClient.get(indexPatternId).then((indexPattern) => {
+ if (indexPattern) {
+ const dateTimeFields = indexPattern.fields
+ .filter((f) => f.type === KBN_FIELD_TYPES.DATE)
+ .map((f) => f.name)
+ .sort();
+ if (!unmounted) {
+ setDateFieldNames(dateTimeFields);
+ }
+ }
+ });
+ return () => {
+ unmounted = true;
+ };
+ }
+ },
+ [indexPatternId, indexPatternsClient]
+ );
+
+ const retentionDateFieldOptions = useMemo(() => {
+ return Array.isArray(dateFieldNames) ? dateFieldNames.map((text: string) => ({ text })) : [];
+ }, [dateFieldNames]);
return (
@@ -112,19 +147,57 @@ export const EditTransformFlyoutForm: FC = ({
paddingSize="s"
>
- {' '}
- dispatch({ field: 'retentionPolicyField', value })}
- value={formFields.retentionPolicyField.value}
- />
+ {
+ // If index pattern or date fields info not available
+ // gracefully defaults to text input
+ indexPatternId ? (
+ 0}
+ error={formFields.retentionPolicyField.errorMessages}
+ helpText={i18n.translate(
+ 'xpack.transform.transformList.editFlyoutFormRetentionPolicyDateFieldHelpText',
+ {
+ defaultMessage:
+ 'Select the date field that can be used to identify out of date documents in the destination index.',
+ }
+ )}
+ >
+
+ dispatch({ field: 'retentionPolicyField', value: e.target.value })
+ }
+ />
+
+ ) : (
+ dispatch({ field: 'retentionPolicyField', value })}
+ value={formFields.retentionPolicyField.value}
+ />
+ )
+ }
{startAction.isModalVisible && }
{editAction.config && editAction.isFlyoutVisible && (
-
+
)}
{deleteAction.isModalVisible && }
>
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c6757b65a01a1..8647ad65c6fd6 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -291,7 +291,6 @@
"core.euiColumnSelector.searchcolumns": "列を検索",
"core.euiColumnSelector.selectAll": "すべて表示",
"core.euiColumnSorting.button": "フィールドの並べ替え",
- "core.euiColumnSorting.buttonActive": "並べ替えられたフィールド",
"core.euiColumnSorting.clearAll": "並び替えを消去",
"core.euiColumnSorting.emptySorting": "現在並び替えられているフィールドはありません",
"core.euiColumnSorting.pickFields": "並び替え基準でフィールドの選択",
@@ -325,7 +324,6 @@
"core.euiFieldPassword.maskPassword": "パスワードをマスク",
"core.euiFieldPassword.showPassword": "プレーンテキストとしてパスワードを表示します。注記:パスワードは画面上に見えるように表示されます。",
"core.euiFilePicker.clearSelectedFiles": "選択したファイルを消去",
- "core.euiFilePicker.filesSelected": "選択されたファイル",
"core.euiFlyout.closeAriaLabel": "このダイアログを閉じる",
"core.euiForm.addressFormErrors": "ハイライトされたエラーを修正してください。",
"core.euiFormControlLayoutClearButton.label": "インプットを消去",
@@ -5642,7 +5640,6 @@
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningTitle": "JVM を特定できませんでした",
"xpack.apm.serviceNodeNameMissing": " (空) ",
"xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrencesCount} occ.",
- "xpack.apm.serviceOverview.dependenciesTableColumnBackend": "バックエンド",
"xpack.apm.serviceOverview.dependenciesTableTitle": "依存関係",
"xpack.apm.serviceOverview.errorsTableColumnLastSeen": "前回の認識",
"xpack.apm.serviceOverview.errorsTableColumnName": "名前",
@@ -20242,7 +20239,6 @@
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.cannotEditLists": "これらの権限がない場合は、値リストを作成したり編集したりできません。",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.cannotEditRules": "その権限がない場合、検出エンジンルールを作製したり編集したりできません。",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageBody.essenceDescription": "この機能のすべてにアクセスするには、次の権限が必要です。サポートについては、管理者にお問い合わせください。",
- "xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageBody.missingFeaturePrivileges": "{index}機能の{privileges}権限が不足しています。{explanation}",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageBody.missingIndexPrivileges": "{index}インデックスの{privileges}権限が不足しています。{explanation}",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageTitle": "権限が不十分です",
"xpack.securitySolution.detectionEngine.mitreAttack.addSubtechniqueTitle": "サブ手法を追加",
@@ -23551,7 +23547,6 @@
"xpack.transform.clone.errorPromptText": "ソースインデックスパターンが存在するかどうかを確認するときにエラーが発生しました",
"xpack.transform.clone.errorPromptTitle": "変換構成の取得中にエラーが発生しました。",
"xpack.transform.clone.fetchErrorPromptText": "KibanaインデックスパターンIDを取得できませんでした。",
- "xpack.transform.clone.noIndexPatternErrorPromptText": "変換を複製できません。{indexPattern}のインデックスパターンが存在しません。",
"xpack.transform.cloneTransform.breadcrumbTitle": "クローン変換",
"xpack.transform.createTransform.breadcrumbTitle": "変換の作成",
"xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "ディスティネーションインデックス{destinationIndex}の削除中にエラーが発生しました",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 8ca5de93df225..77cbf878aac4c 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -294,7 +294,6 @@
"core.euiColumnSelector.searchcolumns": "搜索列",
"core.euiColumnSelector.selectAll": "全部显示",
"core.euiColumnSorting.button": "排序字段",
- "core.euiColumnSorting.buttonActive": "个字段已排序",
"core.euiColumnSorting.clearAll": "清除排序",
"core.euiColumnSorting.emptySorting": "当前未排序任何字段",
"core.euiColumnSorting.pickFields": "选取排序依据的字段",
@@ -328,7 +327,6 @@
"core.euiFieldPassword.maskPassword": "屏蔽密码",
"core.euiFieldPassword.showPassword": "将密码显示为纯文本。注意:这会将您的密码暴露在屏幕上。",
"core.euiFilePicker.clearSelectedFiles": "清除选定的文件",
- "core.euiFilePicker.filesSelected": "个文件已选择",
"core.euiFlyout.closeAriaLabel": "关闭此对话框",
"core.euiForm.addressFormErrors": "请解决突出显示的错误。",
"core.euiFormControlLayoutClearButton.label": "清除输入",
@@ -5670,7 +5668,6 @@
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningTitle": "找不到 JVM",
"xpack.apm.serviceNodeNameMissing": "(空)",
"xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrencesCount} 次",
- "xpack.apm.serviceOverview.dependenciesTableColumnBackend": "后端",
"xpack.apm.serviceOverview.dependenciesTableTitle": "依赖项",
"xpack.apm.serviceOverview.errorsTableColumnLastSeen": "最后看到时间",
"xpack.apm.serviceOverview.errorsTableColumnName": "名称",
@@ -20708,7 +20705,6 @@
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.cannotEditLists": "没有这些权限,将无法创建或编辑值列表。",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.cannotEditRules": "没有该权限,将无法创建或编辑检测引擎规则。",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageBody.essenceDescription": "您需要以下权限,才能完全使用此功能。有关进一步帮助,请联系您的管理员。",
- "xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageBody.missingFeaturePrivileges": "缺失 {privileges} 权限,无法使用 {index} 功能。{explanation}",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageBody.missingIndexPrivileges": "缺失 {privileges} 权限,无法使用 {index} 索引。{explanation}",
"xpack.securitySolution.detectionEngine.missingPrivilegesCallOut.messageTitle": "权限不足",
"xpack.securitySolution.detectionEngine.mitreAttack.addSubtechniqueTitle": "添加子技术",
@@ -24102,7 +24098,6 @@
"xpack.transform.clone.errorPromptText": "检查源索引模式是否存在时发生错误",
"xpack.transform.clone.errorPromptTitle": "获取转换配置时发生错误。",
"xpack.transform.clone.fetchErrorPromptText": "无法提取 Kibana 索引模式 ID。",
- "xpack.transform.clone.noIndexPatternErrorPromptText": "无法克隆转换。对于 {indexPattern},不存在索引模式。",
"xpack.transform.cloneTransform.breadcrumbTitle": "克隆转换",
"xpack.transform.createTransform.breadcrumbTitle": "创建转换",
"xpack.transform.deleteTransform.deleteAnalyticsWithIndexErrorMessage": "删除目标索引 {destinationIndex} 时发生错误",
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana.test.ts
index 5de290e325fe4..ffac7a14804a5 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana.test.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana.test.ts
@@ -22,6 +22,7 @@ describe('Kibana deprecations', () => {
describe('With deprecations', () => {
const kibanaDeprecationsMockResponse: DomainDeprecationDetails[] = [
{
+ title: 'mock-deprecation-title',
correctiveActions: {
manualSteps: ['Step 1', 'Step 2', 'Step 3'],
api: {
@@ -192,6 +193,7 @@ describe('Kibana deprecations', () => {
const kibanaDeprecationsMockResponse: DomainDeprecationDetails[] = [
{
domainId,
+ title: `Failed to fetch deprecations for ${domainId}`,
message: `Failed to get deprecations info for plugin "${domainId}".`,
level: 'fetch_error',
correctiveActions: {
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts
index 0bf9f9932b8a1..f62bb0a4eeeb2 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts
@@ -42,6 +42,7 @@ export const esDeprecationsEmpty: ESUpgradeStatus = {
export const kibanaDeprecations: DomainDeprecationDetails[] = [
{
+ title: 'mock-deprecation-title',
correctiveActions: { manualSteps: ['test-step'] },
domainId: 'xpack.spaces',
level: 'critical',
@@ -49,6 +50,7 @@ export const kibanaDeprecations: DomainDeprecationDetails[] = [
'Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)',
},
{
+ title: 'mock-deprecation-title',
correctiveActions: { manualSteps: ['test-step'] },
domainId: 'xpack.spaces',
level: 'warning',
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.test.ts
index c1bfbf6c4cbfa..63532543a418b 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.test.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.test.ts
@@ -13,6 +13,7 @@ import { getKibanaUpgradeStatus } from './kibana_status';
const mockKibanaDeprecations: DomainDeprecationDetails[] = [
{
+ title: 'mock-deprecation-title',
correctiveActions: {
manualSteps: [
'Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.',
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts b/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts
index fd90c53dac12a..183ffa53cc339 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts
+++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts
@@ -6,7 +6,12 @@
*/
import { BrowserFields, ConfigKeys } from '../types';
-import { Formatter, commonFormatters, arrayToJsonFormatter } from '../common/formatters';
+import {
+ Formatter,
+ commonFormatters,
+ arrayToJsonFormatter,
+ stringToJsonFormatter,
+} from '../common/formatters';
export type BrowserFormatMap = Record;
@@ -15,7 +20,7 @@ export const browserFormatters: BrowserFormatMap = {
[ConfigKeys.SOURCE_ZIP_USERNAME]: null,
[ConfigKeys.SOURCE_ZIP_PASSWORD]: null,
[ConfigKeys.SOURCE_ZIP_FOLDER]: null,
- [ConfigKeys.SOURCE_INLINE]: (fields) => JSON.stringify(fields[ConfigKeys.SOURCE_INLINE]),
+ [ConfigKeys.SOURCE_INLINE]: (fields) => stringToJsonFormatter(fields[ConfigKeys.SOURCE_INLINE]),
[ConfigKeys.PARAMS]: null,
[ConfigKeys.SCREENSHOTS]: null,
[ConfigKeys.SYNTHETICS_ARGS]: (fields) =>
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx
index eca354f30c973..243d709d304ce 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx
@@ -68,6 +68,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
{
id: 'syntheticsBrowserZipURLConfig',
name: zipUrlLabel,
+ 'data-test-subj': `syntheticsSourceTab__zipUrl`,
content: (
<>
@@ -92,6 +93,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
setConfig((prevConfig) => ({ ...prevConfig, zipUrl: value }))
}
value={config.zipUrl}
+ data-test-subj="syntheticsBrowserZipUrl"
/>
({ ...prevConfig, folder: value }))
}
value={config.folder}
+ data-test-subj="syntheticsBrowserZipUrlFolder"
/>
setConfig((prevConfig) => ({ ...prevConfig, params: code }))}
value={config.params}
+ data-test-subj="syntheticsBrowserZipUrlParams"
/>
({ ...prevConfig, username: value }))
}
value={config.username}
+ data-test-subj="syntheticsBrowserZipUrlUsername"
/>
({ ...prevConfig, password: value }))
}
value={config.password}
+ data-test-subj="syntheticsBrowserZipUrlPassword"
/>
>
@@ -199,6 +205,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
defaultMessage="Inline script"
/>
),
+ 'data-test-subj': `syntheticsSourceTab__inline`,
content: (
{
describe('cronToSecondsNormalizer', () => {
@@ -33,4 +38,16 @@ describe('formatters', () => {
expect(objectToJsonFormatter({})).toEqual(null);
});
});
+
+ describe('stringToJsonFormatter', () => {
+ it('takes a string and returns an json string', () => {
+ expect(stringToJsonFormatter('step("test step", () => {})')).toEqual(
+ '"step(\\"test step\\", () => {})"'
+ );
+ });
+
+ it('returns null if the string is falsy', () => {
+ expect(stringToJsonFormatter('')).toEqual(null);
+ });
+ });
});
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts
index 311fa7da13498..4b30f4b4f0484 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts
+++ b/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts
@@ -30,3 +30,5 @@ export const secondsToCronFormatter = (value: string = '') => (value ? `${value}
export const objectToJsonFormatter = (value: Record = {}) =>
Object.keys(value).length ? JSON.stringify(value) : null;
+
+export const stringToJsonFormatter = (value: string = '') => (value ? JSON.stringify(value) : null);
diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap
index fefcb2d3c84e1..c30469eab3c3b 100644
--- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap
@@ -975,7 +975,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
>
= {
- space_1_all_alerts_none_actions: '6ee9630a-a20e-44af-9465-217a3717d2ab',
- space_1_all_with_restricted_fixture: '5cc59319-74ee-4edc-8646-a79ea91067cd',
- space_1_all: 'd41a6abb-b93b-46df-a80a-926221ea847c',
- global_read: '362e362b-a137-4aa2-9434-43e3d0d84a34',
- superuser: 'b384be60-ec53-4b26-857e-0253ee55b277',
+ space_1_all_alerts_none_actions: SavedObjectsUtils.getConvertedObjectId(
+ 'space1',
+ 'alert',
+ '6ee9630a-a20e-44af-9465-217a3717d2ab'
+ ),
+ space_1_all_with_restricted_fixture: SavedObjectsUtils.getConvertedObjectId(
+ 'space1',
+ 'alert',
+ '5cc59319-74ee-4edc-8646-a79ea91067cd'
+ ),
+ space_1_all: SavedObjectsUtils.getConvertedObjectId(
+ 'space1',
+ 'alert',
+ 'd41a6abb-b93b-46df-a80a-926221ea847c'
+ ),
+ global_read: SavedObjectsUtils.getConvertedObjectId(
+ 'space1',
+ 'alert',
+ '362e362b-a137-4aa2-9434-43e3d0d84a34'
+ ),
+ superuser: SavedObjectsUtils.getConvertedObjectId(
+ 'space1',
+ 'alert',
+ 'b384be60-ec53-4b26-857e-0253ee55b277'
+ ),
};
describe('alerts', () => {
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts
index e628f0b3d950e..d0ab2e0189a44 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts
@@ -32,7 +32,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
.then((response: SupertestResponse) => response.body);
}
- describe('update', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/110801
+ describe.skip('update', () => {
const objectRemover = new ObjectRemover(supertest);
after(() => objectRemover.removeAll());
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts
index 99a12dc3437de..f45ad28e2cdc5 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts
@@ -193,7 +193,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
const esResponse = await es.get>({
index: '.kibana',
- id: `${Spaces.space1.id}:alert:${response.body.id}`,
+ id: `alert:${response.body.id}`,
});
expect(esResponse.statusCode).to.eql(200);
const rawActions = (esResponse.body._source as any)?.alert.actions ?? [];
diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts
index 40a451ffb5cfe..bbb0fc60cb3ce 100644
--- a/x-pack/test/api_integration/apis/security/privileges.ts
+++ b/x-pack/test/api_integration/apis/security/privileges.ts
@@ -32,16 +32,7 @@ export default function ({ getService }: FtrProviderContext) {
actions: ['all', 'read'],
stackAlerts: ['all', 'read'],
ml: ['all', 'read'],
- siem: [
- 'all',
- 'read',
- 'minimal_all',
- 'minimal_read',
- 'cases_all',
- 'cases_read',
- 'alerts_all',
- 'alerts_read',
- ],
+ siem: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_all', 'cases_read'],
observabilityCases: ['all', 'read'],
uptime: ['all', 'read'],
infrastructure: ['all', 'read'],
diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
index 23bd70de770ad..7cf1fe4c969cc 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
@@ -398,7 +398,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"kibana.alert.evaluation.value": Array [
50,
],
- "kibana.alert.id": Array [
+ "kibana.alert.instance.id": Array [
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
],
"kibana.alert.reason": Array [
@@ -508,7 +508,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"kibana.alert.evaluation.value": Array [
50,
],
- "kibana.alert.id": Array [
+ "kibana.alert.instance.id": Array [
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
],
"kibana.alert.reason": Array [
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts
index 8eacd4231a92e..4748e39cd3a46 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts
@@ -82,15 +82,15 @@ export default ({ getService }: FtrProviderContext) => {
expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 });
});
- it('should be able to create a signal index when it has not been created yet', async () => {
+ it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => {
const { body } = await supertestWithoutAuth
.post(DETECTION_ENGINE_INDEX_URL)
.set('kbn-xsrf', 'true')
.auth(role, 'changeme')
.send()
- .expect(200);
-
- expect(body).to.eql({ acknowledged: true });
+ .expect(403);
+ expect(body.message).to.match(/^security_exception/);
+ expect(body.status_code).to.eql(403);
});
it('should be able to read the index name and status as not being outdated', async () => {
@@ -103,7 +103,7 @@ export default ({ getService }: FtrProviderContext) => {
.send()
.expect(200);
expect(body).to.eql({
- index_mapping_outdated: false,
+ index_mapping_outdated: null,
name: `${DEFAULT_SIGNALS_INDEX}-default`,
});
});
@@ -129,15 +129,15 @@ export default ({ getService }: FtrProviderContext) => {
expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 });
});
- it('should be able to create a signal index when it has not been created yet.', async () => {
+ it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => {
const { body } = await supertestWithoutAuth
.post(DETECTION_ENGINE_INDEX_URL)
.set('kbn-xsrf', 'true')
.auth(role, 'changeme')
.send()
- .expect(200);
-
- expect(body).to.eql({ acknowledged: true });
+ .expect(403);
+ expect(body.message).to.match(/^security_exception/);
+ expect(body.status_code).to.eql(403);
});
it('should be able to read the index name and status as not being outdated', async () => {
@@ -150,7 +150,7 @@ export default ({ getService }: FtrProviderContext) => {
.send()
.expect(200);
expect(body).to.eql({
- index_mapping_outdated: false,
+ index_mapping_outdated: null,
name: `${DEFAULT_SIGNALS_INDEX}-default`,
});
});
@@ -226,15 +226,15 @@ export default ({ getService }: FtrProviderContext) => {
expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 });
});
- it('should be able to create a signal index when it has not been created yet', async () => {
+ it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => {
const { body } = await supertestWithoutAuth
.post(DETECTION_ENGINE_INDEX_URL)
.set('kbn-xsrf', 'true')
.auth(role, 'changeme')
.send()
- .expect(200);
-
- expect(body).to.eql({ acknowledged: true });
+ .expect(403);
+ expect(body.message).to.match(/^security_exception/);
+ expect(body.status_code).to.eql(403);
});
it('should be able to read the index name and status as not being outdated', async () => {
@@ -272,16 +272,16 @@ export default ({ getService }: FtrProviderContext) => {
.expect(404);
expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 });
});
- // here
- it('should be able to create a signal index when it has not been created yet', async () => {
+
+ it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => {
const { body } = await supertestWithoutAuth
.post(DETECTION_ENGINE_INDEX_URL)
.set('kbn-xsrf', 'true')
.auth(role, 'changeme')
.send()
- .expect(200);
-
- expect(body).to.eql({ acknowledged: true });
+ .expect(403);
+ expect(body.message).to.match(/^security_exception/);
+ expect(body.status_code).to.eql(403);
});
it('should be able to read the index name and status as not being outdated', async () => {
@@ -294,7 +294,7 @@ export default ({ getService }: FtrProviderContext) => {
.send()
.expect(200);
expect(body).to.eql({
- index_mapping_outdated: false,
+ index_mapping_outdated: null,
name: `${DEFAULT_SIGNALS_INDEX}-default`,
});
});
@@ -370,14 +370,15 @@ export default ({ getService }: FtrProviderContext) => {
expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 });
});
- it('should be able to create a signal index when it has not been created yet', async () => {
+ it('should NOT be able to create a signal index when it has not been created yet. Should return a 401 unauthorized', async () => {
const { body } = await supertestWithoutAuth
.post(DETECTION_ENGINE_INDEX_URL)
.set('kbn-xsrf', 'true')
.auth(role, 'changeme')
.send()
- .expect(200);
- expect(body).to.eql({ acknowledged: true });
+ .expect(403);
+ expect(body.message).to.match(/^security_exception/);
+ expect(body.status_code).to.eql(403);
});
it('should be able to read the index name and status as being outdated.', async () => {
@@ -416,14 +417,15 @@ export default ({ getService }: FtrProviderContext) => {
expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 });
});
- it('should be able to create a signal index when it has not been created yet', async () => {
+ it('should NOT be able to create a signal index when it has not been created yet. Should return a 401 unauthorized', async () => {
const { body } = await supertestWithoutAuth
.post(DETECTION_ENGINE_INDEX_URL)
.set('kbn-xsrf', 'true')
.auth(role, 'changeme')
.send()
- .expect(200);
- expect(body).to.eql({ acknowledged: true });
+ .expect(403);
+ expect(body.message).to.match(/^security_exception/);
+ expect(body.status_code).to.eql(403);
});
it('should be able to read the index name and status as being outdated.', async () => {
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/agent/stream/stream.yml.hbs b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/agent/stream/stream.yml.hbs
new file mode 100644
index 0000000000000..2870385f21f95
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/agent/stream/stream.yml.hbs
@@ -0,0 +1 @@
+config.version: "2"
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/fields/fields.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/fields/fields.yml
new file mode 100644
index 0000000000000..6e003ed0ad147
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/fields/fields.yml
@@ -0,0 +1,16 @@
+- name: data_stream.type
+ type: constant_keyword
+ description: >
+ Data stream type.
+- name: data_stream.dataset
+ type: constant_keyword
+ description: >
+ Data stream dataset.
+- name: data_stream.namespace
+ type: constant_keyword
+ description: >
+ Data stream namespace.
+- name: '@timestamp'
+ type: date
+ description: >
+ Event timestamp.
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/manifest.yml
new file mode 100644
index 0000000000000..461d4fa941708
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/data_stream/test_stream/manifest.yml
@@ -0,0 +1,4 @@
+title: Test stream
+type: logs
+streams:
+ - input: test_input
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/docs/README.md
new file mode 100644
index 0000000000000..0b9b18421c9dc
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/docs/README.md
@@ -0,0 +1,3 @@
+# Test package
+
+This is a test package for testing automated upgrades for package policies
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/manifest.yml
new file mode 100644
index 0000000000000..346ea4d2bcfad
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/manifest.yml
@@ -0,0 +1,23 @@
+format_version: 1.0.0
+name: package_policy_upgrade
+title: Tests package policy upgrades
+description: This is a test package for upgrading package policies
+version: 0.7.0-add-stream-with-no-vars
+categories: []
+release: beta
+type: integration
+license: basic
+requirement:
+ elasticsearch:
+ versions: '>7.7.0'
+ kibana:
+ versions: '>7.7.0'
+policy_templates:
+ - name: package_policy_upgrade_new
+ title: Package Policy Upgrade New
+ description: Test Package for Upgrading Package Policies
+ inputs:
+ - type: test_input
+ title: Test Input
+ description: Test Input
+ enabled: true
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/agent/stream/stream.yml.hbs b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/agent/stream/stream.yml.hbs
new file mode 100644
index 0000000000000..2870385f21f95
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/agent/stream/stream.yml.hbs
@@ -0,0 +1 @@
+config.version: "2"
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/fields/fields.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/fields/fields.yml
new file mode 100644
index 0000000000000..6e003ed0ad147
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/fields/fields.yml
@@ -0,0 +1,16 @@
+- name: data_stream.type
+ type: constant_keyword
+ description: >
+ Data stream type.
+- name: data_stream.dataset
+ type: constant_keyword
+ description: >
+ Data stream dataset.
+- name: data_stream.namespace
+ type: constant_keyword
+ description: >
+ Data stream namespace.
+- name: '@timestamp'
+ type: date
+ description: >
+ Event timestamp.
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/manifest.yml
new file mode 100644
index 0000000000000..8b8ea1987ccc3
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/data_stream/test_stream/manifest.yml
@@ -0,0 +1,17 @@
+title: Test stream
+type: logs
+streams:
+ - input: test_input
+ vars:
+ - name: test_var_new
+ type: text
+ title: Test Var New
+ default: Test Var New
+ required: true
+ show_user: true
+ - name: test_var_new_2
+ type: text
+ title: Test Var New 2
+ default: Test Var New 2
+ required: true
+ show_user: true
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/docs/README.md
new file mode 100644
index 0000000000000..0b9b18421c9dc
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/docs/README.md
@@ -0,0 +1,3 @@
+# Test package
+
+This is a test package for testing automated upgrades for package policies
diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/manifest.yml
new file mode 100644
index 0000000000000..bd61453fdaac8
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/manifest.yml
@@ -0,0 +1,23 @@
+format_version: 1.0.0
+name: package_policy_upgrade
+title: Tests package policy upgrades
+description: This is a test package for upgrading package policies
+version: 0.8.0-add-vars-to-stream-with-no-vars
+categories: []
+release: beta
+type: integration
+license: basic
+requirement:
+ elasticsearch:
+ versions: '>7.7.0'
+ kibana:
+ versions: '>7.7.0'
+policy_templates:
+ - name: package_policy_upgrade_new
+ title: Package Policy Upgrade New
+ description: Test Package for Upgrading Package Policies
+ inputs:
+ - type: test_input
+ title: Test Input
+ description: Test Input
+ enabled: true
diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts b/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts
index e75bcfaf75142..3a7d6f5d6b19e 100644
--- a/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts
+++ b/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts
@@ -803,6 +803,126 @@ export default function (providerContext: FtrProviderContext) {
});
});
+ describe('when upgrading to a version where an input with no variables has variables added', function () {
+ withTestPackageVersion('0.8.0-add-vars-to-stream-with-no-vars');
+
+ beforeEach(async function () {
+ const { body: agentPolicyResponse } = await supertest
+ .post(`/api/fleet/agent_policies`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ name: 'Test policy',
+ namespace: 'default',
+ })
+ .expect(200);
+
+ agentPolicyId = agentPolicyResponse.item.id;
+
+ const { body: packagePolicyResponse } = await supertest
+ .post(`/api/fleet/package_policies`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ name: 'package_policy_upgrade_1',
+ description: '',
+ namespace: 'default',
+ policy_id: agentPolicyId,
+ enabled: true,
+ output_id: '',
+ inputs: [
+ {
+ policy_template: 'package_policy_upgrade',
+ type: 'test_input',
+ enabled: true,
+ streams: [
+ {
+ id: 'test-package_policy_upgrade-xxxx',
+ enabled: true,
+ data_stream: {
+ type: 'test_stream',
+ dataset: 'package_policy_upgrade.test_stream',
+ },
+ },
+ ],
+ },
+ ],
+ package: {
+ name: 'package_policy_upgrade',
+ title: 'This is a test package for upgrading package policies',
+ version: '0.7.0-add-stream-with-no-vars',
+ },
+ });
+
+ packagePolicyId = packagePolicyResponse.item.id;
+ });
+
+ afterEach(async function () {
+ await supertest
+ .post(`/api/fleet/package_policies/delete`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({ packagePolicyIds: [packagePolicyId] })
+ .expect(200);
+
+ await supertest
+ .post('/api/fleet/agent_policies/delete')
+ .set('kbn-xsrf', 'xxxx')
+ .send({ agentPolicyId })
+ .expect(200);
+ });
+
+ describe('dry run', function () {
+ it('returns a valid diff', async function () {
+ const { body }: { body: UpgradePackagePolicyDryRunResponse } = await supertest
+ .post(`/api/fleet/package_policies/upgrade`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ packagePolicyIds: [packagePolicyId],
+ dryRun: true,
+ })
+ .expect(200);
+
+ expect(body[0].hasErrors).to.be(false);
+
+ const oldInput = body[0].diff?.[0].inputs.find((input) => input.type === 'test_input');
+ const oldStream = oldInput?.streams.find(
+ (stream) => stream.data_stream.dataset === 'package_policy_upgrade.test_stream'
+ );
+
+ expect(oldStream?.vars).to.be(undefined);
+
+ const newInput = body[0].diff?.[1].inputs.find((input) => input.type === 'test_input');
+ const newStream = newInput?.streams.find(
+ (stream) => stream.data_stream.dataset === 'package_policy_upgrade.test_stream'
+ );
+
+ expect(newStream?.vars).to.eql({
+ test_var_new: {
+ value: 'Test Var New',
+ type: 'text',
+ },
+ test_var_new_2: {
+ value: 'Test Var New 2',
+ type: 'text',
+ },
+ });
+ });
+ });
+
+ describe('upgrade', function () {
+ it('successfully upgrades package policy', async function () {
+ const { body }: { body: UpgradePackagePolicyResponse } = await supertest
+ .post(`/api/fleet/package_policies/upgrade`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ packagePolicyIds: [packagePolicyId],
+ dryRun: false,
+ })
+ .expect(200);
+
+ expect(body[0].success).to.be(true);
+ });
+ });
+ });
+
describe('when package policy is not found', function () {
it('should return an 200 with errors when "dryRun:true" is provided', async function () {
const { body }: { body: UpgradePackagePolicyDryRunResponse } = await supertest
diff --git a/x-pack/test/functional/apps/observability/alerts/index.ts b/x-pack/test/functional/apps/observability/alerts/index.ts
new file mode 100644
index 0000000000000..b30cc9a85d5f8
--- /dev/null
+++ b/x-pack/test/functional/apps/observability/alerts/index.ts
@@ -0,0 +1,53 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import querystring from 'querystring';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+// Based on the x-pack/test/functional/es_archives/observability/alerts archive.
+const DATE_WITH_DATA = {
+ rangeFrom: '2021-08-31T13:36:22.109Z',
+ rangeTo: '2021-09-01T13:36:22.109Z',
+};
+
+export default ({ getPageObjects, getService }: FtrProviderContext) => {
+ const esArchiver = getService('esArchiver');
+
+ // FLAKY, no issue yet
+ describe.skip('Observability alerts', function () {
+ this.tags('includeFirefox');
+
+ const pageObjects = getPageObjects(['common']);
+ const testSubjects = getService('testSubjects');
+
+ before(async () => {
+ await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts');
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'observability',
+ '/alerts',
+ `?${querystring.stringify(DATE_WITH_DATA)}`
+ );
+ });
+
+ after(async () => {
+ await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
+ });
+
+ describe('Alerts table', () => {
+ it('Renders the table', async () => {
+ await testSubjects.existOrFail('events-viewer-panel');
+ });
+
+ it('Renders the correct number of cells', async () => {
+ // NOTE: This isn't ideal, but EuiDataGrid doesn't really have the concept of "rows"
+ const cells = await testSubjects.findAll('dataGridRowCell');
+ expect(cells.length).to.be(54);
+ });
+ });
+ });
+};
diff --git a/x-pack/test/functional/apps/observability/index.ts b/x-pack/test/functional/apps/observability/index.ts
index b7f03b5f27bae..fbb401a67b55d 100644
--- a/x-pack/test/functional/apps/observability/index.ts
+++ b/x-pack/test/functional/apps/observability/index.ts
@@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('Observability specs', function () {
this.tags('ciGroup6');
loadTestFile(require.resolve('./feature_controls'));
+ loadTestFile(require.resolve('./alerts'));
});
}
diff --git a/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts b/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts
index d50943fad991a..5f74b2da213b0 100644
--- a/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts
+++ b/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts
@@ -158,10 +158,7 @@ export default function ({ getService }: FtrProviderContext) {
'should have the retention policy inputs enabled'
);
await transform.editFlyout.openTransformEditAccordionRetentionPolicySettings();
- await transform.editFlyout.assertTransformEditFlyoutInputEnabled(
- 'RetentionPolicyField',
- true
- );
+ await transform.editFlyout.assertTransformEditFlyoutRetentionPolicySelectEnabled(true);
await transform.editFlyout.assertTransformEditFlyoutInputEnabled(
'RetentionPolicyMaxAge',
true
diff --git a/x-pack/test/functional/apps/uptime/synthetics_integration.ts b/x-pack/test/functional/apps/uptime/synthetics_integration.ts
index 146584d138f22..1fe13227d2546 100644
--- a/x-pack/test/functional/apps/uptime/synthetics_integration.ts
+++ b/x-pack/test/functional/apps/uptime/synthetics_integration.ts
@@ -72,6 +72,58 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
],
...config,
},
+ ...(monitorType === 'browser'
+ ? [
+ {
+ data_stream: {
+ dataset: 'browser.network',
+ type: 'synthetics',
+ },
+ id: `${getSyntheticsPolicy(agentFullPolicy)?.streams?.[1]?.id}`,
+ processors: [
+ {
+ add_observer_metadata: {
+ geo: {
+ name: 'Fleet managed',
+ },
+ },
+ },
+ {
+ add_fields: {
+ fields: {
+ 'monitor.fleet_managed': true,
+ },
+ target: '',
+ },
+ },
+ ],
+ },
+ {
+ data_stream: {
+ dataset: 'browser.screenshot',
+ type: 'synthetics',
+ },
+ id: `${getSyntheticsPolicy(agentFullPolicy)?.streams?.[2]?.id}`,
+ processors: [
+ {
+ add_observer_metadata: {
+ geo: {
+ name: 'Fleet managed',
+ },
+ },
+ },
+ {
+ add_fields: {
+ fields: {
+ 'monitor.fleet_managed': true,
+ },
+ target: '',
+ },
+ },
+ ],
+ },
+ ]
+ : []),
],
type: `synthetics/${monitorType}`,
use_output: 'default',
@@ -95,6 +147,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
host,
});
+ const generateBrowserConfig = (config: Record): Record => ({
+ ...basicConfig,
+ ...config,
+ });
+
describe('displays custom UI', () => {
before(async () => {
const version = await uptimeService.syntheticsPackage.getSyntheticsPackageVersion();
@@ -439,6 +496,137 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
})
);
});
+
+ it('allows saving browser monitor', async () => {
+ // This test ensures that updates made to the Synthetics Policy are carried all the way through
+ // to the generated Agent Policy that is dispatch down to the Elastic Agent.
+ const config = generateBrowserConfig({
+ zipUrl: 'http://test.zip',
+ params: JSON.stringify({ url: 'http://localhost:8080' }),
+ folder: 'folder',
+ username: 'username',
+ password: 'password',
+ });
+
+ await uptimePage.syntheticsIntegration.createBasicBrowserMonitorDetails(config);
+ await uptimePage.syntheticsIntegration.confirmAndSave();
+
+ await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
+
+ const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
+ const agentPolicyId = agentPolicy.id;
+ const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
+ agentPolicyId
+ );
+
+ expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
+ generatePolicy({
+ agentFullPolicy,
+ version,
+ name: monitorName,
+ monitorType: 'browser',
+ config: {
+ screenshots: 'on',
+ schedule: '@every 3m',
+ timeout: '16s',
+ tags: [config.tags],
+ 'service.name': config.apmServiceName,
+ 'source.zip_url.url': config.zipUrl,
+ 'source.zip_url.folder': config.folder,
+ 'source.zip_url.username': config.username,
+ 'source.zip_url.password': config.password,
+ params: JSON.parse(config.params),
+ },
+ })
+ );
+ });
+
+ it('allows saving browser monitor with inline script', async () => {
+ // This test ensures that updates made to the Synthetics Policy are carried all the way through
+ // to the generated Agent Policy that is dispatch down to the Elastic Agent.
+ const config = generateBrowserConfig({
+ inlineScript:
+ 'step("load homepage", async () => { await page.goto(\'https://www.elastic.co\'); });',
+ });
+
+ await uptimePage.syntheticsIntegration.createBasicBrowserMonitorDetails(config, true);
+ await uptimePage.syntheticsIntegration.confirmAndSave();
+
+ await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
+
+ const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
+ const agentPolicyId = agentPolicy.id;
+ const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
+ agentPolicyId
+ );
+
+ expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
+ generatePolicy({
+ agentFullPolicy,
+ version,
+ name: monitorName,
+ monitorType: 'browser',
+ config: {
+ screenshots: 'on',
+ schedule: '@every 3m',
+ timeout: '16s',
+ tags: [config.tags],
+ 'service.name': config.apmServiceName,
+ 'source.inline.script': config.inlineScript,
+ },
+ })
+ );
+ });
+
+ it('allows saving browser monitor advanced options', async () => {
+ // This test ensures that updates made to the Synthetics Policy are carried all the way through
+ // to the generated Agent Policy that is dispatch down to the Elastic Agent.
+ const config = generateBrowserConfig({
+ zipUrl: 'http://test.zip',
+ params: JSON.stringify({ url: 'http://localhost:8080' }),
+ folder: 'folder',
+ username: 'username',
+ password: 'password',
+ });
+ const advancedConfig = {
+ screenshots: 'off',
+ syntheticsArgs: '-ssBlocks',
+ };
+
+ await uptimePage.syntheticsIntegration.createBasicBrowserMonitorDetails(config);
+ await uptimePage.syntheticsIntegration.configureBrowserAdvancedOptions(advancedConfig);
+ await uptimePage.syntheticsIntegration.confirmAndSave();
+
+ await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
+
+ const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
+ const agentPolicyId = agentPolicy.id;
+ const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
+ agentPolicyId
+ );
+
+ expect(getSyntheticsPolicy(agentFullPolicy)).to.eql(
+ generatePolicy({
+ agentFullPolicy,
+ version,
+ name: monitorName,
+ monitorType: 'browser',
+ config: {
+ screenshots: advancedConfig.screenshots,
+ schedule: '@every 3m',
+ timeout: '16s',
+ tags: [config.tags],
+ 'service.name': config.apmServiceName,
+ 'source.zip_url.url': config.zipUrl,
+ 'source.zip_url.folder': config.folder,
+ 'source.zip_url.username': config.username,
+ 'source.zip_url.password': config.password,
+ params: JSON.parse(config.params),
+ synthetics_args: [advancedConfig.syntheticsArgs],
+ },
+ })
+ );
+ });
});
});
}
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index c8822b62ebd81..226cebb9afbfa 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -94,6 +94,7 @@ export default async function ({ readConfigFile }) {
'--timelion.ui.enabled=true',
'--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects
'--xpack.observability.unsafe.cases.enabled=true',
+ '--xpack.observability.unsafe.alertingExperience.enabled=true', // NOTE: Can be removed once enabled by default
],
},
uiSettings: {
@@ -207,6 +208,9 @@ export default async function ({ readConfigFile }) {
securitySolution: {
pathname: '/app/security',
},
+ observability: {
+ pathname: '/app/observability',
+ },
},
// choose where screenshots should be saved
diff --git a/x-pack/test/functional/es_archives/observability/alerts/data.json.gz b/x-pack/test/functional/es_archives/observability/alerts/data.json.gz
new file mode 100644
index 0000000000000..45da368188284
Binary files /dev/null and b/x-pack/test/functional/es_archives/observability/alerts/data.json.gz differ
diff --git a/x-pack/test/functional/es_archives/observability/alerts/mappings.json b/x-pack/test/functional/es_archives/observability/alerts/mappings.json
new file mode 100644
index 0000000000000..88d12b7d797bb
--- /dev/null
+++ b/x-pack/test/functional/es_archives/observability/alerts/mappings.json
@@ -0,0 +1,731 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".alerts-observability.apm.alerts-default": {
+ "is_write_index": true
+ }
+ },
+ "index": ".internal.alerts-observability.apm.alerts-default-000001",
+ "mappings": {
+ "_meta": {
+ "kibana": {
+ "version": "8.0.0"
+ },
+ "namespace": "default"
+ },
+ "dynamic": "strict",
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "ecs": {
+ "properties": {
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "action": {
+ "type": "keyword"
+ },
+ "kind": {
+ "type": "keyword"
+ }
+ }
+ },
+ "kibana": {
+ "properties": {
+ "alert": {
+ "properties": {
+ "action_group": {
+ "type": "keyword"
+ },
+ "duration": {
+ "properties": {
+ "us": {
+ "type": "long"
+ }
+ }
+ },
+ "end": {
+ "type": "date"
+ },
+ "evaluation": {
+ "properties": {
+ "threshold": {
+ "scaling_factor": 100,
+ "type": "scaled_float"
+ },
+ "value": {
+ "scaling_factor": 100,
+ "type": "scaled_float"
+ }
+ }
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "reason": {
+ "type": "keyword"
+ },
+ "rule": {
+ "properties": {
+ "author": {
+ "type": "keyword"
+ },
+ "category": {
+ "type": "keyword"
+ },
+ "consumer": {
+ "type": "keyword"
+ },
+ "created_at": {
+ "type": "date"
+ },
+ "created_by": {
+ "type": "keyword"
+ },
+ "description": {
+ "type": "keyword"
+ },
+ "enabled": {
+ "type": "keyword"
+ },
+ "from": {
+ "type": "date"
+ },
+ "interval": {
+ "type": "keyword"
+ },
+ "license": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "note": {
+ "type": "keyword"
+ },
+ "producer": {
+ "type": "keyword"
+ },
+ "references": {
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_mapping": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "operator": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ },
+ "rule_id": {
+ "type": "keyword"
+ },
+ "rule_name_override": {
+ "type": "keyword"
+ },
+ "rule_type_id": {
+ "type": "keyword"
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "severity_mapping": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "operator": {
+ "type": "keyword"
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ },
+ "tags": {
+ "type": "keyword"
+ },
+ "to": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "updated_by": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "start": {
+ "type": "date"
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "system_status": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "workflow_reason": {
+ "type": "keyword"
+ },
+ "workflow_status": {
+ "type": "keyword"
+ },
+ "workflow_user": {
+ "type": "keyword"
+ }
+ }
+ },
+ "space_ids": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "version"
+ }
+ }
+ },
+ "processor": {
+ "properties": {
+ "event": {
+ "type": "keyword"
+ }
+ }
+ },
+ "service": {
+ "properties": {
+ "environment": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ }
+ }
+ },
+ "tags": {
+ "type": "keyword"
+ },
+ "transaction": {
+ "properties": {
+ "type": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "lifecycle": {
+ "name": ".alerts-ilm-policy",
+ "rollover_alias": ".alerts-observability.apm.alerts-default"
+ },
+ "number_of_replicas": "1",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
+
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".alerts-observability.logs.alerts-default": {
+ "is_write_index": true
+ }
+ },
+ "index": ".internal.alerts-observability.logs.alerts-default-000001",
+ "mappings": {
+ "_meta": {
+ "kibana": {
+ "version": "8.0.0"
+ },
+ "namespace": "default"
+ },
+ "dynamic": "strict",
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "ecs": {
+ "properties": {
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "action": {
+ "type": "keyword"
+ },
+ "kind": {
+ "type": "keyword"
+ }
+ }
+ },
+ "kibana": {
+ "properties": {
+ "alert": {
+ "properties": {
+ "action_group": {
+ "type": "keyword"
+ },
+ "duration": {
+ "properties": {
+ "us": {
+ "type": "long"
+ }
+ }
+ },
+ "end": {
+ "type": "date"
+ },
+ "evaluation": {
+ "properties": {
+ "threshold": {
+ "scaling_factor": 100,
+ "type": "scaled_float"
+ },
+ "value": {
+ "scaling_factor": 100,
+ "type": "scaled_float"
+ }
+ }
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "reason": {
+ "type": "keyword"
+ },
+ "rule": {
+ "properties": {
+ "author": {
+ "type": "keyword"
+ },
+ "category": {
+ "type": "keyword"
+ },
+ "consumer": {
+ "type": "keyword"
+ },
+ "created_at": {
+ "type": "date"
+ },
+ "created_by": {
+ "type": "keyword"
+ },
+ "description": {
+ "type": "keyword"
+ },
+ "enabled": {
+ "type": "keyword"
+ },
+ "from": {
+ "type": "date"
+ },
+ "interval": {
+ "type": "keyword"
+ },
+ "license": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "note": {
+ "type": "keyword"
+ },
+ "producer": {
+ "type": "keyword"
+ },
+ "references": {
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_mapping": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "operator": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ },
+ "rule_id": {
+ "type": "keyword"
+ },
+ "rule_name_override": {
+ "type": "keyword"
+ },
+ "rule_type_id": {
+ "type": "keyword"
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "severity_mapping": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "operator": {
+ "type": "keyword"
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ },
+ "tags": {
+ "type": "keyword"
+ },
+ "to": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "updated_by": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "start": {
+ "type": "date"
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "system_status": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "workflow_reason": {
+ "type": "keyword"
+ },
+ "workflow_status": {
+ "type": "keyword"
+ },
+ "workflow_user": {
+ "type": "keyword"
+ }
+ }
+ },
+ "space_ids": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "version"
+ }
+ }
+ },
+ "tags": {
+ "type": "keyword"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "lifecycle": {
+ "name": ".alerts-ilm-policy",
+ "rollover_alias": ".alerts-observability.logs.alerts-default"
+ },
+ "number_of_replicas": "1",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
+
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".alerts-observability.metrics.alerts-default": {
+ "is_write_index": true
+ }
+ },
+ "index": ".internal.alerts-observability.metrics.alerts-default-000001",
+ "mappings": {
+ "_meta": {
+ "kibana": {
+ "version": "8.0.0"
+ },
+ "namespace": "default"
+ },
+ "dynamic": "strict",
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "ecs": {
+ "properties": {
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "action": {
+ "type": "keyword"
+ },
+ "kind": {
+ "type": "keyword"
+ }
+ }
+ },
+ "kibana": {
+ "properties": {
+ "alert": {
+ "properties": {
+ "action_group": {
+ "type": "keyword"
+ },
+ "duration": {
+ "properties": {
+ "us": {
+ "type": "long"
+ }
+ }
+ },
+ "end": {
+ "type": "date"
+ },
+ "evaluation": {
+ "properties": {
+ "threshold": {
+ "scaling_factor": 100,
+ "type": "scaled_float"
+ },
+ "value": {
+ "scaling_factor": 100,
+ "type": "scaled_float"
+ }
+ }
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "reason": {
+ "type": "keyword"
+ },
+ "rule": {
+ "properties": {
+ "author": {
+ "type": "keyword"
+ },
+ "category": {
+ "type": "keyword"
+ },
+ "consumer": {
+ "type": "keyword"
+ },
+ "created_at": {
+ "type": "date"
+ },
+ "created_by": {
+ "type": "keyword"
+ },
+ "description": {
+ "type": "keyword"
+ },
+ "enabled": {
+ "type": "keyword"
+ },
+ "from": {
+ "type": "date"
+ },
+ "interval": {
+ "type": "keyword"
+ },
+ "license": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "note": {
+ "type": "keyword"
+ },
+ "producer": {
+ "type": "keyword"
+ },
+ "references": {
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_mapping": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "operator": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ },
+ "rule_id": {
+ "type": "keyword"
+ },
+ "rule_name_override": {
+ "type": "keyword"
+ },
+ "rule_type_id": {
+ "type": "keyword"
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "severity_mapping": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "operator": {
+ "type": "keyword"
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ },
+ "tags": {
+ "type": "keyword"
+ },
+ "to": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "updated_by": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "start": {
+ "type": "date"
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "system_status": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "workflow_reason": {
+ "type": "keyword"
+ },
+ "workflow_status": {
+ "type": "keyword"
+ },
+ "workflow_user": {
+ "type": "keyword"
+ }
+ }
+ },
+ "space_ids": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "version"
+ }
+ }
+ },
+ "tags": {
+ "type": "keyword"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "lifecycle": {
+ "name": ".alerts-ilm-policy",
+ "rollover_alias": ".alerts-observability.metrics.alerts-default"
+ },
+ "number_of_replicas": "1",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/test/functional/es_archives/task_manager_tasks/data.json b/x-pack/test/functional/es_archives/task_manager_tasks/data.json
new file mode 100644
index 0000000000000..b59abd341a7af
--- /dev/null
+++ b/x-pack/test/functional/es_archives/task_manager_tasks/data.json
@@ -0,0 +1,61 @@
+{
+ "type": "doc",
+ "value": {
+ "id": "task:be7e1250-3322-11eb-94c1-db6995e84f6a",
+ "index": ".kibana_task_manager_1",
+ "source": {
+ "migrationVersion": {
+ "task": "7.16.0"
+ },
+ "references": [
+ ],
+ "task": {
+ "attempts": 0,
+ "params": "{\"spaceId\":\"user1\",\"alertId\":\"0359d7fcc04da9878ee9aadbda38ba55\"}",
+ "retryAt": "2020-11-30T15:43:39.626Z",
+ "runAt": "2020-11-30T15:43:08.277Z",
+ "scheduledAt": "2020-11-30T15:43:08.277Z",
+ "scope": [
+ "testing"
+ ],
+ "startedAt": null,
+ "state": "{}",
+ "status": "idle",
+ "taskType": "alerting:0359d7fcc04da9878ee9aadbda38ba55"
+ },
+ "type": "task",
+ "updated_at": "2020-11-30T15:43:08.277Z"
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "task:be7e1250-3322-11eb-94c1-db6995e8389f",
+ "index": ".kibana_task_manager_1",
+ "source": {
+ "migrationVersion": {
+ "task": "7.16.0"
+ },
+ "references": [
+ ],
+ "task": {
+ "attempts": 0,
+ "params": "{\"spaceId\":\"user1\",\"actionTaskParamsId\":\"6e96ac5e648f57523879661ea72525b7\"}",
+ "retryAt": "2020-11-30T15:43:39.626Z",
+ "runAt": "2020-11-30T15:43:08.277Z",
+ "scheduledAt": "2020-11-30T15:43:08.277Z",
+ "scope": [
+ "testing"
+ ],
+ "startedAt": null,
+ "state": "{}",
+ "status": "idle",
+ "taskType": "actions:6e96ac5e648f57523879661ea72525b7"
+ },
+ "type": "task",
+ "updated_at": "2020-11-30T15:43:08.277Z"
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/test/functional/es_archives/task_manager_tasks/mappings.json b/x-pack/test/functional/es_archives/task_manager_tasks/mappings.json
new file mode 100644
index 0000000000000..6ec81326d1ca4
--- /dev/null
+++ b/x-pack/test/functional/es_archives/task_manager_tasks/mappings.json
@@ -0,0 +1,225 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".kibana": {
+ }
+ },
+ "index": ".kibana_1",
+ "mappings": {
+ "_meta": {
+ "migrationMappingPropertyHashes": {
+ "action": "6e96ac5e648f57523879661ea72525b7",
+ "action_task_params": "a9d49f184ee89641044be0ca2950fa3a",
+ "alert": "0359d7fcc04da9878ee9aadbda38ba55",
+ "api_key_pending_invalidation": "16f515278a295f6245149ad7c5ddedb7",
+ "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd",
+ "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724",
+ "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724",
+ "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd",
+ "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724",
+ "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724",
+ "search-session": "721df406dbb7e35ac22e4df6c3ad2b2a",
+ "canvas-element": "7390014e1091044523666d97247392fc",
+ "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231",
+ "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715",
+ "cases": "477f214ff61acc3af26a7b7818e380c1",
+ "cases-comments": "8a50736330e953bca91747723a319593",
+ "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69",
+ "cases-user-actions": "32277330ec6b721abe3b846cfd939a71",
+ "config": "c63748b75f39d0c54de12d12c1ccbc20",
+ "dashboard": "40554caf09725935e2c02e02563a2d07",
+ "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0",
+ "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862",
+ "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724",
+ "epm-packages": "2b83397e3eaaaa8ef15e38813f3721c3",
+ "event_log_test": "bef808d4a9c27f204ffbda3359233931",
+ "exception-list": "67f055ab8c10abd7b2ebfd969b836788",
+ "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788",
+ "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e",
+ "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9",
+ "fleet-agent-events": "e20a508b6e805189356be381dbfac8db",
+ "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683",
+ "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7",
+ "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1",
+ "index-pattern": "45915a1ad866812242df474eb0479052",
+ "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724",
+ "ingest-agent-policies": "8b0733cce189659593659dad8db426f0",
+ "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e",
+ "ingest-package-policies": "c91ca97b1ff700f0fc64dc6b13d65a85",
+ "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f",
+ "inventory-view": "3d1b76c39bfb2cc8296b024d73854724",
+ "kql-telemetry": "d12a98a6f19a2d273696597547e064ee",
+ "lens": "52346cfec69ff7b47d5f0c12361a2797",
+ "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327",
+ "map": "4a05b35c3a3a58fbc72dd0202dc3487f",
+ "maps-telemetry": "5ef305b18111b77789afefbd36b66171",
+ "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724",
+ "migrationVersion": "4a1746014a75ade3a714e1db5763276f",
+ "ml-job": "3bb64c31915acf93fc724af137a0891b",
+ "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9",
+ "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68",
+ "namespace": "2f4316de49999235636386fe51dc06c1",
+ "namespaces": "2f4316de49999235636386fe51dc06c1",
+ "originId": "2f4316de49999235636386fe51dc06c1",
+ "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9",
+ "references": "7997cf5a56cc02bdc9c93361bde732b0",
+ "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4",
+ "search": "43012c7ebc4cb57054e0a490e4b43023",
+ "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724",
+ "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18",
+ "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0",
+ "siem-ui-timeline": "d12c5474364d737d17252acf1dc4585c",
+ "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084",
+ "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29",
+ "space": "c5ca8acafa0beaa4d08d014a97b6bc6b",
+ "tag": "83d55da58f6530f7055415717ec06474",
+ "telemetry": "36a616f7026dfa617d6655df850fe16d",
+ "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf",
+ "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215",
+ "type": "2f4316de49999235636386fe51dc06c1",
+ "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3",
+ "updated_at": "00da57df13e94e9d98437d13ace4bfe0",
+ "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763",
+ "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b",
+ "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724",
+ "url": "c7f66a0df8b1b52f17c28c4adb111105",
+ "visualization": "f819cf6636b75c9e76ba733a0c6ef355",
+ "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724"
+ }
+ },
+ "dynamic": "strict",
+ "properties": {
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "auto_expand_replicas": "0-1",
+ "number_of_replicas": "0",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
+
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".kibana_task_manager": {
+ }
+ },
+ "index": ".kibana_task_manager_1",
+ "mappings": {
+ "_meta": {
+ "migrationMappingPropertyHashes": {
+ "migrationVersion": "4a1746014a75ade3a714e1db5763276f",
+ "namespace": "2f4316de49999235636386fe51dc06c1",
+ "namespaces": "2f4316de49999235636386fe51dc06c1",
+ "originId": "2f4316de49999235636386fe51dc06c1",
+ "references": "7997cf5a56cc02bdc9c93361bde732b0",
+ "task": "235412e52d09e7165fac8a67a43ad6b4",
+ "type": "2f4316de49999235636386fe51dc06c1",
+ "updated_at": "00da57df13e94e9d98437d13ace4bfe0"
+ }
+ },
+ "dynamic": "strict",
+ "properties": {
+ "migrationVersion": {
+ "dynamic": "true",
+ "properties": {
+ "task": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "references": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "task": {
+ "properties": {
+ "attempts": {
+ "type": "integer"
+ },
+ "ownerId": {
+ "type": "keyword"
+ },
+ "params": {
+ "type": "text"
+ },
+ "retryAt": {
+ "type": "date"
+ },
+ "runAt": {
+ "type": "date"
+ },
+ "schedule": {
+ "properties": {
+ "interval": {
+ "type": "keyword"
+ }
+ }
+ },
+ "scheduledAt": {
+ "type": "date"
+ },
+ "scope": {
+ "type": "keyword"
+ },
+ "startedAt": {
+ "type": "date"
+ },
+ "state": {
+ "type": "text"
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "taskType": {
+ "type": "keyword"
+ },
+ "user": {
+ "type": "keyword"
+ }
+ }
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "auto_expand_replicas": "0-1",
+ "number_of_replicas": "0",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
diff --git a/x-pack/test/functional/page_objects/synthetics_integration_page.ts b/x-pack/test/functional/page_objects/synthetics_integration_page.ts
index daebb1d2a2f99..cfb6e1dac980e 100644
--- a/x-pack/test/functional/page_objects/synthetics_integration_page.ts
+++ b/x-pack/test/functional/page_objects/synthetics_integration_page.ts
@@ -205,6 +205,16 @@ export function SyntheticsIntegrationPageProvider({
*/
async configureRequestBody(testSubj: string, value: string) {
await testSubjects.click(`syntheticsRequestBodyTab__${testSubj}`);
+ await this.fillCodeEditor(value);
+ },
+
+ /**
+ *
+ * Fills the monaco code editor
+ * @params value {string} value of code input
+ *
+ */
+ async fillCodeEditor(value: string) {
const codeEditorContainer = await testSubjects.find('codeEditorContainer');
const textArea = await codeEditorContainer.findByCssSelector('textarea');
await textArea.clearValue();
@@ -273,6 +283,40 @@ export function SyntheticsIntegrationPageProvider({
await this.fillTextInputByTestSubj('syntheticsICMPHostField', host);
},
+ /**
+ * Creates a basic browser monitor
+ * @params name {string} the name of the monitor
+ * @params zipUrl {string} the zip url of the synthetics suites
+ */
+ async createBasicBrowserMonitorDetails(
+ {
+ name,
+ inlineScript,
+ zipUrl,
+ folder,
+ params,
+ username,
+ password,
+ apmServiceName,
+ tags,
+ }: Record,
+ isInline: boolean = false
+ ) {
+ await this.selectMonitorType('browser');
+ await this.fillTextInputByTestSubj('packagePolicyNameInput', name);
+ await this.createBasicMonitorDetails({ name, apmServiceName, tags });
+ if (isInline) {
+ await testSubjects.click('syntheticsSourceTab__inline');
+ await this.fillCodeEditor(inlineScript);
+ return;
+ }
+ await this.fillTextInputByTestSubj('syntheticsBrowserZipUrl', zipUrl);
+ await this.fillTextInputByTestSubj('syntheticsBrowserZipUrlFolder', folder);
+ await this.fillTextInputByTestSubj('syntheticsBrowserZipUrlUsername', username);
+ await this.fillTextInputByTestSubj('syntheticsBrowserZipUrlPassword', password);
+ await this.fillCodeEditor(params);
+ },
+
/**
* Enables TLS
*/
@@ -376,5 +420,16 @@ export function SyntheticsIntegrationPageProvider({
await label.click();
}
},
+
+ /**
+ * Configure browser advanced settings
+ * @params name {string} the name of the monitor
+ * @params zipUrl {string} the zip url of the synthetics suites
+ */
+ async configureBrowserAdvancedOptions({ screenshots, syntheticsArgs }: Record) {
+ await testSubjects.click('syntheticsBrowserAdvancedFieldsAccordion');
+ await testSubjects.selectValue('syntheticsBrowserScreenshots', screenshots);
+ await this.setComboBox('syntheticsBrowserSyntheticsArgs', syntheticsArgs);
+ },
};
}
diff --git a/x-pack/test/functional/page_objects/tag_management_page.ts b/x-pack/test/functional/page_objects/tag_management_page.ts
index bce8912f7fef8..8b85bc89b1181 100644
--- a/x-pack/test/functional/page_objects/tag_management_page.ts
+++ b/x-pack/test/functional/page_objects/tag_management_page.ts
@@ -22,6 +22,7 @@ type TagFormValidation = FillTagFormFields;
* Sub page object to manipulate the create/edit tag modal.
*/
class TagModal extends FtrService {
+ private readonly browser = this.ctx.getService('browser');
private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly retry = this.ctx.getService('retry');
private readonly header = this.ctx.getPageObject('header');
@@ -57,8 +58,14 @@ class TagModal extends FtrService {
}
if (fields.color !== undefined) {
await this.testSubjects.setValue('~createModalField-color', fields.color);
- // Wait for the popover to be closable before moving to the next input
- await new Promise((res) => setTimeout(res, 200));
+ // Close the popover before moving to the next input, as it can get in the way of interacting with other elements
+ await this.testSubjects.existOrFail('euiSaturation');
+ await this.retry.try(async () => {
+ if (await this.testSubjects.exists('euiSaturation', { timeout: 10 })) {
+ await this.browser.pressKeys(this.browser.keys.ENTER);
+ }
+ await this.testSubjects.missingOrFail('euiSaturation', { timeout: 250 });
+ });
}
if (fields.description !== undefined) {
await this.testSubjects.click('createModalField-description');
diff --git a/x-pack/test/functional/services/ml/data_frame_analytics.ts b/x-pack/test/functional/services/ml/data_frame_analytics.ts
index 9998a52a3044e..aafe96c2c4967 100644
--- a/x-pack/test/functional/services/ml/data_frame_analytics.ts
+++ b/x-pack/test/functional/services/ml/data_frame_analytics.ts
@@ -16,6 +16,7 @@ export function MachineLearningDataFrameAnalyticsProvider(
{ getService }: FtrProviderContext,
mlApi: MlApi
) {
+ const retry = getService('retry');
const testSubjects = getService('testSubjects');
return {
@@ -50,12 +51,14 @@ export function MachineLearningDataFrameAnalyticsProvider(
},
async startAnalyticsCreation() {
- if (await testSubjects.exists('mlNoDataFrameAnalyticsFound')) {
- await testSubjects.click('mlAnalyticsCreateFirstButton');
- } else {
- await testSubjects.click('mlAnalyticsButtonCreate');
- }
- await testSubjects.existOrFail('analyticsCreateSourceIndexModal');
+ await retry.tryForTime(20 * 1000, async () => {
+ if (await testSubjects.exists('mlNoDataFrameAnalyticsFound', { timeout: 1000 })) {
+ await testSubjects.click('mlAnalyticsCreateFirstButton');
+ } else {
+ await testSubjects.click('mlAnalyticsButtonCreate');
+ }
+ await testSubjects.existOrFail('analyticsCreateSourceIndexModal');
+ });
},
async waitForAnalyticsCompletion(analyticsId: string) {
diff --git a/x-pack/test/functional/services/ml/job_table.ts b/x-pack/test/functional/services/ml/job_table.ts
index c711ff0ac8909..4a38aa4efe4dd 100644
--- a/x-pack/test/functional/services/ml/job_table.ts
+++ b/x-pack/test/functional/services/ml/job_table.ts
@@ -234,13 +234,17 @@ export function MachineLearningJobTableProvider(
}
public async assertJobRowFields(jobId: string, expectedRow: object) {
- await this.refreshJobList();
- const rows = await this.parseJobTable();
- const jobRow = rows.filter((row) => row.id === jobId)[0];
- expect(jobRow).to.eql(
- expectedRow,
- `Expected job row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify(jobRow)}')`
- );
+ await retry.tryForTime(5000, async () => {
+ await this.refreshJobList();
+ const rows = await this.parseJobTable();
+ const jobRow = rows.filter((row) => row.id === jobId)[0];
+ expect(jobRow).to.eql(
+ expectedRow,
+ `Expected job row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify(
+ jobRow
+ )}')`
+ );
+ });
}
public async assertJobRowDetailsCounts(
@@ -585,9 +589,11 @@ export function MachineLearningJobTableProvider(
}
// Save custom URL
- await testSubjects.click('mlJobAddCustomUrl');
- const expectedIndex = existingCustomUrls.length;
- await customUrls.assertCustomUrlLabel(expectedIndex, customUrl.label);
+ await retry.tryForTime(5000, async () => {
+ await testSubjects.click('mlJobAddCustomUrl');
+ const expectedIndex = existingCustomUrls.length;
+ await customUrls.assertCustomUrlLabel(expectedIndex, customUrl.label);
+ });
// Save the job
await this.saveEditJobFlyoutChanges();
diff --git a/x-pack/test/functional/services/transform/edit_flyout.ts b/x-pack/test/functional/services/transform/edit_flyout.ts
index fcb87fc9bec5b..cc230e2c38fca 100644
--- a/x-pack/test/functional/services/transform/edit_flyout.ts
+++ b/x-pack/test/functional/services/transform/edit_flyout.ts
@@ -37,6 +37,21 @@ export function TransformEditFlyoutProvider({ getService }: FtrProviderContext)
);
},
+ async assertTransformEditFlyoutRetentionPolicySelectEnabled(expectedValue: boolean) {
+ await testSubjects.existOrFail(`transformEditFlyoutRetentionPolicyFieldSelect`, {
+ timeout: 1000,
+ });
+ const isEnabled = await testSubjects.isEnabled(
+ `transformEditFlyoutRetentionPolicyFieldSelect`
+ );
+ expect(isEnabled).to.eql(
+ expectedValue,
+ `Expected 'transformEditFlyoutRetentionPolicyFieldSelect' input to be '${
+ expectedValue ? 'enabled' : 'disabled'
+ }' (got '${isEnabled ? 'enabled' : 'disabled'}')`
+ );
+ },
+
async assertTransformEditFlyoutInputEnabled(input: string, expectedValue: boolean) {
await testSubjects.existOrFail(`transformEditFlyout${input}Input`, { timeout: 1000 });
const isEnabled = await testSubjects.isEnabled(`transformEditFlyout${input}Input`);
diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts
index 43d4995276239..607f2ee120ed1 100644
--- a/x-pack/test/functional/services/transform/wizard.ts
+++ b/x-pack/test/functional/services/transform/wizard.ts
@@ -130,7 +130,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
column: number,
expectedColumnValues: string[]
) {
- await retry.tryForTime(2000, async () => {
+ await retry.tryForTime(20 * 1000, async () => {
// get a 2D array of rows and cell values
// only parse columns up to the one we want to assert
const rows = await this.parseEuiDataGrid(tableSubj, column + 1);
@@ -152,7 +152,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
},
async assertEuiDataGridColumnValuesNotEmpty(tableSubj: string, column: number) {
- await retry.tryForTime(2000, async () => {
+ await retry.tryForTime(20 * 1000, async () => {
// get a 2D array of rows and cell values
// only parse columns up to the one we want to assert
const rows = await this.parseEuiDataGrid(tableSubj, column + 1);
@@ -171,7 +171,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
},
async assertIndexPreview(columns: number, expectedNumberOfRows: number) {
- await retry.tryForTime(2000, async () => {
+ await retry.tryForTime(20 * 1000, async () => {
// get a 2D array of rows and cell values
// only parse the first column as this is sufficient to get assert the row count
const rowsData = await this.parseEuiDataGrid('transformIndexPreview', 1);
diff --git a/x-pack/test/functional_enterprise_search/services/app_search_client.ts b/x-pack/test/functional_enterprise_search/services/app_search_client.ts
index 8e829b97e9dda..457523bccf8c3 100644
--- a/x-pack/test/functional_enterprise_search/services/app_search_client.ts
+++ b/x-pack/test/functional_enterprise_search/services/app_search_client.ts
@@ -105,14 +105,20 @@ const search = async (engineName: string): Promise => {
// Since the App Search API does not issue document receipts, the only way to tell whether or not documents
// are fully indexed is to poll the search endpoint.
export const waitForIndexedDocs = (engineName: string) => {
- return new Promise(async function (resolve) {
- let isReady = false;
- while (!isReady) {
- const response = await search(engineName);
- if (response.results && response.results.length > 0) {
- isReady = true;
- resolve();
+ return new Promise(async function (resolve, reject) {
+ try {
+ let isReady = false;
+
+ while (!isReady) {
+ const response = await search(engineName);
+
+ if (response.results && response.results.length > 0) {
+ isReady = true;
+ resolve();
+ }
}
+ } catch (error) {
+ reject(error);
}
});
};
diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts
index 8975feb6fbe05..b3816ad7563b8 100644
--- a/x-pack/test/lists_api_integration/utils.ts
+++ b/x-pack/test/lists_api_integration/utils.ts
@@ -116,21 +116,29 @@ export const waitFor = async (
timeoutWait: number = 10
) => {
await new Promise(async (resolve, reject) => {
- let found = false;
- let numberOfTries = 0;
- while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) {
- const itPasses = await functionToTest();
- if (itPasses) {
- found = true;
+ try {
+ let found = false;
+ let numberOfTries = 0;
+
+ while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) {
+ const itPasses = await functionToTest();
+
+ if (itPasses) {
+ found = true;
+ } else {
+ numberOfTries++;
+ }
+
+ await new Promise((resolveTimeout) => setTimeout(resolveTimeout, timeoutWait));
+ }
+
+ if (found) {
+ resolve();
} else {
- numberOfTries++;
+ reject(new Error(`timed out waiting for function ${functionName} condition to be true`));
}
- await new Promise((resolveTimeout) => setTimeout(resolveTimeout, timeoutWait));
- }
- if (found) {
- resolve();
- } else {
- reject(new Error(`timed out waiting for function ${functionName} condition to be true`));
+ } catch (error) {
+ reject(error);
}
});
};
diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts
index bab34312c363f..b1de32fdcc93c 100644
--- a/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts
+++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts
@@ -13,5 +13,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./health_route'));
loadTestFile(require.resolve('./task_management'));
loadTestFile(require.resolve('./task_management_removed_types'));
+
+ loadTestFile(require.resolve('./migrations'));
});
}
diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/migrations.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/migrations.ts
new file mode 100644
index 0000000000000..caf62a1d364c0
--- /dev/null
+++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/migrations.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import type { ApiResponse, estypes } from '@elastic/elasticsearch';
+import { TaskInstanceWithDeprecatedFields } from '../../../../plugins/task_manager/server/task';
+import { FtrProviderContext } from '../../../common/ftr_provider_context';
+import { SavedObjectsUtils } from '../../../../../src/core/server/saved_objects';
+
+export default function createGetTests({ getService }: FtrProviderContext) {
+ const es = getService('es');
+ const esArchiver = getService('esArchiver');
+ const ALERT_ID = '0359d7fcc04da9878ee9aadbda38ba55';
+ const ACTION_TASK_PARAMS_ID = '6e96ac5e648f57523879661ea72525b7';
+
+ describe('migrations', () => {
+ before(async () => {
+ await esArchiver.load('x-pack/test/functional/es_archives/task_manager_tasks');
+ });
+
+ after(async () => {
+ await esArchiver.unload('x-pack/test/functional/es_archives/task_manager_tasks');
+ });
+
+ it('8.0.0 migrates actions tasks with legacy id to saved object ids', async () => {
+ // NOTE: We hae to use elastic search directly against the ".kibana" index because alerts do not expose the references which we want to test exists
+ const response = await es.get<{ task: TaskInstanceWithDeprecatedFields }>({
+ index: '.kibana_task_manager',
+ id: 'task:be7e1250-3322-11eb-94c1-db6995e84f6a',
+ });
+ expect(response.statusCode).to.eql(200);
+ expect(response.body._source?.task.params).to.eql(
+ `{"spaceId":"user1","alertId":"${SavedObjectsUtils.getConvertedObjectId(
+ 'user1',
+ 'alert',
+ ALERT_ID
+ )}"}`
+ );
+ });
+
+ it('8.0.0 migrates actions tasks from legacy id to saved object ids', async () => {
+ const searchResult: ApiResponse<
+ estypes.SearchResponse<{ task: TaskInstanceWithDeprecatedFields }>
+ > = await es.search({
+ index: '.kibana_task_manager',
+ body: {
+ query: {
+ term: {
+ _id: 'task:be7e1250-3322-11eb-94c1-db6995e8389f',
+ },
+ },
+ },
+ });
+ expect(searchResult.statusCode).to.equal(200);
+ expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.equal(1);
+ const hit = searchResult.body.hits.hits[0];
+ expect(hit!._source!.task.params!).to.equal(
+ `{"spaceId":"user1","actionTaskParamsId":"${SavedObjectsUtils.getConvertedObjectId(
+ 'user1',
+ 'action_task_params',
+ ACTION_TASK_PARAMS_ID
+ )}"}`
+ );
+ });
+ });
+}
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
index ba046a081b6d8..06398fdcd9658 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
@@ -21,8 +21,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const policyTestResources = getService('policyTestResources');
- // FLAKY: https://github.com/elastic/kibana/issues/100296
- describe.skip('When on the Endpoint Policy Details Page', function () {
+ describe('When on the Endpoint Policy Details Page', function () {
describe('with an invalid policy id', () => {
it('should display an error', async () => {
await pageObjects.policy.navigateToPolicyDetails('invalid-id');
diff --git a/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts b/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts
index 67371338a925f..eec3dd2bb2b6e 100644
--- a/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts
+++ b/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts
@@ -7,7 +7,7 @@
import { JsonObject } from '@kbn/utility-types';
import expect from '@kbn/expect';
-import { ALERT_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
+import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { User } from '../../../../rule_registry/common/lib/authentication/types';
import { TimelineEdges, TimelineNonEcsData } from '../../../../../plugins/timelines/common/';
@@ -77,14 +77,14 @@ export default ({ getService }: FtrProviderContext) => {
field: ALERT_RULE_CONSUMER,
},
{
- field: ALERT_ID,
+ field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
- fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_ID, 'event.kind'],
+ fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
diff --git a/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts b/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts
index 79fd9e6a4fb0b..4deea74d97d25 100644
--- a/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts
+++ b/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts
@@ -7,7 +7,7 @@
import { JsonObject } from '@kbn/utility-types';
import expect from '@kbn/expect';
-import { ALERT_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
+import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { User } from '../../../../rule_registry/common/lib/authentication/types';
import { TimelineEdges, TimelineNonEcsData } from '../../../../../plugins/timelines/common/';
@@ -60,14 +60,14 @@ export default ({ getService }: FtrProviderContext) => {
field: ALERT_RULE_CONSUMER,
},
{
- field: ALERT_ID,
+ field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
- fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_ID, 'event.kind'],
+ fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
diff --git a/x-pack/test/timeline/security_only/tests/basic/events.ts b/x-pack/test/timeline/security_only/tests/basic/events.ts
index e1ab3c47e1117..bf6ef53d76603 100644
--- a/x-pack/test/timeline/security_only/tests/basic/events.ts
+++ b/x-pack/test/timeline/security_only/tests/basic/events.ts
@@ -6,7 +6,7 @@
*/
import { JsonObject } from '@kbn/utility-types';
-import { ALERT_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
+import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { getSpaceUrlPrefix } from '../../../../rule_registry/common/lib/authentication/spaces';
@@ -43,14 +43,14 @@ export default ({ getService }: FtrProviderContext) => {
field: ALERT_RULE_CONSUMER,
},
{
- field: ALERT_ID,
+ field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
- fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_ID, 'event.kind'],
+ fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
diff --git a/x-pack/test/timeline/security_only/tests/trial/events.ts b/x-pack/test/timeline/security_only/tests/trial/events.ts
index e1ab3c47e1117..bf6ef53d76603 100644
--- a/x-pack/test/timeline/security_only/tests/trial/events.ts
+++ b/x-pack/test/timeline/security_only/tests/trial/events.ts
@@ -6,7 +6,7 @@
*/
import { JsonObject } from '@kbn/utility-types';
-import { ALERT_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
+import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { getSpaceUrlPrefix } from '../../../../rule_registry/common/lib/authentication/spaces';
@@ -43,14 +43,14 @@ export default ({ getService }: FtrProviderContext) => {
field: ALERT_RULE_CONSUMER,
},
{
- field: ALERT_ID,
+ field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
- fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_ID, 'event.kind'],
+ fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
diff --git a/x-pack/test/timeline/spaces_only/tests/events.ts b/x-pack/test/timeline/spaces_only/tests/events.ts
index 3867279fda7f2..a7c2a9abeb211 100644
--- a/x-pack/test/timeline/spaces_only/tests/events.ts
+++ b/x-pack/test/timeline/spaces_only/tests/events.ts
@@ -7,7 +7,7 @@
import { JsonObject } from '@kbn/utility-types';
import expect from '@kbn/expect';
-import { ALERT_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
+import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { FtrProviderContext } from '../../../rule_registry/common/ftr_provider_context';
import { getSpaceUrlPrefix } from '../../../rule_registry/common/lib/authentication/spaces';
@@ -38,14 +38,14 @@ export default ({ getService }: FtrProviderContext) => {
field: ALERT_RULE_CONSUMER,
},
{
- field: ALERT_ID,
+ field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
- fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_ID, 'event.kind'],
+ fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
diff --git a/yarn.lock b/yarn.lock
index 4d1520537ef6d..4d49a2f06e1e9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1487,10 +1487,10 @@
resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314"
integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ==
-"@elastic/eui@37.3.0":
- version "37.3.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-37.3.0.tgz#98e19f7fd610df2198de453c6078032057012249"
- integrity sha512-CxWPS8GL+0GN/fUnQ8PRb9t6Iep1UNpBGOfbbx/jyoGmVtoMLXp4RRNCd10iCZR0oXMD8gmtdul5OJwRYLkD7g==
+"@elastic/eui@37.3.1":
+ version "37.3.1"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-37.3.1.tgz#077066c3fb614d6d95b16af7185a3e940e84c38d"
+ integrity sha512-65yUfDnfVWwDJyUX7S/yNiYBwLL6hvARProoHPzSdp3hTWLgMKyhXFVdwqMgjTeyXt/69Haiza+fAzzW174w/w==
dependencies:
"@types/chroma-js" "^2.0.0"
"@types/lodash" "^4.14.160"
@@ -6457,6 +6457,11 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.14.1.tgz#b3d2eb91dafd0fd8b3fce7c61512ac66bd0364aa"
integrity sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w==
+"@typescript-eslint/types@4.28.3":
+ version "4.28.3"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.3.tgz#8fffd436a3bada422c2c1da56060a0566a9506c7"
+ integrity sha512-kQFaEsQBQVtA9VGVyciyTbIg7S3WoKHNuOp/UF5RG40900KtGqfoiETWD/v0lzRXc+euVE9NXmfer9dLkUJrkA==
+
"@typescript-eslint/types@4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.3.0.tgz#1f0b2d5e140543e2614f06d48fb3ae95193c6ddf"
@@ -6490,6 +6495,19 @@
semver "^7.3.2"
tsutils "^3.17.1"
+"@typescript-eslint/typescript-estree@^4.14.1":
+ version "4.28.3"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.3.tgz#253d7088100b2a38aefe3c8dd7bd1f8232ec46fb"
+ integrity sha512-YAb1JED41kJsqCQt1NcnX5ZdTA93vKFCMP4lQYG6CFxd0VzDJcKttRlMrlG+1qiWAw8+zowmHU1H0OzjWJzR2w==
+ dependencies:
+ "@typescript-eslint/types" "4.28.3"
+ "@typescript-eslint/visitor-keys" "4.28.3"
+ debug "^4.3.1"
+ globby "^11.0.3"
+ is-glob "^4.0.1"
+ semver "^7.3.5"
+ tsutils "^3.21.0"
+
"@typescript-eslint/visitor-keys@4.14.1":
version "4.14.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz#e93c2ff27f47ee477a929b970ca89d60a117da91"
@@ -6498,6 +6516,14 @@
"@typescript-eslint/types" "4.14.1"
eslint-visitor-keys "^2.0.0"
+"@typescript-eslint/visitor-keys@4.28.3":
+ version "4.28.3"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.3.tgz#26ac91e84b23529968361045829da80a4e5251c4"
+ integrity sha512-ri1OzcLnk1HH4gORmr1dllxDzzrN6goUIz/P4MHFV0YZJDCADPR3RvYNp0PW2SetKTThar6wlbFTL00hV2Q+fg==
+ dependencies:
+ "@typescript-eslint/types" "4.28.3"
+ eslint-visitor-keys "^2.0.0"
+
"@typescript-eslint/visitor-keys@4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.3.0.tgz#0e5ab0a09552903edeae205982e8521e17635ae0"
@@ -13120,6 +13146,11 @@ eslint-scope@^5.0.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
+eslint-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-traverse/-/eslint-traverse-1.0.0.tgz#108d360a171a6e6334e1af0cee905a93bd0dcc53"
+ integrity sha512-bSp37rQs93LF8rZ409EI369DGCI4tELbFVmFNxI6QbuveS7VRxYVyUhwDafKN/enMyUh88HQQ7ZoGUHtPuGdcw==
+
eslint-utils@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
@@ -27515,6 +27546,13 @@ tsutils@^3.17.1:
dependencies:
tslib "^1.8.1"
+tsutils@^3.21.0:
+ version "3.21.0"
+ resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
+ integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
+ dependencies:
+ tslib "^1.8.1"
+
tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"