diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 86f9f7562434e49..217bb03549343d4 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -62,7 +62,9 @@ yarn kbn watch-bazel === List of Already Migrated Packages to Bazel - @elastic/datemath +- @elastic/safer-lodash-set - @kbn/apm-utils +- @kbn/babel-code-parser - @kbn/babel-preset - @kbn/config-schema - @kbn/std diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index 89fa564b0ac7105..070d511ed8073e8 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -290,6 +290,13 @@ To add a panel to another dashboard, copy the panel. View the underlying documents in a panel, or in a data series. +. In kibana.yml, add the following: ++ +["source","yml"] +----------- +xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled: true +----------- + TIP: *Explore underlying data* is supported only for visualization panels with a single index pattern. To view the underlying documents in the panel: diff --git a/package.json b/package.json index 1625b0305554930..6af5c256c57fa7c 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "@elastic/numeral": "^2.5.0", "@elastic/react-search-ui": "^1.5.1", "@elastic/request-crypto": "1.1.4", - "@elastic/safer-lodash-set": "link:packages/elastic-safer-lodash-set", + "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set/npm_module", "@elastic/search-ui-app-search-connector": "^1.5.0", "@elastic/ui-ace": "0.2.3", "@hapi/boom": "^9.1.1", @@ -437,7 +437,7 @@ "@elastic/makelogs": "^6.0.0", "@istanbuljs/schema": "^0.1.2", "@jest/reporters": "^26.6.2", - "@kbn/babel-code-parser": "link:packages/kbn-babel-code-parser", + "@kbn/babel-code-parser": "link:bazel-bin/packages/kbn-babel-code-parser/npm_module", "@kbn/babel-preset": "link:bazel-bin/packages/kbn-babel-preset/npm_module", "@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode", "@kbn/dev-utils": "link:packages/kbn-dev-utils", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 5c3172a6c636a24..7f5182e9071078b 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -4,7 +4,9 @@ filegroup( name = "build", srcs = [ "//packages/elastic-datemath:build", + "//packages/elastic-safer-lodash-set:build", "//packages/kbn-apm-utils:build", + "//packages/kbn-babel-code-parser:build", "//packages/kbn-babel-preset:build", "//packages/kbn-config-schema:build", "//packages/kbn-std:build", diff --git a/packages/elastic-datemath/BUILD.bazel b/packages/elastic-datemath/BUILD.bazel index bc0c1412ef5f159..f3eb4548088cb86 100644 --- a/packages/elastic-datemath/BUILD.bazel +++ b/packages/elastic-datemath/BUILD.bazel @@ -54,7 +54,7 @@ ts_project( js_library( name = PKG_BASE_NAME, - srcs = [], + srcs = NPM_MODULE_EXTRA_FILES, deps = [":tsc"] + DEPS, package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], @@ -62,7 +62,6 @@ js_library( pkg_npm( name = "npm_module", - srcs = NPM_MODULE_EXTRA_FILES, deps = [ ":%s" % PKG_BASE_NAME, ] diff --git a/packages/elastic-safer-lodash-set/BUILD.bazel b/packages/elastic-safer-lodash-set/BUILD.bazel new file mode 100644 index 000000000000000..cba719ee4f0effa --- /dev/null +++ b/packages/elastic-safer-lodash-set/BUILD.bazel @@ -0,0 +1,65 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "elastic-safer-lodash-set" +PKG_REQUIRE_NAME = "@elastic/safer-lodash-set" + +SOURCE_FILES = glob( + [ + "fp/**/*", + "lodash/**/*", + "index.js", + "set.js", + "setWith.js", + ], + exclude = [ + "**/*.d.ts" + ], +) + +TYPE_FILES = glob([ + "fp/**/*.d.ts", + "index.d.ts", + "set.d.ts", + "setWith.d.ts", +]) + +SRCS = SOURCE_FILES + TYPE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +DEPS = [ + "@npm//lodash", +] + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES + [ + ":srcs", + ], + deps = DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/elastic-safer-lodash-set/tsconfig.json b/packages/elastic-safer-lodash-set/tsconfig.json index 6517e5c60ee01a5..5a29c6ff2dd8818 100644 --- a/packages/elastic-safer-lodash-set/tsconfig.json +++ b/packages/elastic-safer-lodash-set/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "../../build/tsbuildinfo/packages/elastic-safer-lodash-set" + "incremental": false, }, "include": [ "**/*", diff --git a/packages/kbn-apm-config-loader/package.json b/packages/kbn-apm-config-loader/package.json index d198ee57c619d4c..b9dc324ec5e7888 100644 --- a/packages/kbn-apm-config-loader/package.json +++ b/packages/kbn-apm-config-loader/package.json @@ -9,8 +9,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@elastic/safer-lodash-set": "link:../elastic-safer-lodash-set" } } \ No newline at end of file diff --git a/packages/kbn-apm-utils/BUILD.bazel b/packages/kbn-apm-utils/BUILD.bazel index 63adf2b77b51638..335494bea45f00b 100644 --- a/packages/kbn-apm-utils/BUILD.bazel +++ b/packages/kbn-apm-utils/BUILD.bazel @@ -53,7 +53,7 @@ ts_project( js_library( name = PKG_BASE_NAME, - srcs = [], + srcs = NPM_MODULE_EXTRA_FILES, deps = [":tsc"] + DEPS, package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], @@ -61,7 +61,6 @@ js_library( pkg_npm( name = "npm_module", - srcs = NPM_MODULE_EXTRA_FILES, deps = [ ":%s" % PKG_BASE_NAME, ] diff --git a/packages/kbn-babel-code-parser/BUILD.bazel b/packages/kbn-babel-code-parser/BUILD.bazel new file mode 100644 index 000000000000000..3c811f0bd09f571 --- /dev/null +++ b/packages/kbn-babel-code-parser/BUILD.bazel @@ -0,0 +1,71 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("@npm//@babel/cli:index.bzl", "babel") + +PKG_BASE_NAME = "kbn-babel-code-parser" +PKG_REQUIRE_NAME = "@kbn/babel-code-parser" + +SOURCE_FILES = glob( + [ + "src/**/*", + ], + exclude = [ + "**/*.test.*" + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +DEPS = [ + "//packages/kbn-babel-preset", + "@npm//@babel/parser", + "@npm//@babel/traverse", + "@npm//lodash", +] + +babel( + name = "target", + data = [ + ":srcs", + ".babelrc", + ] + DEPS, + output_dir = True, + args = [ + "./%s/src" % package_name(), + "--out-dir", + "$(@D)", + "--quiet" + ], +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = [":target"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-babel-code-parser/package.json b/packages/kbn-babel-code-parser/package.json index a5e05da6f8ee471..da55565c6076c66 100755 --- a/packages/kbn-babel-code-parser/package.json +++ b/packages/kbn-babel-code-parser/package.json @@ -8,10 +8,5 @@ "repository": { "type": "git", "url": "https://github.com/elastic/kibana/tree/master/packages/kbn-babel-code-parser" - }, - "scripts": { - "build": "../../node_modules/.bin/babel src --out-dir target", - "kbn:bootstrap": "yarn build --quiet", - "kbn:watch": "yarn build --watch" } } diff --git a/packages/kbn-babel-preset/BUILD.bazel b/packages/kbn-babel-preset/BUILD.bazel index 13542ed6e73ad4b..06b788010bdf519 100644 --- a/packages/kbn-babel-preset/BUILD.bazel +++ b/packages/kbn-babel-preset/BUILD.bazel @@ -38,7 +38,7 @@ DEPS = [ js_library( name = PKG_BASE_NAME, - srcs = [ + srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], deps = DEPS, @@ -48,7 +48,6 @@ js_library( pkg_npm( name = "npm_module", - srcs = NPM_MODULE_EXTRA_FILES, deps = [ ":%s" % PKG_BASE_NAME, ] diff --git a/packages/kbn-config/package.json b/packages/kbn-config/package.json index 9bf491e300871c8..1611da9aa60d4fb 100644 --- a/packages/kbn-config/package.json +++ b/packages/kbn-config/package.json @@ -10,7 +10,6 @@ "kbn:bootstrap": "yarn build" }, "dependencies": { - "@elastic/safer-lodash-set": "link:../elastic-safer-lodash-set", "@kbn/logging": "link:../kbn-logging" }, "devDependencies": { diff --git a/packages/kbn-tinymath/BUILD.bazel b/packages/kbn-tinymath/BUILD.bazel index ae029c88774e84f..2596a30ea2efa00 100644 --- a/packages/kbn-tinymath/BUILD.bazel +++ b/packages/kbn-tinymath/BUILD.bazel @@ -45,7 +45,7 @@ peggy( js_library( name = PKG_BASE_NAME, - srcs = [ + srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ":grammar" ], @@ -56,7 +56,6 @@ js_library( pkg_npm( name = "npm_module", - srcs = NPM_MODULE_EXTRA_FILES, deps = [ ":%s" % PKG_BASE_NAME, ] diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 2a140388cc184e8..56095336d970b96 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -280,6 +280,34 @@ test('accepts any type of objects for custom headers', () => { expect(() => httpSchema.validate(obj)).not.toThrow(); }); +test('forbids the "location" custom response header', () => { + const httpSchema = config.schema; + const obj = { + customResponseHeaders: { + location: 'string', + Location: 'string', + lOcAtIoN: 'string', + }, + }; + expect(() => httpSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"[customResponseHeaders]: The following custom response headers are not allowed to be set: location, Location, lOcAtIoN"` + ); +}); + +test('forbids the "refresh" custom response header', () => { + const httpSchema = config.schema; + const obj = { + customResponseHeaders: { + refresh: 'string', + Refresh: 'string', + rEfReSh: 'string', + }, + }; + expect(() => httpSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"[customResponseHeaders]: The following custom response headers are not allowed to be set: refresh, Refresh, rEfReSh"` + ); +}); + describe('with TLS', () => { test('throws if TLS is enabled but `redirectHttpFromPort` is equal to `port`', () => { const httpSchema = config.schema; diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 9d0008e1c4011d3..1f8fd95d69051f3 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -26,6 +26,9 @@ const hostURISchema = schema.uri({ scheme: ['http', 'https'] }); const match = (regex: RegExp, errorMsg: string) => (str: string) => regex.test(str) ? undefined : errorMsg; +// The lower-case set of response headers which are forbidden within `customResponseHeaders`. +const RESPONSE_HEADER_DENY_LIST = ['location', 'refresh']; + const configSchema = schema.object( { name: schema.string({ defaultValue: () => hostname() }), @@ -70,6 +73,16 @@ const configSchema = schema.object( securityResponseHeaders: securityResponseHeadersSchema, customResponseHeaders: schema.recordOf(schema.string(), schema.any(), { defaultValue: {}, + validate(value) { + const forbiddenKeys = Object.keys(value).filter((headerName) => + RESPONSE_HEADER_DENY_LIST.includes(headerName.toLowerCase()) + ); + if (forbiddenKeys.length > 0) { + return `The following custom response headers are not allowed to be set: ${forbiddenKeys.join( + ', ' + )}`; + } + }, }), host: schema.string({ defaultValue: 'localhost', diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js index 4d93a5207fa9e4c..09ce57639b9523c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import _ from 'lodash'; @@ -24,7 +25,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiFormLabel, EuiSpacer, EuiFieldText, EuiTitle, @@ -156,32 +156,36 @@ export class AnnotationsEditor extends Component { - - + - - - + - - + - - - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index c5b3d86f61b5d4b..556a3f2f691fb4d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -126,6 +126,7 @@ export const IndexPattern = ({ ); const isTimeSeries = model.type === PANEL_TYPES.TIMESERIES; const isDataTimerangeModeInvalid = + !disabled && selectedTimeRangeOption && !isTimerangeModeEnabled(selectedTimeRangeOption.value, uiRestrictions); diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx index 99c3fa8ea9673f4..f5cc90ee49acdec 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx @@ -5,7 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import React, { Component } from 'react'; import uuid from 'uuid'; import { @@ -23,8 +24,7 @@ import { EuiTitle, EuiHorizontalRule, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; + import type { Writable } from '@kbn/utility-types'; // @ts-ignore @@ -157,18 +157,20 @@ export class GaugePanelConfig extends Component< - - + - - - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx index c3f0f00125769ca..c33b4df914a8161 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx @@ -172,18 +172,20 @@ export class MarkdownPanelConfig extends Component< - - + - - - + @@ -218,35 +220,34 @@ export class MarkdownPanelConfig extends Component< /> - - + - - - - + - - + - - - - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx index f38d0ec83e95744..68486d0d1e83fe8 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx @@ -5,7 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; import uuid from 'uuid'; import { @@ -16,12 +17,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiFormLabel, EuiSpacer, EuiTitle, EuiHorizontalRule, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; // @ts-expect-error import { SeriesEditor } from '../series_editor'; @@ -121,18 +120,20 @@ export class MetricPanelConfig extends Component< - - + - - - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx index 0847a350664945b..4eae56c7486713c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx @@ -17,7 +17,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiFormLabel, EuiSpacer, EuiFieldText, EuiTitle, @@ -28,6 +27,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { FieldSelect } from '../aggs/field_select'; // @ts-expect-error not typed yet import { SeriesEditor } from '../series_editor'; @@ -246,18 +246,20 @@ export class TablePanelConfig extends Component< - - + - - - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx index ae36408a08b46bc..ae9d7326140a7f5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx @@ -5,6 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { @@ -22,8 +24,6 @@ import { EuiTitle, EuiHorizontalRule, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; // @ts-expect-error not typed yet import { SeriesEditor } from '../series_editor'; @@ -212,18 +212,20 @@ export class TimeseriesPanelConfig extends Component< - - + - - - + @@ -333,19 +335,17 @@ export class TimeseriesPanelConfig extends Component< /> - - + - - - - + @@ -366,15 +366,16 @@ export class TimeseriesPanelConfig extends Component< /> - - - - - - + + + diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx index a537a769cac11d2..30d65f6edd84596 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx @@ -5,7 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; import uuid from 'uuid'; import { @@ -23,7 +24,6 @@ import { EuiHorizontalRule, EuiCode, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; // @ts-expect-error not typed yet import { SeriesEditor } from '../series_editor'; @@ -149,18 +149,20 @@ export class TopNPanelConfig extends Component< - - + - - - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/series_config.js b/src/plugins/vis_type_timeseries/public/application/components/series_config.js index 8f3893feb89bdf6..86781c9922e463d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/series_config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/series_config.js @@ -5,7 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import PropTypes from 'prop-types'; import React from 'react'; import { DataFormatPicker } from './data_format_picker'; @@ -21,10 +22,7 @@ import { EuiFormRow, EuiCode, EuiHorizontalRule, - EuiFormLabel, - EuiSpacer, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; import { SeriesConfigQueryBarWithIgnoreGlobalFilter } from './series_config_query_bar_with_ignore_global_filter'; export const SeriesConfig = (props) => { @@ -104,18 +102,17 @@ export const SeriesConfig = (props) => { - - + - - - + - - + - - - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js index 1c3a0411998b0f6..72f5034cfc61b51 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js @@ -5,6 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import PropTypes from 'prop-types'; import React, { useState, useEffect } from 'react'; @@ -23,8 +24,6 @@ import { EuiCode, EuiHorizontalRule, EuiFieldNumber, - EuiFormLabel, - EuiSpacer, } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { SeriesConfigQueryBarWithIgnoreGlobalFilter } from '../../series_config_query_bar_with_ignore_global_filter'; @@ -235,14 +234,13 @@ export const TimeseriesConfig = injectI18n(function (props) { - - - - - + + + ); @@ -331,12 +329,17 @@ export const TimeseriesConfig = injectI18n(function (props) { ? props.model.series_index_pattern : props.indexPatternForQuery; - const initialPalette = { - ...model.palette, + const initialPalette = model.palette ?? { + type: 'palette', + name: 'default', + }; + + const palette = { + ...initialPalette, name: model.split_color_mode === 'kibana' ? 'kibana_palette' - : model.split_color_mode || model.palette.name, + : model.split_color_mode || initialPalette.name, }; return ( @@ -408,14 +411,13 @@ export const TimeseriesConfig = injectI18n(function (props) { - - - - - + + + {palettesRegistry && ( @@ -430,7 +432,7 @@ export const TimeseriesConfig = injectI18n(function (props) { > @@ -443,14 +445,13 @@ export const TimeseriesConfig = injectI18n(function (props) { - - - - - + + + - - + - - - + { + it('renders', () => { + const wrapper = shallow() + .find(EuiEmptyPrompt) + .dive(); + + expect(wrapper.find('h2').text()).toEqual('Create your first synonym set'); + expect(wrapper.find(EuiButton).prop('href')).toEqual( + expect.stringContaining('/synonyms-guide.html') + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx new file mode 100644 index 000000000000000..2eb6643bda5032b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx @@ -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. + */ + +import React from 'react'; + +import { EuiPanel, EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DOCS_PREFIX } from '../../../routes'; + +import { SynonymIcon } from './'; + +export const EmptyState: React.FC = () => { + return ( + + + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.title', { + defaultMessage: 'Create your first synonym set', + })} + + } + body={ +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.description', { + defaultMessage: + 'Synonyms relate queries with similar context or meaning together. Use them to guide users to relevant content.', + })} +

+ } + actions={ + + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.buttonLabel', { + defaultMessage: 'Read the synonyms guide', + })} + + } + /> +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/index.ts new file mode 100644 index 000000000000000..8a2bf1c0d2f7820 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/index.ts @@ -0,0 +1,10 @@ +/* + * 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 { SynonymIcon } from './synonym_icon'; +export { SynonymCard } from './synonym_card'; +export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.test.tsx new file mode 100644 index 000000000000000..ef24e206ed6814b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.test.tsx @@ -0,0 +1,38 @@ +/* + * 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 { EuiCard, EuiButton } from '@elastic/eui'; + +import { SynonymCard, SynonymIcon } from './'; + +describe('SynonymCard', () => { + const MOCK_SYNONYM_SET = { + id: 'syn-1234567890', + synonyms: ['lorem', 'ipsum', 'dolor', 'sit', 'amet'], + }; + + const wrapper = shallow() + .find(EuiCard) + .dive(); + + it('renders with the first synonym as the title', () => { + expect(wrapper.find('h2').text()).toEqual('lorem'); + }); + + it('renders a synonym icon for each subsequent synonym', () => { + expect(wrapper.find(SynonymIcon)).toHaveLength(4); + }); + + it('renders a manage synonym button', () => { + wrapper.find(EuiButton).simulate('click'); + // TODO: expect open modal action + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.tsx new file mode 100644 index 000000000000000..77363306527c3a8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.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 { EuiCard, EuiFlexGroup, EuiFlexItem, EuiText, EuiButton } from '@elastic/eui'; + +import { MANAGE_BUTTON_LABEL } from '../../../../shared/constants'; + +import { SynonymSet } from '../types'; + +import { SynonymIcon } from './'; + +export const SynonymCard: React.FC = (synonymSet) => { + const [firstSynonym, ...remainingSynonyms] = synonymSet.synonyms; + + return ( + + + {} /* TODO */}>{MANAGE_BUTTON_LABEL} + +
+ } + > + + {remainingSynonyms.map((synonym) => ( +
+ {synonym} +
+ ))} +
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.test.tsx new file mode 100644 index 000000000000000..8120532fbd6f613 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.test.tsx @@ -0,0 +1,19 @@ +/* + * 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 { SynonymIcon } from './'; + +describe('SynonymIcon', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.hasClass('euiIcon')).toBe(true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.tsx new file mode 100644 index 000000000000000..f76b8be818c4743 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.tsx @@ -0,0 +1,26 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SynonymIcon: React.FC = ({ ...props }) => ( + + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/constants.ts index cbbd1e631b7ef4a..2cb50b6cba1b3e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/constants.ts @@ -7,6 +7,15 @@ import { i18n } from '@kbn/i18n'; +import { DEFAULT_META } from '../../../shared/constants'; + +export const SYNONYMS_PAGE_META = { + page: { + ...DEFAULT_META.page, + size: 12, // Use a multiple of 3, since synonym cards are in rows of 3 + }, +}; + export const SYNONYMS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.synonyms.title', { defaultMessage: 'Synonyms' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/index.ts index 177bc5eade0f67d..4b9de7ef9060330 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/index.ts @@ -7,3 +7,4 @@ export { SYNONYMS_TITLE } from './constants'; export { Synonyms } from './synonyms'; +export { SynonymsLogic } from './synonyms_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.test.tsx index e093442f77b773d..11692a1542c4d90 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.test.tsx @@ -5,17 +5,123 @@ * 2.0. */ +import { setMockValues, setMockActions, rerender } from '../../../__mocks__'; +import '../../../__mocks__/shallow_useeffect.mock'; import '../../__mocks__/engine_logic.mock'; import React from 'react'; import { shallow } from 'enzyme'; +import { EuiPageHeader, EuiButton, EuiPagination } from '@elastic/eui'; + +import { Loading } from '../../../shared/loading'; + +import { SynonymCard, EmptyState } from './components'; + import { Synonyms } from './'; describe('Synonyms', () => { + const MOCK_SYNONYM_SET = { + id: 'syn-1234567890', + synonyms: ['a', 'b', 'c'], + }; + + const values = { + synonymSets: [MOCK_SYNONYM_SET, MOCK_SYNONYM_SET, MOCK_SYNONYM_SET], + meta: { page: { current: 1 } }, + dataLoading: false, + }; + const actions = { + loadSynonyms: jest.fn(), + onPaginate: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + it('renders', () => { - shallow(); - // TODO: Check for Synonym cards, Synonym modal + const wrapper = shallow(); + + expect(wrapper.find(SynonymCard)).toHaveLength(3); + // TODO: Check for synonym modal + }); + + it('renders a create action button', () => { + const wrapper = shallow() + .find(EuiPageHeader) + .dive() + .children() + .dive(); + + wrapper.find(EuiButton).simulate('click'); + // TODO: Expect open modal action + }); + + it('renders an empty state if no synonyms exist', () => { + setMockValues({ ...values, synonymSets: [] }); + const wrapper = shallow(); + + expect(wrapper.find(EmptyState)).toHaveLength(1); + }); + + describe('loading', () => { + it('renders a loading state on initial page load', () => { + setMockValues({ ...values, synonymSets: [], dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('does not render a full loading state after initial page load', () => { + setMockValues({ ...values, synonymSets: [MOCK_SYNONYM_SET], dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(0); + }); + }); + + describe('API & pagination', () => { + it('loads synonyms on page load and on pagination', () => { + const wrapper = shallow(); + expect(actions.loadSynonyms).toHaveBeenCalledTimes(1); + + setMockValues({ ...values, meta: { page: { current: 5 } } }); + rerender(wrapper); + expect(actions.loadSynonyms).toHaveBeenCalledTimes(2); + }); + + it('automatically paginations users back a page if they delete the only remaining synonym on the page', () => { + setMockValues({ ...values, meta: { page: { current: 5 } }, synonymSets: [] }); + shallow(); + + expect(actions.onPaginate).toHaveBeenCalledWith(4); + }); + + it('does not paginate backwards if the user is on the first page (should show the state instead)', () => { + setMockValues({ ...values, meta: { page: { current: 1 } }, synonymSets: [] }); + const wrapper = shallow(); + + expect(actions.onPaginate).not.toHaveBeenCalled(); + expect(wrapper.find(EmptyState)).toHaveLength(1); + }); + + it('handles off-by-one shenanigans between EuiPagination and our API', () => { + setMockValues({ + ...values, + meta: { page: { total_pages: 10, current: 1 } }, + }); + const wrapper = shallow(); + const pagination = wrapper.find(EuiPagination); + + expect(pagination.prop('pageCount')).toEqual(10); + expect(pagination.prop('activePage')).toEqual(0); + + pagination.simulate('pageClick', 4); + expect(actions.onPaginate).toHaveBeenCalledWith(5); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.tsx index 0b18271660911f8..59bd501f5468116 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.tsx @@ -5,23 +5,86 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; -import { EuiPageHeader, EuiPageContentBody } from '@elastic/eui'; +import { useValues, useActions } from 'kea'; + +import { + EuiPageHeader, + EuiButton, + EuiPageContentBody, + EuiSpacer, + EuiFlexGrid, + EuiFlexItem, + EuiPagination, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { Loading } from '../../../shared/loading'; import { getEngineBreadcrumbs } from '../engine'; +import { SynonymCard, EmptyState } from './components'; import { SYNONYMS_TITLE } from './constants'; +import { SynonymsLogic } from './'; + export const Synonyms: React.FC = () => { + const { loadSynonyms, onPaginate } = useActions(SynonymsLogic); + const { synonymSets, meta, dataLoading } = useValues(SynonymsLogic); + const hasSynonyms = synonymSets.length > 0; + + useEffect(() => { + loadSynonyms(); + }, [meta.page.current]); + + useEffect(() => { + // If users delete the only synonym set on the page, send them back to the previous page + if (!hasSynonyms && meta.page.current !== 1) { + onPaginate(meta.page.current - 1); + } + }, [synonymSets]); + + if (dataLoading && !hasSynonyms) return ; + return ( <> - + {} /* TODO */}> + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetButtonLabel', + { defaultMessage: 'Create a synonym set' } + )} + , + ]} + /> - TODO + + + {hasSynonyms ? ( + <> + + {synonymSets.map(({ id, synonyms }) => ( + + + + ))} + + + onPaginate(pageIndex + 1)} + /> + + ) : ( + + )} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts new file mode 100644 index 000000000000000..2497787a55f1e2c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { LogicMounter, mockHttpValues, mockFlashMessageHelpers } from '../../../__mocks__'; +import '../../__mocks__/engine_logic.mock'; + +import { nextTick } from '@kbn/test/jest'; + +import { SYNONYMS_PAGE_META } from './constants'; + +import { SynonymsLogic } from './'; + +describe('SynonymsLogic', () => { + const { mount } = new LogicMounter(SynonymsLogic); + const { http } = mockHttpValues; + const { flashAPIErrors } = mockFlashMessageHelpers; + + const MOCK_SYNONYMS_RESPONSE = { + meta: { + page: { + current: 1, + size: 12, + total_results: 1, + total_pages: 1, + }, + }, + results: [ + { + id: 'some-synonym-id', + synonyms: ['hello', 'world'], + }, + ], + }; + + const DEFAULT_VALUES = { + dataLoading: true, + synonymSets: [], + meta: SYNONYMS_PAGE_META, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(SynonymsLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('onSynonymsLoad', () => { + it('should set synonyms and meta state, & dataLoading to false', () => { + mount(); + + SynonymsLogic.actions.onSynonymsLoad(MOCK_SYNONYMS_RESPONSE); + + expect(SynonymsLogic.values).toEqual({ + ...DEFAULT_VALUES, + synonymSets: MOCK_SYNONYMS_RESPONSE.results, + meta: MOCK_SYNONYMS_RESPONSE.meta, + dataLoading: false, + }); + }); + }); + + describe('onPaginate', () => { + it('should set meta.page.current state', () => { + mount(); + + SynonymsLogic.actions.onPaginate(3); + + expect(SynonymsLogic.values).toEqual({ + ...DEFAULT_VALUES, + meta: { page: { ...DEFAULT_VALUES.meta.page, current: 3 } }, + }); + }); + }); + }); + + describe('listeners', () => { + describe('loadSynonyms', () => { + it('should set dataLoading state', () => { + mount({ dataLoading: false }); + + SynonymsLogic.actions.loadSynonyms(); + + expect(SynonymsLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: true, + }); + }); + + it('should make an API call and set synonyms & meta state', async () => { + http.get.mockReturnValueOnce(Promise.resolve(MOCK_SYNONYMS_RESPONSE)); + mount(); + jest.spyOn(SynonymsLogic.actions, 'onSynonymsLoad'); + + SynonymsLogic.actions.loadSynonyms(); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/synonyms', { + query: { + 'page[current]': 1, + 'page[size]': 12, + }, + }); + expect(SynonymsLogic.actions.onSynonymsLoad).toHaveBeenCalledWith(MOCK_SYNONYMS_RESPONSE); + }); + + it('handles errors', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount(); + + SynonymsLogic.actions.loadSynonyms(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts new file mode 100644 index 000000000000000..a55fcf83a5f8ba1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts @@ -0,0 +1,79 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { Meta } from '../../../../../common/types'; +import { flashAPIErrors } from '../../../shared/flash_messages'; +import { HttpLogic } from '../../../shared/http'; +import { updateMetaPageIndex } from '../../../shared/table_pagination'; +import { EngineLogic } from '../engine'; + +import { SYNONYMS_PAGE_META } from './constants'; +import { SynonymSet, SynonymsApiResponse } from './types'; + +interface SynonymsValues { + dataLoading: boolean; + synonymSets: SynonymSet[]; + meta: Meta; +} + +interface SynonymsActions { + loadSynonyms(): void; + onSynonymsLoad(response: SynonymsApiResponse): SynonymsApiResponse; + onPaginate(newPageIndex: number): { newPageIndex: number }; +} + +export const SynonymsLogic = kea>({ + path: ['enterprise_search', 'app_search', 'synonyms_logic'], + actions: () => ({ + loadSynonyms: true, + onSynonymsLoad: ({ results, meta }) => ({ results, meta }), + onPaginate: (newPageIndex) => ({ newPageIndex }), + }), + reducers: () => ({ + dataLoading: [ + true, + { + loadSynonyms: () => true, + onSynonymsLoad: () => false, + }, + ], + synonymSets: [ + [], + { + onSynonymsLoad: (_, { results }) => results, + }, + ], + meta: [ + SYNONYMS_PAGE_META, + { + onSynonymsLoad: (_, { meta }) => meta, + onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), + }, + ], + }), + listeners: ({ actions, values }) => ({ + loadSynonyms: async () => { + const { meta } = values; + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + try { + const response = await http.get(`/api/app_search/engines/${engineName}/synonyms`, { + query: { + 'page[current]': meta.page.current, + 'page[size]': meta.page.size, + }, + }); + actions.onSynonymsLoad(response); + } catch (e) { + flashAPIErrors(e); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/types.ts new file mode 100644 index 000000000000000..2f6da766a6d50b6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/types.ts @@ -0,0 +1,18 @@ +/* + * 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 { Meta } from '../../../../../common/types'; + +export interface SynonymSet { + id: string; + synonyms: string[]; +} + +export interface SynonymsApiResponse { + results: SynonymSet[]; + meta: Meta; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx index e7fd1da394bb32c..cb0b02527f756ae 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx @@ -14,18 +14,7 @@ import { usePackageIconType } from '../hooks'; export const PackageIcon: React.FunctionComponent< UsePackageIconType & Omit -> = ({ size = 's', packageName, version, icons, tryApi, ...euiIconProps }) => { +> = ({ packageName, version, icons, tryApi, ...euiIconProps }) => { const iconType = usePackageIconType({ packageName, version, icons, tryApi }); - return ( - - // this collides with some EuiText (+img) CSS from the EuiIcon component - // which makes the button large, wide, and poorly layed out - // override those styles until the bug is fixed or we find a better approach - style={{ margin: 'unset', width: 'unset' }} - size={size} - type={iconType} - {...euiIconProps} - /> - ); + return ; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx index dcc87b0032d77f7..cff0dc55515c4c7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx @@ -71,7 +71,18 @@ export const AgentPolicyPackageBadges: React.FunctionComponent = ({ - + + // this collides with some EuiText (+img) CSS from the EuiIcon component + // which makes the button large, wide, and poorly layed out + // override those styles until the bug is fixed or we find a better approach + { margin: 'unset', width: '16px' } + } + /> {pkg.title} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx deleted file mode 100644 index 63c6897021f4e62..000000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx +++ /dev/null @@ -1,62 +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. - */ - -import React from 'react'; -import styled from 'styled-components'; -import { EuiIcon, EuiPanel } from '@elastic/eui'; - -import type { UsePackageIconType } from '../../../hooks'; -import { usePackageIconType } from '../../../hooks'; -import { Loading } from '../../../components'; - -const PanelWrapper = styled.div` - // NOTE: changes to the width here will impact navigation tabs page layout under integration package details - width: ${(props) => - parseFloat(props.theme.eui.euiSize) * 6 + parseFloat(props.theme.eui.euiSizeXL) * 2}px; - height: 1px; - z-index: 1; -`; - -const Panel = styled(EuiPanel)` - padding: ${(props) => props.theme.eui.spacerSizes.xl}; - margin-bottom: -100%; - svg, - img { - height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; - width: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; - } - .euiFlexItem { - height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; - justify-content: center; - } -`; - -export function IconPanel({ - packageName, - version, - icons, -}: Pick) { - const iconType = usePackageIconType({ packageName, version, icons }); - - return ( - - - - - - ); -} - -export function LoadingIconPanel() { - return ( - - - - - - ); -} diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index a5c19911f60b94f..bfcc20cc88b817d 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -35,6 +35,7 @@ "savedObjects", "kibanaUtils", "kibanaReact", - "embeddable" + "embeddable", + "usageCollection" ] } diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index a3316e0083d35d2..214ce6d11cff212 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -24,6 +24,8 @@ import { toExpression, Ast } from '@kbn/interpreter/common'; import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions'; import { map, distinctUntilChanged, skip } from 'rxjs/operators'; import isEqual from 'fast-deep-equal'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { METRIC_TYPE } from '../../../../../../src/plugins/usage_collection/public'; import { ExpressionRendererEvent, ReactExpressionRendererType, @@ -51,7 +53,7 @@ import { } from '../../types'; import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; -import { getEditPath, DOC_TYPE } from '../../../common'; +import { getEditPath, DOC_TYPE, PLUGIN_ID } from '../../../common'; import { IBasePath } from '../../../../../../src/core/public'; import { LensAttributeService } from '../../lens_attribute_service'; import type { ErrorMessage } from '../types'; @@ -95,6 +97,7 @@ export interface LensEmbeddableDeps { getTrigger?: UiActionsStart['getTrigger'] | undefined; getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions']; capabilities: { canSaveVisualizations: boolean; canSaveDashboards: boolean }; + usageCollection?: UsageCollectionSetup; } export class Embeddable @@ -113,6 +116,14 @@ export class Embeddable private inputReloadSubscriptions: Subscription[]; private isDestroyed?: boolean; + private logError(type: 'runtime' | 'validation') { + this.deps.usageCollection?.reportUiCounter( + PLUGIN_ID, + METRIC_TYPE.COUNT, + type === 'runtime' ? 'embeddable_runtime_error' : 'embeddable_validation_error' + ); + } + private externalSearchContext: { timeRange?: TimeRange; query?: Query; @@ -255,6 +266,9 @@ export class Embeddable const { ast, errors } = await this.deps.documentToExpression(this.savedVis); this.errors = errors; this.expression = ast ? toExpression(ast) : null; + if (errors) { + this.logError('validation'); + } await this.initializeOutput(); this.isInitialized = true; } @@ -326,6 +340,9 @@ export class Embeddable className={input.className} style={input.style} canEdit={this.getIsEditable() && input.viewMode === 'edit'} + onRuntimeError={() => { + this.logError('runtime'); + }} />, domNode ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts index 1a4962bd1fe8e2d..095e18e3fb5eb19 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { RecursiveReadonly } from '@kbn/utility-types'; import { Ast } from '@kbn/interpreter/target/common'; import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { IndexPatternsContract, TimefilterContract, @@ -34,6 +35,7 @@ export interface LensEmbeddableStartServices { expressionRenderer: ReactExpressionRendererType; indexPatternService: IndexPatternsContract; uiActions?: UiActionsStart; + usageCollection?: UsageCollectionSetup; documentToExpression: ( doc: Document ) => Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }>; @@ -87,6 +89,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { attributeService, indexPatternService, capabilities, + usageCollection, } = await this.getStartServices(); const { Embeddable } = await import('../../async_services'); @@ -105,6 +108,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { canSaveDashboards: Boolean(capabilities.dashboard?.showWriteControls), canSaveVisualizations: Boolean(capabilities.visualize.save), }, + usageCollection, }, input, parent diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx index f4d0c85ecbbce00..15d168465ec71ad 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx @@ -38,6 +38,7 @@ export interface ExpressionWrapperProps { style?: React.CSSProperties; className?: string; canEdit: boolean; + onRuntimeError: () => void; } interface VisualizationErrorProps { @@ -106,6 +107,7 @@ export function ExpressionWrapper({ className, errors, canEdit, + onRuntimeError, }: ExpressionWrapperProps) { return ( @@ -123,20 +125,23 @@ export function ExpressionWrapper({ onData$={onData$} renderMode={renderMode} syncColors={syncColors} - renderError={(errorMessage, error) => ( -
- - - - - - {(getOriginalRequestErrorMessages(error) || [errorMessage]).map((message) => ( - {message} - ))} - - -
- )} + renderError={(errorMessage, error) => { + onRuntimeError(); + return ( +
+ + + + + + {(getOriginalRequestErrorMessages(error) || [errorMessage]).map((message) => ( + {message} + ))} + + +
+ ); + }} onEvent={handleEvent} hasCompatibleActions={hasCompatibleActions} /> diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 8769aceca3bfd0a..849baa93652cc66 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { CoreSetup, CoreStart } from 'kibana/public'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; import { @@ -35,6 +36,7 @@ export interface EditorFrameSetupPlugins { embeddable?: EmbeddableSetup; expressions: ExpressionsSetup; charts: ChartsPluginSetup; + usageCollection?: UsageCollectionSetup; } export interface EditorFrameStartPlugins { @@ -101,6 +103,7 @@ export class EditorFrameService { documentToExpression: this.documentToExpression, indexPatternService: deps.data.indexPatterns, uiActions: deps.uiActions, + usageCollection: plugins.usageCollection, }; }; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 81937f3f4155707..99e7199c2d8020f 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -6,6 +6,7 @@ */ import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { DashboardStart } from '../../../../src/plugins/dashboard/public'; @@ -62,6 +63,7 @@ export interface LensPluginSetupDependencies { visualizations: VisualizationsSetup; charts: ChartsPluginSetup; globalSearch?: GlobalSearchPluginSetup; + usageCollection?: UsageCollectionSetup; } export interface LensPluginStartDependencies { @@ -139,6 +141,7 @@ export class LensPlugin { visualizations, charts, globalSearch, + usageCollection, }: LensPluginSetupDependencies ) { this.attributeService = async () => { @@ -153,6 +156,7 @@ export class LensPlugin { embeddable, charts, expressions, + usageCollection, }, this.attributeService ); diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 44e5f9d445c3da9..007368f0997df29 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -54,6 +54,8 @@ export const KBN_TOO_MANY_FEATURES_IMAGE_ID = '__kbn_too_many_features_image_id_ // Centroids are a single point for representing lines, multiLines, polygons, and multiPolygons export const KBN_IS_CENTROID_FEATURE = '__kbn_is_centroid_feature__'; +export const MVT_TOKEN_PARAM_NAME = 'token'; + const MAP_BASE_URL = `/${MAPS_APP_PATH}/${MAP_PATH}`; export function getNewMapPath() { return MAP_BASE_URL; diff --git a/x-pack/plugins/maps/public/classes/fields/mvt_field.ts b/x-pack/plugins/maps/public/classes/fields/mvt_field.ts index 2a837f831198a68..ed2955a1cc16f3c 100644 --- a/x-pack/plugins/maps/public/classes/fields/mvt_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/mvt_field.ts @@ -7,7 +7,8 @@ import { AbstractField, IField } from './field'; import { FIELD_ORIGIN, MVT_FIELD_TYPE } from '../../../common/constants'; -import { ITiledSingleLayerVectorSource, IVectorSource } from '../sources/vector_source'; +import { IVectorSource } from '../sources/vector_source'; +import { ITiledSingleLayerVectorSource } from '../sources/tiled_single_layer_vector_source'; import { MVTFieldDescriptor } from '../../../common/descriptor_types'; export class MVTField extends AbstractField implements IField { diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 5786b5fb194b80d..59edaa8ed1b9511 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -18,6 +18,7 @@ import { DataRequest } from '../util/data_request'; import { AGG_TYPE, FIELD_ORIGIN, + LAYER_TYPE, MAX_ZOOM, MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, MIN_ZOOM, @@ -81,7 +82,7 @@ export interface ILayer { isInitialDataLoadComplete(): boolean; getIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[]; - getType(): string | undefined; + getType(): LAYER_TYPE | undefined; isVisible(): boolean; cloneDescriptor(): Promise; renderStyleEditor( @@ -483,8 +484,8 @@ export class AbstractLayer implements ILayer { mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none'); } - getType(): string | undefined { - return this._descriptor.type; + getType(): LAYER_TYPE | undefined { + return this._descriptor.type as LAYER_TYPE; } areLabelsOnTop(): boolean { diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx index 408c2ec18164d89..e71d32669a564f9 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx @@ -7,6 +7,7 @@ import { MockSyncContext } from '../__fixtures__/mock_sync_context'; import sinon from 'sinon'; +import url from 'url'; jest.mock('../../../kibana_services', () => { return { @@ -38,7 +39,8 @@ const defaultConfig = { function createLayer( layerOptions: Partial = {}, sourceOptions: Partial = {}, - isTimeAware: boolean = false + isTimeAware: boolean = false, + includeToken: boolean = false ): TiledVectorLayer { const sourceDescriptor: TiledSingleLayerVectorSourceDescriptor = { type: SOURCE_TYPES.MVT_SINGLE_LAYER, @@ -57,6 +59,19 @@ function createLayer( }; } + if (includeToken) { + mvtSource.getUrlTemplateWithMeta = async (...args) => { + const superReturn = await MVTSingleLayerVectorSource.prototype.getUrlTemplateWithMeta.call( + mvtSource, + ...args + ); + return { + ...superReturn, + refreshTokenParamName: 'token', + }; + }; + } + const defaultLayerOptions = { ...layerOptions, sourceDescriptor, @@ -115,7 +130,7 @@ describe('syncData', () => { expect(call.args[2]!.minSourceZoom).toEqual(defaultConfig.minSourceZoom); expect(call.args[2]!.maxSourceZoom).toEqual(defaultConfig.maxSourceZoom); expect(call.args[2]!.layerName).toEqual(defaultConfig.layerName); - expect(call.args[2]!.urlTemplate!.startsWith(defaultConfig.urlTemplate)).toEqual(true); + expect(call.args[2]!.urlTemplate).toEqual(defaultConfig.urlTemplate); }); it('Should not resync when no changes to source params', async () => { @@ -193,8 +208,34 @@ describe('syncData', () => { expect(call.args[2]!.minSourceZoom).toEqual(newMeta.minSourceZoom); expect(call.args[2]!.maxSourceZoom).toEqual(newMeta.maxSourceZoom); expect(call.args[2]!.layerName).toEqual(newMeta.layerName); - expect(call.args[2]!.urlTemplate!.startsWith(newMeta.urlTemplate)).toEqual(true); + expect(call.args[2]!.urlTemplate).toEqual(newMeta.urlTemplate); }); }); }); + + describe('refresh token', () => { + const uuidRegex = /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/; + + it(`should add token in url`, async () => { + const layer: TiledVectorLayer = createLayer({}, {}, false, true); + + const syncContext = new MockSyncContext({ dataFilters: {} }); + + await layer.syncData(syncContext); + // @ts-expect-error + sinon.assert.calledOnce(syncContext.startLoading); + // @ts-expect-error + sinon.assert.calledOnce(syncContext.stopLoading); + + // @ts-expect-error + const call = syncContext.stopLoading.getCall(0); + expect(call.args[2]!.minSourceZoom).toEqual(defaultConfig.minSourceZoom); + expect(call.args[2]!.maxSourceZoom).toEqual(defaultConfig.maxSourceZoom); + expect(call.args[2]!.layerName).toEqual(defaultConfig.layerName); + expect(call.args[2]!.urlTemplate.startsWith(defaultConfig.urlTemplate)).toBe(true); + + const parsedUrl = url.parse(call.args[2]!.urlTemplate, true); + expect(!!(parsedUrl.query.token! as string).match(uuidRegex)).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index 90c4896f2a287e9..d452096250576d3 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -14,10 +14,11 @@ import { import { EuiIcon } from '@elastic/eui'; import { Feature } from 'geojson'; import uuid from 'uuid/v4'; +import { parse as parseUrl } from 'url'; import { IVectorStyle, VectorStyle } from '../../styles/vector/vector_style'; import { SOURCE_DATA_REQUEST_ID, LAYER_TYPE } from '../../../../common/constants'; import { VectorLayer, VectorLayerArguments } from '../vector_layer'; -import { ITiledSingleLayerVectorSource } from '../../sources/vector_source'; +import { ITiledSingleLayerVectorSource } from '../../sources/tiled_single_layer_vector_source'; import { DataRequestContext } from '../../../actions'; import { VectorLayerDescriptor, @@ -103,10 +104,20 @@ export class TiledVectorLayer extends VectorLayer { : prevData.urlToken; const newUrlTemplateAndMeta = await this._source.getUrlTemplateWithMeta(searchFilters); + + let urlTemplate; + if (newUrlTemplateAndMeta.refreshTokenParamName) { + const parsedUrl = parseUrl(newUrlTemplateAndMeta.urlTemplate, true); + const separator = !parsedUrl.query || Object.keys(parsedUrl.query).length === 0 ? '?' : '&'; + urlTemplate = `${newUrlTemplateAndMeta.urlTemplate}${separator}${newUrlTemplateAndMeta.refreshTokenParamName}=${urlToken}`; + } else { + urlTemplate = newUrlTemplateAndMeta.urlTemplate; + } + const urlTemplateAndMetaWithToken = { ...newUrlTemplateAndMeta, urlToken, - urlTemplate: newUrlTemplateAndMeta.urlTemplate + `&token=${urlToken}`, + urlTemplate, }; stopLoading(SOURCE_DATA_REQUEST_ID, requestToken, urlTemplateAndMetaWithToken, {}); } catch (error) { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index e9cf62d8f408934..7bca22df9b870ba 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -27,6 +27,7 @@ import { GRID_RESOLUTION, MVT_GETGRIDTILE_API_PATH, MVT_SOURCE_LAYER_NAME, + MVT_TOKEN_PARAM_NAME, RENDER_AS, SOURCE_TYPES, VECTOR_SHAPE_TYPE, @@ -38,7 +39,8 @@ import { registerSource } from '../source_registry'; import { LICENSED_FEATURES } from '../../../licensed_features'; import { getHttp } from '../../../kibana_services'; -import { GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source'; +import { GeoJsonWithMeta } from '../vector_source'; +import { ITiledSingleLayerVectorSource } from '../tiled_single_layer_vector_source'; import { ESGeoGridSourceDescriptor, MapExtent, @@ -50,6 +52,7 @@ import { ISearchSource } from '../../../../../../../src/plugins/data/common/sear import { IndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { isValidStringConfig } from '../../util/valid_string_config'; +import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; export const MAX_GEOTILE_LEVEL = 29; @@ -420,12 +423,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle async getUrlTemplateWithMeta( searchFilters: VectorSourceRequestMeta - ): Promise<{ - layerName: string; - urlTemplate: string; - minSourceZoom: number; - maxSourceZoom: number; - }> { + ): Promise { const indexPattern = await this.getIndexPattern(); const searchSource = await this.makeSearchSource(searchFilters, 0); @@ -453,6 +451,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle &geoFieldType=${geoField.type}`; return { + refreshTokenParamName: MVT_TOKEN_PARAM_NAME, layerName: this.getLayerName(), minSourceZoom: this.getMinZoom(), maxSourceZoom: this.getMaxZoom(), diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index ff4675413985c79..3de98fd54582774 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -31,6 +31,7 @@ import { GIS_API_PATH, MVT_GETTILE_API_PATH, MVT_SOURCE_LAYER_NAME, + MVT_TOKEN_PARAM_NAME, SCALING_TYPES, SOURCE_TYPES, VECTOR_SHAPE_TYPE, @@ -51,17 +52,15 @@ import { import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { IField } from '../../fields/field'; -import { - GeoJsonWithMeta, - ITiledSingleLayerVectorSource, - SourceTooltipConfig, -} from '../vector_source'; +import { GeoJsonWithMeta, SourceTooltipConfig } from '../vector_source'; +import { ITiledSingleLayerVectorSource } from '../tiled_single_layer_vector_source'; import { ITooltipProperty } from '../../tooltips/tooltip_property'; import { DataRequest } from '../../util/data_request'; import { SortDirection, SortDirectionNumeric } from '../../../../../../../src/plugins/data/common'; import { isValidStringConfig } from '../../util/valid_string_config'; import { TopHitsUpdateSourceEditor } from './top_hits'; import { getDocValueAndSourceFields, ScriptField } from './get_docvalue_source_fields'; +import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { defaultMessage: 'Documents', @@ -674,12 +673,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye async getUrlTemplateWithMeta( searchFilters: VectorSourceRequestMeta - ): Promise<{ - layerName: string; - urlTemplate: string; - minSourceZoom: number; - maxSourceZoom: number; - }> { + ): Promise { const indexPattern = await this.getIndexPattern(); const indexSettings = await loadIndexSettings(indexPattern.title); @@ -722,6 +716,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye &geoFieldType=${geoField.type}`; return { + refreshTokenParamName: MVT_TOKEN_PARAM_NAME, layerName: this.getLayerName(), minSourceZoom: this.getMinZoom(), maxSourceZoom: this.getMaxZoom(), diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index 4e4d9e9eee5d20f..92b643643ba2a82 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -10,7 +10,8 @@ import uuid from 'uuid/v4'; import React from 'react'; import { GeoJsonProperties } from 'geojson'; import { AbstractSource, ImmutableSourceProperty, SourceEditorArgs } from '../source'; -import { BoundsFilters, GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source'; +import { BoundsFilters, GeoJsonWithMeta } from '../vector_source'; +import { ITiledSingleLayerVectorSource } from '../tiled_single_layer_vector_source'; import { FIELD_ORIGIN, MAX_ZOOM, @@ -30,6 +31,7 @@ import { MVTField } from '../../fields/mvt_field'; import { UpdateSourceEditor } from './update_source_editor'; import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; +import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; export const sourceTitle = i18n.translate( 'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle', @@ -154,7 +156,7 @@ export class MVTSingleLayerVectorSource return this.getLayerName(); } - async getUrlTemplateWithMeta() { + async getUrlTemplateWithMeta(): Promise { return { urlTemplate: this._descriptor.urlTemplate, layerName: this._descriptor.layerName, diff --git a/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/index.ts b/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/index.ts new file mode 100644 index 000000000000000..30177751a8d5552 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { ITiledSingleLayerVectorSource } from './tiled_single_layer_vector_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/tiled_single_layer_vector_source.ts b/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/tiled_single_layer_vector_source.ts new file mode 100644 index 000000000000000..013c3f9f0d7e17d --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/tiled_single_layer_vector_source/tiled_single_layer_vector_source.ts @@ -0,0 +1,26 @@ +/* + * 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 { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; +import { IVectorSource } from '../vector_source'; + +export interface ITiledSingleLayerMvtParams { + layerName: string; + urlTemplate: string; + minSourceZoom: number; + maxSourceZoom: number; + refreshTokenParamName?: string; +} + +export interface ITiledSingleLayerVectorSource extends IVectorSource { + getUrlTemplateWithMeta( + searchFilters: VectorSourceRequestMeta + ): Promise; + getMinZoom(): number; + getMaxZoom(): number; + getLayerName(): string; +} diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index e86e459851c7064..b28cd7365d69e56 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -66,20 +66,6 @@ export interface IVectorSource extends ISource { getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig; } -export interface ITiledSingleLayerVectorSource extends IVectorSource { - getUrlTemplateWithMeta( - searchFilters: VectorSourceRequestMeta - ): Promise<{ - layerName: string; - urlTemplate: string; - minSourceZoom: number; - maxSourceZoom: number; - }>; - getMinZoom(): number; - getMaxZoom(): number; - getLayerName(): string; -} - export class AbstractVectorSource extends AbstractSource implements IVectorSource { getFieldNames(): string[] { return []; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap index be8c9b0750b94ed..64da5777988d1b8 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap @@ -384,6 +384,546 @@ exports[`should render 1`] = ` `; +exports[`should render line-style with label properties when ES-source is rendered as mvt 1`] = ` + + + + + + + + + + + + + + + + + + + +`; + +exports[`should render polygon-style without label properties when 3rd party mvt 1`] = ` + + + + + + + + + + + +`; + exports[`should render with no style fields 1`] = ` { class MockField extends AbstractField {} -function createLayerMock(numFields: number, supportedShapeTypes: VECTOR_SHAPE_TYPE[]) { +function createLayerMock( + numFields: number, + supportedShapeTypes: VECTOR_SHAPE_TYPE[], + layerType: LAYER_TYPE = LAYER_TYPE.VECTOR, + isESSource: boolean = false +) { const fields: IField[] = []; for (let i = 0; i < numFields; i++) { fields.push(new MockField({ fieldName: `field${i}`, origin: FIELD_ORIGIN.SOURCE })); @@ -39,11 +45,17 @@ function createLayerMock(numFields: number, supportedShapeTypes: VECTOR_SHAPE_TY getStyleEditorFields: async () => { return fields; }, + getType() { + return layerType; + }, getSource: () => { return ({ getSupportedShapeTypes: async () => { return supportedShapeTypes; }, + isESSource() { + return isESSource; + }, } as unknown) as IVectorSource; }, } as unknown) as IVectorLayer; @@ -99,3 +111,35 @@ test('should render with no style fields', async () => { expect(component).toMatchSnapshot(); }); + +test('should render polygon-style without label properties when 3rd party mvt', async () => { + const component = shallow( + + ); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); +}); + +test('should render line-style with label properties when ES-source is rendered as mvt', async () => { + const component = shallow( + + ); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx index 91bcc2dc0685977..4fb2887c52876fb 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx @@ -9,7 +9,7 @@ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { EuiButtonGroup, EuiFormRow, EuiSpacer, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; // @ts-expect-error @@ -25,9 +25,10 @@ import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_palettes'; import { LABEL_BORDER_SIZES, - VECTOR_STYLES, + LAYER_TYPE, STYLE_TYPE, VECTOR_SHAPE_TYPE, + VECTOR_STYLES, } from '../../../../../common/constants'; import { createStyleFieldsHelper, StyleField, StyleFieldsHelper } from '../style_fields_helper'; import { @@ -257,7 +258,18 @@ export class VectorStyleEditor extends Component { ); } - _renderLabelProperties() { + _renderLabelProperties(isPoints: boolean) { + if ( + !isPoints && + this.props.layer.getType() === LAYER_TYPE.TILED_VECTOR && + !this.props.layer.getSource().isESSource() + ) { + // This handles and edge-case + // 3rd party lines and polygons from mvt sources cannot be labeled, because they do not have label-centroid geometries inside the tile. + // These label-centroids are only added for ES-sources + return; + } + const hasLabel = this._hasLabel(); const hasLabelBorder = this._hasLabelBorder(); return ( @@ -456,7 +468,7 @@ export class VectorStyleEditor extends Component { /> - {this._renderLabelProperties()} + {this._renderLabelProperties(true)} ); } @@ -470,7 +482,7 @@ export class VectorStyleEditor extends Component { {this._renderLineWidth()} - {this._renderLabelProperties()} + {this._renderLabelProperties(false)} ); } @@ -487,7 +499,7 @@ export class VectorStyleEditor extends Component { {this._renderLineWidth()} - {this._renderLabelProperties()} + {this._renderLabelProperties(false)} ); } diff --git a/x-pack/plugins/maps/public/connected_components/_index.scss b/x-pack/plugins/maps/public/connected_components/_index.scss index a1a65796dc94ac9..2a6e1a8982e6393 100644 --- a/x-pack/plugins/maps/public/connected_components/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/_index.scss @@ -1,6 +1,6 @@ @import 'map_container/map_container'; @import 'layer_panel/index'; -@import 'widget_overlay/index'; +@import 'right_side_controls/index'; @import 'toolbar_overlay/index'; @import 'mb_map/features_tooltip/index'; @import 'mb_map/scale_control/index'; diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 525ba394ed50370..e0cfe978bf45cf4 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -14,8 +14,7 @@ import uuid from 'uuid/v4'; import { Filter } from 'src/plugins/data/public'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; import { MBMap } from '../mb_map'; -// @ts-expect-error -import { WidgetOverlay } from '../widget_overlay'; +import { RightSideControls } from '../right_side_controls'; import { ToolbarOverlay } from '../toolbar_overlay'; // @ts-expect-error import { LayerPanel } from '../layer_panel'; @@ -263,7 +262,7 @@ export class MapContainer extends Component { getActionContext={getActionContext} /> )} - +
{ test('is rendered', async () => { - const mockLayer1 = { + const mockLayer1 = ({ getAttributions: async () => { return [{ url: '', label: 'attribution with no link' }]; }, - }; - const mockLayer2 = { + } as unknown) as ILayer; + const mockLayer2 = ({ getAttributions: async () => { return [{ url: 'https://coolmaps.com', label: 'attribution with link' }]; }, - }; - const component = shallowWithIntl(); + } as unknown) as ILayer; + const component = shallow( + + ); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js b/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/attribution_control.tsx similarity index 82% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js rename to x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/attribution_control.tsx index 2eb776134286a40..3d36f629446366c 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/attribution_control.tsx @@ -5,12 +5,24 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React, { Component, Fragment } from 'react'; import _ from 'lodash'; import { EuiText, EuiLink } from '@elastic/eui'; import classNames from 'classnames'; +import { Attribution } from '../../../classes/sources/source'; +import { ILayer } from '../../../classes/layers/layer'; -export class AttributionControl extends React.Component { +export interface Props { + isFullScreen: boolean; + layerList: ILayer[]; +} + +interface State { + uniqueAttributions: Attribution[]; +} + +export class AttributionControl extends Component { + private _isMounted = false; state = { uniqueAttributions: [], }; @@ -60,7 +72,7 @@ export class AttributionControl extends React.Component { } }; - _renderAttribution({ url, label }) { + _renderAttribution({ url, label }: Attribution) { if (!url) { return label; } @@ -90,6 +102,7 @@ export class AttributionControl extends React.Component { return (
diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/index.js b/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/index.ts similarity index 64% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/index.js rename to x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/index.ts index 32e93465c3c489b..9c1dfee6e011141 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/attribution_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/index.ts @@ -6,20 +6,17 @@ */ import { connect } from 'react-redux'; -import { AttributionControl } from './view'; +import { AttributionControl } from './attribution_control'; import { getLayerList } from '../../../selectors/map_selectors'; import { getIsFullScreen } from '../../../selectors/ui_selectors'; +import { MapStoreState } from '../../../reducers/store'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { layerList: getLayerList(state), isFullScreen: getIsFullScreen(state), }; } -function mapDispatchToProps() { - return {}; -} - -const connectedViewControl = connect(mapStateToProps, mapDispatchToProps)(AttributionControl); -export { connectedViewControl as AttributionControl }; +const connected = connect(mapStateToProps, {})(AttributionControl); +export { connected as AttributionControl }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/index.js b/x-pack/plugins/maps/public/connected_components/right_side_controls/index.ts similarity index 60% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/index.js rename to x-pack/plugins/maps/public/connected_components/right_side_controls/index.ts index d1f003ae4bc3d87..8b77726e5514dee 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/index.js +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/index.ts @@ -6,15 +6,15 @@ */ import { connect } from 'react-redux'; -import { WidgetOverlay } from './widget_overlay'; - +import { RightSideControls } from './right_side_controls'; import { getMapSettings } from '../../selectors/map_selectors'; +import { MapStoreState } from '../../reducers/store'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { settings: getMapSettings(state), }; } -const connectedWidgetOverlay = connect(mapStateToProps, null)(WidgetOverlay); -export { connectedWidgetOverlay as WidgetOverlay }; +const connected = connect(mapStateToProps, {})(RightSideControls); +export { connected as RightSideControls }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/layer_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/layer_control.test.tsx.snap rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/_index.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_index.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/_index.scss rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_index.scss diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/_layer_control.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/_layer_control.scss rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.ts b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/index.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.ts rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/index.ts diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_control.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_control.test.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_control.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_control.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.ts b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/index.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.ts rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/index.ts diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.test.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.test.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.test.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/_toc_entry.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/_toc_entry.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/_toc_entry.scss rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/_toc_entry.scss diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/action_labels.ts b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/action_labels.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/action_labels.ts rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/action_labels.ts diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.ts b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/index.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.ts rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/index.ts diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.test.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.test.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.test.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx rename to x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/_view_control.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/_mouse_coordinates_control.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/_view_control.scss rename to x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/_mouse_coordinates_control.scss diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/index.js b/x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/index.ts similarity index 61% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/index.js rename to x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/index.ts index a3a7865b61cb654..fa094dd0d6b7ff1 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/index.ts @@ -6,15 +6,16 @@ */ import { connect } from 'react-redux'; -import { ViewControl } from './view_control'; +import { MouseCoordinatesControl } from './mouse_coordinates_control'; import { getMouseCoordinates, getMapZoom } from '../../../selectors/map_selectors'; +import { MapStoreState } from '../../../reducers/store'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { mouseCoordinates: getMouseCoordinates(state), zoom: getMapZoom(state), }; } -const connectedViewControl = connect(mapStateToProps, null)(ViewControl); -export { connectedViewControl as ViewControl }; +const connected = connect(mapStateToProps, {})(MouseCoordinatesControl); +export { connected as MouseCoordinatesControl }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js b/x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/mouse_coordinates_control.tsx similarity index 87% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js rename to x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/mouse_coordinates_control.tsx index 409c6fd5ca44cbc..32c9f2f58ecf2e8 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/mouse_coordinates_control/mouse_coordinates_control.tsx @@ -8,10 +8,18 @@ import _ from 'lodash'; import React, { Fragment } from 'react'; import { EuiText } from '@elastic/eui'; -import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; +import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; + +export interface Props { + mouseCoordinates?: { + lat: number; + lon: number; + }; + zoom: number; +} -export function ViewControl({ mouseCoordinates, zoom }) { +export function MouseCoordinatesControl({ mouseCoordinates, zoom }: Props) { let latLon; if (mouseCoordinates) { latLon = ( diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/widget_overlay.js b/x-pack/plugins/maps/public/connected_components/right_side_controls/right_side_controls.tsx similarity index 71% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/widget_overlay.js rename to x-pack/plugins/maps/public/connected_components/right_side_controls/right_side_controls.tsx index f7a362c79dcc87e..12f283597f42adf 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/widget_overlay.js +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/right_side_controls.tsx @@ -8,10 +8,15 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { LayerControl } from './layer_control'; -import { ViewControl } from './view_control'; +import { MouseCoordinatesControl } from './mouse_coordinates_control'; import { AttributionControl } from './attribution_control'; +import { MapSettings } from '../../reducers/map'; -export function WidgetOverlay({ settings }) { +export interface Props { + settings: MapSettings; +} + +export function RightSideControls({ settings }: Props) { return ( {!settings.hideLayerControl && } - {!settings.hideViewControl && } + + {!settings.hideViewControl && } + diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 06a0f7e17e16494..8e5bf249ae2831d 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -26,9 +26,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiIcon, EuiSpacer, EuiPanel, EuiTitle, + EuiToolTip, EuiAccordion, EuiBadge, } from '@elastic/eui'; @@ -1259,9 +1261,21 @@ export class TimeSeriesExplorer extends React.Component { + + {i18n.translate('xpack.ml.timeSeriesExplorer.intervalLabel', { + defaultMessage: 'Interval', + })} + + + + } > diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts index e53c19f00d1b9c0..b369b20c122ebe7 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts @@ -71,6 +71,13 @@ type TestSubjects = | 'compressToggle' | 'fsRepositoryType' | 'locationInput' + | 'clientInput' + | 'containerInput' + | 'basePathInput' + | 'bucketInput' + | 'pathInput' + | 'uriInput' + | 'bufferSizeInput' | 'maxRestoreBytesInput' | 'maxSnapshotBytesInput' | 'nameInput' diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts index 9864b18c4b8cb7a..85d438fc5f3ae1f 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts @@ -193,7 +193,14 @@ describe('', () => { }); describe('form payload & api errors', () => { - const repository = getRepository(); + const fsRepository = getRepository({ + settings: { + chunkSize: '10mb', + location: '/tmp/es-backups', + maxSnapshotBytesPerSec: '1g', + maxRestoreBytesPerSec: '1g', + }, + }); beforeEach(async () => { httpRequestsMockHelpers.setLoadRepositoryTypesResponse(repositoryTypes); @@ -202,33 +209,237 @@ describe('', () => { }); describe('not source only', () => { - beforeEach(() => { + test('should send the correct payload for FS repository', async () => { + const { form, actions, component } = testBed; + // Fill step 1 required fields and go to step 2 - testBed.form.setInputValue('nameInput', repository.name); - testBed.actions.selectRepositoryType(repository.type); - testBed.actions.clickNextButton(); + form.setInputValue('nameInput', fsRepository.name); + actions.selectRepositoryType(fsRepository.type); + actions.clickNextButton(); + + // Fill step 2 + form.setInputValue('locationInput', fsRepository.settings.location); + form.toggleEuiSwitch('compressToggle'); + form.setInputValue('chunkSizeInput', fsRepository.settings.chunkSize); + form.setInputValue('maxSnapshotBytesInput', fsRepository.settings.maxSnapshotBytesPerSec); + form.setInputValue('maxRestoreBytesInput', fsRepository.settings.maxRestoreBytesPerSec); + form.toggleEuiSwitch('readOnlyToggle'); + + await act(async () => { + actions.clickSubmitButton(); + }); + + component.update(); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ + name: fsRepository.name, + type: fsRepository.type, + settings: { + ...fsRepository.settings, + compress: true, + readonly: true, + }, + }); }); - test('should send the correct payload', async () => { - const { form, actions } = testBed; + test('should send the correct payload for Azure repository', async () => { + const azureRepository = getRepository({ + type: 'azure', + settings: { + chunkSize: '10mb', + maxSnapshotBytesPerSec: '1g', + maxRestoreBytesPerSec: '1g', + client: 'client', + container: 'container', + basePath: 'path', + }, + }); + + const { form, actions, component } = testBed; + + // Fill step 1 required fields and go to step 2 + form.setInputValue('nameInput', azureRepository.name); + actions.selectRepositoryType(azureRepository.type); + actions.clickNextButton(); // Fill step 2 - form.setInputValue('locationInput', repository.settings.location); + form.setInputValue('clientInput', azureRepository.settings.client); + form.setInputValue('containerInput', azureRepository.settings.container); + form.setInputValue('basePathInput', azureRepository.settings.basePath); form.toggleEuiSwitch('compressToggle'); + form.setInputValue('chunkSizeInput', azureRepository.settings.chunkSize); + form.setInputValue( + 'maxSnapshotBytesInput', + azureRepository.settings.maxSnapshotBytesPerSec + ); + form.setInputValue('maxRestoreBytesInput', azureRepository.settings.maxRestoreBytesPerSec); + form.toggleEuiSwitch('readOnlyToggle'); await act(async () => { actions.clickSubmitButton(); - await nextTick(); }); + component.update(); + const latestRequest = server.requests[server.requests.length - 1]; expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: repository.name, - type: repository.type, + name: azureRepository.name, + type: azureRepository.type, settings: { - location: repository.settings.location, - compress: true, + ...azureRepository.settings, + compress: false, + readonly: true, + }, + }); + }); + + test('should send the correct payload for GCS repository', async () => { + const gcsRepository = getRepository({ + type: 'gcs', + settings: { + chunkSize: '10mb', + maxSnapshotBytesPerSec: '1g', + maxRestoreBytesPerSec: '1g', + client: 'test_client', + bucket: 'test_bucket', + basePath: 'test_path', + }, + }); + + const { form, actions, component } = testBed; + + // Fill step 1 required fields and go to step 2 + form.setInputValue('nameInput', gcsRepository.name); + actions.selectRepositoryType(gcsRepository.type); + actions.clickNextButton(); + + // Fill step 2 + form.setInputValue('clientInput', gcsRepository.settings.client); + form.setInputValue('bucketInput', gcsRepository.settings.bucket); + form.setInputValue('basePathInput', gcsRepository.settings.basePath); + form.toggleEuiSwitch('compressToggle'); + form.setInputValue('chunkSizeInput', gcsRepository.settings.chunkSize); + form.setInputValue('maxSnapshotBytesInput', gcsRepository.settings.maxSnapshotBytesPerSec); + form.setInputValue('maxRestoreBytesInput', gcsRepository.settings.maxRestoreBytesPerSec); + form.toggleEuiSwitch('readOnlyToggle'); + + await act(async () => { + actions.clickSubmitButton(); + }); + + component.update(); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ + name: gcsRepository.name, + type: gcsRepository.type, + settings: { + ...gcsRepository.settings, + compress: false, + readonly: true, + }, + }); + }); + + test('should send the correct payload for HDFS repository', async () => { + const hdfsRepository = getRepository({ + type: 'hdfs', + settings: { + uri: 'uri', + path: 'test_path', + chunkSize: '10mb', + maxSnapshotBytesPerSec: '1g', + maxRestoreBytesPerSec: '1g', + }, + }); + + const { form, actions, component } = testBed; + + // Fill step 1 required fields and go to step 2 + form.setInputValue('nameInput', hdfsRepository.name); + actions.selectRepositoryType(hdfsRepository.type); + actions.clickNextButton(); + + // Fill step 2 + form.setInputValue('uriInput', hdfsRepository.settings.uri); + form.setInputValue('pathInput', hdfsRepository.settings.path); + form.toggleEuiSwitch('compressToggle'); + form.setInputValue('chunkSizeInput', hdfsRepository.settings.chunkSize); + form.setInputValue('maxSnapshotBytesInput', hdfsRepository.settings.maxSnapshotBytesPerSec); + form.setInputValue('maxRestoreBytesInput', hdfsRepository.settings.maxRestoreBytesPerSec); + form.toggleEuiSwitch('readOnlyToggle'); + + await act(async () => { + actions.clickSubmitButton(); + }); + + component.update(); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ + name: hdfsRepository.name, + type: hdfsRepository.type, + settings: { + ...hdfsRepository.settings, + uri: `hdfs://${hdfsRepository.settings.uri}`, + compress: false, + readonly: true, + }, + }); + }); + + test('should send the correct payload for S3 repository', async () => { + const { form, actions, component } = testBed; + + const s3Repository = getRepository({ + type: 's3', + settings: { + bucket: 'test_bucket', + client: 'test_client', + basePath: 'test_path', + bufferSize: '1g', + chunkSize: '10mb', + maxSnapshotBytesPerSec: '1g', + maxRestoreBytesPerSec: '1g', + }, + }); + + // Fill step 1 required fields and go to step 2 + form.setInputValue('nameInput', s3Repository.name); + actions.selectRepositoryType(s3Repository.type); + actions.clickNextButton(); + + // Fill step 2 + form.setInputValue('bucketInput', s3Repository.settings.bucket); + form.setInputValue('clientInput', s3Repository.settings.client); + form.setInputValue('basePathInput', s3Repository.settings.basePath); + form.setInputValue('bufferSizeInput', s3Repository.settings.bufferSize); + form.toggleEuiSwitch('compressToggle'); + form.setInputValue('chunkSizeInput', s3Repository.settings.chunkSize); + form.setInputValue('maxSnapshotBytesInput', s3Repository.settings.maxSnapshotBytesPerSec); + form.setInputValue('maxRestoreBytesInput', s3Repository.settings.maxRestoreBytesPerSec); + form.toggleEuiSwitch('readOnlyToggle'); + + await act(async () => { + actions.clickSubmitButton(); + }); + + component.update(); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ + name: s3Repository.name, + type: s3Repository.type, + settings: { + ...s3Repository.settings, + compress: false, + readonly: true, }, }); }); @@ -236,7 +447,13 @@ describe('', () => { test('should surface the API errors from the "save" HTTP request', async () => { const { component, form, actions, find, exists } = testBed; - form.setInputValue('locationInput', repository.settings.location); + // Fill step 1 required fields and go to step 2 + form.setInputValue('nameInput', fsRepository.name); + actions.selectRepositoryType(fsRepository.type); + actions.clickNextButton(); + + // Fill step 2 + form.setInputValue('locationInput', fsRepository.settings.location); form.toggleEuiSwitch('compressToggle'); const error = { @@ -249,10 +466,10 @@ describe('', () => { await act(async () => { actions.clickSubmitButton(); - await nextTick(); - component.update(); }); + component.update(); + expect(exists('saveRepositoryApiError')).toBe(true); expect(find('saveRepositoryApiError').text()).toContain(error.message); }); @@ -261,31 +478,32 @@ describe('', () => { describe('source only', () => { beforeEach(() => { // Fill step 1 required fields and go to step 2 - testBed.form.setInputValue('nameInput', repository.name); - testBed.actions.selectRepositoryType(repository.type); + testBed.form.setInputValue('nameInput', fsRepository.name); + testBed.actions.selectRepositoryType(fsRepository.type); testBed.form.toggleEuiSwitch('sourceOnlyToggle'); // toggle source testBed.actions.clickNextButton(); }); test('should send the correct payload', async () => { - const { form, actions } = testBed; + const { form, actions, component } = testBed; // Fill step 2 - form.setInputValue('locationInput', repository.settings.location); + form.setInputValue('locationInput', fsRepository.settings.location); await act(async () => { actions.clickSubmitButton(); - await nextTick(); }); + component.update(); + const latestRequest = server.requests[server.requests.length - 1]; expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: repository.name, + name: fsRepository.name, type: 'source', settings: { - delegateType: repository.type, - location: repository.settings.location, + delegateType: fsRepository.type, + location: fsRepository.settings.location, }, }); }); diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/azure_settings.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/azure_settings.tsx index adbbe81176bdedf..b2657d0bfc0fb97 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/azure_settings.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/azure_settings.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { AzureRepository, Repository } from '../../../../../common/types'; import { RepositorySettingsValidation } from '../../../services/validation'; -import { textService } from '../../../services/text'; +import { ChunkSizeField, MaxSnapshotsField, MaxRestoreField } from './common'; interface Props { repository: AzureRepository; @@ -53,6 +53,12 @@ export const AzureSettings: React.FunctionComponent = ({ text: option, })); + const updateSettings = (name: string, value: string) => { + updateRepositorySettings({ + [name]: value, + }); + }; + return ( {/* Client field */} @@ -232,139 +238,28 @@ export const AzureSettings: React.FunctionComponent = ({ {/* Chunk size field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.chunkSize)} - error={settingErrors.chunkSize} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - chunkSize: e.target.value, - }); - }} - data-test-subj="chunkSizeInput" - /> - -
+ {/* Max snapshot bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxSnapshotBytesPerSec)} - error={settingErrors.maxSnapshotBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxSnapshotBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxSnapshotBytesInput" - /> - -
+ {/* Max restore bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxRestoreBytesPerSec)} - error={settingErrors.maxRestoreBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxRestoreBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxRestoreBytesInput" - /> - -
+ {/* Location mode field */} void; + error: RepositorySettingsValidation['chunkSize']; +} + +export const ChunkSizeField: React.FunctionComponent = ({ + isInvalid, + error, + defaultValue, + updateSettings, +}) => { + return ( + +

+ +

+ + } + description={ + + } + fullWidth + > + + } + fullWidth + isInvalid={isInvalid} + error={error} + helpText={ + 1g, + example2: 10mb, + example3: 5k, + example4: 1024B, + }} + /> + } + > + updateSettings('chunkSize', e.target.value)} + data-test-subj="chunkSizeInput" + /> + +
+ ); +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/index.ts b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/index.ts new file mode 100644 index 000000000000000..173e13b1b6e1770 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/index.ts @@ -0,0 +1,10 @@ +/* + * 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 { ChunkSizeField } from './chunk_size'; +export { MaxRestoreField } from './max_restore'; +export { MaxSnapshotsField } from './max_snapshots'; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/max_restore.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/max_restore.tsx new file mode 100644 index 000000000000000..281fe26d5b9d34d --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/max_restore.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiDescribedFormGroup, EuiFieldText, EuiFormRow, EuiTitle, EuiCode } from '@elastic/eui'; +import { RepositorySettingsValidation } from '../../../../services/validation'; + +interface Props { + isInvalid: boolean; + defaultValue: string; + updateSettings: (name: string, value: string) => void; + error: RepositorySettingsValidation['maxRestoreBytesPerSec']; +} + +export const MaxRestoreField: React.FunctionComponent = ({ + isInvalid, + error, + defaultValue, + updateSettings, +}) => { + return ( + +

+ +

+ + } + description={ + + } + fullWidth + > + + } + fullWidth + isInvalid={isInvalid} + error={error} + helpText={ + 1g, + example2: 10mb, + example3: 5k, + example4: 1024B, + }} + /> + } + > + updateSettings('maxRestoreBytesPerSec', e.target.value)} + data-test-subj="maxRestoreBytesInput" + /> + +
+ ); +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/max_snapshots.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/max_snapshots.tsx new file mode 100644 index 000000000000000..85b9153c711b9c8 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/common/max_snapshots.tsx @@ -0,0 +1,79 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import { EuiDescribedFormGroup, EuiFieldText, EuiFormRow, EuiTitle, EuiCode } from '@elastic/eui'; +import { RepositorySettingsValidation } from '../../../../services/validation'; + +interface Props { + isInvalid: boolean; + defaultValue: string; + updateSettings: (name: string, value: string) => void; + error: RepositorySettingsValidation['maxSnapshotBytesPerSec']; +} + +export const MaxSnapshotsField: React.FunctionComponent = ({ + isInvalid, + error, + defaultValue, + updateSettings, +}) => { + return ( + +

+ +

+ + } + description={ + + } + fullWidth + > + + } + fullWidth + isInvalid={isInvalid} + error={error} + helpText={ + 1g, + example2: 10mb, + example3: 5k, + example4: 1024B, + defaultSize: 40mb, + }} + /> + } + > + updateSettings('maxSnapshotBytesPerSec', e.target.value)} + data-test-subj="maxSnapshotBytesInput" + /> + +
+ ); +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/fs_settings.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/fs_settings.tsx index 2635cabfa1ef636..af3e6e82312624a 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/fs_settings.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/type_settings/fs_settings.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { FSRepository, Repository } from '../../../../../common/types'; import { RepositorySettingsValidation } from '../../../services/validation'; -import { textService } from '../../../services/text'; +import { ChunkSizeField, MaxRestoreField, MaxSnapshotsField } from './common'; interface Props { repository: FSRepository; @@ -44,6 +44,11 @@ export const FSSettings: React.FunctionComponent = ({ }, } = repository; const hasErrors: boolean = Boolean(Object.keys(settingErrors).length); + const updateSettings = (name: string, value: string) => { + updateRepositorySettings({ + [name]: value, + }); + }; return ( @@ -141,139 +146,28 @@ export const FSSettings: React.FunctionComponent = ({
{/* Chunk size field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.chunkSize)} - error={settingErrors.chunkSize} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - chunkSize: e.target.value, - }); - }} - data-test-subj="chunkSizeInput" - /> - -
+ {/* Max snapshot bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxSnapshotBytesPerSec)} - error={settingErrors.maxSnapshotBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxSnapshotBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxSnapshotBytesInput" - /> - -
+ {/* Max restore bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxRestoreBytesPerSec)} - error={settingErrors.maxRestoreBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxRestoreBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxRestoreBytesInput" - /> - -
+ {/* Readonly field */} = ({ } = repository; const hasErrors: boolean = Boolean(Object.keys(settingErrors).length); + const updateSettings = (name: string, value: string) => { + updateRepositorySettings({ + [name]: value, + }); + }; + return ( {/* Client field */} @@ -220,139 +226,28 @@ export const GCSSettings: React.FunctionComponent = ({ {/* Chunk size field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.chunkSize)} - error={settingErrors.chunkSize} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - chunkSize: e.target.value, - }); - }} - data-test-subj="chunkSizeInput" - /> - -
+ {/* Max snapshot bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxSnapshotBytesPerSec)} - error={settingErrors.maxSnapshotBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxSnapshotBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxSnapshotBytesInput" - /> - -
+ {/* Max restore bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxRestoreBytesPerSec)} - error={settingErrors.maxRestoreBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxRestoreBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxRestoreBytesInput" - /> - -
+ {/* Readonly field */} ; @@ -54,6 +54,11 @@ export const HDFSSettings: React.FunctionComponent = ({ }, } = repository; const hasErrors: boolean = Boolean(Object.keys(settingErrors).length); + const updateSettings = (settingName: string, value: string) => { + updateRepositorySettings({ + [settingName]: value, + }); + }; const [additionalConf, setAdditionalConf] = useState(JSON.stringify(rest, null, 2)); const [isConfInvalid, setIsConfInvalid] = useState(false); @@ -244,49 +249,12 @@ export const HDFSSettings: React.FunctionComponent = ({ {/* Chunk size field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.chunkSize)} - error={settingErrors.chunkSize} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - chunkSize: e.target.value, - }); - }} - data-test-subj="chunkSizeInput" - /> - -
+ {/* Security principal field */} = ({ {/* Max snapshot bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxSnapshotBytesPerSec)} - error={settingErrors.maxSnapshotBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxSnapshotBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxSnapshotBytesInput" - /> - -
+ {/* Max restore bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxRestoreBytesPerSec)} - error={settingErrors.maxRestoreBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxRestoreBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxRestoreBytesInput" - /> - -
+ {/* Readonly field */} = ({ text: option, })); const hasErrors: boolean = Boolean(Object.keys(settingErrors).length); + const updateSettings = (name: string, value: string) => { + updateRepositorySettings({ + [name]: value, + }); + }; const storageClassOptions = ['standard', 'reduced_redundancy', 'standard_ia'].map((option) => ({ value: option, @@ -249,49 +255,12 @@ export const S3Settings: React.FunctionComponent = ({ {/* Chunk size field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.chunkSize)} - error={settingErrors.chunkSize} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - chunkSize: e.target.value, - }); - }} - data-test-subj="chunkSizeInput" - /> - -
+ {/* Server side encryption field */} = ({ fullWidth isInvalid={Boolean(hasErrors && settingErrors.bufferSize)} error={settingErrors.bufferSize} - helpText={textService.getSizeNotationHelpText()} + helpText={ + 1g, + example2: 10mb, + example3: 5k, + example4: 1024B, + defaultSize: 100mb, + defaultPercentage: 5%, + }} + /> + } > = ({ {/* Max snapshot bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxSnapshotBytesPerSec)} - error={settingErrors.maxSnapshotBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxSnapshotBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxSnapshotBytesInput" - /> - -
+ {/* Max restore bytes field */} - -

- -

- - } - description={ - - } - fullWidth - > - - } - fullWidth - isInvalid={Boolean(hasErrors && settingErrors.maxRestoreBytesPerSec)} - error={settingErrors.maxRestoreBytesPerSec} - helpText={textService.getSizeNotationHelpText()} - > - { - updateRepositorySettings({ - maxRestoreBytesPerSec: e.target.value, - }); - }} - data-test-subj="maxRestoreBytesInput" - /> - -
+ {/* Readonly field */} 0 ? groupResult.metrics[0] : null; + const value = metric && metric.length === 2 ? metric[1] : null; + + if (!value) { + logger.debug( + `alert ${ID}:${alertId} "${name}": no metrics found for group ${instanceId}} from groupResult ${JSON.stringify( + groupResult + )}` + ); + continue; + } + const met = compareFn(value, params.threshold); if (!met) continue; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index bde4e24ecd96442..b66fa50ae168aa7 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20806,9 +20806,6 @@ "xpack.snapshotRestore.repositoryForm.typeAzure.basePathDescription": "レポジトリデータへのコンテナーパスです。", "xpack.snapshotRestore.repositoryForm.typeAzure.basePathLabel": "ベースパス", "xpack.snapshotRestore.repositoryForm.typeAzure.basePathTitle": "ベースパス", - "xpack.snapshotRestore.repositoryForm.typeAzure.chunkSizeDescription": "スナップショットの作成時にファイルを小さなユニットに分けます。", - "xpack.snapshotRestore.repositoryForm.typeAzure.chunkSizeLabel": "チャンクサイズ", - "xpack.snapshotRestore.repositoryForm.typeAzure.chunkSizeTitle": "チャンクサイズ", "xpack.snapshotRestore.repositoryForm.typeAzure.clientDescription": "Azure クライアントの名前です。", "xpack.snapshotRestore.repositoryForm.typeAzure.clientLabel": "クライアント", "xpack.snapshotRestore.repositoryForm.typeAzure.clientTitle": "クライアント", @@ -20821,29 +20818,14 @@ "xpack.snapshotRestore.repositoryForm.typeAzure.locationModeDescription": "プライマリまたはセカンダリの場所です。セカンダリの場合、読み込み専用が true です。", "xpack.snapshotRestore.repositoryForm.typeAzure.locationModeLabel": "位置情報モード", "xpack.snapshotRestore.repositoryForm.typeAzure.locationModeTitle": "位置情報モード", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxRestoreBytesDescription": "各ノードのスナップショットの復元レートです。", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxRestoreBytesLabel": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxRestoreBytesTitle": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxSnapshotBytesDescription": "各ノードのスナップショットの作成レートです。", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxSnapshotBytesLabel": "1 秒間の最高スナップショットバイト数", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxSnapshotBytesTitle": "1 秒間の最高スナップショットバイト数", "xpack.snapshotRestore.repositoryForm.typeAzure.readonlyDescription": "このレポジトリへの書き込みアクセスがあるクラスターは 1 つだけでなければなりません。他のクラスターはすべて読み込み専用にする必要があります。", "xpack.snapshotRestore.repositoryForm.typeAzure.readonlyLabel": "読み込み専用レポジトリ", "xpack.snapshotRestore.repositoryForm.typeAzure.readonlyTitle": "読み込み専用", - "xpack.snapshotRestore.repositoryForm.typeFS.chunkSizeDescription": "スナップショットの作成時にファイルを小さなユニットに分けます。", - "xpack.snapshotRestore.repositoryForm.typeFS.chunkSizeLabel": "チャンクサイズ", - "xpack.snapshotRestore.repositoryForm.typeFS.chunkSizeTitle": "チャンクサイズ", "xpack.snapshotRestore.repositoryForm.typeFS.compressDescription": "スナップショット用にインデックスのマッピングと設定ファイルを圧縮します。データファイルは圧縮されません。", "xpack.snapshotRestore.repositoryForm.typeFS.compressLabel": "スナップショットを圧縮", "xpack.snapshotRestore.repositoryForm.typeFS.compressTitle": "スナップショットの圧縮", "xpack.snapshotRestore.repositoryForm.typeFS.locationLabel": "場所 (必須) ", "xpack.snapshotRestore.repositoryForm.typeFS.locationTitle": "ファイルシステムの場所", - "xpack.snapshotRestore.repositoryForm.typeFS.maxRestoreBytesDescription": "各ノードのスナップショットの復元レートです。", - "xpack.snapshotRestore.repositoryForm.typeFS.maxRestoreBytesLabel": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeFS.maxRestoreBytesTitle": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeFS.maxSnapshotBytesDescription": "各ノードのスナップショットの作成レートです。", - "xpack.snapshotRestore.repositoryForm.typeFS.maxSnapshotBytesLabel": "1 秒間の最高スナップショットバイト数", - "xpack.snapshotRestore.repositoryForm.typeFS.maxSnapshotBytesTitle": "1 秒間の最高スナップショットバイト数", "xpack.snapshotRestore.repositoryForm.typeFS.readonlyDescription": "このレポジトリへの書き込みアクセスがあるクラスターは 1 つだけでなければなりません。他のクラスターはすべて読み込み専用にする必要があります。", "xpack.snapshotRestore.repositoryForm.typeFS.readonlyLabel": "読み込み専用レポジトリ", "xpack.snapshotRestore.repositoryForm.typeFS.readonlyTitle": "読み込み専用", @@ -20853,27 +20835,15 @@ "xpack.snapshotRestore.repositoryForm.typeGCS.bucketDescription": "スナップショットに使用する Google Cloud Storage バケットの名前です。", "xpack.snapshotRestore.repositoryForm.typeGCS.bucketLabel": "バケット (必須) ", "xpack.snapshotRestore.repositoryForm.typeGCS.bucketTitle": "バケット", - "xpack.snapshotRestore.repositoryForm.typeGCS.chunkSizeDescription": "スナップショットの作成時にファイルを小さなユニットに分けます。", - "xpack.snapshotRestore.repositoryForm.typeGCS.chunkSizeLabel": "チャンクサイズ", - "xpack.snapshotRestore.repositoryForm.typeGCS.chunkSizeTitle": "チャンクサイズ", "xpack.snapshotRestore.repositoryForm.typeGCS.clientDescription": "Google Cloud Storage クライアントの名前です。", "xpack.snapshotRestore.repositoryForm.typeGCS.clientLabel": "クライアント", "xpack.snapshotRestore.repositoryForm.typeGCS.clientTitle": "クライアント", "xpack.snapshotRestore.repositoryForm.typeGCS.compressDescription": "スナップショット用にインデックスのマッピングと設定ファイルを圧縮します。データファイルは圧縮されません。", "xpack.snapshotRestore.repositoryForm.typeGCS.compressLabel": "スナップショットを圧縮", "xpack.snapshotRestore.repositoryForm.typeGCS.compressTitle": "スナップショットを圧縮", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxRestoreBytesDescription": "各ノードのスナップショットの復元レートです。", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxRestoreBytesLabel": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxRestoreBytesTitle": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxSnapshotBytesDescription": "各ノードのスナップショットの作成レートです。", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxSnapshotBytesLabel": "1 秒間の最高スナップショットバイト数", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxSnapshotBytesTitle": "1 秒間の最高スナップショットバイト数", "xpack.snapshotRestore.repositoryForm.typeGCS.readonlyDescription": "このレポジトリへの書き込みアクセスがあるクラスターは 1 つだけでなければなりません。他のクラスターはすべて読み込み専用にする必要があります。", "xpack.snapshotRestore.repositoryForm.typeGCS.readonlyLabel": "読み込み専用レポジトリ", "xpack.snapshotRestore.repositoryForm.typeGCS.readonlyTitle": "読み込み専用", - "xpack.snapshotRestore.repositoryForm.typeHDFS.chunkSizeDescription": "スナップショットの作成時にファイルを小さなユニットに分けます。", - "xpack.snapshotRestore.repositoryForm.typeHDFS.chunkSizeLabel": "チャンクサイズ", - "xpack.snapshotRestore.repositoryForm.typeHDFS.chunkSizeTitle": "チャンクサイズ", "xpack.snapshotRestore.repositoryForm.typeHDFS.compressDescription": "スナップショット用にインデックスのマッピングと設定ファイルを圧縮します。データファイルは圧縮されません。", "xpack.snapshotRestore.repositoryForm.typeHDFS.compressLabel": "スナップショットを圧縮", "xpack.snapshotRestore.repositoryForm.typeHDFS.compressTitle": "スナップショットの圧縮", @@ -20886,12 +20856,6 @@ "xpack.snapshotRestore.repositoryForm.typeHDFS.loadDefaultsDescription": "デフォルトの Hadoop 構成を読み込みます。", "xpack.snapshotRestore.repositoryForm.typeHDFS.loadDefaultsLabel": "デフォルトを読み込む", "xpack.snapshotRestore.repositoryForm.typeHDFS.loadDefaultsTitle": "デフォルトを読み込む", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxRestoreBytesDescription": "各ノードのスナップショットの復元レートです。", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxRestoreBytesLabel": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxRestoreBytesTitle": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxSnapshotBytesDescription": "各ノードのスナップショットの作成レートです。", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxSnapshotBytesLabel": "1 秒間の最高スナップショットバイト数", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxSnapshotBytesTitle": "1 秒間の最高スナップショットバイト数", "xpack.snapshotRestore.repositoryForm.typeHDFS.pathDescription": "データが保管されているファイルへのパスです。", "xpack.snapshotRestore.repositoryForm.typeHDFS.pathLabel": "パス (必須) ", "xpack.snapshotRestore.repositoryForm.typeHDFS.pathTitle": "パス", @@ -20922,21 +20886,12 @@ "xpack.snapshotRestore.repositoryForm.typeS3.cannedAclDescription": "新しい S3 バケットとオブジェクトに追加する canned ACL です。", "xpack.snapshotRestore.repositoryForm.typeS3.cannedAclLabel": "Canned ACL", "xpack.snapshotRestore.repositoryForm.typeS3.cannedAclTitle": "Canned ACL", - "xpack.snapshotRestore.repositoryForm.typeS3.chunkSizeDescription": "スナップショットの作成時にファイルを小さなユニットに分けます。", - "xpack.snapshotRestore.repositoryForm.typeS3.chunkSizeLabel": "チャンクサイズ", - "xpack.snapshotRestore.repositoryForm.typeS3.chunkSizeTitle": "チャンクサイズ", "xpack.snapshotRestore.repositoryForm.typeS3.clientDescription": "AWS S3 クライアントの名前です。", "xpack.snapshotRestore.repositoryForm.typeS3.clientLabel": "クライアント", "xpack.snapshotRestore.repositoryForm.typeS3.clientTitle": "クライアント", "xpack.snapshotRestore.repositoryForm.typeS3.compressDescription": "スナップショット用にインデックスのマッピングと設定ファイルを圧縮します。データファイルは圧縮されません。", "xpack.snapshotRestore.repositoryForm.typeS3.compressLabel": "スナップショットを圧縮", "xpack.snapshotRestore.repositoryForm.typeS3.compressTitle": "スナップショットの圧縮", - "xpack.snapshotRestore.repositoryForm.typeS3.maxRestoreBytesDescription": "各ノードのスナップショットの復元レートです。", - "xpack.snapshotRestore.repositoryForm.typeS3.maxRestoreBytesLabel": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeS3.maxRestoreBytesTitle": "1 秒間の最高復元バイト数", - "xpack.snapshotRestore.repositoryForm.typeS3.maxSnapshotBytesDescription": "各ノードのスナップショットの作成レートです。", - "xpack.snapshotRestore.repositoryForm.typeS3.maxSnapshotBytesLabel": "1 秒間の最高スナップショットバイト数", - "xpack.snapshotRestore.repositoryForm.typeS3.maxSnapshotBytesTitle": "1 秒間の最高スナップショットバイト数", "xpack.snapshotRestore.repositoryForm.typeS3.readonlyDescription": "このレポジトリへの書き込みアクセスがあるクラスターは 1 つだけでなければなりません。他のクラスターはすべて読み込み専用にする必要があります。", "xpack.snapshotRestore.repositoryForm.typeS3.readonlyLabel": "読み込み専用レポジトリ", "xpack.snapshotRestore.repositoryForm.typeS3.readonlyTitle": "読み込み専用", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 43d537ad560cde7..5fc97e979208316 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -21143,9 +21143,6 @@ "xpack.snapshotRestore.repositoryForm.typeAzure.basePathDescription": "存储库数据的容器路径。", "xpack.snapshotRestore.repositoryForm.typeAzure.basePathLabel": "基路径", "xpack.snapshotRestore.repositoryForm.typeAzure.basePathTitle": "基路径", - "xpack.snapshotRestore.repositoryForm.typeAzure.chunkSizeDescription": "拍取快照时,将文件拆分成更小的单位。", - "xpack.snapshotRestore.repositoryForm.typeAzure.chunkSizeLabel": "块大小", - "xpack.snapshotRestore.repositoryForm.typeAzure.chunkSizeTitle": "块大小", "xpack.snapshotRestore.repositoryForm.typeAzure.clientDescription": "Azure 客户端的名称。", "xpack.snapshotRestore.repositoryForm.typeAzure.clientLabel": "客户端", "xpack.snapshotRestore.repositoryForm.typeAzure.clientTitle": "客户端", @@ -21158,29 +21155,14 @@ "xpack.snapshotRestore.repositoryForm.typeAzure.locationModeDescription": "主要位置或辅助位置。如果为辅助位置,则只读为 true。", "xpack.snapshotRestore.repositoryForm.typeAzure.locationModeLabel": "位置模式", "xpack.snapshotRestore.repositoryForm.typeAzure.locationModeTitle": "位置模式", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxRestoreBytesDescription": "每个节点的快照还原速率。", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxRestoreBytesLabel": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxRestoreBytesTitle": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxSnapshotBytesDescription": "为每个节点创建快照的速率。", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxSnapshotBytesLabel": "每秒最大快照字节数", - "xpack.snapshotRestore.repositoryForm.typeAzure.maxSnapshotBytesTitle": "每秒最大快照字节数", "xpack.snapshotRestore.repositoryForm.typeAzure.readonlyDescription": "仅一个集群应对此存储库有写权限。所有其他集群应为只读。", "xpack.snapshotRestore.repositoryForm.typeAzure.readonlyLabel": "只读存储库", "xpack.snapshotRestore.repositoryForm.typeAzure.readonlyTitle": "只读", - "xpack.snapshotRestore.repositoryForm.typeFS.chunkSizeDescription": "拍取快照时,将文件拆分成更小的单位。", - "xpack.snapshotRestore.repositoryForm.typeFS.chunkSizeLabel": "块大小", - "xpack.snapshotRestore.repositoryForm.typeFS.chunkSizeTitle": "块大小", "xpack.snapshotRestore.repositoryForm.typeFS.compressDescription": "压缩快照的索引映射和设置文件。数据文件未压缩。", "xpack.snapshotRestore.repositoryForm.typeFS.compressLabel": "压缩快照", "xpack.snapshotRestore.repositoryForm.typeFS.compressTitle": "快照压缩", "xpack.snapshotRestore.repositoryForm.typeFS.locationLabel": "位置 (必填) ", "xpack.snapshotRestore.repositoryForm.typeFS.locationTitle": "文件系统位置", - "xpack.snapshotRestore.repositoryForm.typeFS.maxRestoreBytesDescription": "每个节点的快照还原速率。", - "xpack.snapshotRestore.repositoryForm.typeFS.maxRestoreBytesLabel": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeFS.maxRestoreBytesTitle": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeFS.maxSnapshotBytesDescription": "为每个节点创建快照的速率。", - "xpack.snapshotRestore.repositoryForm.typeFS.maxSnapshotBytesLabel": "每秒最大快照字节数", - "xpack.snapshotRestore.repositoryForm.typeFS.maxSnapshotBytesTitle": "每秒最大快照字节数", "xpack.snapshotRestore.repositoryForm.typeFS.readonlyDescription": "仅一个集群应对此存储库有写权限。所有其他集群应为只读。", "xpack.snapshotRestore.repositoryForm.typeFS.readonlyLabel": "只读存储库", "xpack.snapshotRestore.repositoryForm.typeFS.readonlyTitle": "只读", @@ -21190,27 +21172,15 @@ "xpack.snapshotRestore.repositoryForm.typeGCS.bucketDescription": "要用于快照的 Google Cloud Storage 存储桶的名称。", "xpack.snapshotRestore.repositoryForm.typeGCS.bucketLabel": "存储桶 (必填) ", "xpack.snapshotRestore.repositoryForm.typeGCS.bucketTitle": "存储桶", - "xpack.snapshotRestore.repositoryForm.typeGCS.chunkSizeDescription": "拍取快照时,将文件拆分成更小的单位。", - "xpack.snapshotRestore.repositoryForm.typeGCS.chunkSizeLabel": "块大小", - "xpack.snapshotRestore.repositoryForm.typeGCS.chunkSizeTitle": "块大小", "xpack.snapshotRestore.repositoryForm.typeGCS.clientDescription": "Google Cloud Storage 客户端的名称。", "xpack.snapshotRestore.repositoryForm.typeGCS.clientLabel": "客户端", "xpack.snapshotRestore.repositoryForm.typeGCS.clientTitle": "客户端", "xpack.snapshotRestore.repositoryForm.typeGCS.compressDescription": "压缩快照的索引映射和设置文件。数据文件未压缩。", "xpack.snapshotRestore.repositoryForm.typeGCS.compressLabel": "压缩快照", "xpack.snapshotRestore.repositoryForm.typeGCS.compressTitle": "压缩快照", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxRestoreBytesDescription": "每个节点的快照还原速率。", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxRestoreBytesLabel": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxRestoreBytesTitle": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxSnapshotBytesDescription": "为每个节点创建快照的速率。", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxSnapshotBytesLabel": "每秒最大快照字节数", - "xpack.snapshotRestore.repositoryForm.typeGCS.maxSnapshotBytesTitle": "每秒最大快照字节数", "xpack.snapshotRestore.repositoryForm.typeGCS.readonlyDescription": "仅一个集群应对此存储库有写权限。所有其他集群应为只读。", "xpack.snapshotRestore.repositoryForm.typeGCS.readonlyLabel": "只读存储库", "xpack.snapshotRestore.repositoryForm.typeGCS.readonlyTitle": "只读", - "xpack.snapshotRestore.repositoryForm.typeHDFS.chunkSizeDescription": "拍取快照时,将文件拆分成更小的单位。", - "xpack.snapshotRestore.repositoryForm.typeHDFS.chunkSizeLabel": "块大小", - "xpack.snapshotRestore.repositoryForm.typeHDFS.chunkSizeTitle": "块大小", "xpack.snapshotRestore.repositoryForm.typeHDFS.compressDescription": "压缩快照的索引映射和设置文件。数据文件未压缩。", "xpack.snapshotRestore.repositoryForm.typeHDFS.compressLabel": "压缩快照", "xpack.snapshotRestore.repositoryForm.typeHDFS.compressTitle": "快照压缩", @@ -21223,12 +21193,6 @@ "xpack.snapshotRestore.repositoryForm.typeHDFS.loadDefaultsDescription": "加载默认的 Hadoop 配置。", "xpack.snapshotRestore.repositoryForm.typeHDFS.loadDefaultsLabel": "加载默认值", "xpack.snapshotRestore.repositoryForm.typeHDFS.loadDefaultsTitle": "加载默认值", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxRestoreBytesDescription": "每个节点的快照还原速率。", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxRestoreBytesLabel": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxRestoreBytesTitle": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxSnapshotBytesDescription": "为每个节点创建快照的速率。", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxSnapshotBytesLabel": "每秒最大快照字节数", - "xpack.snapshotRestore.repositoryForm.typeHDFS.maxSnapshotBytesTitle": "每秒最大快照字节数", "xpack.snapshotRestore.repositoryForm.typeHDFS.pathDescription": "存储数据的文件路径。", "xpack.snapshotRestore.repositoryForm.typeHDFS.pathLabel": "路径 (必填) ", "xpack.snapshotRestore.repositoryForm.typeHDFS.pathTitle": "路径", @@ -21259,21 +21223,12 @@ "xpack.snapshotRestore.repositoryForm.typeS3.cannedAclDescription": "要添加到新 S3 存储库和对象的标准 ACL。", "xpack.snapshotRestore.repositoryForm.typeS3.cannedAclLabel": "标准 ACL", "xpack.snapshotRestore.repositoryForm.typeS3.cannedAclTitle": "标准 ACL", - "xpack.snapshotRestore.repositoryForm.typeS3.chunkSizeDescription": "拍取快照时,将文件拆分成更小的单位。", - "xpack.snapshotRestore.repositoryForm.typeS3.chunkSizeLabel": "块大小", - "xpack.snapshotRestore.repositoryForm.typeS3.chunkSizeTitle": "块大小", "xpack.snapshotRestore.repositoryForm.typeS3.clientDescription": "AWS S3 客户端的名称。", "xpack.snapshotRestore.repositoryForm.typeS3.clientLabel": "客户端", "xpack.snapshotRestore.repositoryForm.typeS3.clientTitle": "客户端", "xpack.snapshotRestore.repositoryForm.typeS3.compressDescription": "压缩快照的索引映射和设置文件。数据文件未压缩。", "xpack.snapshotRestore.repositoryForm.typeS3.compressLabel": "压缩快照", "xpack.snapshotRestore.repositoryForm.typeS3.compressTitle": "快照压缩", - "xpack.snapshotRestore.repositoryForm.typeS3.maxRestoreBytesDescription": "每个节点的快照还原速率。", - "xpack.snapshotRestore.repositoryForm.typeS3.maxRestoreBytesLabel": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeS3.maxRestoreBytesTitle": "每秒最大还原字节数", - "xpack.snapshotRestore.repositoryForm.typeS3.maxSnapshotBytesDescription": "为每个节点创建快照的速率。", - "xpack.snapshotRestore.repositoryForm.typeS3.maxSnapshotBytesLabel": "每秒最大快照字节数", - "xpack.snapshotRestore.repositoryForm.typeS3.maxSnapshotBytesTitle": "每秒最大快照字节数", "xpack.snapshotRestore.repositoryForm.typeS3.readonlyDescription": "仅一个集群应对此存储库有写权限。所有其他集群应为只读。", "xpack.snapshotRestore.repositoryForm.typeS3.readonlyLabel": "只读存储库", "xpack.snapshotRestore.repositoryForm.typeS3.readonlyTitle": "只读", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts index 679bc3d53c40da3..38d65b923b37491 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts @@ -99,10 +99,10 @@ describe('Jira API', () => { test('should call get issue types API', async () => { const abortCtrl = new AbortController(); http.post.mockResolvedValueOnce(issueTypesResponse); - const res = await getIssueTypes({ http, signal: abortCtrl.signal, connectorId: 'test' }); + const res = await getIssueTypes({ http, signal: abortCtrl.signal, connectorId: 'te/st' }); expect(res).toEqual(issueTypesResponse); - expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"issueTypes","subActionParams":{}}}', signal: abortCtrl.signal, }); @@ -116,12 +116,12 @@ describe('Jira API', () => { const res = await getFieldsByIssueType({ http, signal: abortCtrl.signal, - connectorId: 'test', + connectorId: 'te/st', id: '10006', }); expect(res).toEqual(fieldsResponse); - expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"fieldsByIssueType","subActionParams":{"id":"10006"}}}', signal: abortCtrl.signal, }); @@ -135,12 +135,12 @@ describe('Jira API', () => { const res = await getIssues({ http, signal: abortCtrl.signal, - connectorId: 'test', + connectorId: 'te/st', title: 'test issue', }); expect(res).toEqual(issuesResponse); - expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"issues","subActionParams":{"title":"test issue"}}}', signal: abortCtrl.signal, }); @@ -154,12 +154,12 @@ describe('Jira API', () => { const res = await getIssue({ http, signal: abortCtrl.signal, - connectorId: 'test', + connectorId: 'te/st', id: 'RJ-107', }); expect(res).toEqual(issuesResponse); - expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"issue","subActionParams":{"id":"RJ-107"}}}', signal: abortCtrl.signal, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts index 46ea9dea3aa56cc..83e126ea9d2f6db 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.ts @@ -17,12 +17,15 @@ export async function getIssueTypes({ signal: AbortSignal; connectorId: string; }): Promise> { - return await http.post(`${BASE_ACTION_API_PATH}/connector/${connectorId}/_execute`, { - body: JSON.stringify({ - params: { subAction: 'issueTypes', subActionParams: {} }, - }), - signal, - }); + return await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, + { + body: JSON.stringify({ + params: { subAction: 'issueTypes', subActionParams: {} }, + }), + signal, + } + ); } export async function getFieldsByIssueType({ @@ -36,12 +39,15 @@ export async function getFieldsByIssueType({ connectorId: string; id: string; }): Promise> { - return await http.post(`${BASE_ACTION_API_PATH}/connector/${connectorId}/_execute`, { - body: JSON.stringify({ - params: { subAction: 'fieldsByIssueType', subActionParams: { id } }, - }), - signal, - }); + return await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, + { + body: JSON.stringify({ + params: { subAction: 'fieldsByIssueType', subActionParams: { id } }, + }), + signal, + } + ); } export async function getIssues({ @@ -55,12 +61,15 @@ export async function getIssues({ connectorId: string; title: string; }): Promise> { - return await http.post(`${BASE_ACTION_API_PATH}/connector/${connectorId}/_execute`, { - body: JSON.stringify({ - params: { subAction: 'issues', subActionParams: { title } }, - }), - signal, - }); + return await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, + { + body: JSON.stringify({ + params: { subAction: 'issues', subActionParams: { title } }, + }), + signal, + } + ); } export async function getIssue({ @@ -74,10 +83,13 @@ export async function getIssue({ connectorId: string; id: string; }): Promise> { - return await http.post(`${BASE_ACTION_API_PATH}/connector/${connectorId}/_execute`, { - body: JSON.stringify({ - params: { subAction: 'issue', subActionParams: { id } }, - }), - signal, - }); + return await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, + { + body: JSON.stringify({ + params: { subAction: 'issue', subActionParams: { id } }, + }), + signal, + } + ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts index 01208f93405d254..0d4bf9148a92ff0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts @@ -32,7 +32,7 @@ const incidentTypesResponse = { { id: 16, name: 'TBD / Unknown' }, { id: 15, name: 'Vendor / 3rd party error' }, ], - actionId: 'test', + actionId: 'te/st', }; const severityResponse = { @@ -42,7 +42,7 @@ const severityResponse = { { id: 5, name: 'Medium' }, { id: 6, name: 'High' }, ], - actionId: 'test', + actionId: 'te/st', }; describe('Resilient API', () => { @@ -57,11 +57,11 @@ describe('Resilient API', () => { const res = await getIncidentTypes({ http, signal: abortCtrl.signal, - connectorId: 'test', + connectorId: 'te/st', }); expect(res).toEqual(incidentTypesResponse); - expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"incidentTypes","subActionParams":{}}}', signal: abortCtrl.signal, }); @@ -75,11 +75,11 @@ describe('Resilient API', () => { const res = await getSeverity({ http, signal: abortCtrl.signal, - connectorId: 'test', + connectorId: 'te/st', }); expect(res).toEqual(severityResponse); - expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"severity","subActionParams":{}}}', signal: abortCtrl.signal, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts index 8ea3c3c63e50f0f..6bd9c43105cf0e2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.ts @@ -17,12 +17,15 @@ export async function getIncidentTypes({ signal: AbortSignal; connectorId: string; }): Promise> { - return await http.post(`${BASE_ACTION_API_PATH}/connector/${connectorId}/_execute`, { - body: JSON.stringify({ - params: { subAction: 'incidentTypes', subActionParams: {} }, - }), - signal, - }); + return await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, + { + body: JSON.stringify({ + params: { subAction: 'incidentTypes', subActionParams: {} }, + }), + signal, + } + ); } export async function getSeverity({ @@ -34,10 +37,13 @@ export async function getSeverity({ signal: AbortSignal; connectorId: string; }): Promise> { - return await http.post(`${BASE_ACTION_API_PATH}/connector/${connectorId}/_execute`, { - body: JSON.stringify({ - params: { subAction: 'severity', subActionParams: {} }, - }), - signal, - }); + return await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, + { + body: JSON.stringify({ + params: { subAction: 'severity', subActionParams: {} }, + }), + signal, + } + ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts index 5c814bbfd64505f..ba820efc8111fe3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts @@ -56,12 +56,12 @@ describe('ServiceNow API', () => { const res = await getChoices({ http, signal: abortCtrl.signal, - connectorId: 'test', + connectorId: 'te/st', fields: ['priority'], }); expect(res).toEqual(choicesResponse); - expect(http.post).toHaveBeenCalledWith('/api/actions/connector/test/_execute', { + expect(http.post).toHaveBeenCalledWith('/api/actions/connector/te%2Fst/_execute', { body: '{"params":{"subAction":"getChoices","subActionParams":{"fields":["priority"]}}}', signal: abortCtrl.signal, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts index bb9091559128545..62347580e75ca13 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts @@ -19,10 +19,13 @@ export async function getChoices({ connectorId: string; fields: string[]; }): Promise> { - return await http.post(`${BASE_ACTION_API_PATH}/connector/${connectorId}/_execute`, { - body: JSON.stringify({ - params: { subAction: 'getChoices', subActionParams: { fields } }, - }), - signal, - }); + return await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(connectorId)}/_execute`, + { + body: JSON.stringify({ + params: { subAction: 'getChoices', subActionParams: { fields } }, + }), + signal, + } + ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.test.ts index bb00c8c30e4edef..ba4c62471555b4e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.test.ts @@ -14,7 +14,7 @@ beforeEach(() => jest.resetAllMocks()); describe('deleteActions', () => { test('should call delete API per action', async () => { - const ids = ['1', '2', '3']; + const ids = ['1', '2', '/']; const result = await deleteActions({ ids, http }); expect(result).toEqual({ errors: [], successes: [undefined, undefined, undefined] }); @@ -27,7 +27,7 @@ describe('deleteActions', () => { "/api/actions/connector/2", ], Array [ - "/api/actions/connector/3", + "/api/actions/connector/%2F", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts index c9c25db676a06dd..868e5390045ccd8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/delete.ts @@ -16,7 +16,9 @@ export async function deleteActions({ }): Promise<{ successes: string[]; errors: string[] }> { const successes: string[] = []; const errors: string[] = []; - await Promise.all(ids.map((id) => http.delete(`${BASE_ACTION_API_PATH}/connector/${id}`))).then( + await Promise.all( + ids.map((id) => http.delete(`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`)) + ).then( function (fulfilled) { successes.push(...fulfilled); }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.test.ts index 60cd3132aa756b9..2b0cdcb2ca69b4a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.test.ts @@ -14,7 +14,7 @@ beforeEach(() => jest.resetAllMocks()); describe('executeAction', () => { test('should call execute API', async () => { - const id = '123'; + const id = '12/3'; const params = { stringParams: 'someString', numericParams: 123, @@ -32,7 +32,7 @@ describe('executeAction', () => { }); expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/actions/connector/123/_execute", + "/api/actions/connector/12%2F3/_execute", Object { "body": "{\\"params\\":{\\"stringParams\\":\\"someString\\",\\"numericParams\\":123}}", }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts index 638ceddb5652fb5..d97ad7d5962b74a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/execute.ts @@ -31,8 +31,11 @@ export async function executeAction({ http: HttpSetup; params: Record; }): Promise> { - const res = await http.post(`${BASE_ACTION_API_PATH}/connector/${id}/_execute`, { - body: JSON.stringify({ params }), - }); + const res = await http.post( + `${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}/_execute`, + { + body: JSON.stringify({ params }), + } + ); return rewriteBodyRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts index 29e7a1e4bed3d09..3cee8d225b001e2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts @@ -15,9 +15,9 @@ beforeEach(() => jest.resetAllMocks()); describe('updateActionConnector', () => { test('should call the update API', async () => { - const id = '123'; + const id = '12/3'; const apiResponse = { - connector_type_id: 'test', + connector_type_id: 'te/st', is_preconfigured: false, name: 'My test', config: {}, @@ -27,7 +27,7 @@ describe('updateActionConnector', () => { http.put.mockResolvedValueOnce(apiResponse); const connector: ActionConnectorWithoutId<{}, {}> = { - actionTypeId: 'test', + actionTypeId: 'te/st', isPreconfigured: false, name: 'My test', config: {}, @@ -39,7 +39,7 @@ describe('updateActionConnector', () => { expect(result).toEqual(resolvedValue); expect(http.put.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/actions/connector/123", + "/api/actions/connector/12%2F3", Object { "body": "{\\"name\\":\\"My test\\",\\"config\\":{},\\"secrets\\":{}}", }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts index 18b8871ce25d1ce..1bc0cefc2723b2e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts @@ -30,7 +30,7 @@ export async function updateActionConnector({ connector: Pick; id: string; }): Promise { - const res = await http.put(`${BASE_ACTION_API_PATH}/connector/${id}`, { + const res = await http.put(`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`, { body: JSON.stringify({ name: connector.name, config: connector.config, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.test.ts index e94da81d0f5d510..c7b987f2b04bdfb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.test.ts @@ -18,7 +18,7 @@ describe('loadAlertInstanceSummary', () => { consumer: 'alerts', enabled: true, errorMessages: [], - id: 'test', + id: 'te/st', lastRun: '2021-04-01T22:18:27.609Z', muteAll: false, name: 'test', @@ -35,7 +35,7 @@ describe('loadAlertInstanceSummary', () => { consumer: 'alerts', enabled: true, error_messages: [], - id: 'test', + id: 'te/st', last_run: '2021-04-01T22:18:27.609Z', mute_all: false, name: 'test', @@ -47,11 +47,11 @@ describe('loadAlertInstanceSummary', () => { throttle: null, }); - const result = await loadAlertInstanceSummary({ http, alertId: 'test' }); + const result = await loadAlertInstanceSummary({ http, alertId: 'te/st' }); expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/internal/alerting/rule/test/_alert_summary", + "/internal/alerting/rule/te%2Fst/_alert_summary", ] `); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts index e37c0640ec1c8f4..cb924db74cea557 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/alert_summary.ts @@ -36,6 +36,8 @@ export async function loadAlertInstanceSummary({ http: HttpSetup; alertId: string; }): Promise { - const res = await http.get(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${alertId}/_alert_summary`); + const res = await http.get( + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(alertId)}/_alert_summary` + ); return rewriteBodyRes(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.test.ts index b279e4c0237d967..11e5f4763e775e3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.test.ts @@ -12,7 +12,7 @@ const http = httpServiceMock.createStartContract(); describe('deleteAlerts', () => { test('should call delete API for each alert', async () => { - const ids = ['1', '2', '3']; + const ids = ['1', '2', '/']; const result = await deleteAlerts({ http, ids }); expect(result).toEqual({ errors: [], successes: [undefined, undefined, undefined] }); expect(http.delete.mock.calls).toMatchInlineSnapshot(` @@ -24,7 +24,7 @@ describe('deleteAlerts', () => { "/api/alerting/rule/2", ], Array [ - "/api/alerting/rule/3", + "/api/alerting/rule/%2F", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts index 870d5a409c3dda3..b853e722e6fc366 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/delete.ts @@ -16,7 +16,9 @@ export async function deleteAlerts({ }): Promise<{ successes: string[]; errors: string[] }> { const successes: string[] = []; const errors: string[] = []; - await Promise.all(ids.map((id) => http.delete(`${BASE_ALERTING_API_PATH}/rule/${id}`))).then( + await Promise.all( + ids.map((id) => http.delete(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`)) + ).then( function (fulfilled) { successes.push(...fulfilled); }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.test.ts index 90d1cd13096e848..4323816221c6ed6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.test.ts @@ -13,12 +13,12 @@ beforeEach(() => jest.resetAllMocks()); describe('disableAlert', () => { test('should call disable alert API', async () => { - const result = await disableAlert({ http, id: '1' }); + const result = await disableAlert({ http, id: '1/' }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alerting/rule/1/_disable", + "/api/alerting/rule/1%2F/_disable", ], ] `); @@ -27,7 +27,7 @@ describe('disableAlert', () => { describe('disableAlerts', () => { test('should call disable alert API per alert', async () => { - const ids = ['1', '2', '3']; + const ids = ['1', '2', '/']; const result = await disableAlerts({ http, ids }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` @@ -39,7 +39,7 @@ describe('disableAlerts', () => { "/api/alerting/rule/2/_disable", ], Array [ - "/api/alerting/rule/3/_disable", + "/api/alerting/rule/%2F/_disable", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.ts index cc0939fbebfbded..758e66644b34e79 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/disable.ts @@ -8,7 +8,7 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ALERTING_API_PATH } from '../../constants'; export async function disableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_disable`); + await http.post(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/_disable`); } export async function disableAlerts({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.test.ts index ef65e8b605cba4a..3a54a0772664b87 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.test.ts @@ -13,12 +13,12 @@ beforeEach(() => jest.resetAllMocks()); describe('enableAlert', () => { test('should call enable alert API', async () => { - const result = await enableAlert({ http, id: '1' }); + const result = await enableAlert({ http, id: '1/' }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alerting/rule/1/_enable", + "/api/alerting/rule/1%2F/_enable", ], ] `); @@ -27,7 +27,7 @@ describe('enableAlert', () => { describe('enableAlerts', () => { test('should call enable alert API per alert', async () => { - const ids = ['1', '2', '3']; + const ids = ['1', '2', '/']; const result = await enableAlerts({ http, ids }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` @@ -39,7 +39,7 @@ describe('enableAlerts', () => { "/api/alerting/rule/2/_enable", ], Array [ - "/api/alerting/rule/3/_enable", + "/api/alerting/rule/%2F/_enable", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.ts index 3c16ffaec6223fd..4bb3e3d45fcaea0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/enable.ts @@ -8,7 +8,7 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ALERTING_API_PATH } from '../../constants'; export async function enableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_enable`); + await http.post(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/_enable`); } export async function enableAlerts({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.test.ts index f2d8337eb4091ce..5c71f6433f2b9ab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.test.ts @@ -13,9 +13,10 @@ const http = httpServiceMock.createStartContract(); describe('loadAlert', () => { test('should call get API with base parameters', async () => { - const alertId = uuid.v4(); + const alertId = `${uuid.v4()}/`; + const alertIdEncoded = encodeURIComponent(alertId); const resolvedValue = { - id: '1', + id: '1/', params: { aggType: 'count', termSize: 5, @@ -56,7 +57,7 @@ describe('loadAlert', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlert({ http, alertId })).toEqual({ - id: '1', + id: '1/', params: { aggType: 'count', termSize: 5, @@ -94,6 +95,6 @@ describe('loadAlert', () => { }, ], }); - expect(http.get).toHaveBeenCalledWith(`/api/alerting/rule/${alertId}`); + expect(http.get).toHaveBeenCalledWith(`/api/alerting/rule/${alertIdEncoded}`); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts index 2e4cbc9b50c51b8..9fa882c02fa228f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/get_rule.ts @@ -16,6 +16,6 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - const res = await http.get(`${BASE_ALERTING_API_PATH}/rule/${alertId}`); + const res = await http.get(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(alertId)}`); return transformAlert(res); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.test.ts index 75143dd6b7f8570..804096dbafac895 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.test.ts @@ -13,12 +13,12 @@ beforeEach(() => jest.resetAllMocks()); describe('muteAlert', () => { test('should call mute alert API', async () => { - const result = await muteAlert({ http, id: '1' }); + const result = await muteAlert({ http, id: '1/' }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alerting/rule/1/_mute_all", + "/api/alerting/rule/1%2F/_mute_all", ], ] `); @@ -27,7 +27,7 @@ describe('muteAlert', () => { describe('muteAlerts', () => { test('should call mute alert API per alert', async () => { - const ids = ['1', '2', '3']; + const ids = ['1', '2', '/']; const result = await muteAlerts({ http, ids }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` @@ -39,7 +39,7 @@ describe('muteAlerts', () => { "/api/alerting/rule/2/_mute_all", ], Array [ - "/api/alerting/rule/3/_mute_all", + "/api/alerting/rule/%2F/_mute_all", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.ts index 22a96d7a11ff38f..888cdfa92c8f5e6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute.ts @@ -8,7 +8,7 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ALERTING_API_PATH } from '../../constants'; export async function muteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_mute_all`); + await http.post(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/_mute_all`); } export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.test.ts index 4365cce42c8c3eb..384bc65754b0334 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.test.ts @@ -12,12 +12,12 @@ const http = httpServiceMock.createStartContract(); describe('muteAlertInstance', () => { test('should call mute instance alert API', async () => { - const result = await muteAlertInstance({ http, id: '1', instanceId: '123' }); + const result = await muteAlertInstance({ http, id: '1/', instanceId: '12/3' }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alerting/rule/1/alert/123/_mute", + "/api/alerting/rule/1%2F/alert/12%2F3/_mute", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.ts index 0bb05010cfa3c59..05f2417db947220 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/mute_alert.ts @@ -16,5 +16,9 @@ export async function muteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/alert/${instanceId}/_mute`); + await http.post( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/alert/${encodeURIComponent( + instanceId + )}/_mute` + ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.test.ts index 68a6feeb65e1e74..dfaceffcf8f00a3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.test.ts @@ -13,7 +13,7 @@ beforeEach(() => jest.resetAllMocks()); describe('unmuteAlerts', () => { test('should call unmute alert API per alert', async () => { - const ids = ['1', '2', '3']; + const ids = ['1', '2', '/']; const result = await unmuteAlerts({ http, ids }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` @@ -25,7 +25,7 @@ describe('unmuteAlerts', () => { "/api/alerting/rule/2/_unmute_all", ], Array [ - "/api/alerting/rule/3/_unmute_all", + "/api/alerting/rule/%2F/_unmute_all", ], ] `); @@ -34,12 +34,12 @@ describe('unmuteAlerts', () => { describe('unmuteAlert', () => { test('should call unmute alert API', async () => { - const result = await unmuteAlert({ http, id: '1' }); + const result = await unmuteAlert({ http, id: '1/' }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alerting/rule/1/_unmute_all", + "/api/alerting/rule/1%2F/_unmute_all", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.ts index c65be6a670a897c..bd2139f05264513 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute.ts @@ -8,7 +8,7 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ALERTING_API_PATH } from '../../constants'; export async function unmuteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/_unmute_all`); + await http.post(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/_unmute_all`); } export async function unmuteAlerts({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.test.ts index c0131cbab0ebf1d..d95c95158b0b7e6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.test.ts @@ -12,12 +12,12 @@ const http = httpServiceMock.createStartContract(); describe('unmuteAlertInstance', () => { test('should call mute instance alert API', async () => { - const result = await unmuteAlertInstance({ http, id: '1', instanceId: '123' }); + const result = await unmuteAlertInstance({ http, id: '1/', instanceId: '12/3' }); expect(result).toEqual(undefined); expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alerting/rule/1/alert/123/_unmute", + "/api/alerting/rule/1%2F/alert/12%2F3/_unmute", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.ts index 60d2cca72b85e60..2e37aa2c0ee295f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/unmute_alert.ts @@ -16,5 +16,9 @@ export async function unmuteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERTING_API_PATH}/rule/${id}/alert/${instanceId}/_unmute`); + await http.post( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/alert/${encodeURIComponent( + instanceId + )}/_unmute` + ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.test.ts index 745a94b8d1134b1..3a6059248a3b0b2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.test.ts @@ -32,7 +32,7 @@ describe('updateAlert', () => { }; const resolvedValue: Alert = { ...alertToUpdate, - id: '123', + id: '12/3', enabled: true, alertTypeId: 'test', createdBy: null, @@ -46,11 +46,11 @@ describe('updateAlert', () => { }; http.put.mockResolvedValueOnce(resolvedValue); - const result = await updateAlert({ http, id: '123', alert: alertToUpdate }); + const result = await updateAlert({ http, id: '12/3', alert: alertToUpdate }); expect(result).toEqual(resolvedValue); expect(http.put.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alerting/rule/123", + "/api/alerting/rule/12%2F3", Object { "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"notify_when\\":\\"onThrottleInterval\\",\\"actions\\":[]}", }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts index 44b9306949f8103..930c0c2fb21a08a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/update.ts @@ -41,7 +41,7 @@ export async function updateAlert({ >; id: string; }): Promise { - const res = await http.put(`${BASE_ALERTING_API_PATH}/rule/${id}`, { + const res = await http.put(`${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, { body: JSON.stringify( rewriteBodyRequest( pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions', 'notifyWhen']) diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts index 86d18d98fa0e125..37f6219cf30a561 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts @@ -7,8 +7,14 @@ // test error conditions of calling timeSeriesQuery - postive results tested in FT +import type { estypes } from '@elastic/elasticsearch'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; -import { TimeSeriesQueryParameters, TimeSeriesQuery, timeSeriesQuery } from './time_series_query'; +import { + TimeSeriesQueryParameters, + TimeSeriesQuery, + timeSeriesQuery, + getResultFromEs, +} from './time_series_query'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; @@ -53,3 +59,135 @@ describe('timeSeriesQuery', () => { ); }); }); + +describe('getResultFromEs', () => { + it('correctly parses time series results for count aggregation', () => { + expect( + getResultFromEs(true, false, { + took: 0, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + aggregations: { + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:14:31.075Z-2021-04-22T15:19:31.075Z', + from: 1619104471075, + from_as_string: '2021-04-22T15:14:31.075Z', + to: 1619104771075, + to_as_string: '2021-04-22T15:19:31.075Z', + doc_count: 0, + }, + ], + }, + }, + } as estypes.SearchResponse) + ).toEqual({ + results: [ + { + group: 'all documents', + metrics: [['2021-04-22T15:19:31.075Z', 0]], + }, + ], + }); + }); + + it('correctly parses time series results with no aggregation data for count aggregation', () => { + // this could happen with cross cluster searches when cluster permissions are incorrect + // the query completes but doesn't return any aggregations + expect( + getResultFromEs(true, false, { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + } as estypes.SearchResponse) + ).toEqual({ + results: [], + }); + }); + + it('correctly parses time series results for group aggregation', () => { + expect( + getResultFromEs(false, true, { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 298, relation: 'eq' }, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + sortValueAgg: { value: 0.5000000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + metricAgg: { value: 0.5000000018251423 }, + }, + ], + }, + }, + { + key: 'host-1', + doc_count: 149, + sortValueAgg: { value: 0.5000000011000857 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + metricAgg: { value: 0.5000000011000857 }, + }, + ], + }, + }, + ], + }, + }, + } as estypes.SearchResponse) + ).toEqual({ + results: [ + { + group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 0.5000000018251423]], + }, + { + group: 'host-1', + metrics: [['2021-04-22T15:23:43.191Z', 0.5000000011000857]], + }, + ], + }); + }); + + it('correctly parses time series results with no aggregation data for group aggregation', () => { + // this could happen with cross cluster searches when cluster permissions are incorrect + // the query completes but doesn't return any aggregations + expect( + getResultFromEs(false, true, { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + } as estypes.SearchResponse) + ).toEqual({ + results: [], + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index ad044f4570ea3d4..a2ba8d43c9c60ca 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -147,7 +147,7 @@ export async function timeSeriesQuery( return getResultFromEs(isCountAgg, isGroupAgg, esResult); } -function getResultFromEs( +export function getResultFromEs( isCountAgg: boolean, isGroupAgg: boolean, esResult: estypes.SearchResponse @@ -155,8 +155,8 @@ function getResultFromEs( const aggregations = esResult?.aggregations || {}; // add a fake 'all documents' group aggregation, if a group aggregation wasn't used - if (!isGroupAgg) { - const dateAgg = aggregations.dateAgg || {}; + if (!isGroupAgg && aggregations.dateAgg) { + const dateAgg = aggregations.dateAgg; aggregations.groupAgg = { buckets: [{ key: 'all documents', dateAgg }], diff --git a/x-pack/plugins/uptime/public/pages/settings.test.tsx b/x-pack/plugins/uptime/public/pages/settings.test.tsx index 95fed208f6b0a89..e0b7b70ad46fbe1 100644 --- a/x-pack/plugins/uptime/public/pages/settings.test.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.test.tsx @@ -13,7 +13,8 @@ import { act } from 'react-dom/test-utils'; import * as alertApi from '../state/api/alerts'; describe('settings', () => { - describe('form', () => { + // FLAKY: https://github.com/elastic/kibana/issues/97067 + describe.skip('form', () => { beforeAll(() => { jest.spyOn(alertApi, 'fetchActionTypes').mockImplementation(async () => [ { diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/evaluate.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/evaluate.ts new file mode 100644 index 000000000000000..e1fa889d20daf23 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/evaluate.ts @@ -0,0 +1,188 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common'; +import { DeepPartial } from '../../../../../plugins/ml/common/types/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + const currentTime = `${Date.now()}`; + const generateDestinationIndex = (analyticsId: string) => `user-${analyticsId}`; + const jobEval: any = { + regression: { + index: generateDestinationIndex(`regression_${currentTime}`), + evaluation: { + regression: { + actual_field: 'stab', + predicted_field: 'ml.stab_prediction', + metrics: { + r_squared: {}, + mse: {}, + msle: {}, + huber: {}, + }, + }, + }, + }, + classification: { + index: generateDestinationIndex(`classification_${currentTime}`), + evaluation: { + classification: { + actual_field: 'y', + predicted_field: 'ml.y_prediction', + metrics: { multiclass_confusion_matrix: {}, accuracy: {}, recall: {} }, + }, + }, + }, + }; + const jobAnalysis: any = { + classification: { + source: { + index: ['ft_bank_marketing'], + query: { + match_all: {}, + }, + }, + analysis: { + classification: { + dependent_variable: 'y', + training_percent: 20, + }, + }, + }, + regression: { + source: { + index: ['ft_egs_regression'], + query: { + match_all: {}, + }, + }, + analysis: { + regression: { + dependent_variable: 'stab', + training_percent: 20, + }, + }, + }, + }; + + interface TestConfig { + jobType: string; + config: DeepPartial; + eval: any; + } + + const testJobConfigs: TestConfig[] = ['regression', 'classification'].map((jobType, idx) => { + const analyticsId = `${jobType}_${currentTime}`; + return { + jobType, + config: { + id: analyticsId, + description: `Testing ${jobType} evaluation`, + dest: { + index: generateDestinationIndex(analyticsId), + results_field: 'ml', + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '60mb', + ...jobAnalysis[jobType], + }, + eval: jobEval[jobType], + }; + }); + + async function createJobs(mockJobConfigs: TestConfig[]) { + for (const jobConfig of mockJobConfigs) { + await ml.api.createAndRunDFAJob(jobConfig.config as DataFrameAnalyticsConfig); + } + } + + describe('POST data_frame/_evaluate', () => { + before(async () => { + await esArchiver.loadIfNeeded('ml/bm_classification'); + await esArchiver.loadIfNeeded('ml/egs_regression'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await createJobs(testJobConfigs); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + testJobConfigs.forEach((testConfig) => { + describe(`EvaluateDataFrameAnalytics ${testConfig.jobType}`, async () => { + it(`should evaluate ${testConfig.jobType} analytics job`, async () => { + const { body } = await supertest + .post(`/api/ml/data_frame/_evaluate`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .send(testConfig.eval) + .expect(200); + + if (testConfig.jobType === 'classification') { + const { classification } = body; + expect(body).to.have.property('classification'); + expect(classification).to.have.property('recall'); + expect(classification).to.have.property('accuracy'); + expect(classification).to.have.property('multiclass_confusion_matrix'); + } else { + const { regression } = body; + expect(body).to.have.property('regression'); + expect(regression).to.have.property('mse'); + expect(regression).to.have.property('msle'); + expect(regression).to.have.property('r_squared'); + } + }); + + it(`should evaluate ${testConfig.jobType} job for the user with only view permission`, async () => { + const { body } = await supertest + .post(`/api/ml/data_frame/_evaluate`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .send(testConfig.eval) + .expect(200); + + if (testConfig.jobType === 'classification') { + const { classification } = body; + expect(body).to.have.property('classification'); + expect(classification).to.have.property('recall'); + expect(classification).to.have.property('accuracy'); + expect(classification).to.have.property('multiclass_confusion_matrix'); + } else { + const { regression } = body; + expect(body).to.have.property('regression'); + expect(regression).to.have.property('mse'); + expect(regression).to.have.property('msle'); + expect(regression).to.have.property('r_squared'); + } + }); + + it(`should not allow unauthorized user to evaluate ${testConfig.jobType} job`, async () => { + const { body } = await supertest + .post(`/api/ml/data_frame/_evaluate`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS) + .send(testConfig.eval) + .expect(403); + + expect(body.error).to.eql('Forbidden'); + expect(body.message).to.eql('Forbidden'); + }); + }); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts index 3fdefe2c4bbc241..21ff8f2cc64c16a 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts @@ -20,6 +20,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./get_spaces')); loadTestFile(require.resolve('./update_spaces')); loadTestFile(require.resolve('./delete_spaces')); + loadTestFile(require.resolve('./evaluate')); loadTestFile(require.resolve('./explain')); }); } diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/index.ts b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts index a88389f2498d586..fa24a4ba6a19ee8 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/index.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts @@ -26,6 +26,8 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./dashboard_to_dashboard_drilldown')); loadTestFile(require.resolve('./dashboard_to_url_drilldown')); + // Requires xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled + // setting set in kibana.yml to work (not enabled by default) loadTestFile(require.resolve('./explore_data_panel_action')); // Disabled for now as it requires xpack.discoverEnhanced.actions.exploreDataInChart.enabled diff --git a/x-pack/test/functional/apps/lens/dashboard.ts b/x-pack/test/functional/apps/lens/dashboard.ts index 1490abb320ca64e..9998f1dd4cdcb88 100644 --- a/x-pack/test/functional/apps/lens/dashboard.ts +++ b/x-pack/test/functional/apps/lens/dashboard.ts @@ -86,6 +86,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(hasIpFilter).to.be(true); }); + // Requires xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled + // setting set in kibana.yml to work (not enabled by default) it('should be able to drill down to discover', async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); diff --git a/x-pack/test/functional/apps/monitoring/index.js b/x-pack/test/functional/apps/monitoring/index.js index d595400f3e335fe..37d5d2083c4b1d2 100644 --- a/x-pack/test/functional/apps/monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/index.js @@ -15,32 +15,34 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./cluster/overview')); // loadTestFile(require.resolve('./cluster/license')); + // NOTE: All _mb tests skipped because of various failures: https://github.com/elastic/kibana/issues/98239 + loadTestFile(require.resolve('./elasticsearch/overview')); - loadTestFile(require.resolve('./elasticsearch/overview_mb')); + // loadTestFile(require.resolve('./elasticsearch/overview_mb')); loadTestFile(require.resolve('./elasticsearch/nodes')); - loadTestFile(require.resolve('./elasticsearch/nodes_mb')); + // loadTestFile(require.resolve('./elasticsearch/nodes_mb')); loadTestFile(require.resolve('./elasticsearch/node_detail')); - loadTestFile(require.resolve('./elasticsearch/node_detail_mb')); + // loadTestFile(require.resolve('./elasticsearch/node_detail_mb')); loadTestFile(require.resolve('./elasticsearch/indices')); - loadTestFile(require.resolve('./elasticsearch/indices_mb')); + // loadTestFile(require.resolve('./elasticsearch/indices_mb')); loadTestFile(require.resolve('./elasticsearch/index_detail')); - loadTestFile(require.resolve('./elasticsearch/index_detail_mb')); + // loadTestFile(require.resolve('./elasticsearch/index_detail_mb')); loadTestFile(require.resolve('./elasticsearch/shards')); // loadTestFile(require.resolve('./elasticsearch/shard_activity')); loadTestFile(require.resolve('./kibana/overview')); - loadTestFile(require.resolve('./kibana/overview_mb')); + // loadTestFile(require.resolve('./kibana/overview_mb')); loadTestFile(require.resolve('./kibana/instances')); - loadTestFile(require.resolve('./kibana/instances_mb')); + // loadTestFile(require.resolve('./kibana/instances_mb')); loadTestFile(require.resolve('./kibana/instance')); - loadTestFile(require.resolve('./kibana/instance_mb')); + // loadTestFile(require.resolve('./kibana/instance_mb')); // loadTestFile(require.resolve('./logstash/overview')); // loadTestFile(require.resolve('./logstash/nodes')); // loadTestFile(require.resolve('./logstash/node')); loadTestFile(require.resolve('./logstash/pipelines')); - loadTestFile(require.resolve('./logstash/pipelines_mb')); + // loadTestFile(require.resolve('./logstash/pipelines_mb')); loadTestFile(require.resolve('./beats/cluster')); loadTestFile(require.resolve('./beats/overview')); @@ -51,6 +53,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./enable_monitoring')); loadTestFile(require.resolve('./setup/metricbeat_migration')); - loadTestFile(require.resolve('./setup/metricbeat_migration_mb')); + // loadTestFile(require.resolve('./setup/metricbeat_migration_mb')); }); } diff --git a/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js b/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js index e46b1d161e68a9a..3317513f8157d4b 100644 --- a/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js +++ b/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }) { const instances = getService('monitoringKibanaInstances'); const kibanaClusterSummaryStatus = getService('monitoringKibanaSummaryStatus'); - describe('Kibana instances listing mb', () => { + // Failing: See https://github.com/elastic/kibana/issues/98190 + describe.skip('Kibana instances listing mb', () => { const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); before(async () => { diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 0b22ab920287c96..f171e247472f104 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -90,6 +90,7 @@ export default async function ({ readConfigFile }) { '--usageCollection.maximumWaitTimeForAllCollectorsInS=1', '--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions '--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', + '--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true', '--timelion.ui.enabled=true', '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects ], diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts index 7b760dfb8b6a196..cbb1d2729e74c1f 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts @@ -50,12 +50,25 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return createdAction; } + async function muteAlert(alertId: string) { + const { body: alert } = await supertest + .post(`/api/alerting/rule/${alertId}/_mute_all`) + .set('kbn-xsrf', 'foo'); + return alert; + } + + async function disableAlert(alertId: string) { + const { body: alert } = await supertest + .post(`/api/alerting/rule/${alertId}/_disable`) + .set('kbn-xsrf', 'foo'); + return alert; + } + async function refreshAlertsList() { await testSubjects.click('rulesTab'); } - // FLAKY: https://github.com/elastic/kibana/issues/95591 - describe.skip('alerts list', function () { + describe('alerts list', function () { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('rulesTab'); @@ -138,25 +151,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should re-enable single alert', async () => { const createdAlert = await createAlert(); + await disableAlert(createdAlert.id); await refreshAlertsList(); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); await pageObjects.triggersActionsUI.toggleSwitch('disableSwitch'); - - await pageObjects.triggersActionsUI.ensureRuleActionToggleApplied( - createdAlert.name, - 'disableSwitch', - 'true' - ); - - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); - - await testSubjects.click('collapsedItemActions'); - - await pageObjects.triggersActionsUI.toggleSwitch('disableSwitch'); - await pageObjects.triggersActionsUI.ensureRuleActionToggleApplied( createdAlert.name, 'disableSwitch', @@ -172,7 +173,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('collapsedItemActions'); await pageObjects.triggersActionsUI.toggleSwitch('muteSwitch'); - await pageObjects.triggersActionsUI.ensureRuleActionToggleApplied( createdAlert.name, 'muteSwitch', @@ -182,25 +182,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should unmute single alert', async () => { const createdAlert = await createAlert(); + await muteAlert(createdAlert.id); await refreshAlertsList(); - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); - - await testSubjects.click('collapsedItemActions'); - - await pageObjects.triggersActionsUI.toggleSwitch('muteSwitch'); - - await pageObjects.triggersActionsUI.ensureRuleActionToggleApplied( - createdAlert.name, - 'muteSwitch', - 'true' - ); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); await pageObjects.triggersActionsUI.toggleSwitch('muteSwitch'); - await pageObjects.triggersActionsUI.ensureRuleActionToggleApplied( createdAlert.name, 'muteSwitch', diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 97f8b3f61dc892a..b38b605bc1b6788 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -93,14 +93,18 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function getAlertInstanceSummary(alertId: string) { const { body: summary } = await supertest - .get(`/internal/alerting/rule/${alertId}/_alert_summary`) + .get(`/internal/alerting/rule/${encodeURIComponent(alertId)}/_alert_summary`) .expect(200); return summary; } async function muteAlertInstance(alertId: string, alertInstanceId: string) { const { body: response } = await supertest - .post(`/api/alerting/rule/${alertId}/alert/${alertInstanceId}/_mute`) + .post( + `/api/alerting/rule/${encodeURIComponent(alertId)}/alert/${encodeURIComponent( + alertInstanceId + )}/_mute` + ) .set('kbn-xsrf', 'foo') .expect(204); @@ -640,17 +644,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the muted inactive alert instances', async () => { // mute an alert instance that doesn't exist - await muteAlertInstance(alert.id, 'eu-east'); + await muteAlertInstance(alert.id, 'eu/east'); // refresh to see alert await browser.refresh(); const instancesList: any[] = await pageObjects.alertDetailsUI.getAlertInstancesList(); expect( - instancesList.filter((alertInstance) => alertInstance.instance === 'eu-east') + instancesList.filter((alertInstance) => alertInstance.instance === 'eu/east') ).to.eql([ { - instance: 'eu-east', + instance: 'eu/east', status: 'OK', start: '', duration: '', @@ -693,14 +697,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('allows the user unmute an inactive instance', async () => { - log.debug(`Ensuring eu-east is muted`); - await pageObjects.alertDetailsUI.ensureAlertInstanceMute('eu-east', true); + log.debug(`Ensuring eu/east is muted`); + await pageObjects.alertDetailsUI.ensureAlertInstanceMute('eu/east', true); - log.debug(`Unmuting eu-east`); - await pageObjects.alertDetailsUI.clickAlertInstanceMuteButton('eu-east'); + log.debug(`Unmuting eu/east`); + await pageObjects.alertDetailsUI.clickAlertInstanceMuteButton('eu/east'); - log.debug(`Ensuring eu-east is removed from list`); - await pageObjects.alertDetailsUI.ensureAlertInstanceExistance('eu-east', false); + log.debug(`Ensuring eu/east is removed from list`); + await pageObjects.alertDetailsUI.ensureAlertInstanceExistance('eu/east', false); }); }); diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index 5fa442e289037ea..8eeabf1f5d670dd 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -146,13 +146,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) }, async toggleSwitch(testSubject: string) { const switchBtn = await testSubjects.find(testSubject); - const valueBefore = await switchBtn.getAttribute('aria-checked'); await switchBtn.click(); - await retry.try(async () => { - const switchBtnAfter = await testSubjects.find(testSubject); - const valueAfter = await switchBtnAfter.getAttribute('aria-checked'); - expect(valueAfter).not.to.eql(valueBefore); - }); }, async clickCreateAlertButton() { const createBtn = await find.byCssSelector( @@ -194,9 +188,10 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) switchName: string, shouldBeCheckedAsString: string ) { - await retry.try(async () => { + await retry.tryForTime(30000, async () => { await this.searchAlerts(ruleName); await testSubjects.click('collapsedItemActions'); + const switchControl = await testSubjects.find(switchName); const isChecked = await switchControl.getAttribute('aria-checked'); expect(isChecked).to.eql(shouldBeCheckedAsString); diff --git a/yarn.lock b/yarn.lock index ed20146e4fa5f14..1c33d64afbec05f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1550,7 +1550,7 @@ "@types/node-jose" "1.1.0" node-jose "1.1.0" -"@elastic/safer-lodash-set@link:packages/elastic-safer-lodash-set": +"@elastic/safer-lodash-set@link:bazel-bin/packages/elastic-safer-lodash-set/npm_module": version "0.0.0" uid "" @@ -2585,7 +2585,7 @@ version "0.0.0" uid "" -"@kbn/babel-code-parser@link:packages/kbn-babel-code-parser": +"@kbn/babel-code-parser@link:bazel-bin/packages/kbn-babel-code-parser/npm_module": version "0.0.0" uid ""