diff --git a/.bazelrc.common b/.bazelrc.common index 115c0214b1a53..3de2bceaad3a6 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -94,6 +94,12 @@ build --nolegacy_external_runfiles run --nolegacy_external_runfiles test --nolegacy_external_runfiles +# Runfiles should be enabled un order to use `exports_directories_only` in the yarn_install rule +# https://bazelbuild.github.io/rules_nodejs/Built-ins.html#yarn_install-exports_directories_only +build --enable_runfiles +run --enable_runfiles +test --enable_runfiles + # Turn on --incompatible_strict_action_env which was on by default # in Bazel 0.21.0 but turned off again in 0.22.0. Follow # https://github.com/bazelbuild/bazel/issues/7026 for more details. @@ -104,10 +110,11 @@ build --incompatible_strict_action_env run --incompatible_strict_action_env test --incompatible_strict_action_env +# DISABLED for now due to https://bazelbuild.github.io/rules_nodejs/Built-ins.html#yarn_install-exports_directories_only # Do not build runfile trees by default. If an execution strategy relies on runfile # symlink tree, the tree is created on-demand. See: https://github.com/bazelbuild/bazel/issues/6627 # and https://github.com/bazelbuild/bazel/commit/03246077f948f2790a83520e7dccc2625650e6df -build --nobuild_runfile_links +# build --nobuild_runfile_links # When running `bazel coverage` --instrument_test_targets needs to be set in order to # collect coverage information from test targets diff --git a/.eslintrc.js b/.eslintrc.js index 2eea41984b30e..09de32a91bca3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -445,6 +445,7 @@ module.exports = { '(src|x-pack)/plugins/**/(public|server)/**/*', '!(src|x-pack)/plugins/**/(public|server)/mocks/index.{js,mjs,ts}', '!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,mjs,ts,tsx}', + '!(src|x-pack)/plugins/**/__stories__/index.{js,mjs,ts,tsx}', ], allowSameFolder: true, errorMessage: 'Plugins may only import from top-level public and server modules.', diff --git a/.i18nrc.json b/.i18nrc.json index 0926f73722731..390e5e917d08e 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -16,6 +16,7 @@ "esUi": "src/plugins/es_ui_shared", "devTools": "src/plugins/dev_tools", "expressions": "src/plugins/expressions", + "expressionRevealImage": "src/plugins/expression_reveal_image", "inputControl": "src/plugins/input_control_vis", "inspector": "src/plugins/inspector", "inspectorViews": "src/legacy/core_plugins/inspector_views", diff --git a/.node-version b/.node-version index 62df50f1eefe1..0a9f3027ffc44 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14.17.0 +14.17.2 diff --git a/.nvmrc b/.nvmrc index 62df50f1eefe1..0a9f3027ffc44 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.17.0 +14.17.2 diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index ebf7bbc8488ac..f63eb0ed8a671 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -27,13 +27,13 @@ check_rules_nodejs_version(minimum_version_string = "3.6.0") # we can update that rule. node_repositories( node_repositories = { - "14.17.0-darwin_amd64": ("node-v14.17.0-darwin-x64.tar.gz", "node-v14.17.0-darwin-x64", "7b210652e11d1ee25650c164cf32381895e1dcb3e0ff1d0841d8abc1f47ac73e"), - "14.17.0-linux_arm64": ("node-v14.17.0-linux-arm64.tar.xz", "node-v14.17.0-linux-arm64", "712e5575cee20570a0a56f4d4b4572cb0f2ee2f4bce49433de18be0393e7df22"), - "14.17.0-linux_s390x": ("node-v14.17.0-linux-s390x.tar.xz", "node-v14.17.0-linux-s390x", "6419372b9e9ad37e0bce188dc5740f2f060aaa44454418e462b4088a310a1c0b"), - "14.17.0-linux_amd64": ("node-v14.17.0-linux-x64.tar.xz", "node-v14.17.0-linux-x64", "494b161759a3d19c70e3172d33ce1918dd8df9ad20d29d1652a8387a84e2d308"), - "14.17.0-windows_amd64": ("node-v14.17.0-win-x64.zip", "node-v14.17.0-win-x64", "6582a7259c433e9f667dcc4ed3e5d68bc514caba2eed40e4626c8b4c7e5ecd5c"), + "14.17.2-darwin_amd64": ("node-v14.17.2-darwin-x64.tar.gz", "node-v14.17.2-darwin-x64", "e45db91fc2136202868a5eb7c6d08b0a2b75394fafdf8538f650fa945b7dee16"), + "14.17.2-linux_arm64": ("node-v14.17.2-linux-arm64.tar.xz", "node-v14.17.2-linux-arm64", "3aff08c49b8c0c3443e7a9ea9bfe607867d79e6e5ccf204a5c8f13fb92a48abd"), + "14.17.2-linux_s390x": ("node-v14.17.2-linux-s390x.tar.xz", "node-v14.17.2-linux-s390x", "76f955856626a3e596b438855fdfe438937623dc71af9a25a8466409be470877"), + "14.17.2-linux_amd64": ("node-v14.17.2-linux-x64.tar.xz", "node-v14.17.2-linux-x64", "6cf9db7349407c177b36205feec949729d0ee9db485e19b10b0b1ffca65a3a46"), + "14.17.2-windows_amd64": ("node-v14.17.2-win-x64.zip", "node-v14.17.2-win-x64", "0e27897578752865fa61546d75b20f7cd62957726caab3c03f82c57a4aef5636"), }, - node_version = "14.17.0", + node_version = "14.17.2", node_urls = [ "https://nodejs.org/dist/v{version}/{filename}", ], @@ -59,6 +59,7 @@ yarn_install( "//:preinstall_check.js", "//:node_modules/.yarn-integrity", ], + exports_directories_only = True, symlink_node_modules = True, quiet = False, frozen_lockfile = False, diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 231e089950a28..b4be27eee5ed2 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -72,6 +72,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |This plugin contains reusable code in the form of self-contained modules (or libraries). Each of these modules exports a set of functionality relevant to the domain of the module. +|{kib-repo}blob/{branch}/src/plugins/expression_reveal_image/README.md[expressionRevealImage] +|Expression Reveal Image plugin adds a revealImage function to the expression plugin and an associated renderer. The renderer will display the given percentage of a given image. + + |<> |Expression pipeline is a chain of functions that *pipe* its output to the input of the next function. Functions can be configured using arguments provided diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md index 63302f50204fe..b944c9dcc02a2 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md @@ -18,5 +18,6 @@ export interface EmbeddableEditorState | --- | --- | --- | | [embeddableId](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.embeddableid.md) | string | | | [originatingApp](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.originatingapp.md) | string | | +| [searchSessionId](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.searchsessionid.md) | string | Pass current search session id when navigating to an editor, Editors could use it continue previous search session | | [valueInput](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.valueinput.md) | EmbeddableInput | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.searchsessionid.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.searchsessionid.md new file mode 100644 index 0000000000000..815055fe9f55d --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableeditorstate.searchsessionid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableEditorState](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) > [searchSessionId](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.searchsessionid.md) + +## EmbeddableEditorState.searchSessionId property + +Pass current search session id when navigating to an editor, Editors could use it continue previous search session + +Signature: + +```typescript +searchSessionId?: string; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md index 1c0b1b8bf8b46..b3e851a6d0c30 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md @@ -18,5 +18,6 @@ export interface EmbeddablePackageState | --- | --- | --- | | [embeddableId](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.embeddableid.md) | string | | | [input](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.input.md) | Optional<EmbeddableInput, 'id'> | Optional<SavedObjectEmbeddableInput, 'id'> | | +| [searchSessionId](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.searchsessionid.md) | string | Pass current search session id when navigating to an editor, Editors could use it continue previous search session | | [type](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.type.md) | string | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablepackagestate.searchsessionid.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablepackagestate.searchsessionid.md new file mode 100644 index 0000000000000..3c515b1fb6674 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablepackagestate.searchsessionid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddablePackageState](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) > [searchSessionId](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.searchsessionid.md) + +## EmbeddablePackageState.searchSessionId property + +Pass current search session id when navigating to an editor, Editors could use it continue previous search session + +Signature: + +```typescript +searchSessionId?: string; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md index 46934e119aee0..434f2660e7eff 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md @@ -7,7 +7,7 @@ Signature: ```typescript -interpret(ast: ExpressionAstNode, input: T): Observable; +interpret(ast: ExpressionAstNode, input: T): Observable>; ``` ## Parameters @@ -19,5 +19,5 @@ interpret(ast: ExpressionAstNode, input: T): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md index 30fe9f497f7ee..edaf1c9a9ce9e 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md @@ -26,8 +26,8 @@ export declare class Executionstring | | | [input](./kibana-plugin-plugins-expressions-public.execution.input.md) | | Input | Initial input of the execution.N.B. It is initialized to null rather than undefined for legacy reasons, because in legacy interpreter it was set to null by default. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.execution.inspectoradapters.md) | | InspectorAdapters | | -| [result](./kibana-plugin-plugins-expressions-public.execution.result.md) | | Observable<Output | ExpressionValueError> | Future that tracks result or error of this execution. | -| [state](./kibana-plugin-plugins-expressions-public.execution.state.md) | | ExecutionContainer<Output | ExpressionValueError> | Dynamic state of the execution. | +| [result](./kibana-plugin-plugins-expressions-public.execution.result.md) | | Observable<ExecutionResult<Output | ExpressionValueError>> | Future that tracks result or error of this execution. | +| [state](./kibana-plugin-plugins-expressions-public.execution.state.md) | | ExecutionContainer<ExecutionResult<Output | ExpressionValueError>> | Dynamic state of the execution. | ## Methods diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md index 94f60ccee0f00..a386302a62805 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md @@ -9,5 +9,5 @@ Future that tracks result or error of this execution. Signature: ```typescript -readonly result: Observable; +readonly result: Observable>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md index 64cf81b376948..352226da6d72a 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md @@ -11,7 +11,7 @@ N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons Signature: ```typescript -start(input?: Input): Observable; +start(input?: Input): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ start(input?: Input): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md index ca8b57b760f29..61aa0cf4c5b5d 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md @@ -9,5 +9,5 @@ Dynamic state of the execution. Signature: ```typescript -readonly state: ExecutionContainer; +readonly state: ExecutionContainer>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md index dcd96cf5767bf..852e1f58cc6f3 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md @@ -9,5 +9,5 @@ Returns the final output of expression, if any error happens still wraps that er Signature: ```typescript -getData: () => Promise; +getData: () => Observable>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md index f2c050bbfe0ba..0ac776e4be2b8 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md @@ -25,7 +25,7 @@ export declare class ExecutionContract() => void | Cancel the execution of the expression. This will set abort signal (available in execution context) to aborted state, letting expression functions to stop their execution. | | [execution](./kibana-plugin-plugins-expressions-public.executioncontract.execution.md) | | Execution<Input, Output, InspectorAdapters> | | | [getAst](./kibana-plugin-plugins-expressions-public.executioncontract.getast.md) | | () => ExpressionAstExpression | Get AST used to execute the expression. | -| [getData](./kibana-plugin-plugins-expressions-public.executioncontract.getdata.md) | | () => Promise<Output | ExpressionValueError> | Returns the final output of expression, if any error happens still wraps that error into ExpressionValueError type and returns that. This function never throws. | +| [getData](./kibana-plugin-plugins-expressions-public.executioncontract.getdata.md) | | () => Observable<ExecutionResult<Output | ExpressionValueError>> | Returns the final output of expression, if any error happens still wraps that error into ExpressionValueError type and returns that. This function never throws. | | [getExpression](./kibana-plugin-plugins-expressions-public.executioncontract.getexpression.md) | | () => string | Get string representation of the expression. Returns the original string if execution was started from a string. If execution was started from an AST this method returns a string generated from AST. | | [inspect](./kibana-plugin-plugins-expressions-public.executioncontract.inspect.md) | | () => InspectorAdapters | Get Inspector adapters provided to all functions of expression through execution context. | | [isPending](./kibana-plugin-plugins-expressions-public.executioncontract.ispending.md) | | boolean | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md index 307e6b6bcd5c8..4eefc63d714d1 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md @@ -9,7 +9,7 @@ Execute expression and return result. Signature: ```typescript -run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; +run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ run(ast: string | ExpressionAstExpression, input: Input, params?: Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md index 6b678fc4fbc26..9821f0f921e4d 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md @@ -21,7 +21,7 @@ export interface ExpressionsServiceStart | [getFunction](./kibana-plugin-plugins-expressions-public.expressionsservicestart.getfunction.md) | (name: string) => ReturnType<Executor['getFunction']> | Get a registered ExpressionFunction by its name, which was registered using the registerFunction method. The returned ExpressionFunction instance is an internal representation of the function in Expressions service - do not mutate that object. | | [getRenderer](./kibana-plugin-plugins-expressions-public.expressionsservicestart.getrenderer.md) | (name: string) => ReturnType<ExpressionRendererRegistry['get']> | Get a registered ExpressionRenderer by its name, which was registered using the registerRenderer method. The returned ExpressionRenderer instance is an internal representation of the renderer in Expressions service - do not mutate that object. | | [getType](./kibana-plugin-plugins-expressions-public.expressionsservicestart.gettype.md) | (name: string) => ReturnType<Executor['getType']> | Get a registered ExpressionType by its name, which was registered using the registerType method. The returned ExpressionType instance is an internal representation of the type in Expressions service - do not mutate that object. | -| [run](./kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md) | <Input, Output>(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Promise<Output> | Executes expression string or a parsed expression AST and immediately returns the result.Below example will execute sleep 100 | clog expression with 123 initial input to the first function. +| [run](./kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md) | <Input, Output>(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Observable<ExecutionResult<Output | ExpressionValueError>> | Executes expression string or a parsed expression AST and immediately returns the result.Below example will execute sleep 100 | clog expression with 123 initial input to the first function. ```ts expressions.run('sleep 100 | clog', 123); diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md index 9efca0011174c..0838d640d54e4 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md @@ -24,5 +24,5 @@ expressions.run('...', null, { elasticsearchClient }); Signature: ```typescript -run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Promise; +run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Observable>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md index 4ef1225ae0d7e..69f9d380422b6 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md @@ -22,6 +22,7 @@ export interface IExpressionLoaderParams | [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md) | ExpressionRenderHandlerParams['hasCompatibleActions'] | | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.inspectoradapters.md) | Adapters | | | [onRenderError](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.onrendererror.md) | RenderErrorHandlerFnType | | +| [partial](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md) | boolean | | | [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | RenderMode | | | [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | SerializableState | | | [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | string | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md new file mode 100644 index 0000000000000..84c42c3f59f26 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) > [partial](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md) + +## IExpressionLoaderParams.partial property + +Signature: + +```typescript +partial?: boolean; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md index 92ea071b23dfc..d38027753a6ff 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md @@ -18,7 +18,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams | [dataAttrs](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.dataattrs.md) | string[] | | | [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) | number | | | [expression](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.expression.md) | string | ExpressionAstExpression | | -| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void | | +| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters, partial?: boolean) => void | | | [onEvent](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.onevent.md) | (event: ExpressionRendererEvent) => void | | | [padding](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.padding.md) | 'xs' | 's' | 'm' | 'l' | 'xl' | | | [reload$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.reload_.md) | Observable<unknown> | An observable which can be used to re-run the expression without destroying the component | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md index 05ddb0b13a5be..47559d0f7653c 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md @@ -7,5 +7,5 @@ Signature: ```typescript -onData$?: (data: TData, adapters?: TInspectorAdapters) => void; +onData$?: (data: TData, adapters?: TInspectorAdapters, partial?: boolean) => void; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md index 936e98be589a3..99804dd20841d 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md @@ -7,7 +7,7 @@ Signature: ```typescript -interpret(ast: ExpressionAstNode, input: T): Observable; +interpret(ast: ExpressionAstNode, input: T): Observable>; ``` ## Parameters @@ -19,5 +19,5 @@ interpret(ast: ExpressionAstNode, input: T): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md index a4e324eef6674..47963e5e5ef46 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md @@ -26,8 +26,8 @@ export declare class Executionstring | | | [input](./kibana-plugin-plugins-expressions-server.execution.input.md) | | Input | Initial input of the execution.N.B. It is initialized to null rather than undefined for legacy reasons, because in legacy interpreter it was set to null by default. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-server.execution.inspectoradapters.md) | | InspectorAdapters | | -| [result](./kibana-plugin-plugins-expressions-server.execution.result.md) | | Observable<Output | ExpressionValueError> | Future that tracks result or error of this execution. | -| [state](./kibana-plugin-plugins-expressions-server.execution.state.md) | | ExecutionContainer<Output | ExpressionValueError> | Dynamic state of the execution. | +| [result](./kibana-plugin-plugins-expressions-server.execution.result.md) | | Observable<ExecutionResult<Output | ExpressionValueError>> | Future that tracks result or error of this execution. | +| [state](./kibana-plugin-plugins-expressions-server.execution.state.md) | | ExecutionContainer<ExecutionResult<Output | ExpressionValueError>> | Dynamic state of the execution. | ## Methods diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md index 06cf047ac4160..b3baac5be2fa3 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md @@ -9,5 +9,5 @@ Future that tracks result or error of this execution. Signature: ```typescript -readonly result: Observable; +readonly result: Observable>; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md index dd0456ac09950..0eef7013cb3c6 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md @@ -11,7 +11,7 @@ N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons Signature: ```typescript -start(input?: Input): Observable; +start(input?: Input): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ start(input?: Input): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md index 41e7e693a1da4..b7c26e9dee85a 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md @@ -9,5 +9,5 @@ Dynamic state of the execution. Signature: ```typescript -readonly state: ExecutionContainer; +readonly state: ExecutionContainer>; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md index 2ab534eac2f3a..7b169d05dc31d 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md @@ -9,7 +9,7 @@ Execute expression and return result. Signature: ```typescript -run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; +run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ run(ast: string | ExpressionAstExpression, input: Input, params?: Returns: -`Observable` +`Observable>` diff --git a/docs/maps/images/enable_filter_by_map_extent.png b/docs/maps/images/enable_filter_by_map_extent.png new file mode 100644 index 0000000000000..5132dc8f73dbe Binary files /dev/null and b/docs/maps/images/enable_filter_by_map_extent.png differ diff --git a/docs/maps/images/timeslider.gif b/docs/maps/images/timeslider.gif new file mode 100644 index 0000000000000..463adf9a9300d Binary files /dev/null and b/docs/maps/images/timeslider.gif differ diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index 20320c5a938c9..0922a4cc94c22 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -11,6 +11,7 @@ Create beautiful maps from your geographical data. With **Maps**, you can: * Build maps with multiple layers and indices. +* Animate spatial temporal data. * Upload GeoJSON. * Embed your map in dashboards. * Symbolize features using data values. @@ -39,6 +40,15 @@ Use multiple layers and indices to show all your data in a single map. Show how [role="screenshot"] image::maps/images/sample_data_ecommerce.png[] +[float] +=== Animate spatial temporal data +Data comes to life with animation. Hard to detect patterns in static data pop out with movement. Use time slider to animate your data and gain deeper insights. + +This animated map uses the time slider to show Portland buses over a period of 15 minutes. The routes come alive as the bus locations update with time. + +[role="screenshot"] +image::maps/images/timeslider.gif[] + [float] === Upload GeoJSON Use **Maps** to drag and drop your GeoJSON points, lines, and polygons into Elasticsearch, and then use them as layers in your map. diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 031c7be077f52..ada7551f3e57d 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -8,7 +8,8 @@ Layers that request data from {es} are narrowed when you submit a <> with a configured time field are narrowed by the <>. -Layers narrowed by the time filter contain the clock icon image:maps/images/clock_icon.png[] next to the layer name in the legend. +These layers contain the clock icon image:maps/images/clock_icon.png[clock icon] next to the layer name in the legend. Use the time slider to quickly select time slices within the global time filter range. +Click previous and next buttons to advance the time slice backward or forward. Click play to animate your spatial temporal data. You can create a layer that requests data from {es} from the following: @@ -35,13 +36,30 @@ image::maps/images/global_search_bar.png[] [[maps-create-filter-from-map]] === Create filters from a map -You can create two types of filters by interacting with your map: +Create filters from your map to focus in on just the data you want. *Maps* provides three ways to create filters: -* <> -* <> +* <> +* <> +* <> + +[float] +[[maps-map-extent-filter]] +==== Filter dashboard by map extent + +A map extent shows uniform data across all panels. +As you pan and zoom your map, all panels will update to only include data that is visable in your map. + +To enable filtering your dashboard by map extent: + +* Open the main menu, and then click *Dashboard*. +* Select your dashboard from the list or click *Create dashboard*. +* If your dashboard does not have a map, add a map panel. +* Click the gear icon image:maps/images/gear_icon.png[gear icon] to open the map panel menu. +* Select *More* to view all panel options. +* Select *Enable filter by map extent*. [role="screenshot"] -image::maps/images/create_spatial_filter.png[] +image::maps/images/enable_filter_by_map_extent.png[] [float] [[maps-spatial-filters]] @@ -57,10 +75,12 @@ You can create spatial filters in two ways: Spatial filters have the following properties: * *Geometry label* enables you to provide a meaningful name for your spatial filter. -* *Spatial field* specifies the geo_point or geo_shape field used to determine if a document matches the spatial relation with the specified geometry. * *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#_spatial_relations[spatial relation operator] to use at search time. * *Action* specifies whether to apply the filter to the current view or to a drilldown action. Only available when the map is a panel in a {kibana-ref}/dashboard.html[dashboard] with {kibana-ref}/drilldowns.html[drilldowns]. +[role="screenshot"] +image::maps/images/create_spatial_filter.png[] + [float] [[maps-phrase-filter]] ==== Phrase filters diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index 81f3945779503..8eea3b1ee4552 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -30,6 +30,8 @@ Kibana supports spaces in several ways. You can: The `kibana_admin` role or equivilent is required to manage **Spaces**. +TIP: Looking to support multiple tenants? See <> for more information. + [float] [[spaces-managing]] === View, create, and delete spaces diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index af67b3016454b..516f4c66d47bb 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -27,12 +27,12 @@ Panels display your data in charts, tables, maps, and more, which allow you to c | Display the results from machine learning anomaly detection jobs. | <> -| Display an anomaly chart from the *Anomaly Explorer. +| Display an anomaly chart from the *Anomaly Explorer*. | <> | Display a table of live streaming logs. -| <> +| <> | Add context to your panels with <>, or add dynamic filters with <>. |=== @@ -131,6 +131,38 @@ If you change your mind and want to add the panel to the *Visualize Library*: . Enter the panel title, then click *Save*. +[float] +[[add-text]] +== Add context to panels + +To provide context to your dashboard panels, add *Text* panels that display important information, instructions, images, and more. + +You create *Text* panels using GitHub-flavored Markdown text. For information about GitHub-flavored Markdown text, click *Help*. + +. From the dashboard, click *All types*, then select *Text*. + +. In the *Markdown* field, enter the text, then click *Update*. + +For example, when you enter: + +[role="screenshot"] +image::images/markdown_example_1.png[] + +The following instructions are displayed: + +[role="screenshot"] +image::images/markdown_example_2.png[] + +Or when you enter: + +[role="screenshot"] +image::images/markdown_example_3.png[] + +The following image is displayed: + +[role="screenshot"] +image::images/markdown_example_4.png[] + [float] [[edit-panels]] == Edit panels @@ -316,8 +348,6 @@ include::lens-advanced.asciidoc[] include::create-panels-with-editors.asciidoc[] -include::enhance-dashboards.asciidoc[] - -include::drilldowns.asciidoc[] +include::make-dashboards-interactive.asciidoc[] include::aggregation-reference.asciidoc[] diff --git a/docs/user/dashboard/enhance-dashboards.asciidoc b/docs/user/dashboard/enhance-dashboards.asciidoc index c999ec9b68251..de8af34e6d32e 100644 --- a/docs/user/dashboard/enhance-dashboards.asciidoc +++ b/docs/user/dashboard/enhance-dashboards.asciidoc @@ -8,7 +8,7 @@ To make your dashboard look the way you want, use the editing options. [[add-controls]] === Add controls -To filter the data on your dashboard in real-time, add a *Controls* panel. +To filter the data on your dashboard in real-time, add a *Controls* panel or use a map panel to <>. {kib} supports two types of *Controls*: diff --git a/docs/user/dashboard/images/drilldown_on_data_table.gif b/docs/user/dashboard/images/drilldown_on_data_table.gif new file mode 100644 index 0000000000000..926b0ff43aea6 Binary files /dev/null and b/docs/user/dashboard/images/drilldown_on_data_table.gif differ diff --git a/docs/user/dashboard/images/drilldown_on_panel.png b/docs/user/dashboard/images/drilldown_on_panel.png index 591f3280c7eca..71b83ae457062 100644 Binary files a/docs/user/dashboard/images/drilldown_on_panel.png and b/docs/user/dashboard/images/drilldown_on_panel.png differ diff --git a/docs/user/dashboard/make-dashboards-interactive.asciidoc b/docs/user/dashboard/make-dashboards-interactive.asciidoc new file mode 100644 index 0000000000000..3c2397d0241bb --- /dev/null +++ b/docs/user/dashboard/make-dashboards-interactive.asciidoc @@ -0,0 +1,241 @@ +[[role="xpack"]] +[[drilldowns]] +== Make dashboards interactive + +:keywords: administrator, analyst, concept, task, analyze, dashboard, controls, drilldowns +:description: Add interactive capabilities to your dashboard, such as controls that allow \ +you to apply dashboard-level filters, and drilldowns that allow you to navigate to other \ +dashboards and external websites. + +Add interactive capabilities to your dashboard, such as controls that allow you to apply dashboard-level filters, and drilldowns that allow you to navigate to other dashboards and external websites. + +*Controls* panels allow you to apply dashboard-level filters based on values from a list, or a range of values. + +{kib} supports two types of *Controls*: + +* *Options list* — Filters content based on one or more specified options. The dropdown menu is dynamically populated with the results of a terms aggregation. +For example, use the options list on the sample flight dashboard when you want to filter the data by origin city and destination city. + +* *Range slider* — Filters data within a specified range of numbers. The minimum and maximum values are dynamically populated with the results of a +min and max aggregation. For example, use the range slider when you want to filter the sample flight dashboard by a specific average ticket price. ++ +[role="screenshot"] +image::images/dashboard-controls.png[] + +Panels have built-in interactive capabilities that apply filters to the dashboard data. For example, when you drag a time range or click a pie slice, a filter for the time range or pie slice is applied. Drilldowns let you customize the interactive behavior while keeping the context of the interaction. + +{kib} supports two types of drilldowns: + +* *Dashboard* — Navigates you from one dashboard to another dashboard. For example, when can create a drilldown for a *Lens* panel that navigates you from a summary dashboard to a dashboard with a filter for a specific host name. + +* *URL* — Navigates you from a dashboard to an external website. For example, a website with the specific host name as a parameter. + +++++ + + +
+++++ + +Third-party developers can create drilldowns. To learn how to code drilldowns, refer to {kib-repo}blob/{branch}/x-pack/examples/ui_actions_enhanced_examples[this example plugin]. + +[float] +[[add-controls]] +=== Add Controls panels + +Add interactive dashboard-level filter panels to your dashboard. + +. On the dashboard, click *All types*, then select *Controls*. + +. Click *Options*, then configure the following options: + +* *Update {kib} filters on each change* — When selected, all interactive inputs create filters that refresh the dashboard. When unselected, + {kib} filters are created only when you click *Apply changes*. + +* *Use time filter* — When selected, the aggregations that generate the options list and time range are connected to the <>. + +* *Pin filters for all applications* — When selected, all filters created by interacting with the inputs are automatically pinned. + +. Click *Update* + +[float] +[[dashboard-drilldowns]] +=== Create dashboard drilldowns + +Dashboard drilldowns enable you to open a dashboard from another dashboard, taking the time range, filters, and other parameters with you so the context remains the same. Dashboard drilldowns help you to continue your analysis from a new perspective. + +For example, if you have a dashboard that shows the logs and metrics for multiple data centers, you can create a drilldown that navigates from the dashboard that shows multiple data centers, to a dashboard that shows a single data center or server. + +[role="screenshot"] +image:images/drilldown_on_data_table.gif[Drilldown on data table that navigates to another dashboard] + +The panels you create using the following editors support dashboard drilldowns: + +* *Lens* +* *Maps* +* *TSVB* +* *Vega* +* *Aggregation-based* area chart, data table, heat map, horitizontal bar chart, line chart, pie chart, tag cloud, and vertical bar chart +* *Timelion* + +[float] +==== Create and set up the dashboards you want to connect + +Use the <> data to create a dashboard and add panels, then set a search and filter on the *[Logs] Web traffic* dashboard. + +. Add the *Sample web logs* data. + +. Create a new dashboard, click *Add from Library*, then add the following panels: + +* *[Logs] Heatmap* +* *[Logs] Host, Visits, and Bytes Table* +* *[Logs] Total Requests and Bytes* +* *[Logs] Visitors by OS* + +. Set the <> to *Last 30 days*. + +. Save the dashboard. In the *Title* field, enter `Host Overview`. + +. Open the *[Logs] Web traffic* dashboard. + +. Create a data table. + +.. In the toolbar, click *Edit*. + +.. Click *Create visualization*. + +.. From the *Chart type* dropdown, select *Table*. + +.. From the *Available fields* list, drag and drop the following fields onto the visualization builder: + +* *agent.keyword* + +* *bytes* + +* *geo.src* + +* *ip* + +.. In the editor, remove *Count of records*, then click *Save and return*. + +. On the *[Logs] Web traffic* dashboard, set a search and filter. ++ +[%hardbreaks] +Search: `extension.keyword: ("gz" or "css" or "deb")` +Filter: `geo.src: CN` + +[float] +==== Create the drilldown + +Create a drilldown that opens the *Host Overview* dashboard from the *[Logs] Web traffic* dashboard. + +. Open the panel menu for the data table you created, then select *Create drilldown*. + +. Click *Go to dashboard*. + +.. Give the drilldown a name. For example, `My Drilldown`. + +.. From the *Choose a destination dashboard* dropdown, select *Host Overview*. + +.. To use the geo.src filter, KQL query, and time filter, select *Use filters and query from origin dashboard* and *Use date range from origin dashboard*. + +.. Click *Create drilldown*. + +. Save the dashboard. + +. In the data table panel, hover over a value, click *+*, then select `My Drilldown`. ++ +[role="screenshot"] +image::images/drilldown_on_panel.png[Drilldown on data table that navigates to another dashboard] + +[float] +[[url-drilldowns]] +=== Create URL drilldowns + +URL drilldowns enable you to navigate from a dashboard to external websites. Destination URLs can be dynamic, depending on the dashboard context or user interaction with a panel. To create URL drilldowns, you add <> to a URL template, which configures the behavior of the drilldown. + +[role="screenshot"] +image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigates to Github] + +Some panels support multiple interactions, also known as triggers. +The <> you use to create a <> depends on the trigger you choose. URL drilldowns support these types of triggers: + +* *Single click* — A single data point in the panel. + +* *Range selection* — A range of values in a panel. + +For example, *Single click* has `{{event.value}}` and *Range selection* has `{{event.from}}` and `{{event.to}}`. + +The panels you create using the following editors support dashboard drilldowns: + +* *Lens* +* *Maps* +* *TSVB* +* *Aggregation-based* area chart, data table, heat map, horitizontal bar chart, line chart, pie chart, tag cloud, and vertical bar chart + +[float] +==== Create a URL drilldown + +For example, if you have a dashboard that shows data from a Github repository, you can create a URL drilldown that opens Github from the dashboard panel. + +. Add the *Sample web logs* data. + +. Open the *[Logs] Web traffic* dashboard. + +. In the toolbar, click *Edit*. + +. Open the *[Logs] Visitors by OS* panel menu, then select *Create drilldown*. + +. Click *Go to URL*. + +.. Give the drilldown a name. For example, `Show on Github`. + +.. For the *Trigger*, select *Single click*. + +.. To navigate to the {kib} repository Github issues, enter the following in the *Enter URL* field: ++ +[source, bash] +---- +https://github.com/elastic/kibana/issues?q=is:issue+is:open+{{event.value}} +---- ++ +{kib} substitutes `{{event.value}}` with a value associated with the selected pie slice. + +.. Click *Create drilldown*. + +. Save the dashboard. + +. On the *[Logs] Visitors by OS* panel, click any chart slice, then select *Show on Github*. ++ +[role="screenshot"] +image:images/url_drilldown_popup.png[URL drilldown popup] + +. In the list of {kib} repository issues, verify that the slice value appears. ++ +[role="screenshot"] +image:images/url_drilldown_github.png[Github] + +[float] +[[manage-drilldowns]] +=== Manage drilldowns + +Make changes to your drilldowns, make a copy of your drilldowns for another panel, and delete drilldowns. + +. Open the panel menu that includes the drilldown, then click *Manage drilldowns*. + +. On the *Manage* tab, use the following options: + +* To change drilldowns, click *Edit* next to the drilldown you want to change, make your changes, then click *Save*. + +* To make a copy, click *Copy* next to the drilldown you want to change, enter the drilldown name, then click *Create drilldown*. + +* To delete a drilldown, select the drilldown you want to delete, then click *Delete*. + +include::url-drilldown.asciidoc[] diff --git a/docs/user/production-considerations/task-manager-troubleshooting.asciidoc b/docs/user/production-considerations/task-manager-troubleshooting.asciidoc index 363562d4cd193..98201087b9aae 100644 --- a/docs/user/production-considerations/task-manager-troubleshooting.asciidoc +++ b/docs/user/production-considerations/task-manager-troubleshooting.asciidoc @@ -248,7 +248,7 @@ The API returns the following: "overdue": 10, "overdue_non_recurring": 10, "estimated_schedule_density": [0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0], - "capacity_requirments": { + "capacity_requirements": { "per_minute": 6, "per_hour": 28, "per_day": 2 @@ -737,7 +737,7 @@ Evaluating the preceding health stats in the previous example, you see the follo 0, 3, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0 ], - "capacity_requirments": { # <10> + "capacity_requirements": { # <10> "per_minute": 14, "per_hour": 240, "per_day": 0 @@ -819,7 +819,7 @@ Suppose the output of `stats.workload.value` looked something like this: 0, 31, 0, 12, 16, 31, 0, 10, 0, 10, 3, 22, 0, 10, 0, 2, 10, 10, 1, 0 ], - "capacity_requirments": { + "capacity_requirements": { "per_minute": 329, # <4> "per_hour": 4272, # <5> "per_day": 61 # <6> diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index c62f137f98528..523a90bdf07ce 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -6,7 +6,21 @@ The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built- When you assign a user multiple roles, the user receives a union of the roles’ privileges. Therefore, assigning the `kibana_admin` role in addition to a custom role that grants {kib} privileges is ineffective because `kibana_admin` has access to all the features in all spaces. -NOTE: When running multiple tenants of {kib} by changing the `kibana.index` in your `kibana.yml`, you cannot use `kibana_admin` to grant access. You must create custom roles that authorize the user for that specific tenant. Although multi-tenant installations are supported, the recommended approach to securing access to {kib} segments is to grant users access to specific spaces. +[[xpack-security-multiple-tenants]] +==== Supporting multiple tenants + +There are two approaches to supporting multi-tenancy in {kib}: + +1. *Recommended:* Create a space and a limited role for each tenant, and configure each user with the appropriate role. See +<> for more details. +2. deprecated:[7.13.0,"In 8.0 and later, the `kibana.index` setting will no longer be supported."] Set up separate {kib} instances to work +with a single {es} cluster by changing the `kibana.index` setting in your `kibana.yml` file. ++ +NOTE: When using multiple {kib} instances this way, you cannot use the `kibana_admin` role to grant access. You must create custom roles +that authorize the user for each specific instance. + +Whichever approach you use, be careful when granting cluster privileges and index privileges. Both of these approaches share the same {es} +cluster, and {kib} spaces do not prevent you from granting users of two different tenants access to the same index. [role="xpack"] [[xpack-kibana-role-management]] diff --git a/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc b/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc index 63b83712e3e6e..199f138347fa0 100644 --- a/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc +++ b/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc @@ -11,7 +11,7 @@ This guide introduces you to three of {kib}'s security features: spaces, roles, [float] === Spaces -Do you have multiple teams using {kib}? Do you want a “playground” to experiment with new visualizations or alerts? If so, then <> can help. +Do you have multiple teams or tenants using {kib}? Do you want a “playground” to experiment with new visualizations or alerts? If so, then <> can help. Think of a space as another instance of {kib}. A space allows you to organize your <>, <>, <>, and much more into their own categories. For example, you might have a Marketing space for your marketeers to track the results of their campaigns, and an Engineering space for your developers to {apm-get-started-ref}/overview.html[monitor application performance]. diff --git a/examples/expressions_explorer/public/run_expressions.tsx b/examples/expressions_explorer/public/run_expressions.tsx index 05749a0e89735..a635fab7ec8ae 100644 --- a/examples/expressions_explorer/public/run_expressions.tsx +++ b/examples/expressions_explorer/public/run_expressions.tsx @@ -7,6 +7,7 @@ */ import React, { useState, useEffect, useMemo } from 'react'; +import { pluck } from 'rxjs/operators'; import { EuiCodeBlock, EuiFlexItem, @@ -35,7 +36,7 @@ interface Props { export function RunExpressionsExample({ expressions, inspector }: Props) { const [expression, updateExpression] = useState('markdown "## expressions explorer"'); - const [result, updateResult] = useState({}); + const [result, updateResult] = useState({}); const expressionChanged = (value: string) => { updateExpression(value); @@ -49,17 +50,13 @@ export function RunExpressionsExample({ expressions, inspector }: Props) { ); useEffect(() => { - const runExpression = async () => { - const execution = expressions.execute(expression, null, { - debug: true, - inspectorAdapters, - }); + const execution = expressions.execute(expression, null, { + debug: true, + inspectorAdapters, + }); + const subscription = execution.getData().pipe(pluck('result')).subscribe(updateResult); - const data: any = await execution.getData(); - updateResult(data); - }; - - runExpression(); + return () => subscription.unsubscribe(); }, [expression, expressions, inspectorAdapters]); return ( diff --git a/package.json b/package.json index 1cc379fb807d0..de7df7fea3d8d 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "**/underscore": "^1.13.1" }, "engines": { - "node": "14.17.0", + "node": "14.17.2", "yarn": "^1.21.1" }, "dependencies": { diff --git a/packages/kbn-i18n/GUIDELINE.md b/packages/kbn-i18n/GUIDELINE.md index 437e73bb27019..806e799bd1106 100644 --- a/packages/kbn-i18n/GUIDELINE.md +++ b/packages/kbn-i18n/GUIDELINE.md @@ -19,6 +19,7 @@ Ids should end with: - ErrorMessage (if it's an error message), - LinkText (if it's `` tag), - ToggleSwitch and etc. +- `.markdown` (if it's markdown) There is one more complex case, when we have to divide a single expression into different labels. @@ -110,7 +111,7 @@ Currently, we support the following AngluarJS `i18n` tools, but they will be rem ### Naming convention The message ids chosen for message keys should always be descriptive of the string, and its role in the interface (button label, title, etc.). Think of them as long variable names. When you have to change a message id, adding a progressive number to the existing key should always be used as a last resort. -Here's a rule of id maning: +Here's a rule of id naming: `{plugin}.{area}.[{sub-area}].{element}` @@ -138,6 +139,7 @@ Here's a rule of id maning: 'kbn.management.createIndexPattern.includeSystemIndicesToggleSwitch' 'kbn.management.editIndexPattern.wrongTypeErrorMessage' 'kbn.management.editIndexPattern.scripted.table.nameDescription' + 'xpack.lens.formulaDocumentation.filterRatioDescription.markdown' ``` - For complex messages, which are divided into several parts, use the following approach: @@ -192,6 +194,7 @@ Each message id should end with a type of the message. | tooltip | `kbn.management.editIndexPattern.removeTooltip` | | error message | `kbn.management.createIndexPattern.step.invalidCharactersErrorMessage` | | toggleSwitch | `kbn.management.createIndexPattern.includeSystemIndicesToggleSwitch` | +| markdown | `xpack.lens.formulaDocumentation.filterRatioDescription.markdown` | For example: @@ -281,6 +284,27 @@ For example: /> ``` +- for markdown + ```js + import { Markdown } from '@elastic/eui'; + + + ``` + ### Variety of `values` - Variables @@ -372,6 +396,82 @@ Here is an example of message translation depending on a plural category: When `conflictFieldsLength` equals 1, the result string will be `"A field is defined as several types (string, integer, etc) across the indices that match this pattern."`. In cases when `conflictFieldsLength` has value of 2 or more, the result string - `"2 fields are defined as several types (string, integer, etc) across the indices that match this pattern."`. +### Text with markdown + +There is some support for using markdown and you can use any of the following syntax: + +#### Headers + +```md +# This is an

tag +## This is an

tag +###### This is an

tag +``` + +#### Emphasis + +```md +*This text will be italic* +_This will also be italic_ + +**This text will be bold** +__This will also be bold__ + +_You **can** combine them_ +``` + +#### Lists + ##### Unordered + +```md +* Item 1 +* Item 2 + * Item 2a + * Item 2b +``` + ##### Ordered + +```md +1. Item 1 +1. Item 2 +1. Item 3 + 1. Item 3a + 1. Item 3b +``` +#### Images + +```md +![Github Logo](/images/logo.png) +Format: ![Alt Text](url) +``` + +#### Links + +```md +http://github.com - automatic! +[GitHub](http://github.com) +``` + +#### Blockquotes + +```md +As Kanye West said: + +> We're living the future so +> the present is our past. +``` +#### Code Blocks + +```md +var a = 13; +``` + +#### Inline code + +```md +I think you should use an +`` element here instead +``` ### Splitting Splitting sentences into several keys often inadvertently presumes a grammar, a sentence structure, and such composite strings are often very difficult to translate. @@ -385,6 +485,12 @@ Splitting sentences into several keys often inadvertently presumes a grammar, a If this group of sentences is separated it’s possible that the context of the `'it'` in `'close it'` will be lost. +### Large paragraphs + +Try to avoid using large paragraphs of text. They are difficult to maintain and often need small changes when the information becomes out of date. + +If you have no other choice, you can split paragraphs into a _few_ i18n chunks. Chunks should be split at logical points to ensure they contain enough context to be intelligible on their own. + ### Unit tests #### How to test `FormattedMessage` and `i18n.translate()` components. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c6960621359c7..6627b644daec7 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -99,7 +99,7 @@ pageLoadAssetSize: watcher: 43598 runtimeFields: 41752 stackAlerts: 29684 - presentationUtil: 49767 + presentationUtil: 94301 spacesOss: 18817 indexPatternFieldEditor: 90489 osquery: 107090 @@ -110,4 +110,5 @@ pageLoadAssetSize: timelines: 230410 screenshotMode: 17856 visTypePie: 35583 + expressionRevealImage: 25675 cases: 144442 diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index e0f0432c61463..6fc0841551fad 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -17,6 +17,7 @@ export const storybookAliases = { dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', data_enhanced: 'x-pack/plugins/data_enhanced/.storybook', embeddable: 'src/plugins/embeddable/.storybook', + expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook', infra: 'x-pack/plugins/infra/.storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook', diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 8db6a0e8a8c7f..638b1c83e9dc6 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -40,6 +40,7 @@ export function DashboardApp({ embeddable, onAppLeave, uiSettings, + data, } = useKibana().services; const kbnUrlStateStorage = useMemo( @@ -98,6 +99,13 @@ export function DashboardApp({ ]); }, [chrome, dashboardState.title, dashboardState.viewMode, redirectTo, savedDashboardId]); + // clear search session when leaving dashboard route + useEffect(() => { + return () => { + data.search.session.clear(); + }; + }, [data.search.session]); + return ( <> {isCompleteDashboardAppState(dashboardAppState) && ( diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index e77353000ced4..cb40b30542869 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -260,6 +260,7 @@ export async function mountApp({ } render(app, element); return () => { + dataStart.search.session.clear(); unlistenParentHistory(); unmountComponentAtNode(element); appUnMounted(); diff --git a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts index cb8c5ac5745e4..8b895d739e2d1 100644 --- a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts @@ -64,6 +64,11 @@ export const buildDashboardContainer = async ({ getLatestDashboardState, canStoreSearchSession: dashboardCapabilities.storeSearchSession, }); + + if (incomingEmbeddable?.searchSessionId) { + session.continue(incomingEmbeddable?.searchSessionId); + } + const searchSessionIdFromURL = getSearchSessionIdFromURL(history); if (searchSessionIdFromURL) { session.restore(searchSessionIdFromURL); diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx index 5557bf25d9d85..7f72c77009cb9 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx @@ -87,11 +87,6 @@ export const DashboardListing = ({ }; }, [title, savedObjectsClient, redirectTo, data.query, kbnUrlStateStorage]); - // clear dangling session because they are not required here - useEffect(() => { - data.search.session.clear(); - }, [data.search.session]); - const hideWriteControls = dashboardCapabilities.hideWriteControls; const listingLimit = savedObjects.settings.getListingLimit(); const defaultFilter = title ? `"${title}"` : ''; diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index e5f89bd6a8e90..dab74373efef5 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -204,10 +204,11 @@ export function DashboardTopNav({ path, state: { originatingApp: DashboardConstants.DASHBOARDS_ID, + searchSessionId: data.search.session.getSessionId(), }, }); }, - [trackUiMetric, stateTransferService] + [stateTransferService, data.search.session, trackUiMetric] ); const clearAddPanel = useCallback(() => { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 35094fac1cc0f..66d81d058fc77 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2783,7 +2783,7 @@ export interface WaitUntilNextSessionCompletesOptions { // src/plugins/data/public/index.ts:433:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:436:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/search/session/session_service.ts:56:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/search/session/session_service.ts:62:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index 18d32463864e3..dee0216530205 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -47,5 +47,6 @@ export function getSessionServiceMock(): jest.Mocked { isSessionStorageReady: jest.fn(() => true), getSearchSessionIndicatorUiConfig: jest.fn(() => ({ isDisabled: () => ({ disabled: false }) })), hasAccess: jest.fn(() => true), + continue: jest.fn(), }; } diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 7f388a29cd454..c2c4d1540c387 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -98,12 +98,12 @@ describe('Session service', () => { expect(nowProvider.reset).toHaveBeenCalled(); }); - it("Can clear other apps' session", async () => { + it("Can't clear other apps' session", async () => { sessionService.start(); expect(sessionService.getSessionId()).not.toBeUndefined(); currentAppId$.next('change'); sessionService.clear(); - expect(sessionService.getSessionId()).toBeUndefined(); + expect(sessionService.getSessionId()).not.toBeUndefined(); }); it("Can start a new session in case there is other apps' stale session", async () => { @@ -161,6 +161,72 @@ describe('Session service', () => { }); }); + it('Can continue previous session from another app', async () => { + sessionService.start(); + const sessionId = sessionService.getSessionId(); + + sessionService.clear(); + currentAppId$.next('change'); + sessionService.continue(sessionId!); + + expect(sessionService.getSessionId()).toBe(sessionId); + }); + + it('Calling clear() more than once still allows previous session from another app to continue', async () => { + sessionService.start(); + const sessionId = sessionService.getSessionId(); + + sessionService.clear(); + sessionService.clear(); + + currentAppId$.next('change'); + sessionService.continue(sessionId!); + + expect(sessionService.getSessionId()).toBe(sessionId); + }); + + it('Continue drops storage configuration', () => { + sessionService.start(); + const sessionId = sessionService.getSessionId(); + + sessionService.enableStorage({ + getName: async () => 'Name', + getUrlGeneratorData: async () => ({ + urlGeneratorId: 'id', + initialState: {}, + restoreState: {}, + }), + }); + + expect(sessionService.isSessionStorageReady()).toBe(true); + + sessionService.clear(); + + sessionService.continue(sessionId!); + + expect(sessionService.isSessionStorageReady()).toBe(false); + }); + + // it might be that search requests finish after the session is cleared and before it was continued, + // to avoid "infinite loading" state after we continue the session we have to drop pending searches + it('Continue drops client side loading state', async () => { + const sessionId = sessionService.start(); + + sessionService.trackSearch({ abort: () => {} }); + expect(state$.getValue()).toBe(SearchSessionState.Loading); + + sessionService.clear(); // even allow to call clear multiple times + + expect(state$.getValue()).toBe(SearchSessionState.None); + + sessionService.continue(sessionId!); + expect(sessionService.getSessionId()).toBe(sessionId); + + // the original search was never `untracked`, + // but we still consider this a completed session until new search fire + expect(state$.getValue()).toBe(SearchSessionState.Completed); + }); + test('getSearchOptions infers isRestore & isStored from state', async () => { const sessionId = sessionService.start(); const someOtherId = 'some-other-id'; diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 629d76b07d7ca..32cd620a2adb2 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -20,6 +20,7 @@ import { ConfigSchema } from '../../../config'; import { createSessionStateContainer, SearchSessionState, + SessionStateInternal, SessionMeta, SessionStateContainer, } from './search_session_state'; @@ -35,6 +36,11 @@ export interface TrackSearchDescriptor { abort: () => void; } +/** + * Represents a search session state in {@link SessionService} in any given moment of time + */ +export type SessionSnapshot = SessionStateInternal; + /** * Provide info about current search session to be stored in the Search Session saved object */ @@ -88,6 +94,13 @@ export class SessionService { private toastService?: ToastService; + /** + * Holds snapshot of last cleared session so that it can be continued + * Can be used to re-use a session between apps + * @private + */ + private lastSessionSnapshot?: SessionSnapshot; + constructor( initializerContext: PluginInitializerContext, getStartServices: StartServicesAccessor, @@ -128,6 +141,21 @@ export class SessionService { this.subscription.add( coreStart.application.currentAppId$.subscribe((newAppName) => { this.currentApp = newAppName; + if (!this.getSessionId()) return; + + // Apps required to clean up their sessions before unmounting + // Make sure that apps don't leave sessions open by throwing an error in DEV mode + const message = `Application '${ + this.state.get().appName + }' had an open session while navigating`; + if (initializerContext.env.mode.dev) { + coreStart.fatalErrors.add(message); + } else { + // this should never happen in prod because should be caught in dev mode + // in case this happen we don't want to throw fatal error, as most likely possible bugs are not that critical + // eslint-disable-next-line no-console + console.warn(message); + } }) ); }); @@ -158,6 +186,7 @@ export class SessionService { public destroy() { this.subscription.unsubscribe(); this.clear(); + this.lastSessionSnapshot = undefined; } /** @@ -198,7 +227,9 @@ export class SessionService { */ public start() { if (!this.currentApp) throw new Error('this.currentApp is missing'); + this.state.transitions.start({ appName: this.currentApp }); + return this.getSessionId()!; } @@ -211,10 +242,52 @@ export class SessionService { this.refreshSearchSessionSavedObject(); } + /** + * Continue previous search session + * Can be used to share a running search session between different apps, so they can reuse search cache + * + * This is different from {@link restore} as it reuses search session state and search results held in client memory instead of restoring search results from elasticsearch + * @param sessionId + */ + public continue(sessionId: string) { + if (this.lastSessionSnapshot?.sessionId === sessionId) { + this.state.set({ + ...this.lastSessionSnapshot, + // have to change a name, so that current app can cancel a session that it continues + appName: this.currentApp, + // also have to drop all pending searches which are used to derive client side state of search session indicator, + // if we weren't dropping this searches, then we would get into "infinite loading" state when continuing a session that was cleared with pending searches + // possible solution to this problem is to refactor session service to support multiple sessions + pendingSearches: [], + }); + this.lastSessionSnapshot = undefined; + } else { + // eslint-disable-next-line no-console + console.warn( + `Continue search session: last known search session id: "${this.lastSessionSnapshot?.sessionId}", but received ${sessionId}` + ); + } + } + /** * Cleans up current state */ public clear() { + // make sure apps can't clear other apps' sessions + const currentSessionApp = this.state.get().appName; + if (currentSessionApp && currentSessionApp !== this.currentApp) { + // eslint-disable-next-line no-console + console.warn( + `Skip clearing session "${this.getSessionId()}" because it belongs to a different app. current: "${ + this.currentApp + }", owner: "${currentSessionApp}"` + ); + return; + } + + if (this.getSessionId()) { + this.lastSessionSnapshot = this.state.get(); + } this.state.transitions.clear(); this.searchSessionInfoProvider = undefined; this.searchSessionIndicatorUiConfig = undefined; diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.scss b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.scss index 139230fbdb66a..9ef123fa1a60f 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.scss +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.scss @@ -1,4 +1,5 @@ .dscSidebar { + overflow: hidden; margin: 0 !important; flex-grow: 1; padding-left: $euiSize; diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 058fd832e15db..ea90307ef57a1 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -111,6 +111,7 @@ export class EditPanelAction implements Action { originatingApp: this.currentAppId, valueInput: byValueMode ? this.getExplicitInput({ embeddable }) : undefined, embeddableId: embeddable.id, + searchSessionId: embeddable.getInput().searchSessionId, }; return { app, path, state }; } diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index 5e5ef9c360a64..98cf6e70284cd 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -19,6 +19,12 @@ export interface EmbeddableEditorState { originatingApp: string; embeddableId?: string; valueInput?: EmbeddableInput; + + /** + * Pass current search session id when navigating to an editor, + * Editors could use it continue previous search session + */ + searchSessionId?: string; } export function isEmbeddableEditorState(state: unknown): state is EmbeddableEditorState { @@ -35,6 +41,12 @@ export interface EmbeddablePackageState { type: string; input: Optional | Optional; embeddableId?: string; + + /** + * Pass current search session id when navigating to an editor, + * Editors could use it continue previous search session + */ + searchSessionId?: string; } export function isEmbeddablePackageState(state: unknown): state is EmbeddablePackageState { diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 98c48dbd848b0..a810b1f48a07c 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -365,6 +365,7 @@ export interface EmbeddableEditorState { embeddableId?: string; // (undocumented) originatingApp: string; + searchSessionId?: string; // (undocumented) valueInput?: EmbeddableInput; } @@ -467,6 +468,7 @@ export interface EmbeddablePackageState { embeddableId?: string; // (undocumented) input: Optional | Optional; + searchSessionId?: string; // (undocumented) type: string; } diff --git a/src/plugins/expression_reveal_image/.i18nrc.json b/src/plugins/expression_reveal_image/.i18nrc.json new file mode 100755 index 0000000000000..5b073e4374519 --- /dev/null +++ b/src/plugins/expression_reveal_image/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "expressionRevealImage", + "paths": { + "expressionRevealImage": "." + }, + "translations": ["translations/ja-JP.json"] +} diff --git a/src/plugins/expression_reveal_image/.storybook/main.js b/src/plugins/expression_reveal_image/.storybook/main.js new file mode 100644 index 0000000000000..742239e638b8a --- /dev/null +++ b/src/plugins/expression_reveal_image/.storybook/main.js @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line import/no-commonjs +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/src/plugins/expression_reveal_image/README.md b/src/plugins/expression_reveal_image/README.md new file mode 100755 index 0000000000000..21c27a6eee05b --- /dev/null +++ b/src/plugins/expression_reveal_image/README.md @@ -0,0 +1,9 @@ +# expressionRevealImage + +Expression Reveal Image plugin adds a `revealImage` function to the expression plugin and an associated renderer. The renderer will display the given percentage of a given image. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_reveal_image/common/constants.ts b/src/plugins/expression_reveal_image/common/constants.ts new file mode 100644 index 0000000000000..68ac53171ee7f --- /dev/null +++ b/src/plugins/expression_reveal_image/common/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export const PLUGIN_ID = 'expressionRevealImage'; +export const PLUGIN_NAME = 'expressionRevealImage'; diff --git a/src/plugins/expression_reveal_image/common/expression_functions/index.ts b/src/plugins/expression_reveal_image/common/expression_functions/index.ts new file mode 100644 index 0000000000000..dba24e8a0cb0a --- /dev/null +++ b/src/plugins/expression_reveal_image/common/expression_functions/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { revealImageFunction } from './reveal_image_function'; + +export const functions = [revealImageFunction]; + +export { revealImageFunction }; diff --git a/src/plugins/expression_reveal_image/common/expression_functions/reveal_image.test.ts b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image.test.ts new file mode 100644 index 0000000000000..633a132fea5e3 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image.test.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + functionWrapper, + elasticOutline, + elasticLogo, +} from '../../../presentation_util/common/lib'; +import { getFunctionErrors } from '../i18n'; +import { revealImageFunction } from './reveal_image_function'; +import { Origin } from '../types'; +import { ExecutionContext } from 'src/plugins/expressions'; + +const errors = getFunctionErrors().revealImage; + +describe('revealImageFunction', () => { + const fn = functionWrapper(revealImageFunction); + + it('returns a render as revealImage', () => { + const result = fn( + 0.5, + { + image: null, + emptyImage: null, + origin: Origin.BOTTOM, + }, + {} as ExecutionContext + ); + expect(result).toHaveProperty('type', 'render'); + expect(result).toHaveProperty('as', 'revealImage'); + }); + + describe('context', () => { + it('throws when context is not a number between 0 and 1', () => { + expect(() => { + fn( + 10, + { + image: elasticLogo, + emptyImage: elasticOutline, + origin: Origin.TOP, + }, + {} as ExecutionContext + ); + }).toThrow(new RegExp(errors.invalidPercent(10).message)); + + expect(() => { + fn( + -0.1, + { + image: elasticLogo, + emptyImage: elasticOutline, + origin: Origin.TOP, + }, + {} as ExecutionContext + ); + }).toThrow(new RegExp(errors.invalidPercent(-0.1).message)); + }); + }); + + describe('args', () => { + describe('image', () => { + it('sets the image', () => { + const result = fn( + 0.89, + { + emptyImage: null, + origin: Origin.TOP, + image: elasticLogo, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('image', elasticLogo); + }); + + it('defaults to the Elastic outline logo', () => { + const result = fn( + 0.89, + { + emptyImage: null, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('image', elasticOutline); + }); + }); + + describe('emptyImage', () => { + it('sets the background image', () => { + const result = fn( + 0, + { + emptyImage: elasticLogo, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('emptyImage', elasticLogo); + }); + + it('sets emptyImage to null', () => { + const result = fn( + 0, + { + emptyImage: null, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('emptyImage', null); + }); + }); + + describe('origin', () => { + it('sets which side to start the reveal from', () => { + let result = fn( + 1, + { + emptyImage: null, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'top'); + result = fn( + 1, + { + emptyImage: null, + origin: Origin.LEFT, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'left'); + result = fn( + 1, + { + emptyImage: null, + origin: Origin.BOTTOM, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'bottom'); + result = fn( + 1, + { + emptyImage: null, + origin: Origin.RIGHT, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'right'); + }); + }); + }); +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image_function.ts similarity index 59% rename from x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts rename to src/plugins/expression_reveal_image/common/expression_functions/reveal_image_function.ts index 91d70609ab708..33e61e85f9531 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts +++ b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image_function.ts @@ -1,41 +1,16 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions'; -import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -import { elasticOutline } from '../../lib/elastic_outline'; -import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; +import { resolveWithMissingImage, elasticOutline } from '../../../presentation_util/common/lib'; +import { getFunctionHelp, getFunctionErrors } from '../i18n'; +import { ExpressionRevealImageFunction, Origin } from '../types'; -export enum Origin { - TOP = 'top', - LEFT = 'left', - BOTTOM = 'bottom', - RIGHT = 'right', -} - -interface Arguments { - image: string | null; - emptyImage: string | null; - origin: Origin; -} - -export interface Output { - image: string; - emptyImage: string; - origin: Origin; - percent: number; -} - -export function revealImage(): ExpressionFunctionDefinition< - 'revealImage', - number, - Arguments, - ExpressionValueRender -> { +export const revealImageFunction: ExpressionRevealImageFunction = () => { const { help, args: argHelp } = getFunctionHelp().revealImage; const errors = getFunctionErrors().revealImage; @@ -80,4 +55,4 @@ export function revealImage(): ExpressionFunctionDefinition< }; }, }; -} +}; diff --git a/src/plugins/expression_reveal_image/common/i18n/constants.ts b/src/plugins/expression_reveal_image/common/i18n/constants.ts new file mode 100644 index 0000000000000..413f376515a33 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/constants.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const BASE64 = '`base64`'; +export const URL = 'URL'; diff --git a/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/dict/reveal_image.ts similarity index 52% rename from x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts rename to src/plugins/expression_reveal_image/common/i18n/expression_functions/dict/reveal_image.ts index 374334824d61a..ccf9967bd6a65 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/dict/reveal_image.ts @@ -1,23 +1,21 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { i18n } from '@kbn/i18n'; -import { revealImage } from '../../../canvas_plugin_src/functions/common/revealImage'; -import { FunctionHelp } from '../function_help'; -import { FunctionFactory } from '../../../types'; import { Position } from '../../../types'; import { BASE64, URL } from '../../constants'; -export const help: FunctionHelp> = { - help: i18n.translate('xpack.canvas.functions.revealImageHelpText', { +export const help = { + help: i18n.translate('expressionRevealImage.functions.revealImageHelpText', { defaultMessage: 'Configures an image reveal element.', }), args: { - image: i18n.translate('xpack.canvas.functions.revealImage.args.imageHelpText', { + image: i18n.translate('expressionRevealImage.functions.revealImage.args.imageHelpText', { defaultMessage: 'The image to reveal. Provide an image asset as a {BASE64} data {URL}, ' + 'or pass in a sub-expression.', @@ -26,16 +24,19 @@ export const help: FunctionHelp> = { URL, }, }), - emptyImage: i18n.translate('xpack.canvas.functions.revealImage.args.emptyImageHelpText', { - defaultMessage: - 'An optional background image to reveal over. ' + - 'Provide an image asset as a `{BASE64}` data {URL}, or pass in a sub-expression.', - values: { - BASE64, - URL, - }, - }), - origin: i18n.translate('xpack.canvas.functions.revealImage.args.originHelpText', { + emptyImage: i18n.translate( + 'expressionRevealImage.functions.revealImage.args.emptyImageHelpText', + { + defaultMessage: + 'An optional background image to reveal over. ' + + 'Provide an image asset as a `{BASE64}` data {URL}, or pass in a sub-expression.', + values: { + BASE64, + URL, + }, + } + ), + origin: i18n.translate('expressionRevealImage.functions.revealImage.args.originHelpText', { defaultMessage: 'The position to start the image fill. For example, {list}, or {end}.', values: { list: Object.values(Position) @@ -50,7 +51,7 @@ export const help: FunctionHelp> = { export const errors = { invalidPercent: (percent: number) => new Error( - i18n.translate('xpack.canvas.functions.revealImage.invalidPercentErrorMessage', { + i18n.translate('expressionRevealImage.functions.revealImage.invalidPercentErrorMessage', { defaultMessage: "Invalid value: '{percent}'. Percentage must be between 0 and 1", values: { percent, diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_errors.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_errors.ts new file mode 100644 index 0000000000000..09cd26c9e620b --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_errors.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors as revealImage } from './dict/reveal_image'; + +export const getFunctionErrors = () => ({ + revealImage, +}); diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_help.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_help.ts new file mode 100644 index 0000000000000..30e79b120771b --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_help.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { help as revealImage } from './dict/reveal_image'; + +/** + * Help text for Canvas Functions should be properly localized. This function will + * return a dictionary of help strings, organized by `ExpressionFunctionDefinition` + * specification and then by available arguments within each `ExpressionFunctionDefinition`. + * + * This a function, rather than an object, to future-proof string initialization, + * if ever necessary. + */ +export const getFunctionHelp = () => ({ + revealImage, +}); diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts new file mode 100644 index 0000000000000..3d36b123421f4 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './function_help'; +export * from './function_errors'; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts new file mode 100644 index 0000000000000..4f70f9d30b74b --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { strings as revealImage } from './reveal_image'; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/reveal_image.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/reveal_image.ts new file mode 100644 index 0000000000000..a32fdbd4c0b50 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/reveal_image.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { i18n } from '@kbn/i18n'; + +export const strings = { + getDisplayName: () => + i18n.translate('expressionRevealImage.renderer.revealImage.displayName', { + defaultMessage: 'Image reveal', + }), + getHelpDescription: () => + i18n.translate('expressionRevealImage.renderer.revealImage.helpDescription', { + defaultMessage: 'Reveal a percentage of an image to make a custom gauge-style chart', + }), +}; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/index.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/index.ts new file mode 100644 index 0000000000000..7e637f240d15c --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './renderer_strings'; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/renderer_strings.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/renderer_strings.ts new file mode 100644 index 0000000000000..b74230a2a5d76 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/renderer_strings.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { revealImage } from './dict'; + +/** + * Help text for Canvas Functions should be properly localized. This function will + * return a dictionary of help strings, organized by `ExpressionFunctionDefinition` + * specification and then by available arguments within each `ExpressionFunctionDefinition`. + * + * This a function, rather than an object, to future-proof string initialization, + * if ever necessary. + */ +export const getRendererStrings = () => ({ + revealImage, +}); diff --git a/src/plugins/expression_reveal_image/common/i18n/index.ts b/src/plugins/expression_reveal_image/common/i18n/index.ts new file mode 100644 index 0000000000000..9c50bfab1305d --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/expression_reveal_image/common/index.ts b/src/plugins/expression_reveal_image/common/index.ts new file mode 100755 index 0000000000000..95503b36acdb6 --- /dev/null +++ b/src/plugins/expression_reveal_image/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './constants'; +export * from './expression_functions'; diff --git a/src/plugins/expression_reveal_image/common/types/expression_functions.ts b/src/plugins/expression_reveal_image/common/types/expression_functions.ts new file mode 100644 index 0000000000000..ee291e204acfb --- /dev/null +++ b/src/plugins/expression_reveal_image/common/types/expression_functions.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions'; + +export enum Origin { + TOP = 'top', + LEFT = 'left', + BOTTOM = 'bottom', + RIGHT = 'right', +} + +interface Arguments { + image: string | null; + emptyImage: string | null; + origin: Origin; +} + +export interface Output { + image: string; + emptyImage: string; + origin: Origin; + percent: number; +} + +export type ExpressionRevealImageFunction = () => ExpressionFunctionDefinition< + 'revealImage', + number, + Arguments, + ExpressionValueRender +>; + +export enum Position { + TOP = 'top', + BOTTOM = 'bottom', + LEFT = 'left', + RIGHT = 'right', +} diff --git a/src/plugins/expression_reveal_image/common/types/expression_renderers.ts b/src/plugins/expression_reveal_image/common/types/expression_renderers.ts new file mode 100644 index 0000000000000..77dacaefc1bd1 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/types/expression_renderers.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type OriginString = 'bottom' | 'left' | 'top' | 'right'; + +export interface RevealImageRendererConfig { + percent: number; + origin?: OriginString; + image?: string; + emptyImage?: string; +} + +export interface NodeDimensions { + width: number; + height: number; +} diff --git a/src/plugins/expression_reveal_image/common/types/index.ts b/src/plugins/expression_reveal_image/common/types/index.ts new file mode 100644 index 0000000000000..ec934e7affe88 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/expression_reveal_image/jest.config.js b/src/plugins/expression_reveal_image/jest.config.js new file mode 100644 index 0000000000000..aac5fad293846 --- /dev/null +++ b/src/plugins/expression_reveal_image/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/expression_reveal_image'], +}; diff --git a/src/plugins/expression_reveal_image/kibana.json b/src/plugins/expression_reveal_image/kibana.json new file mode 100755 index 0000000000000..9af9a5857dcfb --- /dev/null +++ b/src/plugins/expression_reveal_image/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "expressionRevealImage", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["expressions", "presentationUtil"], + "optionalPlugins": [], + "requiredBundles": [] +} diff --git a/src/plugins/expression_reveal_image/public/components/index.ts b/src/plugins/expression_reveal_image/public/components/index.ts new file mode 100644 index 0000000000000..23cb4d7a20cb8 --- /dev/null +++ b/src/plugins/expression_reveal_image/public/components/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './reveal_image_component'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/reveal_image.scss b/src/plugins/expression_reveal_image/public/components/reveal_image.scss similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/reveal_image.scss rename to src/plugins/expression_reveal_image/public/components/reveal_image.scss diff --git a/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx new file mode 100644 index 0000000000000..a9c24fca78d9b --- /dev/null +++ b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useRef, useState, useEffect, useCallback } from 'react'; +import { useResizeObserver } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { NodeDimensions, RevealImageRendererConfig, OriginString } from '../../common/types'; +import { isValidUrl, elasticOutline } from '../../../presentation_util/public'; +import './reveal_image.scss'; + +interface RevealImageComponentProps extends RevealImageRendererConfig { + onLoaded: IInterpreterRenderHandlers['done']; + parentNode: HTMLElement; +} + +interface ImageStyles { + width?: string; + height?: string; + clipPath?: string; +} + +interface AlignerStyles { + backgroundImage?: string; +} + +function RevealImageComponent({ + onLoaded, + parentNode, + percent, + origin, + image, + emptyImage, +}: RevealImageComponentProps) { + const [loaded, setLoaded] = useState(false); + const [dimensions, setDimensions] = useState({ + width: 1, + height: 1, + }); + + const imgRef = useRef(null); + + const parentNodeDimensions = useResizeObserver(parentNode); + + // modify the top-level container class + parentNode.className = 'revealImage'; + + // set up the overlay image + const updateImageView = useCallback(() => { + if (imgRef.current) { + setDimensions({ + height: imgRef.current.naturalHeight, + width: imgRef.current.naturalWidth, + }); + + setLoaded(true); + onLoaded(); + } + }, [imgRef, onLoaded]); + + useEffect(() => { + updateImageView(); + }, [parentNodeDimensions, updateImageView]); + + function getClipPath(percentParam: number, originParam: OriginString = 'bottom') { + const directions: Record = { bottom: 0, left: 1, top: 2, right: 3 }; + const values: Array = [0, 0, 0, 0]; + values[directions[originParam]] = `${100 - percentParam * 100}%`; + return `inset(${values.join(' ')})`; + } + + function getImageSizeStyle() { + const imgStyles: ImageStyles = {}; + + const imgDimensions = { + height: dimensions.height, + width: dimensions.width, + ratio: dimensions.height / dimensions.width, + }; + + const domNodeDimensions = { + width: parentNode.clientWidth, + height: parentNode.clientHeight, + ratio: parentNode.clientHeight / parentNode.clientWidth, + }; + + if (imgDimensions.ratio > domNodeDimensions.ratio) { + imgStyles.height = `${domNodeDimensions.height}px`; + imgStyles.width = 'initial'; + } else { + imgStyles.width = `${domNodeDimensions.width}px`; + imgStyles.height = 'initial'; + } + + return imgStyles; + } + + const imgSrc = isValidUrl(image ?? '') ? image : elasticOutline; + + const alignerStyles: AlignerStyles = {}; + + if (isValidUrl(emptyImage ?? '')) { + // only use empty image if one is provided + alignerStyles.backgroundImage = `url(${emptyImage})`; + } + + let imgStyles: ImageStyles = {}; + if (imgRef.current && loaded) imgStyles = getImageSizeStyle(); + + imgStyles.clipPath = getClipPath(percent, origin); + if (imgRef.current && loaded) { + imgRef.current.style.setProperty('-webkit-clip-path', getClipPath(percent, origin)); + } + + return ( +
+ +
+ ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { RevealImageComponent as default }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/__snapshots__/reveal_image.stories.storyshot b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/__snapshots__/reveal_image.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/__snapshots__/reveal_image.stories.storyshot rename to src/plugins/expression_reveal_image/public/expression_renderers/__stories__/__snapshots__/reveal_image.stories.storyshot diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx new file mode 100644 index 0000000000000..bc70b3685e24e --- /dev/null +++ b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { revealImageRenderer } from '../'; +import { elasticOutline, elasticLogo } from '../../../../presentation_util/public'; +import { Render } from '../../../../presentation_util/public/__stories__'; + +import { Origin } from '../../../common/types/expression_functions'; + +storiesOf('renderers/revealImage', module).add('default', () => { + const config = { + image: elasticLogo, + emptyImage: elasticOutline, + origin: Origin.LEFT, + percent: 0.45, + }; + + return ; +}); diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/index.ts b/src/plugins/expression_reveal_image/public/expression_renderers/index.ts new file mode 100644 index 0000000000000..433a81884f157 --- /dev/null +++ b/src/plugins/expression_reveal_image/public/expression_renderers/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { revealImageRenderer } from './reveal_image_renderer'; + +export const renderers = [revealImageRenderer]; + +export { revealImageRenderer }; diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx b/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx new file mode 100644 index 0000000000000..4d84de3da994c --- /dev/null +++ b/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { withSuspense } from '../../../presentation_util/public'; +import { getRendererStrings } from '../../common/i18n'; +import { RevealImageRendererConfig } from '../../common/types'; + +const { revealImage: revealImageStrings } = getRendererStrings(); + +const LazyRevealImageComponent = lazy(() => import('../components/reveal_image_component')); +const RevealImageComponent = withSuspense(LazyRevealImageComponent, null); + +export const revealImageRenderer = (): ExpressionRenderDefinition => ({ + name: 'revealImage', + displayName: revealImageStrings.getDisplayName(), + help: revealImageStrings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: RevealImageRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/expression_reveal_image/public/index.ts b/src/plugins/expression_reveal_image/public/index.ts new file mode 100755 index 0000000000000..00cb14e0fc064 --- /dev/null +++ b/src/plugins/expression_reveal_image/public/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionRevealImagePlugin } from './plugin'; + +export type { ExpressionRevealImagePluginSetup, ExpressionRevealImagePluginStart } from './plugin'; + +export function plugin() { + return new ExpressionRevealImagePlugin(); +} + +export * from './expression_renderers'; diff --git a/src/plugins/expression_reveal_image/public/plugin.ts b/src/plugins/expression_reveal_image/public/plugin.ts new file mode 100755 index 0000000000000..5f6496a25f820 --- /dev/null +++ b/src/plugins/expression_reveal_image/public/plugin.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; +import { revealImageRenderer } from './expression_renderers'; + +interface SetupDeps { + expressions: ExpressionsSetup; +} + +interface StartDeps { + expression: ExpressionsStart; +} + +export type ExpressionRevealImagePluginSetup = void; +export type ExpressionRevealImagePluginStart = void; + +export class ExpressionRevealImagePlugin + implements + Plugin< + ExpressionRevealImagePluginSetup, + ExpressionRevealImagePluginStart, + SetupDeps, + StartDeps + > { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRevealImagePluginSetup { + expressions.registerRenderer(revealImageRenderer); + } + + public start(core: CoreStart): ExpressionRevealImagePluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_reveal_image/server/index.ts b/src/plugins/expression_reveal_image/server/index.ts new file mode 100644 index 0000000000000..b86c356974321 --- /dev/null +++ b/src/plugins/expression_reveal_image/server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionRevealImagePlugin } from './plugin'; + +export type { ExpressionRevealImagePluginSetup, ExpressionRevealImagePluginStart } from './plugin'; + +export function plugin() { + return new ExpressionRevealImagePlugin(); +} diff --git a/src/plugins/expression_reveal_image/server/plugin.ts b/src/plugins/expression_reveal_image/server/plugin.ts new file mode 100644 index 0000000000000..446ef018eb7d3 --- /dev/null +++ b/src/plugins/expression_reveal_image/server/plugin.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server'; +import { revealImageFunction } from '../common'; + +interface SetupDeps { + expressions: ExpressionsServerSetup; +} + +interface StartDeps { + expression: ExpressionsServerStart; +} + +export type ExpressionRevealImagePluginSetup = void; +export type ExpressionRevealImagePluginStart = void; + +export class ExpressionRevealImagePlugin + implements + Plugin< + ExpressionRevealImagePluginSetup, + ExpressionRevealImagePluginStart, + SetupDeps, + StartDeps + > { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRevealImagePluginSetup { + expressions.registerFunction(revealImageFunction); + } + + public start(core: CoreStart): ExpressionRevealImagePluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_reveal_image/tsconfig.json b/src/plugins/expression_reveal_image/tsconfig.json new file mode 100644 index 0000000000000..aa4562ec73576 --- /dev/null +++ b/src/plugins/expression_reveal_image/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../presentation_util/tsconfig.json" }, + { "path": "../expressions/tsconfig.json" }, + ] +} diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 514086e9b19ee..798558ba7ffb6 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { first } from 'rxjs/operators'; import { waitFor } from '@testing-library/react'; import { Execution } from './execution'; import { parseExpression } from '../ast'; @@ -40,9 +39,9 @@ describe('Execution abortion tests', () => { execution.start(); execution.cancel(); - const result = await execution.result.pipe(first()).toPromise(); + const result = await execution.result.toPromise(); - expect(result).toMatchObject({ + expect(result).toHaveProperty('result', { type: 'error', error: { message: 'The expression was aborted.', @@ -58,9 +57,9 @@ describe('Execution abortion tests', () => { jest.advanceTimersByTime(100); execution.cancel(); - const result = await execution.result.pipe(first()).toPromise(); + const result = await execution.result.toPromise(); - expect(result).toMatchObject({ + expect(result).toHaveProperty('result', { type: 'error', error: { message: 'The expression was aborted.', @@ -76,7 +75,7 @@ describe('Execution abortion tests', () => { execution.start(); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); execution.cancel(); @@ -136,7 +135,7 @@ describe('Execution abortion tests', () => { await waitFor(() => expect(started).toHaveBeenCalledTimes(1)); execution.cancel(); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toMatchObject({ type: 'error', error: { diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index feff425cc48ed..8c6f457105d42 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -7,7 +7,7 @@ */ import { of } from 'rxjs'; -import { first, scan } from 'rxjs/operators'; +import { scan } from 'rxjs/operators'; import { TestScheduler } from 'rxjs/testing'; import { Execution } from './execution'; import { parseExpression, ExpressionAstExpression } from '../ast'; @@ -45,7 +45,7 @@ const run = async ( ) => { const execution = createExecution(expression, context); execution.start(input); - return await execution.result.pipe(first()).toPromise(); + return await execution.result.toPromise(); }; let testScheduler: TestScheduler; @@ -84,7 +84,7 @@ describe('Execution', () => { /* eslint-enable no-console */ execution.start(123); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toBe(123); expect(spy).toHaveBeenCalledTimes(1); @@ -102,7 +102,7 @@ describe('Execution', () => { value: -1, }); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -117,7 +117,7 @@ describe('Execution', () => { value: 0, }); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -131,7 +131,7 @@ describe('Execution', () => { // Below 1 is cast to { type: 'num', value: 1 }. execution.start(1); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -143,7 +143,7 @@ describe('Execution', () => { const execution = createExecution('add val=1'); execution.start(Promise.resolve(1)); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -155,7 +155,7 @@ describe('Execution', () => { const execution = createExecution('add val=1'); execution.start(of(1)); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -167,14 +167,14 @@ describe('Execution', () => { const execution = createExecution('add val=1'); testScheduler.run(({ cold, expectObservable }) => { - const input = cold(' -a--b-c-', { a: 1, b: 2, c: 3 }); + const input = cold(' -a--b-c|', { a: 1, b: 2, c: 3 }); const subscription = ' ---^---!'; - const expected = ' ---ab-c-'; + const expected = ' ---ab-c|'; expectObservable(execution.start(input), subscription).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { type: 'num', value: 3 }, - c: { type: 'num', value: 4 }, + a: { partial: false, result: { type: 'num', value: 2 } }, + b: { partial: false, result: { type: 'num', value: 3 } }, + c: { partial: false, result: { type: 'num', value: 4 } }, }); }); }); @@ -187,21 +187,21 @@ describe('Execution', () => { const expected = ' -a-#'; expectObservable(execution.start(input)).toBe(expected, { - a: { type: 'num', value: 2 }, + a: { partial: false, result: { type: 'num', value: 2 } }, }); }); }); - test('does not complete when input completes', () => { + test('completes when input completes', () => { const execution = createExecution('add val=1'); testScheduler.run(({ cold, expectObservable }) => { const input = cold('-a-b|', { a: 1, b: 2 }); - const expected = ' -a-b-'; + const expected = ' -a-b|'; expectObservable(execution.start(input)).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { type: 'num', value: 3 }, + a: expect.objectContaining({ result: { type: 'num', value: 2 } }), + b: expect.objectContaining({ result: { type: 'num', value: 3 } }), }); }); }); @@ -216,9 +216,9 @@ describe('Execution', () => { const input = items.pipe(scan((result, value) => [...result, value], new Array())); expectObservable(execution.start(input), subscription).toBe(expected, { - a: { type: 'num', value: 1 }, - b: { type: 'num', value: 3 }, - c: { type: 'num', value: 6 }, + a: { partial: false, result: { type: 'num', value: 1 } }, + b: { partial: false, result: { type: 'num', value: 3 } }, + c: { partial: false, result: { type: 'num', value: 6 } }, }); }); }); @@ -263,44 +263,51 @@ describe('Execution', () => { describe('execution context', () => { test('context.variables is an object', async () => { const { result } = (await run('introspectContext key="variables"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.types is an object', async () => { const { result } = (await run('introspectContext key="types"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.abortSignal is an object', async () => { const { result } = (await run('introspectContext key="abortSignal"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.inspectorAdapters is an object', async () => { const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.getKibanaRequest is a function if provided', async () => { const { result } = (await run('introspectContext key="getKibanaRequest"', { kibanaRequest: {}, })) as any; - expect(typeof result).toBe('function'); + + expect(result).toHaveProperty('result', expect.any(Function)); }); test('context.getKibanaRequest is undefined if not provided', async () => { const { result } = (await run('introspectContext key="getKibanaRequest"')) as any; - expect(typeof result).toBe('undefined'); + + expect(result).toHaveProperty('result', undefined); }); test('unknown context key is undefined', async () => { const { result } = (await run('introspectContext key="foo"')) as any; - expect(typeof result).toBe('undefined'); + + expect(result).toHaveProperty('result', undefined); }); test('can set context variables', async () => { const variables = { foo: 'bar' }; - const result = await run('var name="foo"', { variables }); + const { result } = await run('var name="foo"', { variables }); expect(result).toBe('bar'); }); }); @@ -308,10 +315,13 @@ describe('Execution', () => { describe('inspector adapters', () => { test('by default, "tables" and "requests" inspector adapters are available', async () => { const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; - expect(result).toMatchObject({ - tables: expect.any(Object), - requests: expect.any(Object), - }); + expect(result).toHaveProperty( + 'result', + expect.objectContaining({ + tables: expect.any(Object), + requests: expect.any(Object), + }) + ); }); test('can set custom inspector adapters', async () => { @@ -319,7 +329,7 @@ describe('Execution', () => { const { result } = (await run('introspectContext key="inspectorAdapters"', { inspectorAdapters, })) as any; - expect(result).toBe(inspectorAdapters); + expect(result).toHaveProperty('result', inspectorAdapters); }); test('can access custom inspector adapters on Execution object', async () => { @@ -335,8 +345,7 @@ describe('Execution', () => { test('context has abortSignal object', async () => { const { result } = (await run('introspectContext key="abortSignal"')) as any; - expect(typeof result).toBe('object'); - expect((result as AbortSignal).aborted).toBe(false); + expect(result).toHaveProperty('result.aborted', false); }); }); @@ -348,7 +357,7 @@ describe('Execution', () => { value: 0, }); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -357,8 +366,8 @@ describe('Execution', () => { }); test('can execute async functions', async () => { - const res = await run('sleep 10 | sleep 10'); - expect(res).toBe(null); + const { result } = await run('sleep 10 | sleep 10'); + expect(result).toBe(null); }); test('result is undefined until execution completes', async () => { @@ -374,7 +383,7 @@ describe('Execution', () => { jest.advanceTimersByTime(10); await new Promise(process.nextTick); - expect(execution.state.get().result).toBe(null); + expect(execution.state.get().result).toHaveProperty('result', null); jest.useRealTimers(); }); @@ -382,7 +391,7 @@ describe('Execution', () => { test('handles functions returning observables', () => { testScheduler.run(({ cold, expectObservable }) => { const arg = cold(' -a-b-c|', { a: 1, b: 2, c: 3 }); - const expected = ' -a-b-c-'; + const expected = ' -a-b-c|'; const observable: ExpressionFunctionDefinition<'observable', any, {}, any> = { name: 'observable', args: {}, @@ -394,14 +403,18 @@ describe('Execution', () => { const result = executor.run('observable', null, {}); - expectObservable(result).toBe(expected, { a: 1, b: 2, c: 3 }); + expectObservable(result).toBe(expected, { + a: { result: 1, partial: true }, + b: { result: 2, partial: true }, + c: { result: 3, partial: false }, + }); }); }); }); describe('when function throws', () => { test('error is reported in output object', async () => { - const result = await run('error "foobar"'); + const { result } = await run('error "foobar"'); expect(result).toMatchObject({ type: 'error', @@ -409,7 +422,7 @@ describe('Execution', () => { }); test('error message is prefixed with function name', async () => { - const result = await run('error "foobar"'); + const { result } = await run('error "foobar"'); expect(result).toMatchObject({ error: { @@ -419,7 +432,7 @@ describe('Execution', () => { }); test('returns error of the first function that throws', async () => { - const result = await run('error "foo" | error "bar"'); + const { result } = await run('error "foo" | error "bar"'); expect(result).toMatchObject({ error: { @@ -432,15 +445,18 @@ describe('Execution', () => { const execution = await createExecution('error "foo"'); execution.start(null); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toMatchObject({ type: 'error', }); expect(execution.state.get().state).toBe('result'); - expect(execution.state.get().result).toMatchObject({ - type: 'error', - }); + expect(execution.state.get().result).toHaveProperty( + 'result', + expect.objectContaining({ + type: 'error', + }) + ); }); test('does not execute remaining functions in pipeline', async () => { @@ -453,7 +469,7 @@ describe('Execution', () => { const executor = createUnitTestExecutor(); executor.registerFunction(spy); - await executor.run('error "..." | spy', null).pipe(first()).toPromise(); + await executor.run('error "..." | spy', null).toPromise(); expect(spy.fn).toHaveBeenCalledTimes(0); }); @@ -483,21 +499,21 @@ describe('Execution', () => { test('execution state is "result" when execution successfully completes', async () => { const execution = createExecution('sleep 1'); execution.start(null); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); expect(execution.state.get().state).toBe('result'); }); test('execution state is "result" when execution successfully completes - 2', async () => { const execution = createExecution('var foo'); execution.start(null); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); expect(execution.state.get().state).toBe('result'); }); }); describe('sub-expressions', () => { test('executes sub-expressions', async () => { - const result = await run('add val={add 5 | access "value"}', {}, null); + const { result } = await run('add val={add 5 | access "value"}', {}, null); expect(result).toMatchObject({ type: 'num', @@ -506,7 +522,7 @@ describe('Execution', () => { }); test('can use global variables', async () => { - const result = await run( + const { result } = await run( 'add val={var foo}', { variables: { @@ -523,7 +539,7 @@ describe('Execution', () => { }); test('can modify global variables', async () => { - const result = await run( + const { result } = await run( 'add val={var_set name=foo value=66 | var bar} | var foo', { variables: { @@ -547,18 +563,20 @@ describe('Execution', () => { const executor = createUnitTestExecutor(); executor.registerFunction(observable); - expect( - executor.run('add val={observable}', 1, {}).pipe(first()).toPromise() - ).resolves.toEqual({ - type: 'num', - value: 2, - }); + expect(executor.run('add val={observable}', 1, {}).toPromise()).resolves.toEqual( + expect.objectContaining({ + result: { + type: 'num', + value: 2, + }, + }) + ); }); test('supports observables in arguments emitting multiple values', () => { testScheduler.run(({ cold, expectObservable }) => { - const arg = cold('-a-b-c-', { a: 1, b: 2, c: 3 }); - const expected = '-a-b-c-'; + const arg = cold('-a-b-c|', { a: 1, b: 2, c: 3 }); + const expected = '-a-b-c|'; const observable = { name: 'observable', args: {}, @@ -571,18 +589,18 @@ describe('Execution', () => { const result = executor.run('add val={observable}', 1, {}); expectObservable(result).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { type: 'num', value: 3 }, - c: { type: 'num', value: 4 }, + a: { partial: true, result: { type: 'num', value: 2 } }, + b: { partial: true, result: { type: 'num', value: 3 } }, + c: { partial: false, result: { type: 'num', value: 4 } }, }); }); }); test('combines multiple observables in arguments', () => { testScheduler.run(({ cold, expectObservable }) => { - const arg1 = cold('--ab-c-', { a: 0, b: 2, c: 4 }); - const arg2 = cold('-a--bc-', { a: 1, b: 3, c: 5 }); - const expected = ' --abc(de)-'; + const arg1 = cold('--ab-c---|', { a: 0, b: 2, c: 4 }); + const arg2 = cold('-a--bc---|', { a: 1, b: 3, c: 5 }); + const expected = ' --abc(de)|'; const observable1 = { name: 'observable1', args: {}, @@ -612,32 +630,11 @@ describe('Execution', () => { const result = executor.run('max val1={observable1} val2={observable2}', {}); expectObservable(result).toBe(expected, { - a: { type: 'num', value: 1 }, - b: { type: 'num', value: 2 }, - c: { type: 'num', value: 3 }, - d: { type: 'num', value: 4 }, - e: { type: 'num', value: 5 }, - }); - }); - }); - - test('does not complete when an argument completes', () => { - testScheduler.run(({ cold, expectObservable }) => { - const arg = cold('-a|', { a: 1 }); - const expected = '-a-'; - const observable = { - name: 'observable', - args: {}, - help: '', - fn: () => arg, - }; - const executor = createUnitTestExecutor(); - executor.registerFunction(observable); - - const result = executor.run('add val={observable}', 1, {}); - - expectObservable(result).toBe(expected, { - a: { type: 'num', value: 2 }, + a: { partial: true, result: { type: 'num', value: 1 } }, + b: { partial: true, result: { type: 'num', value: 2 } }, + c: { partial: true, result: { type: 'num', value: 3 } }, + d: { partial: true, result: { type: 'num', value: 4 } }, + e: { partial: false, result: { type: 'num', value: 5 } }, }); }); }); @@ -645,7 +642,7 @@ describe('Execution', () => { test('handles error in observable arguments', () => { testScheduler.run(({ cold, expectObservable }) => { const arg = cold('-a-#', { a: 1 }, new Error('some error')); - const expected = '-a-b'; + const expected = '-a-(b|)'; const observable = { name: 'observable', args: {}, @@ -658,13 +655,15 @@ describe('Execution', () => { const result = executor.run('add val={observable}', 1, {}); expectObservable(result).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { - error: expect.objectContaining({ - message: '[add] > [observable] > some error', - }), - type: 'error', - }, + a: expect.objectContaining({ result: { type: 'num', value: 2 } }), + b: expect.objectContaining({ + result: { + error: expect.objectContaining({ + message: '[add] > [observable] > some error', + }), + type: 'error', + }, + }), }); }); }); @@ -685,7 +684,7 @@ describe('Execution', () => { }; const executor = createUnitTestExecutor(); executor.registerFunction(requiredArg); - const result = await executor.run('requiredArg', null, {}).pipe(first()).toPromise(); + const { result } = await executor.run('requiredArg', null, {}).toPromise(); expect(result).toMatchObject({ type: 'error', @@ -696,7 +695,7 @@ describe('Execution', () => { }); test('when required argument is missing and has alias, returns error', async () => { - const result = await run('var_set', {}); + const { result } = await run('var_set', {}); expect(result).toMatchObject({ type: 'error', @@ -711,7 +710,7 @@ describe('Execution', () => { test('can execute expression in debug mode', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -726,7 +725,7 @@ describe('Execution', () => { true ); execution.start(0); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -738,7 +737,7 @@ describe('Execution', () => { test('sets "success" flag on all functions to true', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(node.debug?.success).toBe(true); @@ -748,7 +747,7 @@ describe('Execution', () => { test('stores "fn" reference to the function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(node.debug?.fn).toBe('add'); @@ -758,7 +757,7 @@ describe('Execution', () => { test('saves duration it took to execute each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(typeof node.debug?.duration).toBe('number'); @@ -770,7 +769,7 @@ describe('Execution', () => { test('adds .debug field in expression AST on each executed function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(typeof node.debug).toBe('object'); @@ -781,7 +780,7 @@ describe('Execution', () => { test('stores input of each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast; @@ -799,7 +798,7 @@ describe('Execution', () => { test('stores output of each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast; @@ -824,7 +823,7 @@ describe('Execution', () => { true ); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast; @@ -847,7 +846,7 @@ describe('Execution', () => { true ); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast.chain[0].arguments .val[0] as ExpressionAstExpression; @@ -882,7 +881,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const node1 = execution.state.get().ast.chain[0]; const node2 = execution.state.get().ast.chain[1]; @@ -900,7 +899,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const node2 = execution.state.get().ast.chain[1]; @@ -921,7 +920,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const node2 = execution.state.get().ast.chain[1]; diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index b70f261ea4b20..47209c348257e 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -20,7 +20,7 @@ import { Observable, ReplaySubject, } from 'rxjs'; -import { catchError, finalize, map, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { catchError, finalize, map, pluck, shareReplay, switchMap, tap } from 'rxjs/operators'; import { Executor } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; @@ -45,6 +45,21 @@ import { ExpressionExecutionParams } from '../service'; import { TablesAdapter } from '../util/tables_adapter'; import { ExpressionsInspectorAdapter } from '../util/expressions_inspector_adapter'; +/** + * The result returned after an expression function execution. + */ +export interface ExecutionResult { + /** + * Partial result flag. + */ + partial: boolean; + + /** + * The expression function result. + */ + result: Output; +} + /** * AbortController is not available in Node until v15, so we * need to temporarily mock it for plugins using expressions @@ -91,7 +106,7 @@ export class Execution< /** * Dynamic state of the execution. */ - public readonly state: ExecutionContainer; + public readonly state: ExecutionContainer>; /** * Initial input of the execution. @@ -137,7 +152,7 @@ export class Execution< /** * Future that tracks result or error of this execution. */ - public readonly result: Observable; + public readonly result: Observable>; /** * Keeping track of any child executions @@ -174,7 +189,7 @@ export class Execution< this.expression = execution.expression || formatExpression(execution.ast!); const ast = execution.ast || parseExpression(this.expression); - this.state = createExecutionContainer({ + this.state = createExecutionContainer({ ...executor.state.get(), state: 'not-started', ast, @@ -201,12 +216,40 @@ export class Execution< }; this.result = this.input$.pipe( - switchMap((input) => this.race(this.invokeChain(this.state.get().ast.chain, input))), + switchMap((input) => + this.race(this.invokeChain(this.state.get().ast.chain, input)).pipe( + (source) => + new Observable>((subscriber) => { + let latest: ExecutionResult | undefined; + + subscriber.add( + source.subscribe({ + next: (result) => { + latest = { result, partial: true }; + subscriber.next(latest); + }, + error: (error) => subscriber.error(error), + complete: () => { + if (latest) { + latest.partial = false; + } + + subscriber.complete(); + }, + }) + ); + + subscriber.add(() => { + latest = undefined; + }); + }) + ) + ), catchError((error) => { if (this.abortController.signal.aborted) { this.childExecutions.forEach((childExecution) => childExecution.cancel()); - return of(createAbortErrorValue()); + return of({ result: createAbortErrorValue(), partial: false }); } return throwError(error); @@ -236,25 +279,20 @@ export class Execution< * N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons, * because in legacy interpreter it was set to `null` by default. */ - public start(input: Input = null as any): Observable { + public start( + input: Input = null as any + ): Observable> { if (this.hasStarted) throw new Error('Execution already started.'); this.hasStarted = true; this.input = input; this.state.transitions.start(); if (isObservable(input)) { - // `input$` should never complete - input.subscribe( - (value) => this.input$.next(value), - (error) => this.input$.error(error) - ); + input.subscribe(this.input$); } else if (isPromise(input)) { - input.then( - (value) => this.input$.next(value), - (error) => this.input$.error(error) - ); + from(input).subscribe(this.input$); } else { - this.input$.next(input); + of(input).subscribe(this.input$); } return this.result; @@ -439,6 +477,7 @@ export class Execution< const resolveArgFns = mapValues(dealiasedArgAsts, (asts, argName) => asts.map((item) => (subInput = input) => this.interpret(item, subInput).pipe( + pluck('result'), map((output) => { if (isExpressionValueError(output)) { throw output.error; @@ -486,7 +525,7 @@ export class Execution< }); } - public interpret(ast: ExpressionAstNode, input: T): Observable { + public interpret(ast: ExpressionAstNode, input: T): Observable> { switch (getType(ast)) { case 'expression': const execution = this.execution.executor.createExecution( @@ -494,12 +533,13 @@ export class Execution< this.execution.params ); this.childExecutions.push(execution); + return execution.start(input); case 'string': case 'number': case 'null': case 'boolean': - return of(ast); + return of({ result: ast, partial: false }); default: return throwError(new Error(`Unknown AST object: ${JSON.stringify(ast)}`)); } diff --git a/src/plugins/expressions/common/execution/execution_contract.test.ts b/src/plugins/expressions/common/execution/execution_contract.test.ts index 99a5c80de3c46..de209f1dfb4a1 100644 --- a/src/plugins/expressions/common/execution/execution_contract.test.ts +++ b/src/plugins/expressions/common/execution/execution_contract.test.ts @@ -66,26 +66,23 @@ describe('ExecutionContract', () => { }); }); - test('can get error result of the expression execution', async () => { + test('can get error result of the expression execution', () => { const execution = createExecution('foo bar=123'); const contract = new ExecutionContract(execution); execution.start(); - const result = await contract.getData(); - - expect(result).toMatchObject({ - type: 'error', - }); + expect(contract.getData().toPromise()).resolves.toHaveProperty( + 'result', + expect.objectContaining({ type: 'error' }) + ); }); - test('can get result of the expression execution', async () => { + test('can get result of the expression execution', () => { const execution = createExecution('var_set name="foo" value="bar" | var name="foo"'); const contract = new ExecutionContract(execution); execution.start(); - const result = await contract.getData(); - - expect(result).toBe('bar'); + expect(contract.getData().toPromise()).resolves.toHaveProperty('result', 'bar'); }); describe('isPending', () => { diff --git a/src/plugins/expressions/common/execution/execution_contract.ts b/src/plugins/expressions/common/execution/execution_contract.ts index 3cad9cef5e09a..445ceb30b58db 100644 --- a/src/plugins/expressions/common/execution/execution_contract.ts +++ b/src/plugins/expressions/common/execution/execution_contract.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; -import { catchError, take } from 'rxjs/operators'; -import { Execution } from './execution'; +import { of, Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { Execution, ExecutionResult } from './execution'; import { ExpressionValueError } from '../expression_types/specs'; import { ExpressionAstExpression } from '../ast'; @@ -39,22 +39,22 @@ export class ExecutionContract => { - return this.execution.result - .pipe( - take(1), - catchError(({ name, message, stack }) => - of({ + getData = (): Observable> => { + return this.execution.result.pipe( + catchError(({ name, message, stack }) => + of({ + partial: false, + result: { type: 'error', error: { name, message, stack, }, - } as ExpressionValueError) - ) + } as ExpressionValueError, + }) ) - .toPromise(); + ); }; /** diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index a307172aff973..7ca5a005991bd 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -13,7 +13,7 @@ import { Observable } from 'rxjs'; import { ExecutorState, ExecutorContainer } from './container'; import { createExecutorContainer } from './container'; import { AnyExpressionFunctionDefinition, ExpressionFunction } from '../expression_functions'; -import { Execution, ExecutionParams } from '../execution/execution'; +import { Execution, ExecutionParams, ExecutionResult } from '../execution/execution'; import { IRegistry } from '../types'; import { ExpressionType } from '../expression_types/expression_type'; import { AnyExpressionTypeDefinition } from '../expression_types/types'; @@ -160,7 +160,7 @@ export class Executor = Record { + ): Observable> { return this.createExecution(ast, params).start(input); } diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts index cdcae61215fa4..1b060cde7482d 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts @@ -65,7 +65,7 @@ describe('expression_functions', () => { it('sets the variables', async () => { const vars = {}; - const result = await executor + const { result } = await executor .run('var_set name=test1 name=test2 value=1', 2, { variables: vars }) .pipe(first()) .toPromise(); diff --git a/src/plugins/expressions/common/service/expressions_services.test.ts b/src/plugins/expressions/common/service/expressions_services.test.ts index 8f86e81f9d1d5..db73d300e1273 100644 --- a/src/plugins/expressions/common/service/expressions_services.test.ts +++ b/src/plugins/expressions/common/service/expressions_services.test.ts @@ -114,7 +114,7 @@ describe('ExpressionsService', () => { }, }); - const result = await fork.run('__test__', null); + const { result } = await fork.run('__test__', null).toPromise(); expect(result).toBe('123'); }); diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index b3c0167262661..9e569c66bbe16 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import { take } from 'rxjs/operators'; +import { Observable } from 'rxjs'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { KibanaRequest } from 'src/core/server'; import { Executor } from '../executor'; import { AnyExpressionRenderDefinition, ExpressionRendererRegistry } from '../expression_renderers'; import { ExpressionAstExpression } from '../ast'; -import { ExecutionContract } from '../execution/execution_contract'; -import { AnyExpressionTypeDefinition } from '../expression_types'; +import { ExecutionContract, ExecutionResult } from '../execution'; +import { AnyExpressionTypeDefinition, ExpressionValueError } from '../expression_types'; import { AnyExpressionFunctionDefinition } from '../expression_functions'; import { SavedObjectReference } from '../../../../core/types'; import { PersistableStateService, SerializableState } from '../../../kibana_utils/common'; @@ -136,7 +136,7 @@ export interface ExpressionsServiceStart { ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams - ) => Promise; + ) => Observable>; /** * Starts expression execution and immediately returns `ExecutionContract` @@ -243,7 +243,7 @@ export class ExpressionsService implements PersistableStateService this.renderers.register(definition); public readonly run: ExpressionsServiceStart['run'] = (ast, input, params) => - this.executor.run(ast, input, params).pipe(take(1)).toPromise(); + this.executor.run(ast, input, params); public readonly getFunction: ExpressionsServiceStart['getFunction'] = (name) => this.executor.getFunction(name); diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts index 98adec285afd5..86477e53dc1a1 100644 --- a/src/plugins/expressions/public/loader.test.ts +++ b/src/plugins/expressions/public/loader.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { of } from 'rxjs'; import { first, skip, toArray } from 'rxjs/operators'; import { loader, ExpressionLoader } from './loader'; import { Observable } from 'rxjs'; @@ -111,8 +112,29 @@ describe('ExpressionLoader', () => { it('emits on $data when data is available', async () => { const expressionLoader = new ExpressionLoader(element, 'var foo', { variables: { foo: 123 } }); - const response = await expressionLoader.data$.pipe(first()).toPromise(); - expect(response).toBe(123); + const { result } = await expressionLoader.data$.pipe(first()).toPromise(); + expect(result).toBe(123); + }); + + it('ignores partial results by default', async () => { + const expressionLoader = new ExpressionLoader(element, 'var foo', { + variables: { foo: of(1, 2) }, + }); + const { result, partial } = await expressionLoader.data$.pipe(first()).toPromise(); + + expect(partial).toBe(false); + expect(result).toBe(2); + }); + + it('emits partial results if enabled', async () => { + const expressionLoader = new ExpressionLoader(element, 'var foo', { + variables: { foo: of(1, 2) }, + partial: true, + }); + const { result, partial } = await expressionLoader.data$.pipe(first()).toPromise(); + + expect(partial).toBe(true); + expect(result).toBe(1); }); it('emits on loading$ on initial load and on updates', async () => { diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 4165b8906a20e..a51ce35c68180 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { filter, map, delay } from 'rxjs/operators'; import { defaults } from 'lodash'; +import { UnwrapObservable } from '@kbn/utility-types'; import { Adapters } from '../../inspector/public'; import { IExpressionLoaderParams } from './types'; import { ExpressionAstExpression } from '../common'; @@ -20,7 +21,7 @@ import { getExpressionsService } from './services'; type Data = any; export class ExpressionLoader { - data$: Observable; + data$: ReturnType; update$: ExpressionRenderHandler['update$']; render$: ExpressionRenderHandler['render$']; events$: ExpressionRenderHandler['events$']; @@ -28,10 +29,11 @@ export class ExpressionLoader { private execution: ExecutionContract | undefined; private renderHandler: ExpressionRenderHandler; - private dataSubject: Subject; + private dataSubject: Subject>; private loadingSubject: Subject; private data: Data; private params: IExpressionLoaderParams = {}; + private subscription?: Subscription; constructor( element: HTMLElement, @@ -67,8 +69,8 @@ export class ExpressionLoader { } }); - this.data$.subscribe((data) => { - this.render(data); + this.data$.subscribe(({ result }) => { + this.render(result); }); this.render$.subscribe(() => { @@ -87,27 +89,20 @@ export class ExpressionLoader { this.dataSubject.complete(); this.loadingSubject.complete(); this.renderHandler.destroy(); - if (this.execution) { - this.execution.cancel(); - } + this.cancel(); + this.subscription?.unsubscribe(); } cancel() { - if (this.execution) { - this.execution.cancel(); - } + this.execution?.cancel(); } getExpression(): string | undefined { - if (this.execution) { - return this.execution.getExpression(); - } + return this.execution?.getExpression(); } getAst(): ExpressionAstExpression | undefined { - if (this.execution) { - return this.execution.getAst(); - } + return this.execution?.getAst(); } getElement(): HTMLElement { @@ -115,27 +110,25 @@ export class ExpressionLoader { } inspect(): Adapters | undefined { - return this.execution ? (this.execution.inspect() as Adapters) : undefined; + return this.execution?.inspect() as Adapters; } - async update( - expression?: string | ExpressionAstExpression, - params?: IExpressionLoaderParams - ): Promise { + update(expression?: string | ExpressionAstExpression, params?: IExpressionLoaderParams): void { this.setParams(params); this.loadingSubject.next(true); if (expression) { - await this.loadData(expression, this.params); + this.loadData(expression, this.params); } else if (this.data) { this.render(this.data); } } - private loadData = async ( + private loadData = ( expression: string | ExpressionAstExpression, params: IExpressionLoaderParams - ): Promise => { + ) => { + this.subscription?.unsubscribe(); if (this.execution && this.execution.isPending) { this.execution.cancel(); } @@ -148,13 +141,13 @@ export class ExpressionLoader { debug: params.debug, syncColors: params.syncColors, }); - - const prevDataHandler = this.execution; - const data = await prevDataHandler.getData(); - if (this.execution !== prevDataHandler) { - return; - } - this.dataSubject.next(data); + this.subscription = this.execution + .getData() + .pipe( + delay(0), // delaying until the next tick since we execute the expression in the constructor + filter(({ partial }) => params.partial || !partial) + ) + .subscribe((value) => this.dataSubject.next(value)); }; private render(data: Data): void { @@ -184,6 +177,7 @@ export class ExpressionLoader { } this.params.syncColors = params.syncColors; this.params.debug = Boolean(params.debug); + this.params.partial = Boolean(params.partial); this.params.inspectorAdapters = (params.inspectorAdapters || this.execution?.inspect()) as Adapters; diff --git a/src/plugins/expressions/public/plugin.test.ts b/src/plugins/expressions/public/plugin.test.ts index 93353ebbb51b6..1963eb1f1b3f7 100644 --- a/src/plugins/expressions/public/plugin.test.ts +++ b/src/plugins/expressions/public/plugin.test.ts @@ -36,8 +36,10 @@ describe('ExpressionsPublicPlugin', () => { describe('.run()', () => { test('can execute simple expression', async () => { const { setup } = await expressionsPluginMock.createPlugin(); - const bar = await setup.run('var_set name="foo" value="bar" | var name="foo"', null); - expect(bar).toBe('bar'); + const { result } = await setup + .run('var_set name="foo" value="bar" | var name="foo"', null) + .toPromise(); + expect(result).toBe('bar'); }); }); }); diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 97009ae543b97..2d9c6d94cfa6d 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -112,16 +112,17 @@ export class Execution(ast: ExpressionAstNode, input: T): Observable; + interpret(ast: ExpressionAstNode, input: T): Observable>; // (undocumented) invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; // (undocumented) invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; // (undocumented) resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; - readonly result: Observable; - start(input?: Input): Observable; - readonly state: ExecutionContainer; + readonly result: Observable>; + start(input?: Input): Observable>; + // Warning: (ae-forgotten-export) The symbol "ExecutionResult" needs to be exported by the entry point index.d.ts + readonly state: ExecutionContainer>; } // Warning: (ae-forgotten-export) The symbol "StateContainer" needs to be exported by the entry point index.d.ts @@ -155,7 +156,7 @@ export class ExecutionContract; getAst: () => ExpressionAstExpression; - getData: () => Promise; + getData: () => Observable>; getExpression: () => string; inspect: () => InspectorAdapters; // (undocumented) @@ -230,7 +231,7 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) registerType(typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)): void; - run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; + run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; // (undocumented) readonly state: ExecutorContainer; // (undocumented) @@ -637,7 +638,7 @@ export interface ExpressionsServiceStart { getFunction: (name: string) => ReturnType; getRenderer: (name: string) => ReturnType; getType: (name: string) => ReturnType; - run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Promise; + run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Observable>; } // Warning: (ae-missing-release-tag) "ExpressionsSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -907,6 +908,8 @@ export interface IExpressionLoaderParams { // // (undocumented) onRenderError?: RenderErrorHandlerFnType; + // (undocumented) + partial?: boolean; // Warning: (ae-forgotten-export) The symbol "RenderMode" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1073,7 +1076,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { // (undocumented) expression: string | ExpressionAstExpression; // (undocumented) - onData$?: (data: TData, adapters?: TInspectorAdapters) => void; + onData$?: (data: TData, adapters?: TInspectorAdapters, partial?: boolean) => void; // (undocumented) onEvent?: (event: ExpressionRendererEvent) => void; // (undocumented) diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index e7ae6ee45ab60..d31a4c947b09d 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -255,7 +255,7 @@ describe('ExpressionRenderer', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); - const newData = {}; + const result = {}; const inspectData = {}; const onData$ = jest.fn(); @@ -275,11 +275,11 @@ describe('ExpressionRenderer', () => { expect(onData$).toHaveBeenCalledTimes(0); act(() => { - dataSubject.next(newData); + dataSubject.next({ result }); }); expect(onData$).toHaveBeenCalledTimes(1); - expect(onData$.mock.calls[0][0]).toBe(newData); + expect(onData$.mock.calls[0][0]).toBe(result); expect(onData$.mock.calls[0][1]).toBe(inspectData); }); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index ce8547f132c27..719af1b39f89e 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -30,7 +30,11 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { ) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; - onData$?: (data: TData, adapters?: TInspectorAdapters) => void; + onData$?: ( + data: TData, + adapters?: TInspectorAdapters, + partial?: boolean + ) => void; /** * An observable which can be used to re-run the expression without destroying the component */ @@ -135,8 +139,8 @@ export const ReactExpressionRenderer = ({ } if (onData$) { subs.push( - expressionLoaderRef.current.data$.subscribe((newData) => { - onData$(newData, expressionLoaderRef.current?.inspect()); + expressionLoaderRef.current.data$.subscribe(({ partial, result }) => { + onData$(result, expressionLoaderRef.current?.inspect(), partial); }) ); } diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 947b84ec152ac..2375252e82784 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -48,6 +48,7 @@ export interface IExpressionLoaderParams { renderMode?: RenderMode; syncColors?: boolean; hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions']; + partial?: boolean; } export interface ExpressionRenderError extends Error { diff --git a/src/plugins/expressions/server/plugin.test.ts b/src/plugins/expressions/server/plugin.test.ts index d967f9e614678..c41cda36e7623 100644 --- a/src/plugins/expressions/server/plugin.test.ts +++ b/src/plugins/expressions/server/plugin.test.ts @@ -28,8 +28,10 @@ describe('ExpressionsServerPlugin', () => { describe('.run()', () => { test('can execute simple expression', async () => { const { setup } = await expressionsPluginMock.createPlugin(); - const bar = await setup.run('var_set name="foo" value="bar" | var name="foo"', null); - expect(bar).toBe('bar'); + const { result } = await setup + .run('var_set name="foo" value="bar" | var name="foo"', null) + .toPromise(); + expect(result).toBe('bar'); }); }); }); diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 8d2e113e6b6ed..ec16d95ea8a3f 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -110,16 +110,17 @@ export class Execution(ast: ExpressionAstNode, input: T): Observable; + interpret(ast: ExpressionAstNode, input: T): Observable>; // (undocumented) invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; // (undocumented) invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; // (undocumented) resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; - readonly result: Observable; - start(input?: Input): Observable; - readonly state: ExecutionContainer; + readonly result: Observable>; + start(input?: Input): Observable>; + // Warning: (ae-forgotten-export) The symbol "ExecutionResult" needs to be exported by the entry point index.d.ts + readonly state: ExecutionContainer>; } // Warning: (ae-forgotten-export) The symbol "StateContainer" needs to be exported by the entry point index.d.ts @@ -212,7 +213,7 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) registerType(typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)): void; - run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; + run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; // (undocumented) readonly state: ExecutorContainer; // (undocumented) diff --git a/src/plugins/presentation_util/common/lib/index.ts b/src/plugins/presentation_util/common/lib/index.ts new file mode 100644 index 0000000000000..3fe90009ad8df --- /dev/null +++ b/src/plugins/presentation_util/common/lib/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './utils'; +export * from './test_helpers'; diff --git a/src/plugins/presentation_util/common/lib/test_helpers/function_wrapper.ts b/src/plugins/presentation_util/common/lib/test_helpers/function_wrapper.ts new file mode 100644 index 0000000000000..4ec02fd622cf7 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/test_helpers/function_wrapper.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { mapValues } from 'lodash'; +import { + ExpressionValueBoxed, + typeSpecs, + ExpressionFunctionDefinition, +} from '../../../../expressions/common'; + +type FnType = () => typeof typeSpecs[number] & + ExpressionFunctionDefinition, ExpressionValueBoxed>; + +// It takes a function spec and passes in default args into the spec fn +export const functionWrapper = (fnSpec: FnType): ReturnType['fn'] => { + const spec = fnSpec(); + const defaultArgs = mapValues(spec.args, (argSpec) => { + return argSpec.default; + }); + + return (context, args, handlers) => spec.fn(context, { ...defaultArgs, ...args }, handlers); +}; diff --git a/src/plugins/presentation_util/common/lib/test_helpers/index.ts b/src/plugins/presentation_util/common/lib/test_helpers/index.ts new file mode 100644 index 0000000000000..a6ea8da6ac6e9 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/test_helpers/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './function_wrapper'; diff --git a/x-pack/plugins/canvas/common/lib/dataurl.test.ts b/src/plugins/presentation_util/common/lib/utils/dataurl.test.ts similarity index 94% rename from x-pack/plugins/canvas/common/lib/dataurl.test.ts rename to src/plugins/presentation_util/common/lib/utils/dataurl.test.ts index 9ddd0a50ea9d5..5820b10f589fe 100644 --- a/x-pack/plugins/canvas/common/lib/dataurl.test.ts +++ b/src/plugins/presentation_util/common/lib/utils/dataurl.test.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { isValidDataUrl, parseDataUrl } from './dataurl'; diff --git a/x-pack/plugins/canvas/common/lib/dataurl.ts b/src/plugins/presentation_util/common/lib/utils/dataurl.ts similarity index 90% rename from x-pack/plugins/canvas/common/lib/dataurl.ts rename to src/plugins/presentation_util/common/lib/utils/dataurl.ts index 2ae28b621c425..9ac232369cdc1 100644 --- a/x-pack/plugins/canvas/common/lib/dataurl.ts +++ b/src/plugins/presentation_util/common/lib/utils/dataurl.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { fromByteArray } from 'base64-js'; diff --git a/x-pack/plugins/canvas/public/lib/elastic_logo.ts b/src/plugins/presentation_util/common/lib/utils/elastic_logo.ts similarity index 96% rename from x-pack/plugins/canvas/public/lib/elastic_logo.ts rename to src/plugins/presentation_util/common/lib/utils/elastic_logo.ts index 81c79c39143d6..9a789d1a5fb03 100644 --- a/x-pack/plugins/canvas/public/lib/elastic_logo.ts +++ b/src/plugins/presentation_util/common/lib/utils/elastic_logo.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ export const elasticLogo = diff --git a/src/plugins/presentation_util/common/lib/utils/elastic_outline.ts b/src/plugins/presentation_util/common/lib/utils/elastic_outline.ts new file mode 100644 index 0000000000000..4747be58127f7 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/elastic_outline.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const elasticOutline = + 'data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3Csvg%20viewBox%3D%22-3.948730230331421%20-1.7549896240234375%20245.25946044921875%20241.40370178222656%22%20width%3D%22245.25946044921875%22%20height%3D%22241.40370178222656%22%20style%3D%22enable-background%3Anew%200%200%20686.2%20235.7%3B%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%232D2D2D%3B%7D%0A%3C%2Fstyle%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Cg%20transform%3D%22matrix%281%2C%200%2C%200%2C%201%2C%200%2C%200%29%22%3E%0A%20%20%20%20%3Cg%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M329.4%2C160.3l4.7-0.5l0.3%2C9.6c-12.4%2C1.7-23%2C2.6-31.8%2C2.6c-11.7%2C0-20-3.4-24.9-10.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-4.9-6.8-7.3-17.4-7.3-31.7c0-28.6%2C11.4-42.9%2C34.1-42.9c11%2C0%2C19.2%2C3.1%2C24.6%2C9.2c5.4%2C6.1%2C8.1%2C15.8%2C8.1%2C28.9l-0.7%2C9.3h-53.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0%2C9%2C1.6%2C15.7%2C4.9%2C20c3.3%2C4.3%2C8.9%2C6.5%2C17%2C6.5C312.8%2C161.2%2C321.1%2C160.9%2C329.4%2C160.3z%20M325%2C124.9c0-10-1.6-17.1-4.8-21.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.2-4.1-8.4-6.2-15.6-6.2c-7.2%2C0-12.7%2C2.2-16.3%2C6.5c-3.6%2C4.3-5.5%2C11.3-5.6%2C20.9H325z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M354.3%2C171.4V64h12.2v107.4H354.3z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M443.5%2C113.5v41.1c0%2C4.1%2C10.1%2C3.9%2C10.1%2C3.9l-0.6%2C10.8c-8.6%2C0-15.7%2C0.7-20-3.4c-9.8%2C4.3-19.5%2C6.1-29.3%2C6.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.5%2C0-13.2-2.1-17.1-6.4c-3.9-4.2-5.9-10.3-5.9-18.3c0-7.9%2C2-13.8%2C6-17.5c4-3.7%2C10.3-6.1%2C18.9-6.9l25.6-2.4v-7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0-5.5-1.2-9.5-3.6-11.9c-2.4-2.4-5.7-3.6-9.8-3.6l-32.1%2C0V87.2h31.3c9.2%2C0%2C15.9%2C2.1%2C20.1%2C6.4C441.4%2C97.8%2C443.5%2C104.5%2C443.5%2C113.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bz%20M393.3%2C146.7c0%2C10%2C4.1%2C15%2C12.4%2C15c7.4%2C0%2C14.7-1.2%2C21.8-3.7l3.7-1.3v-26.9l-24.1%2C2.3c-4.9%2C0.4-8.4%2C1.8-10.6%2C4.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3BC394.4%2C138.7%2C393.3%2C142.2%2C393.3%2C146.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M491.2%2C98.2c-11.8%2C0-17.8%2C4.1-17.8%2C12.4c0%2C3.8%2C1.4%2C6.5%2C4.1%2C8.1c2.7%2C1.6%2C8.9%2C3.2%2C18.6%2C4.9%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc9.7%2C1.7%2C16.5%2C4%2C20.5%2C7.1c4%2C3%2C6%2C8.7%2C6%2C17.1c0%2C8.4-2.7%2C14.5-8.1%2C18.4c-5.4%2C3.9-13.2%2C5.9-23.6%2C5.9c-6.7%2C0-29.2-2.5-29.2-2.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bl0.7-10.6c12.9%2C1.2%2C22.3%2C2.2%2C28.6%2C2.2c6.3%2C0%2C11.1-1%2C14.4-3c3.3-2%2C5-5.4%2C5-10.1c0-4.7-1.4-7.9-4.2-9.6c-2.8-1.7-9-3.3-18.6-4.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-9.6-1.5-16.4-3.7-20.4-6.7c-4-2.9-6-8.4-6-16.3c0-7.9%2C2.8-13.8%2C8.4-17.6c5.6-3.8%2C12.6-5.7%2C20.9-5.7c6.6%2C0%2C29.6%2C1.7%2C29.6%2C1.7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bv10.7C508.1%2C99%2C498.2%2C98.2%2C491.2%2C98.2z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M581.7%2C99.5h-25.9v39c0%2C9.3%2C0.7%2C15.5%2C2%2C18.4c1.4%2C2.9%2C4.6%2C4.4%2C9.7%2C4.4l14.5-1l0.8%2C10.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.3%2C1.2-12.8%2C1.8-16.6%2C1.8c-8.5%2C0-14.3-2.1-17.6-6.2c-3.3-4.1-4.9-12-4.9-23.6V99.5h-11.6V88.9h11.6V63.9h12.1v24.9h25.9V99.5z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M598.7%2C78.4V64.3h12.2v14.2H598.7z%20M598.7%2C171.4V88.9h12.2v82.5H598.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M663.8%2C87.2c3.6%2C0%2C9.7%2C0.7%2C18.3%2C2l3.9%2C0.5l-0.5%2C9.9c-8.7-1-15.1-1.5-19.2-1.5c-9.2%2C0-15.5%2C2.2-18.8%2C6.6%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.3%2C4.4-5%2C12.6-5%2C24.5c0%2C11.9%2C1.5%2C20.2%2C4.6%2C24.9c3.1%2C4.7%2C9.5%2C7%2C19.3%2C7l19.2-1.5l0.5%2C10.1c-10.1%2C1.5-17.7%2C2.3-22.7%2C2.3%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-12.7%2C0-21.5-3.3-26.3-9.8c-4.8-6.5-7.3-17.5-7.3-33c0-15.5%2C2.6-26.4%2C7.8-32.6C643%2C90.4%2C651.7%2C87.2%2C663.8%2C87.2z%22%2F%3E%0A%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M236.6%2C123.5c0-19.8-12.3-37.2-30.8-43.9c0.8-4.2%2C1.2-8.4%2C1.2-12.7C207%2C30%2C177%2C0%2C140.2%2C0%26%2310%3B%26%239%3B%26%239%3BC118.6%2C0%2C98.6%2C10.3%2C86%2C27.7c-6.2-4.8-13.8-7.4-21.7-7.4c-19.6%2C0-35.5%2C15.9-35.5%2C35.5c0%2C4.3%2C0.8%2C8.5%2C2.2%2C12.4%26%2310%3B%26%239%3B%26%239%3BC12.6%2C74.8%2C0%2C92.5%2C0%2C112.2c0%2C19.9%2C12.4%2C37.3%2C30.9%2C44c-0.8%2C4.1-1.2%2C8.4-1.2%2C12.7c0%2C36.8%2C29.9%2C66.7%2C66.7%2C66.7%26%2310%3B%26%239%3B%26%239%3Bc21.6%2C0%2C41.6-10.4%2C54.1-27.8c6.2%2C4.9%2C13.8%2C7.6%2C21.7%2C7.6c19.6%2C0%2C35.5-15.9%2C35.5-35.5c0-4.3-0.8-8.5-2.2-12.4%26%2310%3B%26%239%3B%26%239%3BC223.9%2C160.9%2C236.6%2C143.2%2C236.6%2C123.5z%20M91.6%2C34.8c10.9-15.9%2C28.9-25.4%2C48.1-25.4c32.2%2C0%2C58.4%2C26.2%2C58.4%2C58.4%26%2310%3B%26%239%3B%26%239%3Bc0%2C3.9-0.4%2C7.7-1.1%2C11.5l-52.2%2C45.8L93%2C101.5L82.9%2C79.9L91.6%2C34.8z%20M65.4%2C29c6.2%2C0%2C12.1%2C2%2C17%2C5.7l-7.8%2C40.3l-35.5-8.4%26%2310%3B%26%239%3B%26%239%3Bc-1.1-3.1-1.7-6.3-1.7-9.7C37.4%2C41.6%2C49.9%2C29%2C65.4%2C29z%20M9.1%2C112.3c0-16.7%2C11-31.9%2C26.9-37.2L75%2C84.4l9.1%2C19.5l-49.8%2C45%26%2310%3B%26%239%3B%26%239%3BC19.2%2C143.1%2C9.1%2C128.6%2C9.1%2C112.3z%20M145.2%2C200.9c-10.9%2C16.1-29%2C25.6-48.4%2C25.6c-32.3%2C0-58.6-26.3-58.6-58.5c0-4%2C0.4-7.9%2C1.1-11.7%26%2310%3B%26%239%3B%26%239%3Bl50.9-46l52%2C23.7l11.5%2C22L145.2%2C200.9z%20M171.2%2C206.6c-6.1%2C0-12-2-16.9-5.8l7.7-40.2l35.4%2C8.3c1.1%2C3.1%2C1.7%2C6.3%2C1.7%2C9.7%26%2310%3B%26%239%3B%26%239%3BC199.2%2C194.1%2C186.6%2C206.6%2C171.2%2C206.6z%20M200.5%2C160.5l-39-9.1l-10.4-19.8l51-44.7c15.1%2C5.7%2C25.2%2C20.2%2C25.2%2C36.5%26%2310%3B%26%239%3B%26%239%3BC227.4%2C140.1%2C216.4%2C155.3%2C200.5%2C160.5z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E'; diff --git a/x-pack/plugins/canvas/common/lib/httpurl.test.ts b/src/plugins/presentation_util/common/lib/utils/httpurl.test.ts similarity index 89% rename from x-pack/plugins/canvas/common/lib/httpurl.test.ts rename to src/plugins/presentation_util/common/lib/utils/httpurl.test.ts index 1cd00114bf7ca..20cd40480691d 100644 --- a/x-pack/plugins/canvas/common/lib/httpurl.test.ts +++ b/src/plugins/presentation_util/common/lib/utils/httpurl.test.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { isValidHttpUrl } from './httpurl'; diff --git a/x-pack/plugins/canvas/common/lib/httpurl.ts b/src/plugins/presentation_util/common/lib/utils/httpurl.ts similarity index 67% rename from x-pack/plugins/canvas/common/lib/httpurl.ts rename to src/plugins/presentation_util/common/lib/utils/httpurl.ts index 4f8b03aa2a062..4777eb4c8128d 100644 --- a/x-pack/plugins/canvas/common/lib/httpurl.ts +++ b/src/plugins/presentation_util/common/lib/utils/httpurl.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ // A cheap regex to distinguish an HTTP URL string from a data URL string diff --git a/src/plugins/presentation_util/common/lib/utils/index.ts b/src/plugins/presentation_util/common/lib/utils/index.ts new file mode 100644 index 0000000000000..eed4acf78b2be --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './dataurl'; +export * from './elastic_logo'; +export * from './elastic_outline'; +export * from './httpurl'; +export * from './missing_asset'; +export * from './resolve_dataurl'; +export * from './url'; diff --git a/src/plugins/presentation_util/common/lib/utils/missing_asset.ts b/src/plugins/presentation_util/common/lib/utils/missing_asset.ts new file mode 100644 index 0000000000000..10d429870c88c --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/missing_asset.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// CC0, source: https://pixabay.com/en/question-mark-confirmation-question-838656/ +export const missingImage = + 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzMSAzMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48ZmlsdGVyIGlkPSJiIiB4PSItLjM2IiB5PSItLjM2IiB3aWR0aD0iMS43MiIgaGVpZ2h0PSIxLjcyIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuNDY0Ii8+PC9maWx0ZXI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMTU5LjM0IiB4Mj0iMTg0LjQ4IiB5MT0iNzI3LjM2IiB5Mj0iNzQ5Ljg5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMTEuNTMgLTU0OS42OCkgc2NhbGUoLjc3MDAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiNlYmYwZWQiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNmYWZhZmEiIG9mZnNldD0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xNS40MzcgMi42OTVsMTQuNTA2IDI0LjQ3NkgxLjI4N2wxNC4xNS0yNC40NzZ6IiBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9InJlZCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBhdGggdHJhbnNmb3JtPSJtYXRyaXgoLjgzMTk3IDAgMCAuNTU0NjYgLTc4LjU4MyAtMzgzLjUxKSIgZD0iTTExMS4zMSA3MzEuMmMtMy4yODMtMy45MjUtMy41OTUtNi4xNDgtMi4wMjQtMTAuNDM4IDMuMzM2LTYuMTQ1IDQuNDk2LTguMDY4IDUuNDEtOS40MDUgMS45MDEgNS4xNjIgMi4xMjYgMTkuMTQtMy4zODYgMTkuODQzeiIgZmlsbD0iI2ZmZiIgZmlsbC1vcGFjaXR5PSIuODc2IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbHRlcj0idXJsKCNiKSIvPjxnIGZpbGwtb3BhY2l0eT0iLjgyIj48cGF0aCBkPSJNMTUuMDQ2IDIwLjIyN2gtLjQxNWMtLjAxMy0uNzQ4LjAyLTEuMzA4LjEwMS0xLjY3OC4wODgtLjM3MS4zMDctLjg4LjY1OC0xLjUyOC4zNTctLjY1NC41OS0xLjE3Ni42OTctMS41NjcuMTE1LS4zOTguMTcyLS44ODcuMTcyLTEuNDY3IDAtLjg5Ni0uMTc1LTEuNTU3LS41MjYtMS45ODItLjM1LS40MjUtLjc2NS0uNjM3LTEuMjQ0LS42MzctLjM2NCAwLS42Ny4wOTgtLjkyLjI5My0uMTg5LjE0OS0uMjgzLjMwNC0uMjgzLjQ2NiAwIC4xMDcuMDY0LjI3Ni4xOTIuNTA1LjI5LjUyLjQzNS45NjEuNDM1IDEuMzI1IDAgLjMzLS4xMTUuNjA3LS4zNDQuODNhMS4xMzggMS4xMzggMCAwIDEtLjg0LjMzM2MtLjM3NyAwLS42OTQtLjEzMS0uOTUtLjM5NC0uMjU2LS4yNy0uMzg0LS42MjQtLjM4NC0xLjA2MiAwLS43OTYuMzQ0LTEuNDk0IDEuMDMxLTIuMDk0LjY4OC0uNiAxLjY0OS0uOSAyLjg4My0uOSAxLjMwOCAwIDIuMzAyLjMxNCAyLjk4My45NC42ODguNjIxIDEuMDMyIDEuMzczIDEuMDMyIDIuMjU2IDAgLjY0LS4xNzYgMS4yMzQtLjUyNiAxLjc4LS4zNTEuNTQtMS4wMjkgMS4xNC0yLjAzMyAxLjgtLjY3NC40NDUtMS4xMi44NDMtMS4zMzUgMS4xOTQtLjIxLjM0My0uMzM3Ljg3My0uMzg0IDEuNTg3bS0uMTEyIDEuNDc3Yy40NTIgMCAuODM2LjE1OCAxLjE1My40NzUuMzE3LjMxNy40NzYuNzAxLjQ3NiAxLjE1MyAwIC40NTItLjE1OS44NC0uNDc2IDEuMTYzYTEuNTcgMS41NyAwIDAgMS0xLjE1My40NzUgMS41NyAxLjU3IDAgMCAxLTEuMTUzLS40NzUgMS42MDQgMS42MDQgMCAwIDEtLjQ3NS0xLjE2M2MwLS40NTIuMTU5LS44MzYuNDc1LTEuMTUzYTEuNTcgMS41NyAwIDAgMSAxLjE1My0uNDc1IiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii40ODYiLz48cGF0aCBkPSJNMTUuMzI3IDIwLjUwOGgtLjQxNWMtLjAxMy0uNzQ4LjAyLTEuMzA4LjEwMS0xLjY3OC4wODgtLjM3MS4zMDctLjg4LjY1OC0xLjUyOC4zNTctLjY1NC41OS0xLjE3Ni42OTctMS41NjcuMTE1LS4zOTguMTcyLS44ODcuMTcyLTEuNDY2IDAtLjg5Ny0uMTc1LTEuNTU4LS41MjYtMS45ODMtLjM1LS40MjQtLjc2NS0uNjM3LTEuMjQzLS42MzctLjM2NSAwLS42NzEuMDk4LS45Mi4yOTMtLjE5LjE0OS0uMjg0LjMwNC0uMjg0LjQ2NiAwIC4xMDguMDY0LjI3Ni4xOTIuNTA1LjI5LjUyLjQzNS45NjEuNDM1IDEuMzI1IDAgLjMzLS4xMTUuNjA3LS4zNDQuODNhMS4xMzggMS4xMzggMCAwIDEtLjg0LjMzM2MtLjM3NyAwLS42OTQtLjEzMS0uOTUtLjM5NC0uMjU2LS4yNy0uMzg0LS42MjQtLjM4NC0xLjA2MiAwLS43OTYuMzQ0LTEuNDkzIDEuMDMxLTIuMDk0LjY4OC0uNiAxLjY0OS0uOSAyLjg4My0uOSAxLjMwOCAwIDIuMzAyLjMxNCAyLjk4My45NC42ODguNjIxIDEuMDMyIDEuMzczIDEuMDMyIDIuMjU2IDAgLjY0LS4xNzYgMS4yMzQtLjUyNiAxLjc4LS4zNS41NC0xLjAyOCAxLjE0LTIuMDMzIDEuOC0uNjc0LjQ0NS0uODUzLjg0My0xLjA2OCAxLjE5NC0uMjEuMzQzLS4zMzcuODczLS4zODUgMS41ODdtLS4zNzggMS40NzdjLjQ1MiAwIC44MzYuMTU4IDEuMTUzLjQ3NS4zMTcuMzE3LjQ3Ni43MDEuNDc2IDEuMTUzIDAgLjQ1Mi0uMTU5Ljg0LS40NzYgMS4xNjNhMS41NyAxLjU3IDAgMCAxLTEuMTUzLjQ3NiAxLjU3IDEuNTcgMCAwIDEtMS4xNTMtLjQ3NiAxLjYwNCAxLjYwNCAwIDAgMS0uNDc1LTEuMTYzYzAtLjQ1Mi4xNTktLjgzNi40NzUtMS4xNTNhMS41NyAxLjU3IDAgMCAxIDEuMTUzLS40NzUiIGZpbGwtb3BhY2l0eT0iLjgyIi8+PC9nPjwvc3ZnPg=='; diff --git a/x-pack/plugins/canvas/common/lib/resolve_dataurl.test.js b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.test.ts similarity index 84% rename from x-pack/plugins/canvas/common/lib/resolve_dataurl.test.js rename to src/plugins/presentation_util/common/lib/utils/resolve_dataurl.test.ts index 72aaa1dfbd502..c2b9a444d20ef 100644 --- a/x-pack/plugins/canvas/common/lib/resolve_dataurl.test.js +++ b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.test.ts @@ -1,11 +1,12 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { missingImage } from '../../common/lib/missing_asset'; +import { missingImage } from './missing_asset'; import { resolveFromArgs, resolveWithMissingImage } from './resolve_dataurl'; describe('resolve_dataurl', () => { diff --git a/x-pack/plugins/canvas/common/lib/resolve_dataurl.ts b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.ts similarity index 75% rename from x-pack/plugins/canvas/common/lib/resolve_dataurl.ts rename to src/plugins/presentation_util/common/lib/utils/resolve_dataurl.ts index 79e49c0595355..db94bdf04c32b 100644 --- a/x-pack/plugins/canvas/common/lib/resolve_dataurl.ts +++ b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.ts @@ -1,13 +1,14 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { get } from 'lodash'; -import { isValidUrl } from '../../common/lib/url'; -import { missingImage } from '../../common/lib/missing_asset'; +import { isValidUrl } from './url'; +import { missingImage } from './missing_asset'; /* * NOTE: args.dataurl can come as an expression here. diff --git a/x-pack/plugins/canvas/common/lib/url.test.ts b/src/plugins/presentation_util/common/lib/utils/url.test.ts similarity index 70% rename from x-pack/plugins/canvas/common/lib/url.test.ts rename to src/plugins/presentation_util/common/lib/utils/url.test.ts index 654602eea2093..4599e776a6266 100644 --- a/x-pack/plugins/canvas/common/lib/url.test.ts +++ b/src/plugins/presentation_util/common/lib/utils/url.test.ts @@ -1,11 +1,12 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { missingImage } from '../../common/lib/missing_asset'; +import { missingImage } from './missing_asset'; import { isValidUrl } from './url'; describe('resolve_dataurl', () => { diff --git a/src/plugins/presentation_util/common/lib/utils/url.ts b/src/plugins/presentation_util/common/lib/utils/url.ts new file mode 100644 index 0000000000000..e6a1064200cc1 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/url.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isValidDataUrl } from './dataurl'; +import { isValidHttpUrl } from './httpurl'; + +export function isValidUrl(url: string) { + return isValidDataUrl(url) || isValidHttpUrl(url); +} diff --git a/src/plugins/presentation_util/jest.config.js b/src/plugins/presentation_util/jest.config.js new file mode 100644 index 0000000000000..2250d70acb475 --- /dev/null +++ b/src/plugins/presentation_util/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/presentation_util'], +}; diff --git a/src/plugins/presentation_util/kibana.json b/src/plugins/presentation_util/kibana.json index c7d272dcd02a1..22ec919457cce 100644 --- a/src/plugins/presentation_util/kibana.json +++ b/src/plugins/presentation_util/kibana.json @@ -4,6 +4,9 @@ "kibanaVersion": "kibana", "server": true, "ui": true, + "extraPublicDirs": [ + "common/lib" + ], "requiredPlugins": [ "savedObjects" ], diff --git a/src/plugins/presentation_util/public/__stories__/index.tsx b/src/plugins/presentation_util/public/__stories__/index.tsx new file mode 100644 index 0000000000000..078a16cb8cab2 --- /dev/null +++ b/src/plugins/presentation_util/public/__stories__/index.tsx @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './render'; diff --git a/src/plugins/presentation_util/public/__stories__/render.tsx b/src/plugins/presentation_util/public/__stories__/render.tsx new file mode 100644 index 0000000000000..29d95e6bf2819 --- /dev/null +++ b/src/plugins/presentation_util/public/__stories__/render.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { action } from '@storybook/addon-actions'; +import React, { useRef, useEffect } from 'react'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; + +export const defaultHandlers: IInterpreterRenderHandlers = { + getRenderMode: () => 'display', + isSyncColorsEnabled: () => false, + done: action('done'), + onDestroy: action('onDestroy'), + reload: action('reload'), + update: action('update'), + event: action('event'), +}; + +/* + Uses a RenderDefinitionFactory and Config to render into an element. + + Intended to be used for stories for RenderDefinitionFactory +*/ +interface RenderAdditionalProps { + height?: string; + width?: string; + handlers?: IInterpreterRenderHandlers; +} + +export const Render = ({ + renderer, + config, + ...rest +}: Renderer extends () => ExpressionRenderDefinition + ? { renderer: Renderer; config: Config } & RenderAdditionalProps + : { renderer: undefined; config: undefined } & RenderAdditionalProps) => { + const { height, width, handlers } = { + height: '200px', + width: '200px', + handlers: defaultHandlers, + ...rest, + }; + + const containerRef = useRef(null); + + useEffect(() => { + if (renderer && containerRef.current !== null) { + renderer().render(containerRef.current, config, handlers); + } + }, [renderer, config, handlers]); + + return ( +
+ {' '} +
+ ); +}; diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index 9f17133c5b35a..1e26011ff58ae 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -28,6 +28,7 @@ export { export { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types'; export { SaveModalDashboardProps } from './components/types'; export { projectIDs, ProjectID, Project } from '../common/labs'; +export * from '../common/lib'; export { LazyLabsBeakerButton, diff --git a/src/plugins/presentation_util/tsconfig.json b/src/plugins/presentation_util/tsconfig.json index c0fafe8c3aaba..b389d94b19413 100644 --- a/src/plugins/presentation_util/tsconfig.json +++ b/src/plugins/presentation_util/tsconfig.json @@ -7,6 +7,9 @@ "declaration": true, "declarationMap": true }, + "extraPublicDirs": [ + "common" + ], "include": [ "common/**/*", "public/**/*", diff --git a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx index da65b5b9fdda8..0a2e4ff78be26 100644 --- a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { debounce } from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; @@ -116,7 +116,7 @@ class SavedObjectFinderUi extends React.Component< private isComponentMounted: boolean = false; - private debouncedFetch = _.debounce(async (query: string) => { + private debouncedFetch = debounce(async (query: string) => { const metaDataMap = this.getSavedObjectMetaDataMap(); const fields = Object.values(metaDataMap) diff --git a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts index 1f2f7dc573dc7..40baff22f52c8 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { cloneDeep, defaults, forOwn, assign } from 'lodash'; import { EsResponse, SavedObject, SavedObjectConfig, SavedObjectKibanaServices } from '../../types'; import { SavedObjectNotFound } from '../../../../kibana_utils/public'; import { @@ -28,7 +28,7 @@ export async function applyESResp( ) { const mapping = expandShorthand(config.mapping ?? {}); const savedObjectType = config.type || ''; - savedObject._source = _.cloneDeep(resp._source); + savedObject._source = cloneDeep(resp._source); if (typeof resp.found === 'boolean' && !resp.found) { throw new SavedObjectNotFound(savedObjectType, savedObject.id || ''); } @@ -42,10 +42,10 @@ export async function applyESResp( } // assign the defaults to the response - _.defaults(savedObject._source, savedObject.defaults); + defaults(savedObject._source, savedObject.defaults); // transform the source using _deserializers - _.forOwn(mapping, (fieldMapping, fieldName) => { + forOwn(mapping, (fieldMapping, fieldName) => { if (fieldMapping._deserialize && typeof fieldName === 'string') { savedObject._source[fieldName] = fieldMapping._deserialize( savedObject._source[fieldName] as string @@ -54,7 +54,7 @@ export async function applyESResp( }); // Give obj all of the values in _source.fields - _.assign(savedObject, savedObject._source); + assign(savedObject, savedObject._source); savedObject.lastSavedTitle = savedObject.title; if (meta.searchSourceJSON) { diff --git a/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts b/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts index f1bc614dd1197..7ed729b4b7a0f 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { SavedObjectAttributes } from 'kibana/public'; import { SavedObject, SavedObjectKibanaServices } from '../../types'; @@ -40,7 +40,7 @@ export async function createSource( return await savedObjectsClient.create(esType, source, options); } catch (err) { // record exists, confirm overwriting - if (_.get(err, 'res.status') === 409) { + if (get(err, 'res.status') === 409) { const confirmMessage = i18n.translate( 'savedObjects.confirmModal.overwriteConfirmationMessage', { diff --git a/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts index cf0cea8d368da..b6dddf8d82b72 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { cloneDeep, assign } from 'lodash'; import { SavedObjectsClientContract } from 'kibana/public'; import { SavedObject, SavedObjectConfig } from '../../types'; @@ -24,7 +24,7 @@ export async function intializeSavedObject( if (!savedObject.id) { // just assign the defaults and be done - _.assign(savedObject, savedObject.defaults); + assign(savedObject, savedObject.defaults); await savedObject.hydrateIndexPattern!(); if (typeof config.afterESResp === 'function') { savedObject = await config.afterESResp(savedObject); @@ -36,7 +36,7 @@ export async function intializeSavedObject( const respMapped = { _id: resp.id, _type: resp.type, - _source: _.cloneDeep(resp.attributes), + _source: cloneDeep(resp.attributes), references: resp.references, found: !!resp._version, }; diff --git a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts index eb9bef788fcdc..efe7a85f8f1e1 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { forOwn } from 'lodash'; import { SavedObject, SavedObjectConfig } from '../../types'; import { extractSearchSourceReferences } from '../../../../data/public'; import { expandShorthand } from './field_mapping'; @@ -17,7 +17,7 @@ export function serializeSavedObject(savedObject: SavedObject, config: SavedObje const attributes = {} as Record; const references = []; - _.forOwn(mapping, (fieldMapping, fieldName) => { + forOwn(mapping, (fieldMapping, fieldName) => { if (typeof fieldName !== 'string') { return; } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js index bb264aaacbfbf..801681dbd531f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js @@ -10,6 +10,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { keys, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { pluck } from 'rxjs/operators'; const MIN_CHART_HEIGHT = 300; @@ -55,7 +56,9 @@ class VisEditorVisualizationUI extends Component { await this._handler.render(this._visEl.current); this.props.eventEmitter.emit('embeddableRendered'); - this._subscription = this._handler.handler.data$.subscribe((data) => onDataChange(data.value)); + this._subscription = this._handler.handler.data$ + .pipe(pluck('result')) + .subscribe((data) => onDataChange(data.value)); } /** diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 7f1ea64bcd979..ea11560e37b6f 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -447,7 +447,9 @@ export class VisualBuilderPageObject extends FtrService { const metricsIndexPatternInput = 'metricsIndexPatternInput'; if (useKibanaIndices !== undefined) { - await this.switchIndexPatternSelectionMode(useKibanaIndices); + await this.retry.try(async () => { + await this.switchIndexPatternSelectionMode(useKibanaIndices); + }); } if (useKibanaIndices === false) { diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx index cf720657291f8..b03451bdebad2 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx @@ -9,7 +9,7 @@ import './main.scss'; import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@elastic/eui'; -import { first } from 'rxjs/operators'; +import { first, pluck } from 'rxjs/operators'; import { IInterpreterRenderHandlers, ExpressionValue, @@ -59,7 +59,9 @@ class Main extends React.Component<{}, State> { inspectorAdapters: adapters, searchContext: initialContext as any, }) - .getData(); + .getData() + .pipe(pluck('result')) + .toPromise(); }; let lastRenderHandler: ExpressionRenderHandler; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts index 923113cc99ba8..a4903fedd22d5 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts @@ -7,6 +7,7 @@ */ import { schema } from '@kbn/config-schema'; +import { pluck } from 'rxjs/operators'; import { CoreSetup, Plugin, HttpResponsePayload } from '../../../../../src/core/server'; import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; import { ExpressionsServerStart } from '../../../../../src/plugins/expressions/server'; @@ -32,13 +33,12 @@ export class TestPlugin implements Plugin { const [, { expressions }] = await core.getStartServices(); - const output = await expressions.run( - req.body.expression, - req.body.input, - { + const output = await expressions + .run(req.body.expression, req.body.input, { kibanaRequest: req, - } - ); + }) + .pipe(pluck('result')) + .toPromise(); return res.ok({ body: output }); } ); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 65b28015f7f93..2c5287525c597 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -152,7 +152,7 @@ export class ActionsPlugin implements Plugin()) diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index df63625bf242d..b906983017ff6 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -153,7 +153,7 @@ export class AlertingPlugin { constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.create().pipe(first()).toPromise(); - this.logger = initializerContext.logger.get('plugins', 'alerting'); + this.logger = initializerContext.logger.get(); this.taskRunnerFactory = new TaskRunnerFactory(); this.alertsClientFactory = new AlertsClientFactory(); this.alertingAuthorizationClientFactory = new AlertingAuthorizationClientFactory(); diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 21aef379715c7..ae4510b10acd4 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -3,39 +3,34 @@ "version": "8.0.0", "kibanaVersion": "kibana", "requiredPlugins": [ - "features", "apmOss", "data", - "licensing", - "triggersActionsUi", "embeddable", + "features", + "fleet", "infra", + "licensing", "observability", - "ruleRegistry" + "ruleRegistry", + "triggersActionsUi" ], "optionalPlugins": [ - "spaces", - "cloud", - "usageCollection", - "taskManager", "actions", "alerting", - "security", - "ml", + "cloud", "home", "maps", - "fleet" + "ml", + "security", + "spaces", + "taskManager", + "usageCollection" ], "server": true, "ui": true, - "configPath": [ - "xpack", - "apm" - ], - "extraPublicDirs": [ - "public/style/variables" - ], + "configPath": ["xpack", "apm"], "requiredBundles": [ + "fleet", "home", "kibanaReact", "kibanaUtils", @@ -43,4 +38,4 @@ "ml", "observability" ] -} \ No newline at end of file +} diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx index d069d4a11b494..16b8cc34e9752 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx @@ -17,7 +17,7 @@ import { import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; export default { - title: 'app/TransactionDurationAlertTrigger', + title: 'alerting/TransactionDurationAlertTrigger', component: TransactionDurationAlertTrigger, decorators: [ (Story: ComponentType) => { @@ -26,7 +26,7 @@ export default { http: { get: (endpoint: string) => { if (endpoint === '/api/apm/environments') { - return Promise.resolve(['production']); + return Promise.resolve({ environments: ['production'] }); } else { return Promise.resolve({ transactionTypes: ['request'], diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/FormRowSelect.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/ServicePage/FormRowSelect.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/FormRowSelect.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/ServicePage/FormRowSelect.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/ConfirmDeleteModal.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/ConfirmDeleteModal.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/index.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx rename to x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/index.tsx index c098be41968dd..93ae8b270b5de 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/index.tsx @@ -22,13 +22,12 @@ import { getOptionLabel } from '../../../../../../common/agent_configuration/all import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; import { useTheme } from '../../../../../hooks/use_theme'; -import { px, units } from '../../../../../style/variables'; import { createAgentConfigurationHref, editAgentConfigurationHref, } from '../../../../shared/Links/apm/agentConfigurationLinks'; import { LoadingStatePrompt } from '../../../../shared/LoadingStatePrompt'; -import { ITableColumn, ManagedTable } from '../../../../shared/ManagedTable'; +import { ITableColumn, ManagedTable } from '../../../../shared/managed_table'; import { TimestampTooltip } from '../../../../shared/TimestampTooltip'; import { ConfirmDeleteModal } from './ConfirmDeleteModal'; @@ -125,7 +124,7 @@ export function AgentConfigurationList({ { field: 'applied_by_agent', align: 'center', - width: px(units.double), + width: theme.eui.euiSizeXL, name: '', sortable: true, render: (isApplied: boolean) => ( @@ -190,7 +189,7 @@ export function AgentConfigurationList({ ...(canSave ? [ { - width: px(units.double), + width: theme.eui.euiSizeXL, name: '', render: (config: Config) => ( ( { +function Wrapper({ children }: { children?: ReactNode }) { + return ( + + {children} + + ); +} + +describe('DeleteButton', () => { beforeAll(() => { jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({}); }); + it('deletes a custom link', async () => { const onDeleteMock = jest.fn(); const { getByText } = render( - - - + , + { wrapper: Wrapper } ); + await act(async () => { fireEvent.click(getByText('Delete')); }); + expect(onDeleteMock).toHaveBeenCalledTimes(1); }); }); diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/DeleteButton.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/DeleteButton.tsx index eb6dfdd763ac4..c6547aaff0671 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/DeleteButton.tsx @@ -9,9 +9,9 @@ import { EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; import React, { useState } from 'react'; -import { px, unit } from '../../../../../../style/variables'; import { callApmApi } from '../../../../../../services/rest/createCallApmApi'; import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; +import { useTheme } from '../../../../../../hooks/use_theme'; interface Props { onDelete: () => void; @@ -21,6 +21,7 @@ interface Props { export function DeleteButton({ onDelete, customLinkId }: Props) { const [isDeleting, setIsDeleting] = useState(false); const { toasts } = useApmPluginContext().core.notifications; + const theme = useTheme(); return ( {i18n.translate('xpack.apm.settings.customizeUI.customLink.delete', { defaultMessage: 'Delete', diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/Documentation.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/Documentation.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/Documentation.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/Documentation.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/FiltersSection.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/FiltersSection.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/FiltersSection.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/FiltersSection.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/FlyoutFooter.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/FlyoutFooter.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/FlyoutFooter.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/FlyoutFooter.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/LinkSection.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/LinkSection.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/LinkSection.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/LinkSection.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/helper.test.ts b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/helper.test.ts similarity index 98% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/helper.test.ts rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/helper.test.ts index c4420f3a59c81..449968b82943f 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/helper.test.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/helper.test.ts @@ -8,7 +8,7 @@ import { getSelectOptions, replaceTemplateVariables, -} from '../CreateEditCustomLinkFlyout/helper'; +} from '../create_edit_custom_link_flyout/helper'; import { Transaction } from '../../../../../../../typings/es_schemas/ui/transaction'; describe('Custom link helper', () => { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/helper.ts b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/helper.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/helper.ts rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/helper.ts diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.stories.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.stories.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.test.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.tsx diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/saveCustomLink.ts b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/saveCustomLink.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/saveCustomLink.ts rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/saveCustomLink.ts diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/custom_link_table.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/custom_link_table.tsx index bb948ea665b1e..9593802407193 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkTable.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/custom_link_table.tsx @@ -5,22 +5,21 @@ * 2.0. */ -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem, - EuiText, EuiSpacer, + EuiText, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; +import React, { useState } from 'react'; import { CustomLink } from '../../../../../../common/custom_link/custom_link_types'; -import { units, px } from '../../../../../style/variables'; -import { ManagedTable } from '../../../../shared/ManagedTable'; -import { TimestampTooltip } from '../../../../shared/TimestampTooltip'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; import { LoadingStatePrompt } from '../../../../shared/LoadingStatePrompt'; +import { ManagedTable } from '../../../../shared/managed_table'; +import { TimestampTooltip } from '../../../../shared/TimestampTooltip'; interface Props { items: CustomLink[]; @@ -50,7 +49,7 @@ export function CustomLinkTable({ items = [], onCustomLinkSelected }: Props) { truncateText: true, }, { - width: px(160), + width: 160, align: 'right', field: '@timestamp', name: i18n.translate( @@ -63,7 +62,7 @@ export function CustomLinkTable({ items = [], onCustomLinkSelected }: Props) { ), }, { - width: px(units.triple), + width: '48px', name: '', actions: [ ...(canSave diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.test.tsx similarity index 99% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.test.tsx index 7d119b8c406da..22fbfb04734ba 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.test.tsx @@ -22,7 +22,7 @@ import { expectTextsInDocument, expectTextsNotInDocument, } from '../../../../../utils/testHelpers'; -import * as saveCustomLink from './CreateEditCustomLinkFlyout/saveCustomLink'; +import * as saveCustomLink from './create_edit_custom_link_flyout/saveCustomLink'; const data = { customLinks: [ diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.tsx index c1315f165abdb..beea1d8276846 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.tsx @@ -8,21 +8,21 @@ import { EuiFlexGroup, EuiFlexItem, - EuiTitle, - EuiText, EuiSpacer, + EuiText, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useEffect, useState } from 'react'; import { INVALID_LICENSE } from '../../../../../../common/custom_link'; import { CustomLink } from '../../../../../../common/custom_link/custom_link_types'; -import { FETCH_STATUS, useFetcher } from '../../../../../hooks/use_fetcher'; import { useLicenseContext } from '../../../../../context/license/use_license_context'; +import { FETCH_STATUS, useFetcher } from '../../../../../hooks/use_fetcher'; import { LicensePrompt } from '../../../../shared/license_prompt'; import { CreateCustomLinkButton } from './CreateCustomLinkButton'; -import { CreateEditCustomLinkFlyout } from './CreateEditCustomLinkFlyout'; -import { CustomLinkTable } from './CustomLinkTable'; +import { CreateEditCustomLinkFlyout } from './create_edit_custom_link_flyout'; +import { CustomLinkTable } from './custom_link_table'; import { EmptyPrompt } from './EmptyPrompt'; export function CustomLinkOverview() { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/index.tsx similarity index 87% rename from x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx rename to x-pack/plugins/apm/public/components/app/Settings/customize_ui/index.tsx index 9ce1f1325bb2c..9d3ee3fa47c1a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/index.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { CustomLinkOverview } from './CustomLink'; +import { CustomLinkOverview } from './custom_link'; export function CustomizeUI() { return ; diff --git a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx index 526aad56e743e..f0d7f8d60eb6c 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx @@ -6,33 +6,32 @@ */ import { - ScaleType, - Chart, - LineSeries, Axis, + Chart, CurveType, + LineSeries, Position, - timeFormatter, + ScaleType, Settings, + timeFormatter, } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { useUiTracker } from '../../../../../observability/public'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; -import { px } from '../../../style/variables'; +import { ChartContainer } from '../../shared/charts/chart_container'; import { CorrelationsTable, SelectedSignificantTerm, } from './correlations_table'; -import { ChartContainer } from '../../shared/charts/chart_container'; -import { useTheme } from '../../../hooks/use_theme'; import { CustomFields } from './custom_fields'; import { useFieldNames } from './use_field_names'; -import { useLocalStorage } from '../../../hooks/useLocalStorage'; -import { useUiTracker } from '../../../../../observability/public'; type OverallErrorsApiResponse = NonNullable< APIReturnType<'GET /api/apm/correlations/errors/overall_timeseries'> @@ -221,7 +220,7 @@ function ErrorTimeseriesChart({ return ( - + theme.eui.euiSize}; `; const TransactionLinkName = euiStyled.div` - margin-left: ${px(units.half)}; + margin-left: ${({ theme }) => theme.eui.euiSizeS}; display: inline-block; vertical-align: middle; `; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx rename to x-pack/plugins/apm/public/components/app/error_group_details/index.tsx index 3d22c3863c100..344393d42506f 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx @@ -19,32 +19,31 @@ import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useTrackPageview } from '../../../../../observability/public'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; -import { useFetcher } from '../../../hooks/use_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables'; -import { DetailView } from './DetailView'; -import { ErrorDistribution } from './Distribution'; import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { DetailView } from './detail_view'; +import { ErrorDistribution } from './Distribution'; const Titles = euiStyled.div` - margin-bottom: ${px(units.plus)}; + margin-bottom: ${({ theme }) => theme.eui.euiSizeL}; `; const Label = euiStyled.div` - margin-bottom: ${px(units.quarter)}; - font-size: ${fontSizes.small}; + margin-bottom: ${({ theme }) => theme.eui.euiSizeXS}; + font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; color: ${({ theme }) => theme.eui.euiColorDarkShade}; `; const Message = euiStyled.div` - font-family: ${fontFamilyCode}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; font-weight: bold; - font-size: ${fontSizes.large}; - margin-bottom: ${px(units.half)}; + font-size: ${({ theme }) => theme.eui.euiFontSizeM}; + margin-bottom: ${({ theme }) => theme.eui.euiSizeS}; `; const Culprit = euiStyled.div` - font-family: ${fontFamilyCode}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; `; function getShortGroupId(errorGroupId?: string) { diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx index e368230fbc77e..a2a92b7e16f8e 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx @@ -13,6 +13,7 @@ import { mockMoment, toJson } from '../../../../utils/testHelpers'; import { ErrorGroupList } from './index'; import props from './__fixtures__/props.json'; import { MemoryRouter } from 'react-router-dom'; +import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { return { @@ -41,13 +42,18 @@ describe('ErrorGroupOverview -> List', () => { it('should render with data', () => { const wrapper = mount( - - - - - - - + + + + + + + + + ); expect(toJson(wrapper)).toMatchSnapshot(); diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap index 92337a97573e4..2b85d6bb3c229 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap @@ -268,7 +268,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` exports[`ErrorGroupOverview -> List should render with data 1`] = ` .c0 { - font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; + font-family: 'Roboto Mono','Consolas','Menlo','Courier',monospace; } .c2 { @@ -286,8 +286,8 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` } .c3 { - font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; - font-size: 16px; + font-family: 'Roboto Mono','Consolas','Menlo','Courier',monospace; + font-size: 18px; max-width: 100%; white-space: nowrap; overflow: hidden; @@ -295,7 +295,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` } .c4 { - font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; + font-family: 'Roboto Mono','Consolas','Menlo','Courier',monospace; }
theme.eui.euiCodeFontFamily}; `; const MessageAndCulpritCell = euiStyled.div` @@ -40,13 +33,13 @@ const ErrorLink = euiStyled(ErrorOverviewLink)` `; const MessageLink = euiStyled(ErrorDetailLink)` - font-family: ${fontFamilyCode}; - font-size: ${fontSizes.large}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; + font-size: ${({ theme }) => theme.eui.euiFontSizeM}; ${truncate('100%')}; `; const Culprit = euiStyled.div` - font-family: ${fontFamilyCode}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; `; type ErrorGroupItem = APIReturnType<'GET /api/apm/services/{serviceName}/errors'>['errorGroups'][0]; @@ -86,7 +79,7 @@ function ErrorGroupList({ items, serviceName }: Props) { ), field: 'groupId', sortable: false, - width: px(unit * 6), + width: unit * 6, render: (groupId: string) => { return ( diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx index 886ef8412f35b..4c622758e6c8b 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx @@ -18,7 +18,7 @@ import { useTrackPageview } from '../../../../../observability/public'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { ErrorDistribution } from '../ErrorGroupDetails/Distribution'; +import { ErrorDistribution } from '../error_group_details/Distribution'; import { ErrorGroupList } from './List'; interface ErrorGroupOverviewProps { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index e4a0ef3ef09f1..cac94885511c1 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -18,8 +18,8 @@ import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useUpgradeAssistantHref } from '../../shared/Links/kibana'; import { SearchBar } from '../../shared/search_bar'; import { NoServicesMessage } from './no_services_message'; -import { ServiceList } from './ServiceList'; -import { MLCallout } from './ServiceList/MLCallout'; +import { ServiceList } from './service_list'; +import { MLCallout } from './service_list/MLCallout'; const initialData = { items: [], diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/HealthBadge.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/service_list/HealthBadge.tsx diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/MLCallout.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/service_list/MLCallout.tsx diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/ServiceListMetric.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/ServiceListMetric.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/ServiceListMetric.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/service_list/ServiceListMetric.tsx diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/service_api_mock_data.ts b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/__fixtures__/service_api_mock_data.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/__fixtures__/service_api_mock_data.ts rename to x-pack/plugins/apm/public/components/app/service_inventory/service_list/__fixtures__/service_api_mock_data.ts diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx index 6c40639594adf..b4644068fd782 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx @@ -5,31 +5,35 @@ * 2.0. */ -import { EuiFlexItem, EuiFlexGroup, EuiToolTip } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiText, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { orderBy } from 'lodash'; import React, { useMemo } from 'react'; import { ValuesType } from 'utility-types'; -import { orderBy } from 'lodash'; -import { EuiIcon } from '@elastic/eui'; -import { EuiText } from '@elastic/eui'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; +import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; +import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../../../common/transaction_types'; -import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { - asPercent, asMillisecondDuration, + asPercent, asTransactionRate, } from '../../../../../common/utils/formatters'; -import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; -import { fontSizes, px, truncate, unit } from '../../../../style/variables'; -import { ManagedTable, ITableColumn } from '../../../shared/ManagedTable'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; +import { truncate, unit } from '../../../../utils/style'; +import { AgentIcon } from '../../../shared/agent_icon'; import { EnvironmentBadge } from '../../../shared/EnvironmentBadge'; import { ServiceOrTransactionsOverviewLink } from '../../../shared/Links/apm/service_transactions_overview_link'; -import { AgentIcon } from '../../../shared/agent_icon'; +import { ITableColumn, ManagedTable } from '../../../shared/managed_table'; import { HealthBadge } from './HealthBadge'; import { ServiceListMetric } from './ServiceListMetric'; @@ -47,7 +51,7 @@ function formatString(value?: string | null) { } const AppLink = euiStyled(ServiceOrTransactionsOverviewLink)` - font-size: ${fontSizes.large}; + font-size: ${({ theme }) => theme.eui.euiFontSizeM} ${truncate('100%')}; `; @@ -80,7 +84,7 @@ export function getServiceColumns({ name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { defaultMessage: 'Health', }), - width: px(unit * 6), + width: unit * 6, sortable: true, render: (_, { healthStatus }) => { return ( @@ -130,7 +134,7 @@ export function getServiceColumns({ name: i18n.translate('xpack.apm.servicesTable.environmentColumnLabel', { defaultMessage: 'Environment', }), - width: px(unit * 10), + width: unit * 10, sortable: true, render: (_, { environments }) => ( @@ -144,7 +148,7 @@ export function getServiceColumns({ 'xpack.apm.servicesTable.transactionColumnLabel', { defaultMessage: 'Transaction type' } ), - width: px(unit * 10), + width: unit * 10, sortable: true, }, ] @@ -164,7 +168,7 @@ export function getServiceColumns({ /> ), align: 'left', - width: px(unit * 10), + width: unit * 10, }, { field: 'transactionsPerMinute', @@ -181,7 +185,7 @@ export function getServiceColumns({ /> ), align: 'left', - width: px(unit * 10), + width: unit * 10, }, { field: 'transactionErrorRate', @@ -204,7 +208,7 @@ export function getServiceColumns({ ); }, align: 'left', - width: px(unit * 10), + width: unit * 10, }, ]; } diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/service_list.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/service_list/service_list.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx index fe3922060533a..6b7626514d03f 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx @@ -17,7 +17,7 @@ import { Popover } from '.'; import exampleGroupedConnectionsData from '../__stories__/example_grouped_connections.json'; export default { - title: 'app/service_map/Popover', + title: 'app/ServiceMap/Popover', component: Popover, decorators: [ (Story: ComponentType) => { diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/ServiceStatsFetcher.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/ServiceStatsFetcher.tsx index a71f299ab296c..3155a65b06aca 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/ServiceStatsFetcher.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/ServiceStatsFetcher.tsx @@ -18,7 +18,7 @@ import { ServiceNodeStats } from '../../../../../common/service_map'; import { ServiceStatsList } from './ServiceStatsList'; import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { AnomalyDetection } from './AnomalyDetection'; +import { AnomalyDetection } from './anomaly_detection'; import { ServiceAnomalyStats } from '../../../../../common/anomaly_detection'; interface ServiceStatsFetcherProps { diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/AnomalyDetection.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/anomaly_detection.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/service_map/Popover/AnomalyDetection.tsx rename to x-pack/plugins/apm/public/components/app/service_map/Popover/anomaly_detection.tsx index c98116a69da66..1ceb90ff838ad 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/AnomalyDetection.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/anomaly_detection.tsx @@ -5,30 +5,29 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; -import React from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiTitle, - EuiIconTip, EuiHealth, + EuiIconTip, + EuiTitle, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; +import { + getSeverity, + ServiceAnomalyStats, +} from '../../../../../common/anomaly_detection'; import { getServiceHealthStatus, getServiceHealthStatusColor, } from '../../../../../common/service_health_status'; +import { TRANSACTION_REQUEST } from '../../../../../common/transaction_types'; +import { asDuration, asInteger } from '../../../../../common/utils/formatters'; import { useTheme } from '../../../../hooks/use_theme'; -import { fontSize, px } from '../../../../style/variables'; -import { asInteger, asDuration } from '../../../../../common/utils/formatters'; import { MLSingleMetricLink } from '../../../shared/Links/MachineLearningLinks/MLSingleMetricLink'; import { popoverWidth } from '../cytoscape_options'; -import { TRANSACTION_REQUEST } from '../../../../../common/transaction_types'; -import { - getSeverity, - ServiceAnomalyStats, -} from '../../../../../common/anomaly_detection'; const HealthStatusTitle = euiStyled(EuiTitle)` display: inline; @@ -47,8 +46,8 @@ const SubduedText = euiStyled.span` const EnableText = euiStyled.section` color: ${({ theme }) => theme.eui.euiTextSubduedColor}; line-height: 1.4; - font-size: ${fontSize}; - width: ${px(popoverWidth)}; + font-size: ${({ theme }) => theme.eui.euiFontSizeS}; + width: ${popoverWidth}px; `; export const ContentLine = euiStyled.section` diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx index 83f0a3ea7e4b9..a8f004a7295d9 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx @@ -10,7 +10,7 @@ import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_rea import { ServiceStatsList } from './ServiceStatsList'; export default { - title: 'app/service_map/Popover/ServiceStatsList', + title: 'app/ServiceMap/Popover/ServiceStatsList', component: ServiceStatsList, decorators: [ (Story: ComponentType) => ( diff --git a/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx index dbab10d7b93b6..8bc0d7239e9c5 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx @@ -12,7 +12,7 @@ import { Cytoscape } from '../Cytoscape'; import { Centerer } from './centerer'; export default { - title: 'app/service_map/Cytoscape', + title: 'app/ServiceMap/Cytoscape', component: Cytoscape, decorators: [ (Story: ComponentType) => ( diff --git a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx index 84351d5716edb..45de632a152d4 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx @@ -25,7 +25,7 @@ import exampleResponseOpbeansBeats from './example_response_opbeans_beats.json'; import exampleResponseTodo from './example_response_todo.json'; import { generateServiceMapElements } from './generate_service_map_elements'; -const STORYBOOK_PATH = 'app/service_map/Cytoscape/Example data'; +const STORYBOOK_PATH = 'app/ServiceMap/Example data'; const SESSION_STORAGE_KEY = `${STORYBOOK_PATH}/pre-loaded map`; function getSessionJson() { @@ -40,7 +40,7 @@ function getHeight() { } export default { - title: 'app/service_map/Cytoscape/Example data', + title: 'app/ServiceMap/Example data', component: Cytoscape, decorators: [ (Story: ComponentType) => ( diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index a147528d42cae..07afcbc9c4682 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -20,12 +20,12 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { px, truncate, unit } from '../../../style/variables'; +import { truncate, unit } from '../../../utils/style'; import { MetricsChart } from '../../shared/charts/metrics_chart'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; @@ -36,7 +36,7 @@ const INITIAL_DATA = { const Truncate = euiStyled.span` display: block; - ${truncate(px(unit * 12))} + ${truncate(unit * 12)} `; interface ServiceNodeMetricsProps { diff --git a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx index 1a432f90f1e3a..58541e2c5501b 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx @@ -19,16 +19,16 @@ import { } from '../../../../common/utils/formatters'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { px, truncate, unit } from '../../../style/variables'; +import { truncate, unit } from '../../../utils/style'; import { ServiceNodeMetricOverviewLink } from '../../shared/Links/apm/ServiceNodeMetricOverviewLink'; -import { ITableColumn, ManagedTable } from '../../shared/ManagedTable'; +import { ITableColumn, ManagedTable } from '../../shared/managed_table'; const INITIAL_PAGE_SIZE = 25; const INITIAL_SORT_FIELD = 'cpu'; const INITIAL_SORT_DIRECTION = 'desc'; const ServiceNodeName = euiStyled.div` - ${truncate(px(8 * unit))} + ${truncate(8 * unit)} `; interface ServiceNodeOverviewProps { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index b1a4d5ca5fda7..0067558865bd6 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -15,19 +15,19 @@ import { import { i18n } from '@kbn/i18n'; import { keyBy } from 'lodash'; import React from 'react'; -import { offsetPreviousPeriodCoordinates } from '../../../../../common/utils/offset_previous_period_coordinate'; -import { Coordinate } from '../../../../../typings/timeseries'; import { getNextEnvironmentUrlParam } from '../../../../../common/environment_filter_values'; import { asMillisecondDuration, asPercent, asTransactionRate, } from '../../../../../common/utils/formatters'; +import { offsetPreviousPeriodCoordinates } from '../../../../../common/utils/offset_previous_period_coordinate'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceDependencyItem } from '../../../../../server/lib/services/get_service_dependencies'; +import { Coordinate } from '../../../../../typings/timeseries'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { px, unit } from '../../../../style/variables'; +import { unit } from '../../../../utils/style'; import { AgentIcon } from '../../../shared/agent_icon'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; @@ -156,7 +156,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { defaultMessage: 'Latency (avg.)', } ), - width: px(unit * 10), + width: `${unit * 10}px`, render: (_, { latency }) => { return ( { return ( { return ( { return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx index 4ad83f7d87426..b458f6147b3f1 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx @@ -9,12 +9,12 @@ import { EuiBasicTableColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { asInteger } from '../../../../../common/utils/formatters'; -import { px, unit } from '../../../../style/variables'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; +import { unit } from '../../../../utils/style'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -import { APIReturnType } from '../../../../services/rest/createCallApmApi'; type ErrorGroupMainStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/main_statistics'>; type ErrorGroupDetailedStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/detailed_statistics'>; @@ -61,7 +61,7 @@ export function getColumns({ render: (_, { last_seen: lastSeen }) => { return ; }, - width: px(unit * 9), + width: `${unit * 9}px`, }, { field: 'occurrences', @@ -71,7 +71,7 @@ export function getColumns({ defaultMessage: 'Occurrences', } ), - width: px(unit * 12), + width: `${unit * 12}px`, render: (_, { occurrences, group_id: errorGroupId }) => { const currentPeriodTimeseries = errorGroupDetailedStatistics?.currentPeriod?.[errorGroupId] diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx index a92efff103910..f9600b9d7f418 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx @@ -25,14 +25,14 @@ import { asTransactionRate, } from '../../../../../common/utils/formatters'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { px, unit } from '../../../../style/variables'; +import { unit } from '../../../../utils/style'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink'; import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { getLatencyColumnLabel } from '../get_latency_column_label'; -import { InstanceActionsMenu } from './instance_actions_menu'; import { MainStatsServiceInstanceItem } from '../service_overview_instances_chart_and_table'; +import { InstanceActionsMenu } from './instance_actions_menu'; type ServiceInstanceDetailedStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; @@ -98,7 +98,7 @@ export function getColumns({ { field: 'latency', name: getLatencyColumnLabel(latencyAggregationType), - width: px(unit * 10), + width: `${unit * 10}px`, render: (_, { serviceNodeName, latency }) => { const currentPeriodTimestamp = detailedStatsData?.currentPeriod?.[serviceNodeName]?.latency; @@ -123,7 +123,7 @@ export function getColumns({ 'xpack.apm.serviceOverview.instancesTableColumnThroughput', { defaultMessage: 'Throughput' } ), - width: px(unit * 10), + width: `${unit * 10}px`, render: (_, { serviceNodeName, throughput }) => { const currentPeriodTimestamp = detailedStatsData?.currentPeriod?.[serviceNodeName]?.throughput; @@ -149,7 +149,7 @@ export function getColumns({ 'xpack.apm.serviceOverview.instancesTableColumnErrorRate', { defaultMessage: 'Error rate' } ), - width: px(unit * 8), + width: `${unit * 8}px`, render: (_, { serviceNodeName, errorRate }) => { const currentPeriodTimestamp = detailedStatsData?.currentPeriod?.[serviceNodeName]?.errorRate; @@ -175,7 +175,7 @@ export function getColumns({ 'xpack.apm.serviceOverview.instancesTableColumnCpuUsage', { defaultMessage: 'CPU usage (avg.)' } ), - width: px(unit * 8), + width: `${unit * 8}px`, render: (_, { serviceNodeName, cpuUsage }) => { const currentPeriodTimestamp = detailedStatsData?.currentPeriod?.[serviceNodeName]?.cpuUsage; @@ -201,7 +201,7 @@ export function getColumns({ 'xpack.apm.serviceOverview.instancesTableColumnMemoryUsage', { defaultMessage: 'Memory usage (avg.)' } ), - width: px(unit * 9), + width: `${unit * 9}px`, render: (_, { serviceNodeName, memoryUsage }) => { const currentPeriodTimestamp = detailedStatsData?.currentPeriod?.[serviceNodeName]?.memoryUsage; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx index f03c2b2fc9091..a2aaa61e8a661 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx @@ -20,8 +20,7 @@ import { SERVICE_NODE_NAME } from '../../../../../../common/elasticsearch_fieldn import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; -import { px } from '../../../../../style/variables'; -import { pushNewItemToKueryBar } from '../../../../shared/KueryBar/utils'; +import { pushNewItemToKueryBar } from '../../../../shared/kuery_bar/utils'; import { useMetricOverviewHref } from '../../../../shared/Links/apm/MetricOverviewLink'; import { useServiceNodeMetricOverviewHref } from '../../../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { useInstanceDetailsFetcher } from '../use_instance_details_fetcher'; @@ -33,7 +32,7 @@ interface Props { onClose: () => void; } -const POPOVER_WIDTH = px(305); +const POPOVER_WIDTH = '305px'; export function InstanceActionsMenu({ serviceName, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx index 35cecc49e293b..0c77051bea293 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx @@ -28,10 +28,9 @@ import { useUrlParams } from '../../../../context/url_params_context/use_url_par import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { pct } from '../../../../style/variables'; import { getAgentIcon } from '../../../shared/agent_icon/get_agent_icon'; import { KeyValueFilterList } from '../../../shared/key_value_filter_list'; -import { pushNewItemToKueryBar } from '../../../shared/KueryBar/utils'; +import { pushNewItemToKueryBar } from '../../../shared/kuery_bar/utils'; import { getCloudIcon, getContainerIcon } from '../../../shared/service_icons'; import { useInstanceDetailsFetcher } from './use_instance_details_fetcher'; @@ -78,7 +77,7 @@ export function InstanceDetails({ serviceName, serviceNodeName }: Props) { status === FETCH_STATUS.NOT_INITIATED ) { return ( -
+
); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index 9ac1c7d64d8b2..2e46e23ccaa42 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -16,7 +16,7 @@ import { asTransactionRate, } from '../../../../../common/utils/formatters'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { px, unit } from '../../../../style/variables'; +import { unit } from '../../../../utils/style'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; @@ -71,7 +71,7 @@ export function getColumns({ field: 'latency', sortable: true, name: getLatencyColumnLabel(latencyAggregationType), - width: px(unit * 10), + width: `${unit * 10}px`, render: (_, { latency, name }) => { const currentTimeseries = transactionGroupDetailedStatistics?.currentPeriod?.[name]?.latency; @@ -97,7 +97,7 @@ export function getColumns({ 'xpack.apm.serviceOverview.transactionsTableColumnThroughput', { defaultMessage: 'Throughput' } ), - width: px(unit * 10), + width: `${unit * 10}px`, render: (_, { throughput, name }) => { const currentTimeseries = transactionGroupDetailedStatistics?.currentPeriod?.[name]?.throughput; @@ -124,7 +124,7 @@ export function getColumns({ 'xpack.apm.serviceOverview.transactionsTableColumnErrorRate', { defaultMessage: 'Error rate' } ), - width: px(unit * 8), + width: `${unit * 8}px`, render: (_, { errorRate, name }) => { const currentTimeseries = transactionGroupDetailedStatistics?.currentPeriod?.[name]?.errorRate; @@ -150,7 +150,7 @@ export function getColumns({ 'xpack.apm.serviceOverview.transactionsTableColumnImpact', { defaultMessage: 'Impact' } ), - width: px(unit * 5), + width: `${unit * 5}px`, render: (_, { name }) => { const currentImpact = transactionGroupDetailedStatistics?.currentPeriod?.[name]?.impact ?? diff --git a/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx b/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx index 1adf58d0394c6..82ac3a04f63f1 100644 --- a/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx +++ b/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx @@ -13,16 +13,16 @@ import { Settings, TooltipInfo, } from '@elastic/charts'; -import { EuiInMemoryTable } from '@elastic/eui'; -import { EuiFieldText } from '@elastic/eui'; -import { EuiToolTip } from '@elastic/eui'; import { EuiCheckbox, + EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiInMemoryTable, euiPaletteForTemperature, EuiText, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { find, sumBy } from 'lodash'; @@ -44,7 +44,7 @@ import { } from '../../../../common/utils/formatters'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useTheme } from '../../../hooks/use_theme'; -import { px, unit } from '../../../style/variables'; +import { unit } from '../../../utils/style'; const colors = euiPaletteForTemperature(100).slice(50, 85); @@ -335,7 +335,7 @@ export function ServiceProfilingFlamegraph({ /> - + formatValue(item.value, valueUnit), - width: px(unit * 6), + width: `${unit * 6}px`, }, ]} /> diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index bf60463255d64..d280b36a603ba 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -11,7 +11,7 @@ import { useUrlParams } from '../../../context/url_params_context/use_url_params import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { SearchBar } from '../../shared/search_bar'; -import { TraceList } from './TraceList'; +import { TraceList } from './trace_list'; type TracesAPIResponse = APIReturnType<'GET /api/apm/traces'>; const DEFAULT_RESPONSE: TracesAPIResponse = { diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/TraceList.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/app/trace_overview/TraceList.tsx rename to x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx index 774333c35b479..f1c8df553abf7 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/TraceList.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx @@ -13,18 +13,18 @@ import { asMillisecondDuration, asTransactionRate, } from '../../../../common/utils/formatters'; -import { fontSizes, truncate } from '../../../style/variables'; +import { APIReturnType } from '../../../services/rest/createCallApmApi'; +import { truncate } from '../../../utils/style'; import { EmptyMessage } from '../../shared/EmptyMessage'; import { ImpactBar } from '../../shared/ImpactBar'; -import { ITableColumn, ManagedTable } from '../../shared/ManagedTable'; -import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt'; import { TransactionDetailLink } from '../../shared/Links/apm/transaction_detail_link'; -import { APIReturnType } from '../../../services/rest/createCallApmApi'; +import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt'; +import { ITableColumn, ManagedTable } from '../../shared/managed_table'; type TraceGroup = APIReturnType<'GET /api/apm/traces'>['items'][0]; const StyledTransactionLink = euiStyled(TransactionDetailLink)` - font-size: ${fontSizes.large}; + font-size: ${({ theme }) => theme.eui.euiFontSizeM}; ${truncate('100%')}; `; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx index c7dae6ce3d1d4..f59b3ddab7c05 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx @@ -29,7 +29,7 @@ import type { IUrlParams } from '../../../../context/url_params_context/types'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { unit } from '../../../../style/variables'; +import { unit } from '../../../../utils/style'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; import { CustomTooltip } from './custom_tooltip'; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx deleted file mode 100644 index 3584309ebb20c..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx +++ /dev/null @@ -1,51 +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, { Fragment } from 'react'; -import { EuiSpacer, EuiTitle } from '@elastic/eui'; -import { euiStyled } from '../../../../../../../../../../../src/plugins/kibana_react/common'; -import { - borderRadius, - fontFamilyCode, - fontSize, - px, - unit, - units, -} from '../../../../../../../style/variables'; -import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; - -const ContextUrl = euiStyled.div` - padding: ${px(units.half)} ${px(unit)}; - background: ${({ theme }) => theme.eui.euiColorLightestShade}; - border-radius: ${borderRadius}; - border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; - font-family: ${fontFamilyCode}; - font-size: ${fontSize}; -`; - -interface Props { - httpContext: NonNullable['http']; -} - -export function HttpContext({ httpContext }: Props) { - const url = httpContext?.url?.original; - - if (!url) { - return null; - } - - return ( - - -

HTTP URL

-
- - {url} - -
- ); -} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx index 3cac05ba2d96a..1e13e224a511a 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx @@ -19,7 +19,7 @@ import { HeightRetainer } from '../../shared/HeightRetainer'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; import { TransactionDistribution } from './Distribution'; import { useWaterfallFetcher } from './use_waterfall_fetcher'; -import { WaterfallWithSummmary } from './WaterfallWithSummmary'; +import { WaterfallWithSummary } from './waterfall_with_summary'; interface Sample { traceId: string; @@ -107,7 +107,7 @@ export function TransactionDetails() { - ; @@ -39,7 +39,7 @@ interface Props { traceSamples: DistributionBucket['samples']; } -export function WaterfallWithSummmary({ +export function WaterfallWithSummary({ urlParams, waterfall, exceedsMax, diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.test.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks.test.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.test.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks.test.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.test.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks.test.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.test.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks.test.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/index.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/index.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/index.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Marks/index.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/FlyoutTopLevelProperties.tsx similarity index 97% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/FlyoutTopLevelProperties.tsx index 5f4ed551597ed..0ad0fe872a840 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/FlyoutTopLevelProperties.tsx @@ -16,7 +16,7 @@ import { import { Transaction } from '../../../../../../../typings/es_schemas/ui/transaction'; import { ServiceOrTransactionsOverviewLink } from '../../../../../shared/Links/apm/service_transactions_overview_link'; import { TransactionDetailLink } from '../../../../../shared/Links/apm/transaction_detail_link'; -import { StickyProperties } from '../../../../../shared/StickyProperties'; +import { StickyProperties } from '../../../../../shared/sticky_properties'; interface Props { transaction?: Transaction; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/ResponsiveFlyout.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/ResponsiveFlyout.tsx diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/TransactionFlyout/index.tsx similarity index 98% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/TransactionFlyout/index.tsx index 680edf880a70e..6468e6ed1e2c8 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/TransactionFlyout/index.tsx @@ -18,7 +18,7 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/transaction'; -import { TransactionActionMenu } from '../../../../../../shared/TransactionActionMenu/TransactionActionMenu'; +import { TransactionActionMenu } from '../../../../../../shared/transaction_action_menu/TransactionActionMenu'; import { TransactionSummary } from '../../../../../../shared/Summary/TransactionSummary'; import { FlyoutTopLevelProperties } from '../FlyoutTopLevelProperties'; import { ResponsiveFlyout } from '../ResponsiveFlyout'; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallFlyout.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/WaterfallFlyout.tsx similarity index 97% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallFlyout.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/WaterfallFlyout.tsx index fddf4fcb4efc9..ec6d550affb91 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallFlyout.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/WaterfallFlyout.tsx @@ -8,7 +8,7 @@ import { History } from 'history'; import React from 'react'; import { useHistory } from 'react-router-dom'; -import { SpanFlyout } from './SpanFlyout'; +import { SpanFlyout } from './span_flyout'; import { TransactionFlyout } from './TransactionFlyout'; import { IWaterfall } from './waterfall_helpers/waterfall_helpers'; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/accordion_waterfall.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/accordion_waterfall.tsx similarity index 98% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/accordion_waterfall.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/accordion_waterfall.tsx index b0721791081fa..1935d373caf79 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/accordion_waterfall.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/accordion_waterfall.tsx @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import React, { useState } from 'react'; import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common'; import { Margins } from '../../../../../shared/charts/Timeline'; -import { WaterfallItem } from './WaterfallItem'; +import { WaterfallItem } from './waterfall_item'; import { IWaterfall, IWaterfallSpanOrTransaction, diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/StickySpanProperties.tsx similarity index 97% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/StickySpanProperties.tsx index 60d328c98a4b7..9e1174818c43b 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/StickySpanProperties.tsx @@ -19,7 +19,7 @@ import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/transaction'; import { ServiceOrTransactionsOverviewLink } from '../../../../../../shared/Links/apm/service_transactions_overview_link'; import { TransactionDetailLink } from '../../../../../../shared/Links/apm/transaction_detail_link'; -import { StickyProperties } from '../../../../../../shared/StickyProperties'; +import { StickyProperties } from '../../../../../../shared/sticky_properties'; interface Props { span: Span; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/database_context.tsx similarity index 76% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/database_context.tsx index 6fd6873cf565e..4279205743442 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/database_context.tsx @@ -9,39 +9,35 @@ import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { tint } from 'polished'; import React, { Fragment } from 'react'; +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'; import xcode from 'react-syntax-highlighter/dist/cjs/styles/hljs/xcode'; -import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; import { euiStyled } from '../../../../../../../../../../../src/plugins/kibana_react/common'; import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; -import { - borderRadius, - fontFamilyCode, - fontSize, - px, - unit, - units, -} from '../../../../../../../style/variables'; -import { TruncateHeightSection } from './TruncateHeightSection'; +import { useTheme } from '../../../../../../../hooks/use_theme'; +import { TruncateHeightSection } from './truncate_height_section'; SyntaxHighlighter.registerLanguage('sql', sql); const DatabaseStatement = euiStyled.div` - padding: ${px(units.half)} ${px(unit)}; + padding: ${({ theme }) => + `${theme.eui.paddingSizes.s} ${theme.eui.paddingSizes.m}`}; background: ${({ theme }) => tint(0.9, theme.eui.euiColorWarning)}; - border-radius: ${borderRadius}; + border-radius: ${({ theme }) => theme.eui.euiBorderRadiusSmall}; border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; - font-family: ${fontFamilyCode}; - font-size: ${fontSize}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; + font-size: ${({ theme }) => theme.eui.euiFontSizeS}; `; -const dbSyntaxLineHeight = unit * 1.5; - interface Props { dbContext?: NonNullable['db']; } export function DatabaseContext({ dbContext }: Props) { + const theme = useTheme(); + const dbSyntaxLineHeight = theme.eui.euiSizeL; + const previewHeight = 240; // 10 * dbSyntaxLineHeight + if (!dbContext || !dbContext.statement) { return null; } @@ -64,7 +60,7 @@ export function DatabaseContext({ dbContext }: Props) { - + theme.eui.euiSizeXS}; `; const HttpInfoContainer = euiStyled('div')` - margin-right: ${px(units.quarter)}; + margin-right: ${({ theme }) => theme.eui.euiSizeXS}; `; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/truncate_height_section.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/truncate_height_section.tsx index 181fcb91ba3e6..4c845e16348e7 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/span_flyout/truncate_height_section.tsx @@ -9,10 +9,9 @@ import { EuiIcon, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { Fragment, ReactNode, useEffect, useRef, useState } from 'react'; import { euiStyled } from '../../../../../../../../../../../src/plugins/kibana_react/common'; -import { px, units } from '../../../../../../../style/variables'; const ToggleButtonContainer = euiStyled.div` - margin-top: ${px(units.half)}; + margin-top: ${({ theme }) => theme.eui.euiSizeS} user-select: none; `; @@ -41,7 +40,7 @@ export function TruncateHeightSection({ children, previewHeight }: Props) { ref={contentContainerEl} style={{ overflow: 'hidden', - maxHeight: isOpen ? 'initial' : px(previewHeight), + maxHeight: isOpen ? 'initial' : previewHeight, }} > {children} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/sync_badge.stories.tsx similarity index 91% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.stories.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/sync_badge.stories.tsx index 8275aa1e5f156..6b52fbe2d784a 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/sync_badge.stories.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { SyncBadge, SyncBadgeProps } from './SyncBadge'; +import { SyncBadge, SyncBadgeProps } from './sync_badge'; export default { title: 'app/TransactionDetails/SyncBadge', diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/sync_badge.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/sync_badge.tsx index b9e4c6951fa06..cfc369fa12a26 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/sync_badge.tsx @@ -9,11 +9,10 @@ import { EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common'; -import { px, units } from '../../../../../../style/variables'; const SpanBadge = euiStyled(EuiBadge)` display: inline-block; - margin-right: ${px(units.quarter)}; + margin-right: ${({ theme }) => theme.eui.euiSizeXS}; `; export interface SyncBadgeProps { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/spans.json b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/mock_responses/spans.json similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/spans.json rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/mock_responses/spans.json diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/transaction.json b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/mock_responses/transaction.json similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/transaction.json rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/mock_responses/transaction.json diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.test.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.test.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_item.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_item.tsx index f3e1547c4b8b8..a2c2c869a079c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_item.tsx @@ -5,20 +5,18 @@ * 2.0. */ -import React, { ReactNode } from 'react'; - import { EuiIcon, EuiText, EuiTitle, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import React, { ReactNode } from 'react'; import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common'; -import { asDuration } from '../../../../../../../common/utils/formatters'; import { isRumAgentName } from '../../../../../../../common/agent_name'; -import { px, unit, units } from '../../../../../../style/variables'; -import { ErrorCount } from '../../ErrorCount'; -import { IWaterfallSpanOrTransaction } from './waterfall_helpers/waterfall_helpers'; -import { ErrorOverviewLink } from '../../../../../shared/Links/apm/ErrorOverviewLink'; import { TRACE_ID } from '../../../../../../../common/elasticsearch_fieldnames'; -import { SyncBadge } from './SyncBadge'; +import { asDuration } from '../../../../../../../common/utils/formatters'; import { Margins } from '../../../../../shared/charts/Timeline'; +import { ErrorOverviewLink } from '../../../../../shared/Links/apm/ErrorOverviewLink'; +import { ErrorCount } from '../../ErrorCount'; +import { SyncBadge } from './sync_badge'; +import { IWaterfallSpanOrTransaction } from './waterfall_helpers/waterfall_helpers'; type ItemType = 'transaction' | 'span' | 'error'; @@ -37,10 +35,10 @@ const Container = euiStyled.div` position: relative; display: block; user-select: none; - padding-top: ${px(units.half)}; - padding-bottom: ${px(units.plus)}; - margin-right: ${(props) => px(props.timelineMargins.right)}; - margin-left: ${(props) => px(props.timelineMargins.left)}; + padding-top: ${({ theme }) => theme.eui.paddingSizes.s}; + padding-bottom: ${({ theme }) => theme.eui.euiSizeM}; + margin-right: ${(props) => props.timelineMargins.right}px; + margin-left: ${(props) => props.timelineMargins.left}px; background-color: ${({ isSelected, theme }) => isSelected ? theme.eui.euiColorLightestShade : 'initial'}; cursor: pointer; @@ -53,7 +51,7 @@ const Container = euiStyled.div` const ItemBar = euiStyled.div` box-sizing: border-box; position: relative; - height: ${px(unit)}; + height: ${({ theme }) => theme.eui.euiSize}; min-width: 2px; background-color: ${(props) => props.color}; `; @@ -63,11 +61,11 @@ const ItemText = euiStyled.span` right: 0; display: flex; align-items: center; - height: ${px(units.plus)}; + height: ${({ theme }) => theme.eui.euiSizeL}; /* add margin to all direct descendants */ & > * { - margin-right: ${px(units.half)}; + margin-right: ${({ theme }) => theme.eui.euiSizeS}; white-space: nowrap; } `; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/WaterfallLegends.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallLegends.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/WaterfallLegends.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallLegends.tsx index d6f6de2d9179b..aaa9b3e45ee22 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/WaterfallLegends.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallLegends.tsx @@ -10,7 +10,7 @@ import { EuiFlexItem } from '@elastic/eui'; import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { Legend } from '../../../../shared/charts/Legend'; +import { Legend } from '../../../../shared/charts/Timeline/legend'; import { IWaterfallLegend, WaterfallLegendType, diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/index.tsx rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts rename to x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 4f0f92cafa5e7..041c12822357c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -24,7 +24,7 @@ import { useUrlParams } from '../../../context/url_params_context/use_url_params import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; -import { TransactionList } from './TransactionList'; +import { TransactionList } from './transaction_list'; import { useRedirect } from './useRedirect'; import { useTransactionListFetcher } from './use_transaction_list'; diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/index.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/index.tsx rename to x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/index.tsx index 795a6e66f70a4..dc3bf924d6fdc 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/index.tsx @@ -15,9 +15,9 @@ import { asMillisecondDuration, asTransactionRate, } from '../../../../../common/utils/formatters'; -import { fontFamilyCode, truncate } from '../../../../style/variables'; +import { truncate } from '../../../../utils/style'; import { ImpactBar } from '../../../shared/ImpactBar'; -import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable'; +import { ITableColumn, ManagedTable } from '../../../shared/managed_table'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; import { EmptyMessage } from '../../../shared/EmptyMessage'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; @@ -27,7 +27,7 @@ type TransactionGroup = APIReturnType<'GET /api/apm/services/{serviceName}/trans // Truncate both the link and the child span (the tooltip anchor.) The link so // it doesn't overflow, and the anchor so we get the ellipsis. const TransactionNameLink = euiStyled(TransactionDetailLink)` - font-family: ${fontFamilyCode}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; white-space: nowrap; ${truncate('100%')}; diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/transaction_list.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx rename to x-pack/plugins/apm/public/components/app/transaction_overview/transaction_list/transaction_list.stories.tsx diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_custom_assets_extension.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_custom_assets_extension.tsx new file mode 100644 index 0000000000000..40b902b7729d0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_custom_assets_extension.tsx @@ -0,0 +1,36 @@ +/* + * 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'; + +import { + CustomAssetsAccordionProps, + CustomAssetsAccordion, +} from '../../../../fleet/public'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { ApmPluginStartDeps } from '../../plugin'; + +export function ApmCustomAssetsExtension() { + const { http } = useKibana().services; + const basePath = http?.basePath.get(); + + const views: CustomAssetsAccordionProps['views'] = [ + { + name: i18n.translate('xpack.apm.fleetIntegration.assets.name', { + defaultMessage: 'Services', + }), + url: `${basePath}/app/apm`, + description: i18n.translate( + 'xpack.apm.fleetIntegration.assets.description', + { defaultMessage: 'View application traces and service maps in APM' } + ), + }, + ]; + + return ; +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_enrollment_flyout_extension.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_enrollment_flyout_extension.tsx new file mode 100644 index 0000000000000..6b3ba6a8f05db --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_enrollment_flyout_extension.tsx @@ -0,0 +1,58 @@ +/* + * 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 { EuiButton, EuiText, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { AgentEnrollmentFlyoutFinalStepExtension } from '../../../../fleet/public'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { ApmPluginStartDeps } from '../../plugin'; + +function StepComponent() { + const { http } = useKibana().services; + const installApmAgentLink = http?.basePath.prepend('/app/home#/tutorial/apm'); + + return ( + <> + +

+ {i18n.translate( + 'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentDescription', + { + defaultMessage: + 'After the agent starts, you can install APM agents on your hosts to collect data from your applications and services.', + } + )} +

+
+ + + + {i18n.translate( + 'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText', + { defaultMessage: 'Install APM Agent' } + )} + + + ); +} + +export function getApmEnrollmentFlyoutData(): Pick< + AgentEnrollmentFlyoutFinalStepExtension, + 'title' | 'Component' +> { + return { + title: i18n.translate( + 'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentTitle', + { + defaultMessage: 'Install APM Agent', + } + ), + Component: StepComponent, + }; +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/index.ts b/x-pack/plugins/apm/public/components/fleet_integration/index.ts new file mode 100644 index 0000000000000..a676a13c3741b --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/index.ts @@ -0,0 +1,9 @@ +/* + * 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 * from './apm_enrollment_flyout_extension'; +export * from './lazy_apm_custom_assets_extension'; diff --git a/x-pack/plugins/apm/public/components/fleet_integration/lazy_apm_custom_assets_extension.tsx b/x-pack/plugins/apm/public/components/fleet_integration/lazy_apm_custom_assets_extension.tsx new file mode 100644 index 0000000000000..04f5042f3103f --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/lazy_apm_custom_assets_extension.tsx @@ -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 { lazy } from 'react'; + +export const LazyApmCustomAssetsExtension = lazy(async () => { + const { ApmCustomAssetsExtension } = await import( + './apm_custom_assets_extension' + ); + + return { + default: ApmCustomAssetsExtension, + }; +}); diff --git a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx index 5214489c9142b..e00b7893b548e 100644 --- a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx +++ b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx @@ -11,14 +11,14 @@ import { RouteComponentProps } from 'react-router-dom'; import { getServiceNodeName } from '../../../common/service_nodes'; import { APMRouteDefinition } from '../../application/routes'; import { toQuery } from '../shared/Links/url_helpers'; -import { ErrorGroupDetails } from '../app/ErrorGroupDetails'; +import { ErrorGroupDetails } from '../app/error_group_details'; import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; import { ServiceNodeMetrics } from '../app/service_node_metrics'; import { SettingsTemplate } from './templates/settings_template'; -import { AgentConfigurations } from '../app/Settings/AgentConfigurations'; +import { AgentConfigurations } from '../app/Settings/agent_configurations'; import { AnomalyDetection } from '../app/Settings/anomaly_detection'; import { ApmIndices } from '../app/Settings/ApmIndices'; -import { CustomizeUI } from '../app/Settings/CustomizeUI'; +import { CustomizeUI } from '../app/Settings/customize_ui'; import { Schema } from '../app/Settings/schema'; import { TraceLink } from '../app/TraceLink'; import { TransactionLink } from '../app/transaction_link'; @@ -37,7 +37,7 @@ import { TransactionOverview } from '../app/transaction_overview'; import { ServiceInventory } from '../app/service_inventory'; import { TraceOverview } from '../app/trace_overview'; import { useFetcher } from '../../hooks/use_fetcher'; -import { AgentConfigurationCreateEdit } from '../app/Settings/AgentConfigurations/AgentConfigurationCreateEdit'; +import { AgentConfigurationCreateEdit } from '../app/Settings/agent_configurations/AgentConfigurationCreateEdit'; // These component function definitions are used below with the `component` // property of the route definitions. diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx index ef74894169d72..a03b0b5f2ae01 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx @@ -8,14 +8,13 @@ import { size } from 'lodash'; import { tint } from 'polished'; import React from 'react'; +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; import javascript from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'; import python from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'; import ruby from 'react-syntax-highlighter/dist/cjs/languages/hljs/ruby'; import xcode from 'react-syntax-highlighter/dist/cjs/styles/hljs/xcode'; -import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { StackframeWithLineContext } from '../../../../typings/es_schemas/raw/fields/stackframe'; -import { borderRadius, px, unit, units } from '../../../style/variables'; SyntaxHighlighter.registerLanguage('javascript', javascript); SyntaxHighlighter.registerLanguage('python', python); @@ -23,15 +22,15 @@ SyntaxHighlighter.registerLanguage('ruby', ruby); const ContextContainer = euiStyled.div` position: relative; - border-radius: ${borderRadius}; + border-radius: ${({ theme }) => theme.eui.euiBorderRadiusSmall}; `; -const LINE_HEIGHT = units.eighth * 9; +const LINE_HEIGHT = 18; const LineHighlight = euiStyled.div<{ lineNumber: number }>` position: absolute; width: 100%; - height: ${px(units.eighth * 9)}; - top: ${(props) => px(props.lineNumber * LINE_HEIGHT)}; + height: ${LINE_HEIGHT}px; + top: ${(props) => props.lineNumber * LINE_HEIGHT}px; pointer-events: none; background-color: ${({ theme }) => tint(0.9, theme.eui.euiColorWarning)}; `; @@ -40,7 +39,7 @@ const LineNumberContainer = euiStyled.div<{ isLibraryFrame: boolean }>` position: absolute; top: 0; left: 0; - border-radius: ${borderRadius}; + border-radius: ${({ theme }) => theme.eui.euiBorderRadiusSmall}; background: ${({ isLibraryFrame, theme }) => isLibraryFrame ? theme.eui.euiColorEmptyShade @@ -49,29 +48,29 @@ const LineNumberContainer = euiStyled.div<{ isLibraryFrame: boolean }>` const LineNumber = euiStyled.div<{ highlight: boolean }>` position: relative; - min-width: ${px(units.eighth * 21)}; - padding-left: ${px(units.half)}; - padding-right: ${px(units.quarter)}; + min-width: 42px; + padding-left: ${({ theme }) => theme.eui.paddingSizes.s}; + padding-right: ${({ theme }) => theme.eui.paddingSizes.xs}; color: ${({ theme }) => theme.eui.euiColorMediumShade}; - line-height: ${px(unit + units.eighth)}; + line-height: ${LINE_HEIGHT}px; text-align: right; border-right: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; background-color: ${({ highlight, theme }) => highlight ? tint(0.9, theme.eui.euiColorWarning) : null}; &:last-of-type { - border-radius: 0 0 0 ${borderRadius}; + border-radius: 0 0 0 ${({ theme }) => theme.eui.euiBorderRadiusSmall}; } `; const LineContainer = euiStyled.div` overflow: auto; - margin: 0 0 0 ${px(units.eighth * 21)}; + margin: 0 0 0 42px; padding: 0; background-color: ${({ theme }) => theme.eui.euiColorEmptyShade}; &:last-of-type { - border-radius: 0 0 ${borderRadius} 0; + border-radius: 0 0 ${({ theme }) => theme.eui.euiBorderRadiusSmall} 0; } `; @@ -83,8 +82,8 @@ const Line = euiStyled.pre` border: 0; border-radius: 0; overflow: initial; - padding: 0 ${px(LINE_HEIGHT)}; - line-height: ${px(LINE_HEIGHT)}; + padding: 0 ${LINE_HEIGHT}px; + line-height: ${LINE_HEIGHT}px; `; const Code = euiStyled.code` diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx index d361634759390..0985153c2d39e 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx @@ -12,22 +12,16 @@ import { Stackframe as StackframeType, StackframeWithLineContext, } from '../../../../typings/es_schemas/raw/fields/stackframe'; -import { - borderRadius, - fontFamilyCode, - fontSize, -} from '../../../style/variables'; import { Context } from './Context'; -import { FrameHeading } from './FrameHeading'; +import { FrameHeading } from './frame_heading'; import { Variables } from './Variables'; -import { px, units } from '../../../style/variables'; const ContextContainer = euiStyled.div<{ isLibraryFrame: boolean }>` position: relative; - font-family: ${fontFamilyCode}; - font-size: ${fontSize}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; + font-size: ${({ theme }) => theme.eui.euiFontSizeS}; border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; - border-radius: ${borderRadius}; + border-radius: ${({ theme }) => theme.eui.euiBorderRadiusSmall}; background: ${({ isLibraryFrame, theme }) => isLibraryFrame ? theme.eui.euiColorEmptyShade @@ -36,7 +30,7 @@ const ContextContainer = euiStyled.div<{ isLibraryFrame: boolean }>` // Indent the non-context frames the same amount as the accordion control const NoContextFrameHeadingWrapper = euiStyled.div` - margin-left: ${px(units.unit + units.half + units.quarter)}; + margin-left: 28px; `; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx index 7c09048593710..a43cd26e7f94a 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Variables.tsx @@ -9,15 +9,16 @@ import { EuiAccordion } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { borderRadius, px, unit, units } from '../../../style/variables'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { KeyValueTable } from '../KeyValueTable'; import { flattenObject } from '../../../utils/flattenObject'; const VariablesContainer = euiStyled.div` background: ${({ theme }) => theme.eui.euiColorEmptyShade}; - border-radius: 0 0 ${borderRadius} ${borderRadius}; - padding: ${px(units.half)} ${px(unit)}; + border-radius: 0 0 ${({ theme }) => + `${theme.eui.euiBorderRadiusSmall} ${theme.eui.euiBorderRadiusSmall}`}; + padding: ${({ theme }) => + `${theme.eui.paddingSizes.s} ${theme.eui.paddingSizes.m}`}; `; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/cause_stacktrace.test.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx rename to x-pack/plugins/apm/public/components/shared/Stacktrace/cause_stacktrace.test.tsx index a0ca8dd05b87f..cc23452127fa4 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/cause_stacktrace.test.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import React from 'react'; import { shallow } from 'enzyme'; -import { CauseStacktrace } from './CauseStacktrace'; +import React from 'react'; import { mountWithTheme } from '../../../utils/testHelpers'; +import { CauseStacktrace } from './cause_stacktrace'; describe('CauseStacktrace', () => { describe('render', () => { diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/cause_stacktrace.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx rename to x-pack/plugins/apm/public/components/shared/Stacktrace/cause_stacktrace.tsx index 090ba0e8e28cf..0ee4f66e2c24c 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/cause_stacktrace.tsx @@ -5,17 +5,16 @@ * 2.0. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiTitle } from '@elastic/eui'; -import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { px, unit, units } from '../../../style/variables'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; import { Stacktrace } from '.'; +import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; const Accordion = euiStyled(EuiAccordion)` border-top: ${({ theme }) => theme.eui.euiBorderThin}; - margin-top: ${px(units.half)}; + margin-top: ${({ theme }) => theme.eui.euiSizeS}; `; const CausedByContainer = euiStyled('h5')` @@ -31,7 +30,7 @@ const CausedByHeading = euiStyled('span')` `; const FramesContainer = euiStyled('div')` - padding-left: ${px(unit)}; + padding-left: ${({ theme }) => theme.eui.paddingSizes.m}; `; function CausedBy({ message }: { message: string }) { diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading.test.tsx similarity index 99% rename from x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.test.tsx rename to x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading.test.tsx index 794d88461358e..2eb3320ef3aa3 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { renderWithTheme } from '../../../utils/testHelpers'; -import { FrameHeading } from './FrameHeading'; +import { FrameHeading } from './frame_heading'; function getRenderedStackframeText( stackframe: Stackframe, diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx rename to x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading.tsx index 68b0893e1d8d3..8c9b9ae26c140 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/frame_heading.tsx @@ -8,7 +8,6 @@ import React, { ComponentType } from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; -import { fontFamilyCode, fontSize, px, units } from '../../../style/variables'; import { CSharpFrameHeadingRenderer, DefaultFrameHeadingRenderer, @@ -21,9 +20,9 @@ import { const FileDetails = euiStyled.div` color: ${({ theme }) => theme.eui.euiColorDarkShade}; line-height: 1.5; /* matches the line-hight of the accordion container button */ - padding: ${px(units.eighth)} 0; - font-family: ${fontFamilyCode}; - font-size: ${fontSize}; + padding: 2px 0; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; + font-size: ${({ theme }) => theme.eui.euiFontSizeS}; `; const LibraryFrameFileDetail = euiStyled.span` diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx index 19af5da30cff2..3395b22988e8c 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.tsx @@ -10,7 +10,7 @@ import { isEmpty, last } from 'lodash'; import React, { Fragment } from 'react'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { EmptyMessage } from '../../shared/EmptyMessage'; -import { LibraryStacktrace } from './LibraryStacktrace'; +import { LibraryStacktrace } from './library_stacktrace'; import { Stackframe as StackframeComponent } from './Stackframe'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/library_stacktrace.test.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx rename to x-pack/plugins/apm/public/components/shared/Stacktrace/library_stacktrace.test.tsx index d583d4c700939..d10e6c46cd12c 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/library_stacktrace.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { renderWithTheme } from '../../../utils/testHelpers'; -import { LibraryStacktrace } from './LibraryStacktrace'; +import { LibraryStacktrace } from './library_stacktrace'; describe('LibraryStacktrace', () => { describe('render', () => { diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/library_stacktrace.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx rename to x-pack/plugins/apm/public/components/shared/Stacktrace/library_stacktrace.tsx index de417b465638f..08399d09bed86 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/library_stacktrace.tsx @@ -10,11 +10,10 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; -import { px, units } from '../../../style/variables'; import { Stackframe as StackframeComponent } from './Stackframe'; const LibraryStacktraceAccordion = euiStyled(EuiAccordion)` - margin: ${px(units.quarter)} 0; + margin: ${({ theme }) => theme.eui.euiSizeXS} 0; `; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx b/x-pack/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx index 1ceccc5203fb2..e0710556096c9 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip, EuiText } from '@elastic/eui'; import { asDuration } from '../../../../common/utils/formatters'; -import { PercentOfParent } from '../../app/transaction_details/WaterfallWithSummmary/PercentOfParent'; +import { PercentOfParent } from '../../app/transaction_details/waterfall_with_summary/PercentOfParent'; interface Props { duration: number; diff --git a/x-pack/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx b/x-pack/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx index 8755003c89af2..6939aaf49373e 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx @@ -10,9 +10,9 @@ import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { Summary } from './'; import { TimestampTooltip } from '../TimestampTooltip'; import { DurationSummaryItem } from './DurationSummaryItem'; -import { ErrorCountSummaryItemBadge } from './ErrorCountSummaryItemBadge'; +import { ErrorCountSummaryItemBadge } from './error_count_summary_item_badge'; import { isRumAgentName } from '../../../../common/agent_name'; -import { HttpInfoSummaryItem } from './HttpInfoSummaryItem'; +import { HttpInfoSummaryItem } from './http_info_summary_item'; import { TransactionResultSummaryItem } from './TransactionResultSummaryItem'; import { UserAgentSummaryItem } from './UserAgentSummaryItem'; diff --git a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.test.tsx b/x-pack/plugins/apm/public/components/shared/Summary/error_count_summary_item_badge.test.tsx similarity index 90% rename from x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.test.tsx rename to x-pack/plugins/apm/public/components/shared/Summary/error_count_summary_item_badge.test.tsx index 9996d1ea61a76..c6b77bddbf544 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/error_count_summary_item_badge.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { ErrorCountSummaryItemBadge } from './ErrorCountSummaryItemBadge'; +import { ErrorCountSummaryItemBadge } from './error_count_summary_item_badge'; import { expectTextsInDocument, renderWithTheme, diff --git a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx b/x-pack/plugins/apm/public/components/shared/Summary/error_count_summary_item_badge.tsx similarity index 87% rename from x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx rename to x-pack/plugins/apm/public/components/shared/Summary/error_count_summary_item_badge.tsx index ec309f2f74d10..17189f1e40d94 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/error_count_summary_item_badge.tsx @@ -10,15 +10,13 @@ import { i18n } from '@kbn/i18n'; import { EuiBadge } from '@elastic/eui'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useTheme } from '../../../hooks/use_theme'; -import { px } from '../../../../public/style/variables'; -import { units } from '../../../style/variables'; interface Props { count: number; } const Badge = euiStyled(EuiBadge)` - margin-top: ${px(units.eighth)}; + margin-top: 2px; `; export function ErrorCountSummaryItemBadge({ count }: Props) { diff --git a/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/HttpInfoSummaryItem.test.tsx b/x-pack/plugins/apm/public/components/shared/Summary/http_info_summary_item/http_info_summary_item.test.tsx similarity index 69% rename from x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/HttpInfoSummaryItem.test.tsx rename to x-pack/plugins/apm/public/components/shared/Summary/http_info_summary_item/http_info_summary_item.test.tsx index 71d7177051b9d..3e1848af316fe 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/HttpInfoSummaryItem.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/http_info_summary_item/http_info_summary_item.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { HttpInfoSummaryItem } from '.'; import * as exampleTransactions from '../__fixtures__/transactions'; +import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; describe('HttpInfoSummaryItem', () => { describe('render', () => { @@ -19,18 +20,24 @@ describe('HttpInfoSummaryItem', () => { it('renders', () => { expect(() => - shallow() + shallow(, { + wrappingComponent: EuiThemeProvider, + }) ).not.toThrowError(); }); it('renders empty component if no url is provided', () => { - const component = shallow(); + const component = shallow(, { + wrappingComponent: EuiThemeProvider, + }); expect(component.isEmptyRender()).toBeTruthy(); }); describe('with status code 100', () => { it('shows a success color', () => { - const wrapper = mount(); + const wrapper = mount(, { + wrappingComponent: EuiThemeProvider, + }); expect(wrapper.find('HttpStatusBadge').prop('status')).toEqual(100); }); @@ -39,7 +46,9 @@ describe('HttpInfoSummaryItem', () => { describe('with status code 200', () => { it('shows a success color', () => { const p = { ...props, status: 200 }; - const wrapper = mount(); + const wrapper = mount(, { + wrappingComponent: EuiThemeProvider, + }); expect(wrapper.find('HttpStatusBadge').prop('status')).toEqual(200); }); @@ -49,7 +58,9 @@ describe('HttpInfoSummaryItem', () => { it('shows a warning color', () => { const p = { ...props, status: 301 }; - const wrapper = mount(); + const wrapper = mount(, { + wrappingComponent: EuiThemeProvider, + }); expect(wrapper.find('HttpStatusBadge').prop('status')).toEqual(301); }); @@ -59,7 +70,9 @@ describe('HttpInfoSummaryItem', () => { it('shows a error color', () => { const p = { ...props, status: 404 }; - const wrapper = mount(); + const wrapper = mount(, { + wrappingComponent: EuiThemeProvider, + }); expect(wrapper.find('HttpStatusBadge').prop('status')).toEqual(404); }); @@ -69,7 +82,9 @@ describe('HttpInfoSummaryItem', () => { it('shows a error color', () => { const p = { ...props, status: 502 }; - const wrapper = mount(); + const wrapper = mount(, { + wrappingComponent: EuiThemeProvider, + }); expect(wrapper.find('HttpStatusBadge').prop('status')).toEqual(502); }); @@ -79,7 +94,9 @@ describe('HttpInfoSummaryItem', () => { it('shows the default color', () => { const p = { ...props, status: 700 }; - const wrapper = mount(); + const wrapper = mount(, { + wrappingComponent: EuiThemeProvider, + }); expect(wrapper.find('HttpStatusBadge').prop('status')).toEqual(700); }); diff --git a/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/index.tsx b/x-pack/plugins/apm/public/components/shared/Summary/http_info_summary_item/index.tsx similarity index 87% rename from x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/index.tsx rename to x-pack/plugins/apm/public/components/shared/Summary/http_info_summary_item/index.tsx index d72f03c386226..d10d15f8240a1 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/http_info_summary_item/index.tsx @@ -5,21 +5,21 @@ * 2.0. */ -import React from 'react'; -import { EuiToolTip, EuiBadge } from '@elastic/eui'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import React from 'react'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; -import { units, px, truncate, unit } from '../../../../style/variables'; +import { truncate, unit } from '../../../../utils/style'; import { HttpStatusBadge } from '../HttpStatusBadge'; const HttpInfoBadge = euiStyled(EuiBadge)` - margin-right: ${px(units.quarter)}; + margin-right: ${({ theme }) => theme.eui.euiSizeXS}; `; const Url = euiStyled('span')` display: inline-block; vertical-align: bottom; - ${truncate(px(unit * 24))}; + ${truncate(unit * 24)}; `; interface HttpInfoProps { method?: string; diff --git a/x-pack/plugins/apm/public/components/shared/Summary/index.tsx b/x-pack/plugins/apm/public/components/shared/Summary/index.tsx index 395156800dceb..1880ee00f3de7 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/index.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import React from 'react'; import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { px, units } from '../../../../public/style/variables'; import { Maybe } from '../../../../typings/common'; interface Props { @@ -18,7 +17,7 @@ interface Props { const Item = euiStyled(EuiFlexItem)` flex-wrap: nowrap; border-right: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; - padding-right: ${px(units.half)}; + padding-right: ${({ theme }) => theme.eui.paddingSizes.s}; flex-flow: row nowrap; line-height: 1.5; align-items: center !important; diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx index 28c000310346d..b800ef41fbcac 100644 --- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx @@ -17,14 +17,14 @@ import { ENVIRONMENT_ALL, getEnvironmentLabel, } from '../../../../common/environment_filter_values'; -import { getAPMHref } from '../Links/apm/APMLink'; import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useLicenseContext } from '../../../context/license/use_license_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; -import { units } from '../../../style/variables'; +import { getAPMHref } from '../Links/apm/APMLink'; export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; @@ -39,6 +39,7 @@ export function AnomalyDetectionSetupLink() { const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); const { basePath } = core.http; + const theme = useTheme(); return ( )} - + {ANOMALY_DETECTION_LINK_LABEL} diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/AgentMarker.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/agent_marker.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/AgentMarker.test.tsx.snap rename to x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/agent_marker.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/index.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/index.test.tsx.snap index f108eb7ebf3ea..5657732eb241b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/__snapshots__/index.test.tsx.snap @@ -4,7 +4,7 @@ exports[`Marker renders agent marker 1`] = ` @@ -25,7 +25,7 @@ exports[`Marker renders error marker 1`] = ` diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/agent_marker.test.tsx similarity index 81% rename from x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.test.tsx rename to x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/agent_marker.test.tsx index 1411a264b065e..0b7e405ae5d95 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/agent_marker.test.tsx @@ -7,9 +7,9 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { AgentMarker } from './AgentMarker'; -import { AgentMark } from '../../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { AgentMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks'; +import { AgentMarker } from './agent_marker'; describe('AgentMarker', () => { const mark = { diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/agent_marker.tsx similarity index 80% rename from x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx rename to x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/agent_marker.tsx index 3b7f0fab6c2a7..947c7a93f38b1 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/agent_marker.tsx @@ -5,23 +5,22 @@ * 2.0. */ -import React from 'react'; import { EuiToolTip } from '@elastic/eui'; +import React from 'react'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; import { asDuration } from '../../../../../../common/utils/formatters'; import { useTheme } from '../../../../../hooks/use_theme'; -import { px, units } from '../../../../../style/variables'; -import { Legend } from '../../Legend'; -import { AgentMark } from '../../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; +import { AgentMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks'; +import { Legend } from '../legend'; const NameContainer = euiStyled.div` border-bottom: 1px solid ${({ theme }) => theme.eui.euiColorMediumShade}; - padding-bottom: ${px(units.half)}; + padding-bottom: ${({ theme }) => theme.eui.paddingSizes.s}; `; const TimeContainer = euiStyled.div` color: ${({ theme }) => theme.eui.euiColorMediumShade}; - padding-top: ${px(units.half)}; + padding-top: ${({ theme }) => theme.eui.paddingSizes.s}; `; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/error_marker.test.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx rename to x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/error_marker.test.tsx index 36634f97a3a45..fdb97ea4fadde 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/error_marker.test.tsx @@ -14,8 +14,8 @@ import { expectTextsInDocument, renderWithTheme, } from '../../../../../utils/testHelpers'; -import { ErrorMark } from '../../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; -import { ErrorMarker } from './ErrorMarker'; +import { ErrorMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks'; +import { ErrorMarker } from './error_marker'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/error_marker.tsx similarity index 88% rename from x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx rename to x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/error_marker.tsx index 044070303d2ff..b1e902957bfd7 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/error_marker.tsx @@ -5,36 +5,35 @@ * 2.0. */ -import React, { useState } from 'react'; import { EuiPopover, EuiText } from '@elastic/eui'; +import React, { useState } from 'react'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; -import { asDuration } from '../../../../../../common/utils/formatters'; -import { useTheme } from '../../../../../hooks/use_theme'; import { TRACE_ID, TRANSACTION_ID, } from '../../../../../../common/elasticsearch_fieldnames'; +import { asDuration } from '../../../../../../common/utils/formatters'; import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; -import { px, unit, units } from '../../../../../style/variables'; -import { ErrorMark } from '../../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; +import { useTheme } from '../../../../../hooks/use_theme'; +import { ErrorMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks'; import { ErrorDetailLink } from '../../../Links/apm/ErrorDetailLink'; -import { Legend, Shape } from '../../Legend'; +import { Legend, Shape } from '../legend'; interface Props { mark: ErrorMark; } const Popover = euiStyled.div` - max-width: ${px(280)}; + max-width: 280px; `; const TimeLegend = euiStyled(Legend)` - margin-bottom: ${px(unit)}; + margin-bottom: ${({ theme }) => theme.eui.euiSize}; `; const ErrorLink = euiStyled(ErrorDetailLink)` display: block; - margin: ${px(units.half)} 0 ${px(units.half)} 0; + margin: ${({ theme }) => `${theme.eui.euiSizeS} 0 ${theme.eui.euiSizeS} 0`}; overflow-wrap: break-word; `; @@ -102,7 +101,7 @@ export function ErrorMarker({ mark }: Props) { ( -
@
+
@
)} /> { it('renders agent marker', () => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/index.tsx index bece72b398d31..88318eb0e0c2d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/index.tsx @@ -7,11 +7,10 @@ import React from 'react'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; -import { px } from '../../../../../style/variables'; -import { AgentMarker } from './AgentMarker'; -import { ErrorMarker } from './ErrorMarker'; -import { AgentMark } from '../../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; -import { ErrorMark } from '../../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; +import { AgentMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks'; +import { ErrorMark } from '../../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks'; +import { AgentMarker } from './agent_marker'; +import { ErrorMarker } from './error_marker'; interface Props { mark: ErrorMark | AgentMark; @@ -26,7 +25,7 @@ const MarkerContainer = euiStyled.div` export function Marker({ mark, x }: Props) { const legendWidth = 11; return ( - + {mark.type === 'errorMark' ? ( ) : ( diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx index 428da80fb808a..2dcc969f661b8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { VerticalGridLines, XYPlot } from 'react-vis'; import { useTheme } from '../../../../hooks/use_theme'; -import { Mark } from '../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks'; +import { Mark } from '../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks'; import { PlotValues } from './plotUtils'; interface VerticalLinesProps { diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.tsx index 650faa195271c..6c7cb7a067d2e 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/index.tsx @@ -8,11 +8,11 @@ import PropTypes from 'prop-types'; import React, { PureComponent, ReactNode } from 'react'; import { makeWidthFlexible } from 'react-vis'; +import { AgentMark } from '../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_agent_marks'; +import { ErrorMark } from '../../../app/transaction_details/waterfall_with_summary/waterfall_container/Marks/get_error_marks'; import { getPlotValues } from './plotUtils'; -import { TimelineAxis } from './TimelineAxis'; +import { TimelineAxis } from './timeline_axis'; import { VerticalLines } from './VerticalLines'; -import { ErrorMark } from '../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; -import { AgentMark } from '../../../app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; export type Mark = AgentMark | ErrorMark; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/legend.tsx similarity index 75% rename from x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx rename to x-pack/plugins/apm/public/components/shared/charts/Timeline/legend.tsx index f81da48b760e7..c7066565f0b22 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/legend.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { useTheme } from '../../../../hooks/use_theme'; -import { fontSizes, px, units } from '../../../../style/variables'; export enum Shape { circle = 'circle', @@ -17,7 +16,6 @@ export enum Shape { interface ContainerProps { onClick: (e: Event) => void; - fontSize?: string; clickable: boolean; disabled: boolean; } @@ -25,7 +23,7 @@ interface ContainerProps { const Container = euiStyled.div` display: flex; align-items: center; - font-size: ${(props) => props.fontSize}; + font-size: ${({ theme }) => theme.eui.euiFontSizeS}; color: ${({ theme }) => theme.eui.euiColorDarkShade}; cursor: ${(props) => (props.clickable ? 'pointer' : 'initial')}; opacity: ${(props) => (props.disabled ? 0.4 : 1)}; @@ -33,16 +31,17 @@ const Container = euiStyled.div` `; interface IndicatorProps { - radius: number; color: string; shape: Shape; withMargin: boolean; } +const radius = 11; + export const Indicator = euiStyled.span` - width: ${(props) => px(props.radius)}; - height: ${(props) => px(props.radius)}; - margin-right: ${(props) => (props.withMargin ? px(props.radius / 2) : 0)}; + width: ${radius}px; + height: ${radius}px; + margin-right: ${(props) => (props.withMargin ? `${radius / 2}px` : 0)}; background: ${(props) => props.color}; border-radius: ${(props) => { return props.shape === Shape.circle ? '100%' : '0'; @@ -53,8 +52,6 @@ interface Props { onClick?: any; text?: string; color?: string; - fontSize?: string; - radius?: number; disabled?: boolean; clickable?: boolean; shape?: Shape; @@ -65,8 +62,6 @@ export function Legend({ onClick, text, color, - fontSize = fontSizes.small, - radius = units.minus - 1, disabled = false, clickable = false, shape = Shape.circle, @@ -81,18 +76,12 @@ export function Legend({ onClick={onClick} disabled={disabled} clickable={clickable || Boolean(onClick)} - fontSize={fontSize} {...rest} > {indicator ? ( indicator() ) : ( - + )} {text} diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/timeline_axis.tsx similarity index 97% rename from x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx rename to x-pack/plugins/apm/public/components/shared/charts/Timeline/timeline_axis.tsx index 32b78bf8818a3..cf942ebb75776 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/timeline_axis.tsx @@ -5,12 +5,11 @@ * 2.0. */ -import React, { ReactNode } from 'react'; import { inRange } from 'lodash'; +import React, { ReactNode } from 'react'; import { XAxis, XYPlot } from 'react-vis'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { useTheme } from '../../../../hooks/use_theme'; -import { px } from '../../../../style/variables'; import { Mark } from './'; import { LastTickValue } from './LastTickValue'; import { Marker } from './Marker'; @@ -59,7 +58,7 @@ export function TimelineAxis({ position: 'sticky', top: 0, borderBottom: `1px solid ${theme.eui.euiColorMediumShade}`, - height: px(margins.top), + height: margins.top, zIndex: 2, width: '100%', }} diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx index 59205ef498534..6b93fe9605e42 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx @@ -5,9 +5,6 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React from 'react'; -import { EuiIcon } from '@elastic/eui'; import { AreaSeries, Chart, @@ -16,11 +13,13 @@ import { ScaleType, Settings, } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; import { merge } from 'lodash'; -import { Coordinate } from '../../../../../typings/timeseries'; +import React from 'react'; import { useChartTheme } from '../../../../../../observability/public'; -import { px, unit } from '../../../../style/variables'; +import { Coordinate } from '../../../../../typings/timeseries'; import { useTheme } from '../../../../hooks/use_theme'; +import { unit } from '../../../../utils/style'; import { getComparisonChartTheme } from '../../time_comparison/get_time_range_comparison'; export type Color = @@ -73,8 +72,8 @@ export function SparkPlot({ const colorValue = theme.eui[color]; const chartSize = { - height: px(24), - width: compact ? px(unit * 3) : px(unit * 4), + height: theme.eui.euiSizeL, + width: compact ? unit * 3 : unit * 4, }; const Sparkline = hasComparisonSeries ? LineSeries : AreaSeries; diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index 9e7a3ac744ffe..9667bbd33cc73 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx @@ -40,7 +40,7 @@ import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useTheme } from '../../../hooks/use_theme'; import { useAnnotationsContext } from '../../../context/annotations/use_annotations_context'; import { useChartPointerEventContext } from '../../../context/chart_pointer_event/use_chart_pointer_event_context'; -import { unit } from '../../../style/variables'; +import { unit } from '../../../utils/style'; import { ChartContainer } from './chart_container'; import { onBrushEnd, isTimeseriesEmpty } from './helper/helper'; import { getLatencyChartSelector } from '../../../selectors/latency_chart_selectors'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx index 436eca4781502..a68373892e78b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx @@ -34,7 +34,7 @@ import { useTheme } from '../../../../hooks/use_theme'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useAnnotationsContext } from '../../../../context/annotations/use_annotations_context'; import { useChartPointerEventContext } from '../../../../context/chart_pointer_event/use_chart_pointer_event_context'; -import { unit } from '../../../../style/variables'; +import { unit } from '../../../../utils/style'; import { ChartContainer } from '../../charts/chart_container'; import { isTimeseriesEmpty, onBrushEnd } from '../../charts/helper/helper'; diff --git a/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx b/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx index 54d8790c32d33..d28cc1646f16a 100644 --- a/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx @@ -19,7 +19,6 @@ import { import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; import styled from 'styled-components'; -import { px, units } from '../../../style/variables'; interface KeyValue { key: string; @@ -34,7 +33,8 @@ const StyledEuiAccordion = styled(EuiAccordion)` `; const StyledEuiDescriptionList = styled(EuiDescriptionList)` - margin: ${px(units.half)} ${px(units.half)} 0 ${px(units.half)}; + margin: ${({ theme }) => + `${theme.eui.euiSizeS} ${theme.eui.euiSizeS} 0 ${theme.eui.euiSizeS}`}; .descriptionList__title, .descriptionList__description { border-bottom: ${({ theme }) => theme.eui.euiBorderThin}; diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/ClickOutside.js b/x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/ClickOutside.js similarity index 100% rename from x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/ClickOutside.js rename to x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/ClickOutside.js diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js b/x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/Suggestion.js similarity index 84% rename from x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js rename to x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/Suggestion.js index 987daf67b1fc7..26b99c4e54f65 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js +++ b/x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/Suggestion.js @@ -9,13 +9,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { EuiIcon } from '@elastic/eui'; -import { - fontFamilyCode, - px, - units, - fontSizes, - unit, -} from '../../../../style/variables'; +import { unit } from '../../../../utils/style'; import { tint } from 'polished'; function getIconColor(type, theme) { @@ -40,23 +34,23 @@ const Description = euiStyled.div` display: inline; span { - font-family: ${fontFamilyCode}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; color: ${({ theme }) => theme.eui.euiColorFullShade}; - padding: 0 ${px(units.quarter)}; + padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs}; display: inline-block; } } `; const ListItem = euiStyled.li` - font-size: ${fontSizes.small}; - height: ${px(units.double)}; + font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; + height: ${({ theme }) => theme.eui.euiSizeXL}; align-items: center; display: flex; background: ${({ selected, theme }) => selected ? theme.eui.euiColorLightestShade : 'initial'}; cursor: pointer; - border-radius: ${px(units.quarter)}; + border-radius: ${({ theme }) => theme.eui.euiBorderRadiusSmall}; ${Description} { p span { @@ -69,19 +63,19 @@ const ListItem = euiStyled.li` `; const Icon = euiStyled.div` - flex: 0 0 ${px(units.double)}; + flex: 0 0 ${({ theme }) => theme.eui.euiSizeXL}; background: ${({ type, theme }) => tint(0.9, getIconColor(type, theme))}; color: ${({ type, theme }) => getIconColor(type, theme)}; width: 100%; height: 100%; text-align: center; - line-height: ${px(units.double)}; + line-height: ${({ theme }) => theme.eui.euiSizeXL}; `; const TextValue = euiStyled.div` - flex: 0 0 ${px(unit * 16)}; + flex: 0 0 ${unit * 16}px; color: ${({ theme }) => theme.eui.euiColorDarkestShade}; - padding: 0 ${px(units.half)}; + padding: 0 ${({ theme }) => theme.eui.paddingSizes.s}; `; function getEuiIconType(type) { diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js b/x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/Suggestions.js similarity index 88% rename from x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js rename to x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/Suggestions.js index 405be89c6629c..386eb7e1e0d7d 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js +++ b/x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/Suggestions.js @@ -5,25 +5,28 @@ * 2.0. */ -import React, { Component } from 'react'; +import { isEmpty } from 'lodash'; +import { tint } from 'polished'; import PropTypes from 'prop-types'; +import React, { Component } from 'react'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; -import { isEmpty } from 'lodash'; +import { unit } from '../../../../utils/style'; import Suggestion from './Suggestion'; -import { units, px, unit } from '../../../../style/variables'; -import { tint } from 'polished'; const List = euiStyled.ul` width: 100%; border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; - border-radius: ${px(units.quarter)}; - box-shadow: 0px ${px(units.quarter)} ${px(units.double)} - ${({ theme }) => tint(0.9, theme.eui.euiColorFullShade)}; + border-radius: ${({ theme }) => theme.eui.euiBorderRadiusSmall}; + box-shadow: 0 ${({ theme }) => + `${theme.eui.euiSizeXS} ${theme.eui.euiSizeXL} ${tint( + 0.9, + theme.eui.euiColorFullShade + )}`}; position: absolute; background: ${({ theme }) => theme.eui.euiColorEmptyShade}; z-index: 10; left: 0; - max-height: ${px(unit * 20)}; + max-height: ${unit * 20}px; overflow: scroll; `; diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/index.js b/x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/index.js similarity index 100% rename from x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/index.js rename to x-pack/plugins/apm/public/components/shared/kuery_bar/Typeahead/index.js diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts similarity index 100% rename from x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts rename to x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx rename to x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/use_processor_event.ts b/x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts similarity index 100% rename from x-pack/plugins/apm/public/components/shared/KueryBar/use_processor_event.ts rename to x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/utils.ts b/x-pack/plugins/apm/public/components/shared/kuery_bar/utils.ts similarity index 100% rename from x-pack/plugins/apm/public/components/shared/KueryBar/utils.ts rename to x-pack/plugins/apm/public/components/shared/kuery_bar/utils.ts diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap b/x-pack/plugins/apm/public/components/shared/managed_table/__snapshots__/managed_table.test.tsx.snap similarity index 90% rename from x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap rename to x-pack/plugins/apm/public/components/shared/managed_table/__snapshots__/managed_table.test.tsx.snap index 655fc5a25b9ef..55e0bc0fe2431 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap +++ b/x-pack/plugins/apm/public/components/shared/managed_table/__snapshots__/managed_table.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ManagedTable component should render a page-full of items, with defaults 1`] = ` +exports[`ManagedTable should render a page-full of items, with defaults 1`] = ` `; -exports[`ManagedTable component should render when specifying initial values 1`] = ` +exports[`ManagedTable should render when specifying initial values 1`] = ` { field?: string; dataType?: string; align?: string; - width?: string; + width?: string | number; sortable?: boolean; render?: (value: any, item: T) => unknown; } diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/ManagedTable.test.js b/x-pack/plugins/apm/public/components/shared/managed_table/managed_table.test.tsx similarity index 54% rename from x-pack/plugins/apm/public/components/shared/ManagedTable/ManagedTable.test.js rename to x-pack/plugins/apm/public/components/shared/managed_table/managed_table.test.tsx index ab96eba14b403..c474d98a3f0d4 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/ManagedTable.test.js +++ b/x-pack/plugins/apm/public/components/shared/managed_table/managed_table.test.tsx @@ -7,39 +7,41 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { UnoptimizedManagedTable } from '.'; +import { ITableColumn, UnoptimizedManagedTable } from '.'; -describe('ManagedTable component', () => { - let people; - let columns; +interface Person { + name: string; + age: number; +} - beforeEach(() => { - people = [ - { name: 'Jess', age: 29 }, - { name: 'Becky', age: 43 }, - { name: 'Thomas', age: 31 }, - ]; - columns = [ - { - field: 'name', - name: 'Name', - sortable: true, - render: (name) => `Name: ${name}`, - }, - { field: 'age', name: 'Age', render: (age) => `Age: ${age}` }, - ]; - }); +describe('ManagedTable', () => { + const people: Person[] = [ + { name: 'Jess', age: 29 }, + { name: 'Becky', age: 43 }, + { name: 'Thomas', age: 31 }, + ]; + const columns: Array> = [ + { + field: 'name', + name: 'Name', + sortable: true, + render: (name) => `Name: ${name}`, + }, + { field: 'age', name: 'Age', render: (age) => `Age: ${age}` }, + ]; it('should render a page-full of items, with defaults', () => { expect( - shallow() + shallow( + columns={columns} items={people} /> + ) ).toMatchSnapshot(); }); it('should render when specifying initial values', () => { expect( shallow( - columns={columns} items={people} initialSortField="age" diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.tsx index 74374e331ee8e..4f9e58239855d 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar.tsx @@ -19,9 +19,8 @@ import { enableInspectEsQueries } from '../../../../observability/public'; import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; import { useKibanaUrl } from '../../hooks/useKibanaUrl'; import { useBreakPoints } from '../../hooks/use_break_points'; -import { px } from '../../style/variables'; import { DatePicker } from './DatePicker'; -import { KueryBar } from './KueryBar'; +import { KueryBar } from './kuery_bar'; import { TimeComparison } from './time_comparison'; import { TransactionTypeSelect } from './transaction_type_select'; @@ -125,7 +124,7 @@ export function SearchBar({ responsive={false} > {showTimeComparison && ( - + )} diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx index 05305558564f1..695c941c61ed4 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx @@ -14,7 +14,6 @@ import { } from '@elastic/eui'; import React from 'react'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import { px } from '../../../style/variables'; interface IconPopoverProps { title: string; @@ -59,7 +58,7 @@ export function IconPopover({ closePopover={onClose} > {title} -
+
{isLoading ? ( ) : ( diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap b/x-pack/plugins/apm/public/components/shared/sticky_properties/__snapshots__/sticky_properties.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap rename to x-pack/plugins/apm/public/components/shared/sticky_properties/__snapshots__/sticky_properties.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx b/x-pack/plugins/apm/public/components/shared/sticky_properties/index.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx rename to x-pack/plugins/apm/public/components/shared/sticky_properties/index.tsx index ee764db516d72..49488ba09e068 100644 --- a/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/sticky_properties/index.tsx @@ -9,13 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { - fontFamilyCode, - fontSizes, - px, - truncate, - units, -} from '../../../style/variables'; +import { truncate } from '../../../utils/style'; export interface IStickyProperty { val: JSX.Element | string | Date; @@ -26,12 +20,12 @@ export interface IStickyProperty { } const TooltipFieldName = euiStyled.span` - font-family: ${fontFamilyCode}; + font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; `; const PropertyLabel = euiStyled.div` - margin-bottom: ${px(units.half)}; - font-size: ${fontSizes.small}; + margin-bottom: ${({ theme }) => theme.eui.euiSizeS}; + font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; color: ${({ theme }) => theme.eui.euiColorMediumShade}; span { diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js b/x-pack/plugins/apm/public/components/shared/sticky_properties/sticky_properties.test.tsx similarity index 80% rename from x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js rename to x-pack/plugins/apm/public/components/shared/sticky_properties/sticky_properties.test.tsx index eba70a171a1df..d29702752c159 100644 --- a/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js +++ b/x-pack/plugins/apm/public/components/shared/sticky_properties/sticky_properties.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { StickyProperties } from './index'; +import { StickyProperties } from '.'; import { shallow } from 'enzyme'; import { USER_ID, URL_FULL } from '../../../../common/elasticsearch_fieldnames'; import { mockMoment } from '../../../utils/testHelpers'; @@ -35,7 +35,7 @@ describe('StickyProperties', () => { { label: 'User ID', fieldName: USER_ID, - val: 1337, + val: '1337', }, ]; @@ -52,7 +52,7 @@ describe('StickyProperties', () => { { label: 'My Number', fieldName: 'myNumber', - val: 1337, + val: '1337', }, ]; @@ -66,25 +66,6 @@ describe('StickyProperties', () => { expect(wrapper).toEqual('1337'); }); - it('should not stringify booleans', () => { - const stickyProperties = [ - { - label: 'My boolean', - fieldName: 'myBoolean', - val: true, - }, - ]; - - const wrapper = shallow( - - ) - .find('PropertyValue') - .dive() - .text(); - - expect(wrapper).toEqual(''); - }); - it('should render nested components', () => { const stickyProperties = [ { diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx index ed9d1a15cdbca..d7dfd3de2b628 100644 --- a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx @@ -10,13 +10,12 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; import React from 'react'; import { useHistory } from 'react-router-dom'; -import { useUiTracker } from '../../../../../observability/public'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; +import { useUiTracker } from '../../../../../observability/public'; import { getDateDifference } from '../../../../common/utils/formatters'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { px, unit } from '../../../style/variables'; -import * as urlHelpers from '../../shared/Links/url_helpers'; import { useBreakPoints } from '../../../hooks/use_break_points'; +import * as urlHelpers from '../../shared/Links/url_helpers'; import { getTimeRangeComparison, TimeRangeComparisonType, @@ -28,7 +27,7 @@ const PrependContainer = euiStyled.div` align-items: center; background-color: ${({ theme }) => theme.eui.euiFormInputGroupLabelBackground}; - padding: 0 ${px(unit)}; + padding: 0 ${({ theme }) => theme.eui.paddingSizes.m}; `; function getDateFormat({ diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.test.tsx diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.tsx similarity index 98% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.tsx index b28b364adcf30..a4f7c2b663484 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/TransactionActionMenu.tsx @@ -22,7 +22,7 @@ import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useLicenseContext } from '../../../context/license/use_license_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { CustomLinkMenuSection } from './CustomLinkMenuSection'; +import { CustomLinkMenuSection } from './custom_link_menu_section'; import { getSections } from './sections'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__fixtures__/mockData.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/__fixtures__/mockData.ts similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__fixtures__/mockData.ts rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/__fixtures__/mockData.ts diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/__snapshots__/TransactionActionMenu.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__snapshots__/TransactionActionMenu.test.tsx.snap rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/__snapshots__/TransactionActionMenu.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/CustomLinkToolbar.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/CustomLinkToolbar.test.tsx diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/CustomLinkToolbar.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/CustomLinkToolbar.tsx diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkList.test.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/custom_link_list.test.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkList.test.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/custom_link_list.test.tsx index c0c4613e033c9..e063fb36b8e9c 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkList.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/custom_link_list.test.tsx @@ -5,15 +5,15 @@ * 2.0. */ -import React from 'react'; import { render } from '@testing-library/react'; -import { CustomLinkList } from './CustomLinkList'; +import React from 'react'; +import { CustomLink } from '../../../../../common/custom_link/custom_link_types'; +import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { expectTextsInDocument, expectTextsNotInDocument, } from '../../../../utils/testHelpers'; -import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { CustomLink } from '../../../../../common/custom_link/custom_link_types'; +import { CustomLinkList } from './custom_link_list'; describe('CustomLinkList', () => { const customLinks = [ diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkList.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/custom_link_list.tsx similarity index 89% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkList.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/custom_link_list.tsx index 02d6dd436040f..7ea7cd35c443a 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkList.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/custom_link_list.tsx @@ -8,12 +8,12 @@ import Mustache from 'mustache'; import React from 'react'; import { - SectionLinks, SectionLink, + SectionLinks, } from '../../../../../../observability/public'; import { CustomLink } from '../../../../../common/custom_link/custom_link_types'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { px, unit } from '../../../../style/variables'; +import { unit } from '../../../../utils/style'; export function CustomLinkList({ customLinks, @@ -23,7 +23,7 @@ export function CustomLinkList({ transaction: Transaction; }) { return ( - + {customLinks.map((link) => { const href = getHref(link, transaction); return ( diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.test.tsx diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.tsx index cbbf34c78c4af..bc234b88a08e4 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.tsx @@ -5,37 +5,36 @@ * 2.0. */ -import React, { useMemo, useState } from 'react'; import { - EuiText, + EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer, - EuiButtonEmpty, + EuiText, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import { EuiToolTip } from '@elastic/eui'; -import { NO_PERMISSION_LABEL } from '../../../../../common/custom_link'; -import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import React, { useMemo, useState } from 'react'; import { ActionMenuDivider, Section, SectionSubtitle, SectionTitle, } from '../../../../../../observability/public'; -import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { CustomLinkList } from './CustomLinkList'; -import { CustomLinkToolbar } from './CustomLinkToolbar'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { LoadingStatePrompt } from '../../LoadingStatePrompt'; -import { px } from '../../../../style/variables'; -import { CreateEditCustomLinkFlyout } from '../../../app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout'; -import { convertFiltersToQuery } from '../../../app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/helper'; +import { NO_PERMISSION_LABEL } from '../../../../../common/custom_link'; import { CustomLink, Filter, } from '../../../../../common/custom_link/custom_link_types'; +import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { CreateEditCustomLinkFlyout } from '../../../app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout'; +import { convertFiltersToQuery } from '../../../app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/helper'; +import { LoadingStatePrompt } from '../../LoadingStatePrompt'; +import { CustomLinkToolbar } from './CustomLinkToolbar'; +import { CustomLinkList } from './custom_link_list'; const DEFAULT_LINKS_TO_SHOW = 3; @@ -165,7 +164,7 @@ function BottomSection({ return ( - + {i18n.translate('xpack.apm.customLink.empty', { defaultMessage: 'No custom links found. Set up your own custom links, e.g., a link to a specific Dashboard or external link.', diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.test.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.test.ts rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts rename to x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts diff --git a/x-pack/plugins/apm/public/components/shared/truncate_with_tooltip/index.tsx b/x-pack/plugins/apm/public/components/shared/truncate_with_tooltip/index.tsx index 63e0b84362073..d9268c14aa2ea 100644 --- a/x-pack/plugins/apm/public/components/shared/truncate_with_tooltip/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/truncate_with_tooltip/index.tsx @@ -8,7 +8,7 @@ import { EuiToolTip } from '@elastic/eui'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { truncate } from '../../../style/variables'; +import { truncate } from '../../../utils/style'; const tooltipAnchorClassname = '_apm_truncate_tooltip_anchor_'; diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 46cbc26a74d2d..0cd5009570613 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -22,6 +22,7 @@ import type { DataPublicPluginStart, } from '../../../../src/plugins/data/public'; import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; +import type { FleetStart } from '../../fleet/public'; import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import type { PluginSetupContract as AlertingPluginPublicSetup, @@ -43,6 +44,10 @@ import type { } from '../../triggers_actions_ui/public'; import { registerApmAlerts } from './components/alerting/register_apm_alerts'; import { featureCatalogueEntry } from './featureCatalogueEntry'; +import { + getApmEnrollmentFlyoutData, + LazyApmCustomAssetsExtension, +} from './components/fleet_integration'; export type ApmPluginSetup = ReturnType; @@ -69,6 +74,7 @@ export interface ApmPluginStartDeps { ml?: MlPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; observability: ObservabilityPublicStart; + fleet: FleetStart; } export class ApmPlugin implements Plugin { @@ -303,5 +309,22 @@ export class ApmPlugin implements Plugin { return {}; } - public start(core: CoreStart, plugins: ApmPluginStartDeps) {} + public start(core: CoreStart, plugins: ApmPluginStartDeps) { + const { fleet } = plugins; + + const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData(); + + fleet.registerExtension({ + package: 'apm', + view: 'agent-enrollment-flyout', + title: agentEnrollmentExtensionData.title, + Component: agentEnrollmentExtensionData.Component, + }); + + fleet.registerExtension({ + package: 'apm', + view: 'package-detail-assets', + Component: LazyApmCustomAssetsExtension, + }); + } } diff --git a/x-pack/plugins/apm/public/style/variables.ts b/x-pack/plugins/apm/public/style/variables.ts deleted file mode 100644 index 07ccf97c0dab0..0000000000000 --- a/x-pack/plugins/apm/public/style/variables.ts +++ /dev/null @@ -1,56 +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. - */ - -// Units -export const unit = 16; - -export const units = { - unit, - eighth: unit / 8, - quarter: unit / 4, - half: unit / 2, - minus: unit * 0.75, - plus: unit * 1.5, - double: unit * 2, - triple: unit * 3, - quadruple: unit * 4, -}; - -export function px(value: number): string { - return `${value}px`; -} - -export function pct(value: number): string { - return `${value}%`; -} - -// Styling -export const borderRadius = '4px'; - -// Fonts -export const fontFamilyCode = - '"Roboto Mono", Consolas, Menlo, Courier, monospace'; - -// Font sizes -export const fontSize = '14px'; - -export const fontSizes = { - tiny: '10px', - small: '12px', - large: '16px', - xlarge: '20px', - xxlarge: '30px', -}; - -export function truncate(width: string) { - return ` - max-width: ${width}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - `; -} diff --git a/x-pack/plugins/apm/public/utils/style.ts b/x-pack/plugins/apm/public/utils/style.ts new file mode 100644 index 0000000000000..3c8aa5d339c6e --- /dev/null +++ b/x-pack/plugins/apm/public/utils/style.ts @@ -0,0 +1,17 @@ +/* + * 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 const unit = 16; + +export function truncate(width: string | number) { + return ` + max-width: ${typeof width === 'string' ? width : `${width}px`}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + `; +} diff --git a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.ts b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.ts index 3f36515e72a7a..eb82a89811087 100644 --- a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.ts @@ -13,24 +13,18 @@ export function getBucketSize({ start, end, numBuckets = 100, + minBucketSize, }: { start: number; end: number; numBuckets?: number; + minBucketSize?: number; }) { const duration = moment.duration(end - start, 'ms'); const bucketSize = Math.max( calculateAuto.near(numBuckets, duration).asSeconds(), - 1 + minBucketSize || 1 ); - const intervalString = `${bucketSize}s`; - if (bucketSize < 0) { - return { - bucketSize: 0, - intervalString: 'auto', - }; - } - - return { bucketSize, intervalString }; + return { bucketSize, intervalString: `${bucketSize}s` }; } diff --git a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.test.ts b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.test.ts new file mode 100644 index 0000000000000..6af6d3342986c --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.test.ts @@ -0,0 +1,44 @@ +/* + * 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 { getBucketSizeForAggregatedTransactions } from './'; + +describe('getBucketSizeForAggregatedTransactions', () => { + describe('when searchAggregatedTransactions is enabled', () => { + it('returns min bucket size when date difference is lower than 60s', () => { + expect( + getBucketSizeForAggregatedTransactions({ + start: new Date('2021-06-30T15:00:00.000Z').valueOf(), + end: new Date('2021-06-30T15:00:30.000Z').valueOf(), + numBuckets: 10, + searchAggregatedTransactions: true, + }) + ).toEqual({ bucketSize: 60, intervalString: '60s' }); + }); + it('returns bucket size when date difference is greater than 60s', () => { + expect( + getBucketSizeForAggregatedTransactions({ + start: new Date('2021-06-30T15:00:00.000Z').valueOf(), + end: new Date('2021-06-30T15:30:00.000Z').valueOf(), + numBuckets: 10, + searchAggregatedTransactions: true, + }) + ).toEqual({ bucketSize: 300, intervalString: '300s' }); + }); + }); + describe('when searchAggregatedTransactions is disabled', () => { + it('returns 1s as bucket size', () => { + expect( + getBucketSizeForAggregatedTransactions({ + start: new Date('2021-06-30T15:00:00.000Z').valueOf(), + end: new Date('2021-06-30T15:00:30.000Z').valueOf(), + numBuckets: 10, + searchAggregatedTransactions: false, + }) + ).toEqual({ bucketSize: 1, intervalString: '1s' }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.ts new file mode 100644 index 0000000000000..b475e518ce982 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size_for_aggregated_transactions/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { getBucketSize } from '../get_bucket_size'; + +export function getBucketSizeForAggregatedTransactions({ + start, + end, + numBuckets = 100, + searchAggregatedTransactions, +}: { + start: number; + end: number; + numBuckets?: number; + searchAggregatedTransactions?: boolean; +}) { + const minBucketSize = searchAggregatedTransactions ? 60 : undefined; + return getBucketSize({ start, end, numBuckets, minBucketSize }); +} diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts index 7d9dca9b2a706..6110ad3459911 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts @@ -20,7 +20,7 @@ import { getTransactionDurationFieldForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; import { calculateThroughput } from '../../helpers/calculate_throughput'; -import { getBucketSize } from '../../helpers/get_bucket_size'; +import { getBucketSizeForAggregatedTransactions } from '../../helpers/get_bucket_size_for_aggregated_transactions'; import { getLatencyAggregation, getLatencyValue, @@ -78,11 +78,14 @@ export async function getServiceInstancesTransactionStatistics< }): Promise>> { const { apmEventClient } = setup; - const { intervalString, bucketSize } = getBucketSize({ - start, - end, - numBuckets, - }); + const { intervalString, bucketSize } = getBucketSizeForAggregatedTransactions( + { + start, + end, + numBuckets, + searchAggregatedTransactions, + } + ); const field = getTransactionDurationFieldForAggregatedTransactions( searchAggregatedTransactions diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts index 36d372e322cbc..ea33c942cfc3b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts @@ -6,7 +6,6 @@ */ import { keyBy } from 'lodash'; -import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -15,10 +14,11 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; import { environmentQuery, - rangeQuery, kqlQuery, + rangeQuery, } from '../../../server/utils/queries'; import { Coordinate } from '../../../typings/timeseries'; import { @@ -26,7 +26,7 @@ import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../helpers/aggregated_transactions'; -import { getBucketSize } from '../helpers/get_bucket_size'; +import { getBucketSizeForAggregatedTransactions } from '../helpers/get_bucket_size_for_aggregated_transactions'; import { getLatencyAggregation, getLatencyValue, @@ -68,7 +68,12 @@ export async function getServiceTransactionGroupDetailedStatistics({ }> > { const { apmEventClient } = setup; - const { intervalString } = getBucketSize({ start, end, numBuckets }); + const { intervalString } = getBucketSizeForAggregatedTransactions({ + start, + end, + numBuckets, + searchAggregatedTransactions, + }); const field = getTransactionDurationFieldForAggregatedTransactions( searchAggregatedTransactions diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts index 019ab8770887a..7f48c591521e7 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts @@ -17,8 +17,8 @@ import { } from '../../../../common/transaction_types'; import { environmentQuery, - rangeQuery, kqlQuery, + rangeQuery, } from '../../../../server/utils/queries'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { @@ -26,8 +26,8 @@ import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; -import { getBucketSize } from '../../helpers/get_bucket_size'; import { calculateThroughput } from '../../helpers/calculate_throughput'; +import { getBucketSizeForAggregatedTransactions } from '../../helpers/get_bucket_size_for_aggregated_transactions'; import { calculateTransactionErrorPercentage, getOutcomeAggregation, @@ -117,10 +117,11 @@ export async function getServiceTransactionStats({ timeseries: { date_histogram: { field: '@timestamp', - fixed_interval: getBucketSize({ + fixed_interval: getBucketSizeForAggregatedTransactions({ start, end, numBuckets: 20, + searchAggregatedTransactions, }).intervalString, min_doc_count: 0, extended_bounds: { min: start, max: end }, diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts index 0490c31e7c63d..7eacf47f15b7a 100644 --- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts @@ -12,14 +12,14 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery, - rangeQuery, kqlQuery, + rangeQuery, } from '../../../server/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, } from '../helpers/aggregated_transactions'; -import { getBucketSize } from '../helpers/get_bucket_size'; +import { getBucketSizeForAggregatedTransactions } from '../helpers/get_bucket_size_for_aggregated_transactions'; import { Setup } from '../helpers/setup_request'; interface Options { @@ -44,7 +44,11 @@ function fetcher({ end, }: Options) { const { apmEventClient } = setup; - const { intervalString } = getBucketSize({ start, end }); + const { intervalString } = getBucketSizeForAggregatedTransactions({ + start, + end, + searchAggregatedTransactions, + }); const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index 6499e80be9302..cc3a13ef5c648 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -5,9 +5,6 @@ * 2.0. */ -import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; -import { Coordinate } from '../../../typings/timeseries'; - import { EVENT_OUTCOME, SERVICE_NAME, @@ -15,16 +12,18 @@ import { TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; import { environmentQuery, - rangeQuery, kqlQuery, + rangeQuery, } from '../../../server/utils/queries'; +import { Coordinate } from '../../../typings/timeseries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, } from '../helpers/aggregated_transactions'; -import { getBucketSize } from '../helpers/get_bucket_size'; +import { getBucketSizeForAggregatedTransactions } from '../helpers/get_bucket_size_for_aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { calculateTransactionErrorPercentage, @@ -101,7 +100,11 @@ export async function getErrorRate({ timeseries: { date_histogram: { field: '@timestamp', - fixed_interval: getBucketSize({ start, end }).intervalString, + fixed_interval: getBucketSizeForAggregatedTransactions({ + start, + end, + searchAggregatedTransactions, + }).intervalString, min_doc_count: 0, extended_bounds: { min: start, max: end }, }, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index 1f8170921aac3..e3f59ca2e4328 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; import { @@ -14,18 +13,19 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { environmentQuery, - rangeQuery, kqlQuery, + rangeQuery, } from '../../../../server/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../../../lib/helpers/aggregated_transactions'; -import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; +import { getBucketSizeForAggregatedTransactions } from '../../helpers/get_bucket_size_for_aggregated_transactions'; import { getLatencyAggregation, getLatencyValue, @@ -58,7 +58,11 @@ function searchLatency({ end: number; }) { const { apmEventClient } = setup; - const { intervalString } = getBucketSize({ start, end }); + const { intervalString } = getBucketSizeForAggregatedTransactions({ + start, + end, + searchAggregatedTransactions, + }); const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts index ed85e700c3473..ff3534159d19b 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts @@ -15,15 +15,15 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery, - rangeQuery, kqlQuery, + rangeQuery, } from '../../../../server/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, } from '../../../lib/helpers/aggregated_transactions'; -import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; +import { getBucketSizeForAggregatedTransactions } from '../../helpers/get_bucket_size_for_aggregated_transactions'; import { getThroughputBuckets } from './transform'; export type ThroughputChartsResponse = PromiseReturnType< @@ -115,7 +115,12 @@ export async function getThroughputCharts({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }) { - const { bucketSize, intervalString } = getBucketSize(setup); + const { bucketSize, intervalString } = getBucketSizeForAggregatedTransactions( + { + ...setup, + searchAggregatedTransactions, + } + ); const response = await searchThroughput({ environment, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js index 0e7a1afb0dbb1..dcb6035dbb687 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable } from '../common/__fixtures__/test_tables'; import { fontStyle } from '../common/__fixtures__/test_styles'; import { markdown } from './markdown'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__fixtures__/test_styles.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__fixtures__/test_styles.js index d61fef7abced8..fa831cacbcb18 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__fixtures__/test_styles.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__fixtures__/test_styles.js @@ -5,7 +5,7 @@ * 2.0. */ -import { elasticLogo } from '../../../lib/elastic_logo'; +import { elasticLogo } from '../../../../../../../src/plugins/presentation_util/common/lib'; export const fontStyle = { type: 'style', diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/all.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/all.test.js index c09c3ff99d89c..7d983e02f1123 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/all.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/all.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { all } from './all'; describe('all', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.test.js index 7e018427dc4c7..85e062f454bc5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { emptyTable, testTable } from './__fixtures__/test_tables'; import { alterColumn } from './alterColumn'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/any.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/any.test.js index d95029fef8144..b691595409c62 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/any.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/any.test.js @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { any } from './any'; describe('any', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.test.js index e7c2d3047bb91..fe297e00e7b35 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { asFn } from './as'; describe('as', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axis_config.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axis_config.test.js index 491558486eb44..1538ee8254ec3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axis_config.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axis_config.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { testTable } from './__fixtures__/test_tables'; import { axisConfig } from './axisConfig'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js index adee8a56dea49..d5621943bccaf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js @@ -7,7 +7,7 @@ import { of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { caseFn } from './case'; describe('case', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/clear.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/clear.test.js index 43c24f10c0465..0834dc27d321b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/clear.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/clear.test.js @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable } from './__fixtures__/test_tables'; import { clear } from './clear'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.test.js index d76c7a9174b81..d7f28559ee0ef 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/columns.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { emptyTable, testTable } from './__fixtures__/test_tables'; import { columns } from './columns'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.test.js index b0d80debf4ec3..c04f132a577fd 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { compare } from './compare'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts index d30324e0e2bfe..12aad5d609414 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts @@ -8,7 +8,7 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { ContainerStyle, Overflow, BackgroundRepeat, BackgroundSize } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; -import { isValidUrl } from '../../../common/lib/url'; +import { isValidUrl } from '../../../../../../src/plugins/presentation_util/common/lib'; interface Output extends ContainerStyle { type: 'containerStyle'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/container_style.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/container_style.test.js index b0a6ddf2caa74..7a3599f47ec86 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/container_style.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/container_style.test.js @@ -5,8 +5,10 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; -import { elasticLogo } from '../../lib/elastic_logo'; +import { + elasticLogo, + functionWrapper, +} from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { containerStyle } from './containerStyle'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.test.js index 7cefb41754fd4..e4c45f228aa17 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/context.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable, emptyTable } from './__fixtures__/test_tables'; import { context } from './context'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/csv.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/csv.test.ts index 93cf07a9dd5dd..cfef618bee39d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/csv.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/csv.test.ts @@ -5,11 +5,11 @@ * 2.0. */ -// @ts-expect-error untyped lib -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { csv } from './csv'; -import { Datatable } from 'src/plugins/expressions'; +import { Datatable, ExecutionContext, SerializableState } from 'src/plugins/expressions'; +import { Adapters } from 'src/plugins/inspector'; const errors = getFunctionErrors().csv; @@ -30,43 +30,59 @@ describe('csv', () => { it('should return a datatable', () => { expect( - fn(null, { - data: `name,number + fn( + null, + { + data: `name,number one,1 two,2 fourty two,42`, - }) + }, + {} as ExecutionContext + ) ).toEqual(expected); }); it('should allow custom delimiter', () => { expect( - fn(null, { - data: `name\tnumber + fn( + null, + { + data: `name\tnumber one\t1 two\t2 fourty two\t42`, - delimiter: '\t', - }) + delimiter: '\t', + }, + {} as ExecutionContext + ) ).toEqual(expected); expect( - fn(null, { - data: `name%SPLIT%number + fn( + null, + { + data: `name%SPLIT%number one%SPLIT%1 two%SPLIT%2 fourty two%SPLIT%42`, - delimiter: '%SPLIT%', - }) + delimiter: '%SPLIT%', + }, + {} as ExecutionContext + ) ).toEqual(expected); }); it('should allow custom newline', () => { expect( - fn(null, { - data: `name,number\rone,1\rtwo,2\rfourty two,42`, - newline: '\r', - }) + fn( + null, + { + data: `name,number\rone,1\rtwo,2\rfourty two,42`, + newline: '\r', + }, + {} as ExecutionContext + ) ).toEqual(expected); }); @@ -83,10 +99,14 @@ fourty two%SPLIT%42`, }; expect( - fn(null, { - data: `foo," bar ", baz, " buz " + fn( + null, + { + data: `foo," bar ", baz, " buz " 1,2,3,4`, - }) + }, + {} as ExecutionContext + ) ).toEqual(expectedResult); }); @@ -106,22 +126,30 @@ fourty two%SPLIT%42`, }; expect( - fn(null, { - data: `foo," bar ", baz, " buz " + fn( + null, + { + data: `foo," bar ", baz, " buz " 1," best ",3, " ok" " good", bad, better , " worst " `, - }) + }, + {} as ExecutionContext + ) ).toEqual(expectedResult); }); it('throws when given invalid csv', () => { expect(() => { - fn(null, { - data: `name,number + fn( + null, + { + data: `name,number one|1 two.2 fourty two,42`, - }); + }, + {} as ExecutionContext + ); }).toThrow(new RegExp(errors.invalidInputCSV().message)); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/date.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/date.test.js index 08c43caaf8b9e..cd06ce5fbb463 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/date.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/date.test.js @@ -6,7 +6,7 @@ */ import sinon from 'sinon'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { date } from './date'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.test.js index f19318753611c..00429779e2ff1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/do.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { doFn } from './do'; describe('do', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdown_control.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdown_control.test.ts index d8f2e8518daf0..254efd9f5f0d9 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdown_control.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/dropdown_control.test.ts @@ -5,23 +5,30 @@ * 2.0. */ -// @ts-expect-error untyped local -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable, relationalTable } from './__fixtures__/test_tables'; import { dropdownControl } from './dropdownControl'; +import { ExecutionContext, SerializableState } from 'src/plugins/expressions'; +import { Adapters } from 'src/plugins/inspector'; describe('dropdownControl', () => { const fn = functionWrapper(dropdownControl); it('returns a render as dropdown_filter', () => { - expect(fn(testTable, { filterColumn: 'name', valueColumn: 'name' })).toHaveProperty( - 'type', - 'render' - ); - expect(fn(testTable, { filterColumn: 'name', valueColumn: 'name' })).toHaveProperty( - 'as', - 'dropdown_filter' - ); + expect( + fn( + testTable, + { filterColumn: 'name', valueColumn: 'name' }, + {} as ExecutionContext + ) + ).toHaveProperty('type', 'render'); + expect( + fn( + testTable, + { filterColumn: 'name', valueColumn: 'name' }, + {} as ExecutionContext + ) + ).toHaveProperty('as', 'dropdown_filter'); }); describe('args', () => { @@ -32,12 +39,24 @@ describe('dropdownControl', () => { unique.find(([value, label]) => value === name) ? unique : [...unique, [name, name]], [] ); - expect(fn(testTable, { valueColumn: 'name' }).value.choices).toEqual(uniqueNames); + expect( + fn( + testTable, + { valueColumn: 'name' }, + {} as ExecutionContext + )?.value?.choices + ).toEqual(uniqueNames); }); it('returns an empty array when provided an invalid column', () => { - expect(fn(testTable, { valueColumn: 'foo' }).value.choices).toEqual([]); - expect(fn(testTable, { valueColumn: '' }).value.choices).toEqual([]); + expect( + fn(testTable, { valueColumn: 'foo' }, {} as ExecutionContext) + ?.value?.choices + ).toEqual([]); + expect( + fn(testTable, { valueColumn: '' }, {} as ExecutionContext) + ?.value?.choices + ).toEqual([]); }); }); @@ -45,7 +64,11 @@ describe('dropdownControl', () => { it('populates dropdown choices with labels from label column', () => { const expectedChoices = relationalTable.rows.map((row) => [row.id, row.name]); expect( - fn(relationalTable, { valueColumn: 'id', labelColumn: 'name' }).value.choices + fn( + relationalTable, + { valueColumn: 'id', labelColumn: 'name' }, + {} as ExecutionContext + )?.value?.choices ).toEqual(expectedChoices); }); }); @@ -53,19 +76,30 @@ describe('dropdownControl', () => { describe('filterColumn', () => { it('sets which column the filter is applied to', () => { - expect(fn(testTable, { filterColumn: 'name' }).value).toHaveProperty('column', 'name'); - expect(fn(testTable, { filterColumn: 'name', valueColumn: 'price' }).value).toHaveProperty( - 'column', - 'name' - ); + expect( + fn(testTable, { filterColumn: 'name' }, {} as ExecutionContext) + ?.value + ).toHaveProperty('column', 'name'); + expect( + fn( + testTable, + { filterColumn: 'name', valueColumn: 'price' }, + {} as ExecutionContext + )?.value + ).toHaveProperty('column', 'name'); }); it('defaults to valueColumn if not provided', () => { - expect(fn(testTable, { valueColumn: 'price' }).value).toHaveProperty('column', 'price'); + expect( + fn(testTable, { valueColumn: 'price' }, {} as ExecutionContext) + ?.value + ).toHaveProperty('column', 'price'); }); it('sets column to undefined if no args are provided', () => { - expect(fn(testTable).value).toHaveProperty('column', undefined); + expect( + fn(testTable, {}, {} as ExecutionContext)?.value + ).toHaveProperty('column', undefined); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/eq.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/eq.test.js index 5f8d9e042125f..5e710fc109396 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/eq.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/eq.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { eq } from './eq'; describe('eq', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/exactly.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/exactly.test.js index 10781a7af452d..9d3dcb6a99167 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/exactly.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/exactly.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { emptyFilter } from './__fixtures__/test_filters'; import { exactly } from './exactly'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js index 8c328e3d8adf6..edc2c1db18f64 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js @@ -7,7 +7,7 @@ import { of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable } from './__fixtures__/test_tables'; import { filterrows } from './filterrows'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatdate.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatdate.test.js index 6fda32dfef51a..e725dccc8ca34 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatdate.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatdate.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { formatdate } from './formatdate'; describe('formatdate', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.test.js index 37d3d2d905e67..e957bf115198f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { formatnumber } from './formatnumber'; describe('formatnumber', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.test.js index 2dda4d8f4258e..a556c2ddeb48a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { emptyTable, testTable } from './__fixtures__/test_tables'; import { getCell } from './getCell'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gt.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gt.test.js index 576d2a54dd59b..53675fca2b3ae 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gt.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gt.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { gt } from './gt'; describe('gt', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gte.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gte.test.js index 174f617f47a8c..aefb2ccf926ae 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gte.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/gte.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { gte } from './gte'; describe('gte', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/head.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/head.test.js index c25d0f7ae727f..4721eaf6cb530 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/head.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/head.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { emptyTable, testTable } from './__fixtures__/test_tables'; import { head } from './head'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js index cab331807e44c..df576a6a2507f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js @@ -7,7 +7,7 @@ import { of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { ifFn } from './if'; describe('if', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.test.js index cd0809d9b30a2..45b26cd25937d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.test.js @@ -6,14 +6,14 @@ */ import expect from '@kbn/expect'; -// import { functionWrapper } from '../../../test_helpers/function_wrapper'; -import { elasticLogo } from '../../lib/elastic_logo'; -import { elasticOutline } from '../../lib/elastic_outline'; +import { + elasticLogo, + elasticOutline, +} from '../../../../../../src/plugins/presentation_util/common/lib'; // import { image } from './image'; // TODO: the test was not running and is not up to date describe.skip('image', () => { - // const fn = functionWrapper(image); const fn = jest.fn(); it('returns an image object using a dataUrl', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts index b4d067280cb69..c3e64e48b23fc 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts @@ -7,9 +7,10 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; - -import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -import { elasticLogo } from '../../lib/elastic_logo'; +import { + elasticLogo, + resolveWithMissingImage, +} from '../../../../../../src/plugins/presentation_util/common/lib'; export enum ImageMode { CONTAIN = 'contain', diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts index 5c4d1d55cff04..9da646e695861 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts @@ -43,7 +43,6 @@ import { replace } from './replace'; import { rounddate } from './rounddate'; import { rowCount } from './rowCount'; import { repeatImage } from './repeat_image'; -import { revealImage } from './revealImage'; import { seriesStyle } from './seriesStyle'; import { shape } from './shape'; import { sort } from './sort'; @@ -94,7 +93,6 @@ export const functions = [ render, repeatImage, replace, - revealImage, rounddate, rowCount, seriesStyle, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.test.js index 12b1002d1e377..94fef857983bc 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/join_rows.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { testTable } from './__fixtures__/test_tables'; import { joinRows } from './join_rows'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lt.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lt.test.js index 8f16e446997ea..1ecfca9fc2f94 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lt.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lt.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { lt } from './lt'; describe('lt', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lte.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lte.test.js index 954b30e8c3c92..f32d2d23027c3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lte.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/lte.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { lte } from './lte'; describe('lte', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.test.js index a99d4823e5930..3f2d0ad2cb76e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { fontStyle } from './__fixtures__/test_styles'; import { metric } from './metric'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/neq.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/neq.test.js index 0a1980760cd09..88c7a5c18bc7a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/neq.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/neq.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { neq } from './neq'; describe('neq', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js index 5bf100eb90f4c..282cb2460d61c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js @@ -7,7 +7,7 @@ import { of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { testTable } from './__fixtures__/test_tables'; import { ply } from './ply'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js index f516cbbe5258f..6438e2a4d19c0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { progress } from './progress'; import { fontStyle } from './__fixtures__/test_styles'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.test.js index 6a91f4c280692..3248af5504093 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { DEFAULT_ELEMENT_CSS } from '../../../common/lib/constants'; import { testTable } from './__fixtures__/test_tables'; import { fontStyle, containerStyle } from './__fixtures__/test_styles'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts index cc7fc00a5df1f..7a52833693cc6 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts @@ -49,7 +49,6 @@ export function render(): ExpressionFunctionDefinition< 'plot', 'progress', 'repeatImage', - 'revealImage', 'shape', 'table', 'time_filter', diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js index f95d3d0ec03d0..97f0552721ccf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js @@ -5,9 +5,11 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; -import { elasticOutline } from '../../lib/elastic_outline'; -import { elasticLogo } from '../../lib/elastic_logo'; +import { + elasticLogo, + elasticOutline, + functionWrapper, +} from '../../../../../../src/plugins/presentation_util/common/lib'; import { repeatImage } from './repeat_image'; describe('repeatImage', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.ts index 6e62139e4da0d..904b2478760ab 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.ts @@ -6,8 +6,10 @@ */ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -import { elasticOutline } from '../../lib/elastic_outline'; +import { + elasticOutline, + resolveWithMissingImage, +} from '../../../../../../src/plugins/presentation_util/common/lib'; import { Render } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/replace.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/replace.test.js index 26e44f48f685d..6025ff354cd8d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/replace.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/replace.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { replace } from './replace'; describe('replace', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/reveal_image.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/reveal_image.test.js deleted file mode 100644 index d97168c3aacc1..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/reveal_image.test.js +++ /dev/null @@ -1,88 +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 { functionWrapper } from '../../../test_helpers/function_wrapper'; -import { elasticOutline } from '../../lib/elastic_outline'; -import { elasticLogo } from '../../lib/elastic_logo'; -import { getFunctionErrors } from '../../../i18n'; -import { revealImage } from './revealImage'; - -const errors = getFunctionErrors().revealImage; - -describe('revealImage', () => { - const fn = functionWrapper(revealImage); - - it('returns a render as revealImage', () => { - const result = fn(0.5); - expect(result).toHaveProperty('type', 'render'); - expect(result).toHaveProperty('as', 'revealImage'); - }); - - describe('context', () => { - it('throws when context is not a number between 0 and 1', () => { - expect(() => { - fn(10, { - image: elasticLogo, - emptyImage: elasticOutline, - origin: 'top', - }); - }).toThrow(new RegExp(errors.invalidPercent(10).message)); - - expect(() => { - fn(-0.1, { - image: elasticLogo, - emptyImage: elasticOutline, - origin: 'top', - }); - }).toThrow(new RegExp(errors.invalidPercent(-0.1).message)); - }); - }); - - describe('args', () => { - describe('image', () => { - it('sets the image', () => { - const result = fn(0.89, { image: elasticLogo }).value; - expect(result).toHaveProperty('image', elasticLogo); - }); - - it('defaults to the Elastic outline logo', () => { - const result = fn(0.89).value; - expect(result).toHaveProperty('image', elasticOutline); - }); - }); - - describe('emptyImage', () => { - it('sets the background image', () => { - const result = fn(0, { emptyImage: elasticLogo }).value; - expect(result).toHaveProperty('emptyImage', elasticLogo); - }); - - it('sets emptyImage to null', () => { - const result = fn(0).value; - expect(result).toHaveProperty('emptyImage', null); - }); - }); - - describe('origin', () => { - it('sets which side to start the reveal from', () => { - let result = fn(1, { origin: 'top' }).value; - expect(result).toHaveProperty('origin', 'top'); - result = fn(1, { origin: 'left' }).value; - expect(result).toHaveProperty('origin', 'left'); - result = fn(1, { origin: 'bottom' }).value; - expect(result).toHaveProperty('origin', 'bottom'); - result = fn(1, { origin: 'right' }).value; - expect(result).toHaveProperty('origin', 'right'); - }); - - it('defaults to bottom', () => { - const result = fn(1).value; - expect(result).toHaveProperty('origin', 'bottom'); - }); - }); - }); -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rounddate.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rounddate.test.js index f2c2f8af50a81..0ef832d973271 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rounddate.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rounddate.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { rounddate } from './rounddate'; describe('rounddate', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.test.js index b47bb662f43d4..7a32849e9161a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/rowCount.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { emptyTable, testTable } from './__fixtures__/test_tables'; import { rowCount } from './rowCount'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/series_style.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/series_style.test.js index ebd1f370db343..6e91b84d82b6f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/series_style.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/series_style.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { seriesStyle } from './seriesStyle'; describe('seriesStyle', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/sort.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/sort.test.js index 97f8b20c57efa..f59c517c91d88 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/sort.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/sort.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable } from './__fixtures__/test_tables'; import { sort } from './sort'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js index 3a3bb46e4d395..0260c9e77c424 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable, emptyTable } from './__fixtures__/test_tables'; import { staticColumn } from './staticColumn'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/string.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/string.test.js index c598c036bcaa9..48af07b7cd880 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/string.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/string.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { string } from './string'; describe('string', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js index 7a6d483d6c72b..c6f592889c991 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js @@ -7,7 +7,7 @@ import { of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { switchFn } from './switch'; describe('switch', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js index 2eff610ac8ee5..42e5150b03637 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable } from './__fixtures__/test_tables'; import { fontStyle } from './__fixtures__/test_styles'; import { table } from './table'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/tail.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/tail.test.js index 93461a2ef4575..420489754d20e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/tail.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/tail.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { emptyTable, testTable } from './__fixtures__/test_tables'; import { tail } from './tail'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js index e45a11b786d19..f45ec981b1a8a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.test.js @@ -6,7 +6,7 @@ */ import sinon from 'sinon'; -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { emptyFilter } from './__fixtures__/test_filters'; import { timefilter } from './timefilter'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter_control.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter_control.test.js index cf2c316507c35..b4a476807b7ee 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter_control.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter_control.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; import { timefilterControl } from './timefilterControl'; describe('timefilterControl', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_logo.ts b/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_logo.ts deleted file mode 100644 index 1ade7f1f269c0..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_logo.ts +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -export const elasticLogo = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmlld0JveD0iMCAwIDI3MC42MDAwMSAyNjkuNTQ2NjYiCiAgIGhlaWdodD0iMjY5LjU0NjY2IgogICB3aWR0aD0iMjcwLjYwMDAxIgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpZD0ic3ZnMiIKICAgdmVyc2lvbj0iMS4xIj48bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE4Ij48cmRmOlJERj48Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+PGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+PGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZGVmcwogICAgIGlkPSJkZWZzNiIgLz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMzMzMzMzMywwLDAsLTEuMzMzMzMzMywwLDI2OS41NDY2NykiCiAgICAgaWQ9ImcxMCI+PGcKICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoMC4xKSIKICAgICAgIGlkPSJnMTIiPjxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTQiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiCiAgICAgICAgIGQ9Im0gMjAyOS40OCw5NjIuNDQxIGMgMCwxNzAuMDk5IC0xMDUuNDYsMzE4Ljc5OSAtMjY0LjE3LDM3Ni42NTkgNi45OCwzNS44NiAxMC42Miw3MS43MSAxMC42MiwxMDkuMDUgMCwzMTYuMTkgLTI1Ny4yNCw1NzMuNDMgLTU3My40Nyw1NzMuNDMgLTE4NC43MiwwIC0zNTYuNTU4LC04OC41OSAtNDY0LjUzLC0yMzcuODUgLTUzLjA5LDQxLjE4IC0xMTguMjg1LDYzLjc1IC0xODYuMzA1LDYzLjc1IC0xNjcuODM2LDAgLTMwNC4zODMsLTEzNi41NCAtMzA0LjM4MywtMzA0LjM4IDAsLTM3LjA4IDYuNjE3LC03Mi41OCAxOS4wMzEsLTEwNi4wOCBDIDEwOC40ODgsMTM4MC4wOSAwLDEyMjcuODkgMCwxMDU4Ljg4IDAsODg3LjkxIDEwNS45NzcsNzM4LjUzOSAyNjUuMzk4LDY4MS4wOSBjIC02Ljc2OSwtMzUuNDQyIC0xMC40NiwtNzIuMDIgLTEwLjQ2LC0xMDkgQyAyNTQuOTM4LDI1Ni42MjEgNTExLjU2NiwwIDgyNy4wMjcsMCAxMDEyLjIsMCAxMTgzLjk0LDg4Ljk0MTQgMTI5MS4zLDIzOC44MzIgYyA1My40NSwtNDEuOTYxIDExOC44LC02NC45OTIgMTg2LjU2LC02NC45OTIgMTY3LjgzLDAgMzA0LjM4LDEzNi40OTIgMzA0LjM4LDMwNC4zMzIgMCwzNy4wNzggLTYuNjIsNzIuNjI5IC0xOS4wMywxMDYuMTI5IDE1Ny43OCw1Ni44NzkgMjY2LjI3LDIwOS4xMjkgMjY2LjI3LDM3OC4xNCIgLz48cGF0aAogICAgICAgICBpZD0icGF0aDE2IgogICAgICAgICBzdHlsZT0iZmlsbDojZmFjZjA5O2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIgogICAgICAgICBkPSJtIDc5Ny44OTgsMTE1MC45MyA0NDQuMDcyLC0yMDIuNDUgNDQ4LjA1LDM5Mi41OCBjIDYuNDksMzIuMzkgOS42Niw2NC42NyA5LjY2LDk4LjQ2IDAsMjc2LjIzIC0yMjQuNjgsNTAwLjk1IC01MDAuOSw1MDAuOTUgLTE2NS4yNCwwIC0zMTkuMzcsLTgxLjM2IC00MTMuMDUzLC0yMTcuNzkgbCAtNzQuNTI0LC0zODYuNjQgODYuNjk1LC0xODUuMTEiIC8+PHBhdGgKICAgICAgICAgaWQ9InBhdGgxOCIKICAgICAgICAgc3R5bGU9ImZpbGw6IzQ5YzFhZTtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIKICAgICAgICAgZD0ibSAzMzguMjIzLDY4MC42NzIgYyAtNi40ODksLTMyLjM4MyAtOS44MDksLTY1Ljk4MSAtOS44MDksLTk5Ljk3MyAwLC0yNzYuOTI5IDIyNS4zMzYsLTUwMi4yNTc2IDUwMi4zMTMsLTUwMi4yNTc2IDE2Ni41OTMsMCAzMjEuNDczLDgyLjExNzYgNDE1LjAxMywyMTkuOTQ5NiBsIDczLjk3LDM4NS4zNDcgLTk4LjcyLDE4OC42MjEgTCA3NzUuMTU2LDEwNzUuNTcgMzM4LjIyMyw2ODAuNjcyIiAvPjxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjAiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNlZjI5OWI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiCiAgICAgICAgIGQ9Im0gMzM1LjQxLDE0NDkuMTggMzA0LjMzMiwtNzEuODYgNjYuNjgsMzQ2LjAyIGMgLTQxLjU4NiwzMS43OCAtOTIuOTMsNDkuMTggLTE0NS43MzEsNDkuMTggLTEzMi4yNSwwIC0yMzkuODEyLC0xMDcuNjEgLTIzOS44MTIsLTIzOS44NyAwLC0yOS4yMSA0Ljg3OSwtNTcuMjIgMTQuNTMxLC04My40NyIgLz48cGF0aAogICAgICAgICBpZD0icGF0aDIyIgogICAgICAgICBzdHlsZT0iZmlsbDojNGNhYmU0O2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIgogICAgICAgICBkPSJNIDMwOC45OTIsMTM3Ni43IEMgMTczLjAyLDEzMzEuNjQgNzguNDgwNSwxMjAxLjMgNzguNDgwNSwxMDU3LjkzIDc4LjQ4MDUsOTE4LjM0IDE2NC44Miw3OTMuNjggMjk0LjQwNiw3NDQuMzUyIGwgNDI2Ljk4MSwzODUuOTM4IC03OC4zOTUsMTY3LjUxIC0zMzQsNzguOSIgLz48cGF0aAogICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICBzdHlsZT0iZmlsbDojODVjZTI2O2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIgogICAgICAgICBkPSJtIDEzMjMuOCwyOTguNDEgYyA0MS43NCwtMzIuMDkgOTIuODMsLTQ5LjU5IDE0NC45OCwtNDkuNTkgMTMyLjI1LDAgMjM5LjgxLDEwNy41NTkgMjM5LjgxLDIzOS44MjEgMCwyOS4xNiAtNC44OCw1Ny4xNjggLTE0LjUzLDgzLjQxOCBsIC0zMDQuMDgsNzEuMTYgLTY2LjE4LC0zNDQuODA5IiAvPjxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjYiCiAgICAgICAgIHN0eWxlPSJmaWxsOiMzMTc3YTc7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiCiAgICAgICAgIGQ9Im0gMTM4NS42Nyw3MjIuOTMgMzM0Ljc2LC03OC4zMDEgYyAxMzYuMDIsNDQuOTYxIDIzMC41NiwxNzUuMzUxIDIzMC41NiwzMTguNzYyIDAsMTM5LjMzOSAtODYuNTQsMjYzLjg1OSAtMjE2LjM4LDMxMy4wMzkgbCAtNDM3Ljg0LC0zODMuNTkgODguOSwtMTY5LjkxIiAvPjwvZz48L2c+PC9zdmc+'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_outline.ts b/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_outline.ts deleted file mode 100644 index 7271f5b32d547..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_outline.ts +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -export const elasticOutline = 'data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3Csvg%20viewBox%3D%22-3.948730230331421%20-1.7549896240234375%20245.25946044921875%20241.40370178222656%22%20width%3D%22245.25946044921875%22%20height%3D%22241.40370178222656%22%20style%3D%22enable-background%3Anew%200%200%20686.2%20235.7%3B%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%232D2D2D%3B%7D%0A%3C%2Fstyle%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Cg%20transform%3D%22matrix%281%2C%200%2C%200%2C%201%2C%200%2C%200%29%22%3E%0A%20%20%20%20%3Cg%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M329.4%2C160.3l4.7-0.5l0.3%2C9.6c-12.4%2C1.7-23%2C2.6-31.8%2C2.6c-11.7%2C0-20-3.4-24.9-10.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-4.9-6.8-7.3-17.4-7.3-31.7c0-28.6%2C11.4-42.9%2C34.1-42.9c11%2C0%2C19.2%2C3.1%2C24.6%2C9.2c5.4%2C6.1%2C8.1%2C15.8%2C8.1%2C28.9l-0.7%2C9.3h-53.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0%2C9%2C1.6%2C15.7%2C4.9%2C20c3.3%2C4.3%2C8.9%2C6.5%2C17%2C6.5C312.8%2C161.2%2C321.1%2C160.9%2C329.4%2C160.3z%20M325%2C124.9c0-10-1.6-17.1-4.8-21.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.2-4.1-8.4-6.2-15.6-6.2c-7.2%2C0-12.7%2C2.2-16.3%2C6.5c-3.6%2C4.3-5.5%2C11.3-5.6%2C20.9H325z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M354.3%2C171.4V64h12.2v107.4H354.3z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M443.5%2C113.5v41.1c0%2C4.1%2C10.1%2C3.9%2C10.1%2C3.9l-0.6%2C10.8c-8.6%2C0-15.7%2C0.7-20-3.4c-9.8%2C4.3-19.5%2C6.1-29.3%2C6.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.5%2C0-13.2-2.1-17.1-6.4c-3.9-4.2-5.9-10.3-5.9-18.3c0-7.9%2C2-13.8%2C6-17.5c4-3.7%2C10.3-6.1%2C18.9-6.9l25.6-2.4v-7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0-5.5-1.2-9.5-3.6-11.9c-2.4-2.4-5.7-3.6-9.8-3.6l-32.1%2C0V87.2h31.3c9.2%2C0%2C15.9%2C2.1%2C20.1%2C6.4C441.4%2C97.8%2C443.5%2C104.5%2C443.5%2C113.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bz%20M393.3%2C146.7c0%2C10%2C4.1%2C15%2C12.4%2C15c7.4%2C0%2C14.7-1.2%2C21.8-3.7l3.7-1.3v-26.9l-24.1%2C2.3c-4.9%2C0.4-8.4%2C1.8-10.6%2C4.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3BC394.4%2C138.7%2C393.3%2C142.2%2C393.3%2C146.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M491.2%2C98.2c-11.8%2C0-17.8%2C4.1-17.8%2C12.4c0%2C3.8%2C1.4%2C6.5%2C4.1%2C8.1c2.7%2C1.6%2C8.9%2C3.2%2C18.6%2C4.9%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc9.7%2C1.7%2C16.5%2C4%2C20.5%2C7.1c4%2C3%2C6%2C8.7%2C6%2C17.1c0%2C8.4-2.7%2C14.5-8.1%2C18.4c-5.4%2C3.9-13.2%2C5.9-23.6%2C5.9c-6.7%2C0-29.2-2.5-29.2-2.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bl0.7-10.6c12.9%2C1.2%2C22.3%2C2.2%2C28.6%2C2.2c6.3%2C0%2C11.1-1%2C14.4-3c3.3-2%2C5-5.4%2C5-10.1c0-4.7-1.4-7.9-4.2-9.6c-2.8-1.7-9-3.3-18.6-4.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-9.6-1.5-16.4-3.7-20.4-6.7c-4-2.9-6-8.4-6-16.3c0-7.9%2C2.8-13.8%2C8.4-17.6c5.6-3.8%2C12.6-5.7%2C20.9-5.7c6.6%2C0%2C29.6%2C1.7%2C29.6%2C1.7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bv10.7C508.1%2C99%2C498.2%2C98.2%2C491.2%2C98.2z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M581.7%2C99.5h-25.9v39c0%2C9.3%2C0.7%2C15.5%2C2%2C18.4c1.4%2C2.9%2C4.6%2C4.4%2C9.7%2C4.4l14.5-1l0.8%2C10.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.3%2C1.2-12.8%2C1.8-16.6%2C1.8c-8.5%2C0-14.3-2.1-17.6-6.2c-3.3-4.1-4.9-12-4.9-23.6V99.5h-11.6V88.9h11.6V63.9h12.1v24.9h25.9V99.5z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M598.7%2C78.4V64.3h12.2v14.2H598.7z%20M598.7%2C171.4V88.9h12.2v82.5H598.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M663.8%2C87.2c3.6%2C0%2C9.7%2C0.7%2C18.3%2C2l3.9%2C0.5l-0.5%2C9.9c-8.7-1-15.1-1.5-19.2-1.5c-9.2%2C0-15.5%2C2.2-18.8%2C6.6%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.3%2C4.4-5%2C12.6-5%2C24.5c0%2C11.9%2C1.5%2C20.2%2C4.6%2C24.9c3.1%2C4.7%2C9.5%2C7%2C19.3%2C7l19.2-1.5l0.5%2C10.1c-10.1%2C1.5-17.7%2C2.3-22.7%2C2.3%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-12.7%2C0-21.5-3.3-26.3-9.8c-4.8-6.5-7.3-17.5-7.3-33c0-15.5%2C2.6-26.4%2C7.8-32.6C643%2C90.4%2C651.7%2C87.2%2C663.8%2C87.2z%22%2F%3E%0A%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M236.6%2C123.5c0-19.8-12.3-37.2-30.8-43.9c0.8-4.2%2C1.2-8.4%2C1.2-12.7C207%2C30%2C177%2C0%2C140.2%2C0%26%2310%3B%26%239%3B%26%239%3BC118.6%2C0%2C98.6%2C10.3%2C86%2C27.7c-6.2-4.8-13.8-7.4-21.7-7.4c-19.6%2C0-35.5%2C15.9-35.5%2C35.5c0%2C4.3%2C0.8%2C8.5%2C2.2%2C12.4%26%2310%3B%26%239%3B%26%239%3BC12.6%2C74.8%2C0%2C92.5%2C0%2C112.2c0%2C19.9%2C12.4%2C37.3%2C30.9%2C44c-0.8%2C4.1-1.2%2C8.4-1.2%2C12.7c0%2C36.8%2C29.9%2C66.7%2C66.7%2C66.7%26%2310%3B%26%239%3B%26%239%3Bc21.6%2C0%2C41.6-10.4%2C54.1-27.8c6.2%2C4.9%2C13.8%2C7.6%2C21.7%2C7.6c19.6%2C0%2C35.5-15.9%2C35.5-35.5c0-4.3-0.8-8.5-2.2-12.4%26%2310%3B%26%239%3B%26%239%3BC223.9%2C160.9%2C236.6%2C143.2%2C236.6%2C123.5z%20M91.6%2C34.8c10.9-15.9%2C28.9-25.4%2C48.1-25.4c32.2%2C0%2C58.4%2C26.2%2C58.4%2C58.4%26%2310%3B%26%239%3B%26%239%3Bc0%2C3.9-0.4%2C7.7-1.1%2C11.5l-52.2%2C45.8L93%2C101.5L82.9%2C79.9L91.6%2C34.8z%20M65.4%2C29c6.2%2C0%2C12.1%2C2%2C17%2C5.7l-7.8%2C40.3l-35.5-8.4%26%2310%3B%26%239%3B%26%239%3Bc-1.1-3.1-1.7-6.3-1.7-9.7C37.4%2C41.6%2C49.9%2C29%2C65.4%2C29z%20M9.1%2C112.3c0-16.7%2C11-31.9%2C26.9-37.2L75%2C84.4l9.1%2C19.5l-49.8%2C45%26%2310%3B%26%239%3B%26%239%3BC19.2%2C143.1%2C9.1%2C128.6%2C9.1%2C112.3z%20M145.2%2C200.9c-10.9%2C16.1-29%2C25.6-48.4%2C25.6c-32.3%2C0-58.6-26.3-58.6-58.5c0-4%2C0.4-7.9%2C1.1-11.7%26%2310%3B%26%239%3B%26%239%3Bl50.9-46l52%2C23.7l11.5%2C22L145.2%2C200.9z%20M171.2%2C206.6c-6.1%2C0-12-2-16.9-5.8l7.7-40.2l35.4%2C8.3c1.1%2C3.1%2C1.7%2C6.3%2C1.7%2C9.7%26%2310%3B%26%239%3B%26%239%3BC199.2%2C194.1%2C186.6%2C206.6%2C171.2%2C206.6z%20M200.5%2C160.5l-39-9.1l-10.4-19.8l51-44.7c15.1%2C5.7%2C25.2%2C20.2%2C25.2%2C36.5%26%2310%3B%26%239%3B%26%239%3BC227.4%2C140.1%2C216.4%2C155.3%2C200.5%2C160.5z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx index 7276a55bdf49d..8839910d78e0d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { image } from '../image'; import { Render } from './render'; -import { elasticLogo } from '../../lib/elastic_logo'; +import { elasticLogo } from '../../../../../../src/plugins/presentation_util/common/lib'; storiesOf('renderers/image', module).add('default', () => { const config = { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/repeat_image.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/repeat_image.stories.tsx index 8dd059cf7a32f..ed2706389d83d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/repeat_image.stories.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/repeat_image.stories.tsx @@ -9,8 +9,10 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { repeatImage } from '../repeat_image'; import { Render } from './render'; -import { elasticLogo } from '../../lib/elastic_logo'; -import { elasticOutline } from '../../lib/elastic_outline'; +import { + elasticLogo, + elasticOutline, +} from '../../../../../../src/plugins/presentation_util/common/lib'; storiesOf('renderers/repeatImage', module).add('default', () => { const config = { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts index c6b40936c288a..3295332bb6316 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts @@ -14,7 +14,6 @@ import { pie } from './pie'; import { plot } from './plot'; import { progress } from './progress'; import { repeatImage } from './repeat_image'; -import { revealImage } from './reveal_image'; import { shape } from './shape'; import { table } from './table'; import { text } from './text'; @@ -29,7 +28,6 @@ export const renderFunctions = [ plot, progress, repeatImage, - revealImage, shape, table, text, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts new file mode 100644 index 0000000000000..bf9b6a744e686 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts @@ -0,0 +1,11 @@ +/* + * 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 { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public'; + +export const renderFunctions = [revealImageRenderer]; +export const renderFunctionFactories = []; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx index 8c88fe820d5d3..86e9daed105db 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx @@ -7,8 +7,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; -import { elasticLogo } from '../lib/elastic_logo'; -import { isValidUrl } from '../../common/lib/url'; +import { elasticLogo, isValidUrl } from '../../../../../src/plugins/presentation_util/common/lib'; import { Return as Arguments } from '../functions/common/image'; import { RendererStrings } from '../../i18n'; import { RendererFactory } from '../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/index.ts index 3c2d90f81eedc..16a052edbbe82 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/index.ts @@ -15,11 +15,23 @@ import { renderFunctionFactories as filterFactories, } from './filters'; +import { + renderFunctions as externalFunctions, + renderFunctionFactories as externalFactories, +} from './external'; + import { renderFunctions as coreFunctions, renderFunctionFactories as coreFactories } from './core'; -export const renderFunctions = [...coreFunctions, ...filterFunctions, ...embeddableFunctions]; +export const renderFunctions = [ + ...coreFunctions, + ...filterFunctions, + ...embeddableFunctions, + ...externalFunctions, +]; + export const renderFunctionFactories = [ ...coreFactories, ...embeddableFactories, ...filterFactories, + ...externalFactories, ]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.ts index 8286609aa334f..149a887683413 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.ts @@ -7,8 +7,10 @@ import $ from 'jquery'; import { times } from 'lodash'; -import { elasticOutline } from '../lib/elastic_outline'; -import { isValidUrl } from '../../common/lib/url'; +import { + elasticOutline, + isValidUrl, +} from '../../../../../src/plugins/presentation_util/common/lib'; import { RendererStrings, ErrorStrings } from '../../i18n'; import { Return as Arguments } from '../functions/common/repeat_image'; import { RendererFactory } from '../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/reveal_image.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/reveal_image.stories.tsx deleted file mode 100644 index 672cecca1bead..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/reveal_image.stories.tsx +++ /dev/null @@ -1,25 +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 { storiesOf } from '@storybook/react'; -import { revealImage } from '../'; -import { Render } from '../../__stories__/render'; -import { elasticOutline } from '../../../lib/elastic_outline'; -import { elasticLogo } from '../../../lib/elastic_logo'; -import { Origin } from '../../../functions/common/revealImage'; - -storiesOf('renderers/revealImage', module).add('default', () => { - const config = { - image: elasticLogo, - emptyImage: elasticOutline, - origin: Origin.LEFT, - percent: 0.45, - }; - - return ; -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.ts deleted file mode 100644 index 8d9ceb70f17a6..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.ts +++ /dev/null @@ -1,88 +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 { elasticOutline } from '../../lib/elastic_outline'; -import { isValidUrl } from '../../../common/lib/url'; -import { RendererStrings } from '../../../i18n'; -import { RendererFactory } from '../../../types'; -import { Output as Arguments } from '../../functions/common/revealImage'; - -const { revealImage: strings } = RendererStrings; - -export const revealImage: RendererFactory = () => ({ - name: 'revealImage', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render(domNode, config, handlers) { - const aligner = document.createElement('div'); - const img = new Image(); - - // modify the top-level container class - domNode.className = 'revealImage'; - - // set up the overlay image - function onLoad() { - setSize(); - finish(); - } - img.onload = onLoad; - - img.className = 'revealImage__image'; - img.style.clipPath = getClipPath(config.percent, config.origin); - img.style.setProperty('-webkit-clip-path', getClipPath(config.percent, config.origin)); - img.src = isValidUrl(config.image) ? config.image : elasticOutline; - handlers.onResize(onLoad); - - // set up the underlay, "empty" image - aligner.className = 'revealImageAligner'; - aligner.appendChild(img); - if (isValidUrl(config.emptyImage)) { - // only use empty image if one is provided - aligner.style.backgroundImage = `url(${config.emptyImage})`; - } - - function finish() { - const firstChild = domNode.firstChild; - if (firstChild) { - domNode.replaceChild(aligner, firstChild); - } else { - domNode.appendChild(aligner); - } - handlers.done(); - } - - function getClipPath(percent: number, origin = 'bottom') { - const directions: Record = { bottom: 0, left: 1, top: 2, right: 3 }; - const values: Array = [0, 0, 0, 0]; - values[directions[origin]] = `${100 - percent * 100}%`; - return `inset(${values.join(' ')})`; - } - - function setSize() { - const imgDimensions = { - height: img.naturalHeight, - width: img.naturalWidth, - ratio: img.naturalHeight / img.naturalWidth, - }; - - const domNodeDimensions = { - height: domNode.clientHeight, - width: domNode.clientWidth, - ratio: domNode.clientHeight / domNode.clientWidth, - }; - - if (imgDimensions.ratio > domNodeDimensions.ratio) { - img.style.height = `${domNodeDimensions.height}px`; - img.style.width = 'initial'; - } else { - img.style.width = `${domNodeDimensions.width}px`; - img.style.height = 'initial'; - } - } - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index 2caf41f0777e1..480d8ea364c42 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -10,10 +10,12 @@ import PropTypes from 'prop-types'; import { EuiSpacer, EuiFormRow, EuiButtonGroup } from '@elastic/eui'; import { get } from 'lodash'; import { AssetPicker } from '../../../../public/components/asset_picker'; -import { elasticOutline } from '../../../lib/elastic_outline'; -import { resolveFromArgs } from '../../../../common/lib/resolve_dataurl'; -import { isValidHttpUrl } from '../../../../common/lib/httpurl'; -import { encode } from '../../../../common/lib/dataurl'; +import { + encode, + elasticOutline, + isValidHttpUrl, + resolveFromArgs, +} from '../../../../../../../src/plugins/presentation_util/public'; import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { VALID_IMAGE_TYPES } from '../../../../common/lib/constants'; import { ArgumentStrings } from '../../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/image.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/image.js index 37b22e376141f..f974667b7fad9 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/image.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/image.js @@ -5,8 +5,10 @@ * 2.0. */ -import { elasticLogo } from '../../lib/elastic_logo'; -import { resolveFromArgs } from '../../../common/lib/resolve_dataurl'; +import { + elasticLogo, + resolveFromArgs, +} from '../../../../../../src/plugins/presentation_util/common/lib'; import { ViewStrings } from '../../../i18n'; const { Image: strings } = ViewStrings; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/revealImage.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/revealImage.js index 30e0b9a640f92..f9bba68c56949 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/revealImage.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/revealImage.js @@ -6,7 +6,6 @@ */ import { ViewStrings } from '../../../i18n'; - const { RevealImage: strings } = ViewStrings; export const revealImage = () => ({ diff --git a/x-pack/plugins/canvas/common/lib/index.ts b/x-pack/plugins/canvas/common/lib/index.ts index afce09c6d5ee9..a23b569640f5a 100644 --- a/x-pack/plugins/canvas/common/lib/index.ts +++ b/x-pack/plugins/canvas/common/lib/index.ts @@ -8,7 +8,6 @@ export * from './datatable'; export * from './autocomplete'; export * from './constants'; -export * from './dataurl'; export * from './errors'; export * from './expression_form_handlers'; export * from './fetch'; @@ -16,10 +15,6 @@ export * from './fonts'; export * from './get_field_type'; export * from './get_legend_config'; export * from './hex_to_rgb'; -export * from './httpurl'; -export * from './missing_asset'; export * from './palettes'; export * from './pivot_object_array'; -export * from './resolve_dataurl'; export * from './unquote_string'; -export * from './url'; diff --git a/x-pack/plugins/canvas/common/lib/missing_asset.ts b/x-pack/plugins/canvas/common/lib/missing_asset.ts deleted file mode 100644 index d47648b44059c..0000000000000 --- a/x-pack/plugins/canvas/common/lib/missing_asset.ts +++ /dev/null @@ -1,3 +0,0 @@ -/* eslint-disable */ -// CC0, source: https://pixabay.com/en/question-mark-confirmation-question-838656/ -export const missingImage = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzMSAzMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48ZmlsdGVyIGlkPSJiIiB4PSItLjM2IiB5PSItLjM2IiB3aWR0aD0iMS43MiIgaGVpZ2h0PSIxLjcyIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuNDY0Ii8+PC9maWx0ZXI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMTU5LjM0IiB4Mj0iMTg0LjQ4IiB5MT0iNzI3LjM2IiB5Mj0iNzQ5Ljg5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMTEuNTMgLTU0OS42OCkgc2NhbGUoLjc3MDAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiNlYmYwZWQiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNmYWZhZmEiIG9mZnNldD0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xNS40MzcgMi42OTVsMTQuNTA2IDI0LjQ3NkgxLjI4N2wxNC4xNS0yNC40NzZ6IiBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9InJlZCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBhdGggdHJhbnNmb3JtPSJtYXRyaXgoLjgzMTk3IDAgMCAuNTU0NjYgLTc4LjU4MyAtMzgzLjUxKSIgZD0iTTExMS4zMSA3MzEuMmMtMy4yODMtMy45MjUtMy41OTUtNi4xNDgtMi4wMjQtMTAuNDM4IDMuMzM2LTYuMTQ1IDQuNDk2LTguMDY4IDUuNDEtOS40MDUgMS45MDEgNS4xNjIgMi4xMjYgMTkuMTQtMy4zODYgMTkuODQzeiIgZmlsbD0iI2ZmZiIgZmlsbC1vcGFjaXR5PSIuODc2IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbHRlcj0idXJsKCNiKSIvPjxnIGZpbGwtb3BhY2l0eT0iLjgyIj48cGF0aCBkPSJNMTUuMDQ2IDIwLjIyN2gtLjQxNWMtLjAxMy0uNzQ4LjAyLTEuMzA4LjEwMS0xLjY3OC4wODgtLjM3MS4zMDctLjg4LjY1OC0xLjUyOC4zNTctLjY1NC41OS0xLjE3Ni42OTctMS41NjcuMTE1LS4zOTguMTcyLS44ODcuMTcyLTEuNDY3IDAtLjg5Ni0uMTc1LTEuNTU3LS41MjYtMS45ODItLjM1LS40MjUtLjc2NS0uNjM3LTEuMjQ0LS42MzctLjM2NCAwLS42Ny4wOTgtLjkyLjI5My0uMTg5LjE0OS0uMjgzLjMwNC0uMjgzLjQ2NiAwIC4xMDcuMDY0LjI3Ni4xOTIuNTA1LjI5LjUyLjQzNS45NjEuNDM1IDEuMzI1IDAgLjMzLS4xMTUuNjA3LS4zNDQuODNhMS4xMzggMS4xMzggMCAwIDEtLjg0LjMzM2MtLjM3NyAwLS42OTQtLjEzMS0uOTUtLjM5NC0uMjU2LS4yNy0uMzg0LS42MjQtLjM4NC0xLjA2MiAwLS43OTYuMzQ0LTEuNDk0IDEuMDMxLTIuMDk0LjY4OC0uNiAxLjY0OS0uOSAyLjg4My0uOSAxLjMwOCAwIDIuMzAyLjMxNCAyLjk4My45NC42ODguNjIxIDEuMDMyIDEuMzczIDEuMDMyIDIuMjU2IDAgLjY0LS4xNzYgMS4yMzQtLjUyNiAxLjc4LS4zNTEuNTQtMS4wMjkgMS4xNC0yLjAzMyAxLjgtLjY3NC40NDUtMS4xMi44NDMtMS4zMzUgMS4xOTQtLjIxLjM0My0uMzM3Ljg3My0uMzg0IDEuNTg3bS0uMTEyIDEuNDc3Yy40NTIgMCAuODM2LjE1OCAxLjE1My40NzUuMzE3LjMxNy40NzYuNzAxLjQ3NiAxLjE1MyAwIC40NTItLjE1OS44NC0uNDc2IDEuMTYzYTEuNTcgMS41NyAwIDAgMS0xLjE1My40NzUgMS41NyAxLjU3IDAgMCAxLTEuMTUzLS40NzUgMS42MDQgMS42MDQgMCAwIDEtLjQ3NS0xLjE2M2MwLS40NTIuMTU5LS44MzYuNDc1LTEuMTUzYTEuNTcgMS41NyAwIDAgMSAxLjE1My0uNDc1IiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii40ODYiLz48cGF0aCBkPSJNMTUuMzI3IDIwLjUwOGgtLjQxNWMtLjAxMy0uNzQ4LjAyLTEuMzA4LjEwMS0xLjY3OC4wODgtLjM3MS4zMDctLjg4LjY1OC0xLjUyOC4zNTctLjY1NC41OS0xLjE3Ni42OTctMS41NjcuMTE1LS4zOTguMTcyLS44ODcuMTcyLTEuNDY2IDAtLjg5Ny0uMTc1LTEuNTU4LS41MjYtMS45ODMtLjM1LS40MjQtLjc2NS0uNjM3LTEuMjQzLS42MzctLjM2NSAwLS42NzEuMDk4LS45Mi4yOTMtLjE5LjE0OS0uMjg0LjMwNC0uMjg0LjQ2NiAwIC4xMDguMDY0LjI3Ni4xOTIuNTA1LjI5LjUyLjQzNS45NjEuNDM1IDEuMzI1IDAgLjMzLS4xMTUuNjA3LS4zNDQuODNhMS4xMzggMS4xMzggMCAwIDEtLjg0LjMzM2MtLjM3NyAwLS42OTQtLjEzMS0uOTUtLjM5NC0uMjU2LS4yNy0uMzg0LS42MjQtLjM4NC0xLjA2MiAwLS43OTYuMzQ0LTEuNDkzIDEuMDMxLTIuMDk0LjY4OC0uNiAxLjY0OS0uOSAyLjg4My0uOSAxLjMwOCAwIDIuMzAyLjMxNCAyLjk4My45NC42ODguNjIxIDEuMDMyIDEuMzczIDEuMDMyIDIuMjU2IDAgLjY0LS4xNzYgMS4yMzQtLjUyNiAxLjc4LS4zNS41NC0xLjAyOCAxLjE0LTIuMDMzIDEuOC0uNjc0LjQ0NS0uODUzLjg0My0xLjA2OCAxLjE5NC0uMjEuMzQzLS4zMzcuODczLS4zODUgMS41ODdtLS4zNzggMS40NzdjLjQ1MiAwIC44MzYuMTU4IDEuMTUzLjQ3NS4zMTcuMzE3LjQ3Ni43MDEuNDc2IDEuMTUzIDAgLjQ1Mi0uMTU5Ljg0LS40NzYgMS4xNjNhMS41NyAxLjU3IDAgMCAxLTEuMTUzLjQ3NiAxLjU3IDEuNTcgMCAwIDEtMS4xNTMtLjQ3NiAxLjYwNCAxLjYwNCAwIDAgMS0uNDc1LTEuMTYzYzAtLjQ1Mi4xNTktLjgzNi40NzUtMS4xNTNhMS41NyAxLjU3IDAgMCAxIDEuMTUzLS40NzUiIGZpbGwtb3BhY2l0eT0iLjgyIi8+PC9nPjwvc3ZnPg=='; diff --git a/x-pack/plugins/canvas/i18n/functions/function_errors.ts b/x-pack/plugins/canvas/i18n/functions/function_errors.ts index 4a85018c1b4ac..a01cb09a38347 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_errors.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_errors.ts @@ -19,7 +19,6 @@ import { errors as joinRows } from './dict/join_rows'; import { errors as ply } from './dict/ply'; import { errors as pointseries } from './dict/pointseries'; import { errors as progress } from './dict/progress'; -import { errors as revealImage } from './dict/reveal_image'; import { errors as timefilter } from './dict/timefilter'; import { errors as to } from './dict/to'; @@ -38,7 +37,6 @@ export const getFunctionErrors = () => ({ ply, pointseries, progress, - revealImage, timefilter, to, }); diff --git a/x-pack/plugins/canvas/i18n/functions/function_help.ts b/x-pack/plugins/canvas/i18n/functions/function_help.ts index 512ebc4ff8c93..b72d410ddd63f 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_help.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_help.ts @@ -57,7 +57,6 @@ import { help as progress } from './dict/progress'; import { help as render } from './dict/render'; import { help as repeatImage } from './dict/repeat_image'; import { help as replace } from './dict/replace'; -import { help as revealImage } from './dict/reveal_image'; import { help as rounddate } from './dict/rounddate'; import { help as rowCount } from './dict/row_count'; import { help as savedLens } from './dict/saved_lens'; @@ -218,7 +217,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({ render, repeatImage, replace, - revealImage, rounddate, rowCount, savedLens, diff --git a/x-pack/plugins/canvas/i18n/renderers.ts b/x-pack/plugins/canvas/i18n/renderers.ts index f74516433f924..29687155818e7 100644 --- a/x-pack/plugins/canvas/i18n/renderers.ts +++ b/x-pack/plugins/canvas/i18n/renderers.ts @@ -139,16 +139,6 @@ export const RendererStrings = { defaultMessage: 'Repeat an image a given number of times', }), }, - revealImage: { - getDisplayName: () => - i18n.translate('xpack.canvas.renderer.revealImage.displayName', { - defaultMessage: 'Image reveal', - }), - getHelpDescription: () => - i18n.translate('xpack.canvas.renderer.revealImage.helpDescription', { - defaultMessage: 'Reveal a percentage of an image to make a custom gauge-style chart', - }), - }, shape: { getDisplayName: () => i18n.translate('xpack.canvas.renderer.shape.displayName', { diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json index 5faeaefc9e392..85d2e0709cb3e 100644 --- a/x-pack/plugins/canvas/kibana.json +++ b/x-pack/plugins/canvas/kibana.json @@ -10,6 +10,7 @@ "charts", "data", "embeddable", + "expressionRevealImage", "expressions", "features", "inspector", diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts index f8c6354d3935f..e3824798d1df1 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts @@ -13,7 +13,7 @@ import { getId } from '../../lib/get_id'; // @ts-expect-error untyped local import { findExistingAsset } from '../../lib/find_existing_asset'; import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; -import { encode } from '../../../common/lib/dataurl'; +import { encode } from '../../../../../../src/plugins/presentation_util/public'; // @ts-expect-error untyped local import { elementsRegistry } from '../../lib/elements_registry'; // @ts-expect-error untyped local diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/custom_element_modal.stories.tsx b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/custom_element_modal.stories.tsx index 2e6d83cb1c8ac..93574270757f6 100644 --- a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/custom_element_modal.stories.tsx +++ b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/custom_element_modal.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { CustomElementModal } from '../custom_element_modal'; -import { elasticLogo } from '../../../lib/elastic_logo'; +import { elasticLogo } from '../../../../../../../src/plugins/presentation_util/public'; storiesOf('components/Elements/CustomElementModal', module) .add('with title', () => ( diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx index 86d9cab4eeea1..51ffe57fe5e76 100644 --- a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx +++ b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx @@ -29,7 +29,7 @@ import { import { i18n } from '@kbn/i18n'; import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; -import { encode } from '../../../common/lib/dataurl'; +import { encode } from '../../../../../../src/plugins/presentation_util/public'; import { ElementCard } from '../element_card'; const MAX_NAME_LENGTH = 40; diff --git a/x-pack/plugins/canvas/public/components/download/download.tsx b/x-pack/plugins/canvas/public/components/download/download.tsx index 856d6cb7e080e..89cd999481007 100644 --- a/x-pack/plugins/canvas/public/components/download/download.tsx +++ b/x-pack/plugins/canvas/public/components/download/download.tsx @@ -9,7 +9,7 @@ import { toByteArray } from 'base64-js'; import fileSaver from 'file-saver'; import PropTypes from 'prop-types'; import React, { ReactElement } from 'react'; -import { parseDataUrl } from '../../../common/lib/dataurl'; +import { parseDataUrl } from '../../../../../../src/plugins/presentation_util/public'; interface Props { children: ReactElement; diff --git a/x-pack/plugins/canvas/public/components/element_card/__stories__/element_card.stories.tsx b/x-pack/plugins/canvas/public/components/element_card/__stories__/element_card.stories.tsx index ae0d4328aa98d..4c68f185b196f 100644 --- a/x-pack/plugins/canvas/public/components/element_card/__stories__/element_card.stories.tsx +++ b/x-pack/plugins/canvas/public/components/element_card/__stories__/element_card.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { ElementCard } from '../element_card'; -import { elasticLogo } from '../../../lib/elastic_logo'; +import { elasticLogo } from '../../../../../../../src/plugins/presentation_util/public'; storiesOf('components/Elements/ElementCard', module) .addDecorator((story) => ( diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx index 716f757b7c25e..dd7df7059a7a4 100644 --- a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx +++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx @@ -13,7 +13,7 @@ import { SavedObjectFinderUi, SavedObjectMetaData, } from '../../../../../../src/plugins/saved_objects/public/'; -import { useServices } from '../../services'; +import { usePlatformService, useServices } from '../../services'; const strings = { getNoItemsText: () => @@ -33,9 +33,10 @@ export interface Props { export const AddEmbeddableFlyout: FC = ({ onSelect, availableEmbeddables, onClose }) => { const services = useServices(); - const { embeddables, platform } = services; + const platformService = usePlatformService(); + const { embeddables } = services; const { getEmbeddableFactories } = embeddables; - const { getSavedObjects, getUISettings } = platform; + const { getSavedObjects, getUISettings } = platformService; const onAddPanel = (id: string, savedObjectType: string, name: string) => { const embeddableFactories = getEmbeddableFactories(); diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx index 6f98baf944bac..81532816d9c83 100644 --- a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx +++ b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx @@ -9,7 +9,7 @@ import React, { FC } from 'react'; import { ExpressionFunction } from 'src/plugins/expressions'; import { EuiButtonEmpty } from '@elastic/eui'; import copy from 'copy-to-clipboard'; -import { notifyService } from '../../services'; +import { useNotifyService } from '../../services'; import { generateFunctionReference } from './generate_function_reference'; interface Props { @@ -17,16 +17,15 @@ interface Props { } export const FunctionReferenceGenerator: FC = ({ functionRegistry }) => { + const notifyService = useNotifyService(); const functionDefinitions = Object.values(functionRegistry); const copyDocs = () => { copy(generateFunctionReference(functionDefinitions)); - notifyService - .getService() - .success( - `Please paste updated docs into '/kibana/docs/canvas/canvas-function-reference.asciidoc' and commit your changes.`, - { title: 'Copied function docs to clipboard' } - ); + notifyService.success( + `Please paste updated docs into '/kibana/docs/canvas/canvas-function-reference.asciidoc' and commit your changes.`, + { title: 'Copied function docs to clipboard' } + ); }; return ( diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts b/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts index 8f9b3923ff120..075e65bc24dab 100644 --- a/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts +++ b/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts @@ -10,7 +10,8 @@ import pluralize from 'pluralize'; import { ExpressionFunction, ExpressionFunctionParameter } from 'src/plugins/expressions'; import { functions as browserFunctions } from '../../../canvas_plugin_src/functions/browser'; import { functions as serverFunctions } from '../../../canvas_plugin_src/functions/server'; -import { isValidDataUrl, DATATABLE_COLUMN_TYPES } from '../../../common/lib'; +import { DATATABLE_COLUMN_TYPES } from '../../../common/lib'; +import { isValidDataUrl } from '../../../../../../src/plugins/presentation_util/public'; import { getFunctionExamples, FunctionExample } from './function_examples'; const ALPHABET = 'abcdefghijklmnopqrstuvwxyz'.split(''); diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/fixtures/test_elements.tsx b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/fixtures/test_elements.tsx index 17d6a3d11b60f..ef48b9815062c 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/fixtures/test_elements.tsx +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/fixtures/test_elements.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { elasticLogo } from '../../../../lib/elastic_logo'; +import { elasticLogo } from '../../../../../../../../src/plugins/presentation_util/public'; export const testCustomElements = [ { diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.ts b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.ts index 9b592d402f84c..524c1a48b6cee 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.ts +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.ts @@ -11,7 +11,7 @@ import { compose, withState } from 'recompose'; import { camelCase } from 'lodash'; import { cloneSubgraphs } from '../../lib/clone_subgraphs'; import * as customElementService from '../../lib/custom_element_service'; -import { withServices, WithServicesProps } from '../../services'; +import { withServices, WithServicesProps, pluginServices } from '../../services'; // @ts-expect-error untyped local import { selectToplevelNodes } from '../../state/actions/transient'; // @ts-expect-error untyped local @@ -68,6 +68,7 @@ const mergeProps = ( dispatchProps: DispatchProps, ownProps: OwnPropsWithState & WithServicesProps ): ComponentProps => { + const notifyService = pluginServices.getServices().notify; const { pageId } = stateProps; const { onClose, search, setCustomElements } = ownProps; @@ -94,7 +95,7 @@ const mergeProps = ( try { await findCustomElements(); } catch (err) { - ownProps.services.notify.error(err, { + notifyService.error(err, { title: `Couldn't find custom elements`, }); } @@ -105,7 +106,7 @@ const mergeProps = ( await customElementService.remove(id); await findCustomElements(); } catch (err) { - ownProps.services.notify.error(err, { + notifyService.error(err, { title: `Couldn't delete custom elements`, }); } @@ -121,7 +122,7 @@ const mergeProps = ( }); await findCustomElements(); } catch (err) { - ownProps.services.notify.error(err, { + notifyService.error(err, { title: `Couldn't update custom elements`, }); } diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx index dca549b6b38ed..1c95e844997a0 100644 --- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx +++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx @@ -97,7 +97,7 @@ export const Toolbar: FC = ({ {workpadName} - + ( workpad: { id: 'coolworkpad', name: 'Workpad of Cool', height: 10, width: 7 }, pageCount: 11, }} - sharingServices={{ basePath: platformService.getBasePathInterface() }} + sharingServices={{}} onExport={action('onExport')} /> )); @@ -30,7 +29,6 @@ storiesOf('components/WorkpadHeader/ShareMenu', module).add('with Reporting', () pageCount: 11, }} sharingServices={{ - basePath: platformService.getBasePathInterface(), reporting: reportingService.start, }} onExport={action('onExport')} diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/__stories__/flyout.stories.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/__stories__/flyout.stories.tsx index 4d38cc9fb88b4..d0c1eb550ea60 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/__stories__/flyout.stories.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/__stories__/flyout.stories.tsx @@ -26,17 +26,12 @@ storiesOf('components/WorkpadHeader/ShareMenu/ShareWebsiteFlyout', module) }, }) .add('default', () => ( - + )) .add('unsupported renderers', () => ( )); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx index 5da009e050a27..be337a6dcf00c 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/flyout.component.tsx @@ -24,13 +24,42 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { arrayBufferFetch } from '../../../../../common/lib/fetch'; +import { API_ROUTE_SHAREABLE_ZIP } from '../../../../../common/lib/constants'; +import { CanvasRenderedWorkpad } from '../../../../../shareable_runtime/types'; +import { + downloadRenderedWorkpad, + downloadRuntime, + downloadZippedRuntime, +} from '../../../../lib/download_workpad'; import { ZIP, CANVAS, HTML } from '../../../../../i18n/constants'; import { OnCloseFn } from '../share_menu.component'; import { WorkpadStep } from './workpad_step'; import { RuntimeStep } from './runtime_step'; import { SnippetsStep } from './snippets_step'; +import { useNotifyService, usePlatformService } from '../../../../services'; const strings = { + getCopyShareConfigMessage: () => + i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage', { + defaultMessage: 'Copied share markup to clipboard', + }), + getShareableZipErrorTitle: (workpadName: string) => + i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle', { + defaultMessage: + "Failed to create {ZIP} file for '{workpadName}'. The workpad may be too large. You'll need to download the files separately.", + values: { + ZIP, + workpadName, + }, + }), + getUnknownExportErrorMessage: (type: string) => + i18n.translate('xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage', { + defaultMessage: 'Unknown export type: {type}', + values: { + type, + }, + }), getRuntimeStepTitle: () => i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadRuntimeTitle', { defaultMessage: 'Download runtime', @@ -66,10 +95,9 @@ export type OnDownloadFn = (type: 'share' | 'shareRuntime' | 'shareZip') => void export type OnCopyFn = () => void; export interface Props { - onCopy: OnCopyFn; - onDownload: OnDownloadFn; onClose: OnCloseFn; unsupportedRenderers?: string[]; + renderedWorkpad: CanvasRenderedWorkpad; } const steps = (onDownload: OnDownloadFn, onCopy: OnCopyFn) => [ @@ -88,11 +116,39 @@ const steps = (onDownload: OnDownloadFn, onCopy: OnCopyFn) => [ ]; export const ShareWebsiteFlyout: FC = ({ - onCopy, - onDownload, onClose, unsupportedRenderers, + renderedWorkpad, }) => { + const notifyService = useNotifyService(); + const platformService = usePlatformService(); + const onCopy = () => { + notifyService.info(strings.getCopyShareConfigMessage()); + }; + + const onDownload = (type: 'share' | 'shareRuntime' | 'shareZip') => { + switch (type) { + case 'share': + downloadRenderedWorkpad(renderedWorkpad); + return; + case 'shareRuntime': + downloadRuntime(platformService.getBasePath()); + case 'shareZip': + const basePath = platformService.getBasePath(); + arrayBufferFetch + .post(`${basePath}${API_ROUTE_SHAREABLE_ZIP}`, JSON.stringify(renderedWorkpad)) + .then((blob) => downloadZippedRuntime(blob.data)) + .catch((err: Error) => { + notifyService.error(err, { + title: strings.getShareableZipErrorTitle(renderedWorkpad.name), + }); + }); + return; + default: + throw new Error(strings.getUnknownExportErrorMessage(type)); + } + }; + const link = ( - i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage', { - defaultMessage: 'Copied share markup to clipboard', - }), - getShareableZipErrorTitle: (workpadName: string) => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle', { - defaultMessage: - "Failed to create {ZIP} file for '{workpadName}'. The workpad may be too large. You'll need to download the files separately.", - values: { - ZIP, - workpadName, - }, - }), - getUnknownExportErrorMessage: (type: string) => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage', { - defaultMessage: 'Unknown export type: {type}', - values: { - type, - }, - }), -}; - const getUnsupportedRenderers = (state: State) => { const renderers: string[] = []; const expressions = getRenderedWorkpadExpressions(state); @@ -85,41 +52,10 @@ export const ShareWebsiteFlyout = compose connect(mapStateToProps), withKibana, withProps( - ({ - unsupportedRenderers, + ({ unsupportedRenderers, renderedWorkpad, onClose, workpad }: Props): ComponentProps => ({ renderedWorkpad, - onClose, - workpad, - kibana, - }: Props & WithKibanaProps): ComponentProps => ({ unsupportedRenderers, onClose, - onCopy: () => { - kibana.services.canvas.notify.info(strings.getCopyShareConfigMessage()); - }, - onDownload: (type) => { - switch (type) { - case 'share': - downloadRenderedWorkpad(renderedWorkpad); - return; - case 'shareRuntime': - downloadRuntime(kibana.services.http.basePath.get()); - return; - case 'shareZip': - const basePath = kibana.services.http.basePath.get(); - arrayBufferFetch - .post(`${basePath}${API_ROUTE_SHAREABLE_ZIP}`, JSON.stringify(renderedWorkpad)) - .then((blob) => downloadZippedRuntime(blob.data)) - .catch((err: Error) => { - kibana.services.canvas.notify.error(err, { - title: strings.getShareableZipErrorTitle(workpad.name), - }); - }); - return; - default: - throw new Error(strings.getUnknownExportErrorMessage(type)); - } - }, }) ) )(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx index 5ccc09bf3586b..8d150d3d36972 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx @@ -9,11 +9,11 @@ import React, { FunctionComponent, useState } from 'react'; import PropTypes from 'prop-types'; import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IBasePath } from 'kibana/public'; import { ReportingStart } from '../../../../../reporting/public'; import { PDF, JSON } from '../../../../i18n/constants'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; +import { usePlatformService } from '../../../services'; import { ClosePopoverFn, Popover } from '../../popover'; import { ShareWebsiteFlyout } from './flyout'; import { CanvasWorkpadSharingData, getPdfJobParams } from './utils'; @@ -59,8 +59,6 @@ export interface Props { /** Canvas workpad to export as PDF **/ sharingData: CanvasWorkpadSharingData; sharingServices: { - /** BasePath dependency **/ - basePath: IBasePath; /** Reporting dependency **/ reporting?: ReportingStart; }; @@ -76,6 +74,7 @@ export const ShareMenu: FunctionComponent = ({ sharingServices: services, onExport, }) => { + const platformService = usePlatformService(); const [showFlyout, setShowFlyout] = useState(false); const onClose = () => { @@ -102,7 +101,9 @@ export const ShareMenu: FunctionComponent = ({ title: strings.getShareDownloadPDFTitle(), content: ( getPdfJobParams(sharingData, services.basePath)} + getJobParams={() => + getPdfJobParams(sharingData, platformService.getBasePathInterface()) + } layoutOption="canvas" onClose={closePopover} /> diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts index ef13655b66aca..f514f813599b6 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts @@ -41,12 +41,11 @@ export const ShareMenu = compose( withProps( ({ workpad, pageCount, services }: Props & WithServicesProps): ComponentProps => { const { - platform, reporting: { start: reporting }, } = services; return { - sharingServices: { basePath: platform.getBasePathInterface(), reporting }, + sharingServices: { reporting }, sharingData: { workpad, pageCount }, onExport: (type) => { switch (type) { diff --git a/x-pack/plugins/canvas/public/functions/pie.test.js b/x-pack/plugins/canvas/public/functions/pie.test.js index b1c1746340892..5e35cc3bf523c 100644 --- a/x-pack/plugins/canvas/public/functions/pie.test.js +++ b/x-pack/plugins/canvas/public/functions/pie.test.js @@ -5,8 +5,8 @@ * 2.0. */ -import { functionWrapper } from '../../test_helpers/function_wrapper'; import { testPie } from '../../canvas_plugin_src/functions/common/__fixtures__/test_pointseries'; +import { functionWrapper } from '../../../../../src/plugins/presentation_util/public'; import { fontStyle, grayscalePalette, diff --git a/x-pack/plugins/canvas/public/functions/plot.test.js b/x-pack/plugins/canvas/public/functions/plot.test.js index 5ed858961d798..8dd2470ea17dc 100644 --- a/x-pack/plugins/canvas/public/functions/plot.test.js +++ b/x-pack/plugins/canvas/public/functions/plot.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper } from '../../test_helpers/function_wrapper'; +import { functionWrapper } from '../../../../../src/plugins/presentation_util/public'; import { testPlot } from '../../canvas_plugin_src/functions/common/__fixtures__/test_pointseries'; import { fontStyle, diff --git a/x-pack/plugins/canvas/public/lib/custom_element_service.ts b/x-pack/plugins/canvas/public/lib/custom_element_service.ts index aa3229456ebf6..6da624bb5d3ae 100644 --- a/x-pack/plugins/canvas/public/lib/custom_element_service.ts +++ b/x-pack/plugins/canvas/public/lib/custom_element_service.ts @@ -9,10 +9,10 @@ import { AxiosPromise } from 'axios'; import { API_ROUTE_CUSTOM_ELEMENT } from '../../common/lib/constants'; import { fetch } from '../../common/lib/fetch'; import { CustomElement } from '../../types'; -import { platformService } from '../services'; +import { pluginServices } from '../services'; const getApiPath = function () { - const basePath = platformService.getService().getBasePath(); + const basePath = pluginServices.getServices().platform.getBasePath(); return `${basePath}${API_ROUTE_CUSTOM_ELEMENT}`; }; diff --git a/x-pack/plugins/canvas/public/lib/download_workpad.ts b/x-pack/plugins/canvas/public/lib/download_workpad.ts index 8deda818a43d3..a346de3322d09 100644 --- a/x-pack/plugins/canvas/public/lib/download_workpad.ts +++ b/x-pack/plugins/canvas/public/lib/download_workpad.ts @@ -8,7 +8,10 @@ import fileSaver from 'file-saver'; import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../common/lib/constants'; import { ErrorStrings } from '../../i18n'; -import { notifyService } from '../services'; + +// TODO: clint - convert this whole file to hooks +import { pluginServices } from '../services'; + // @ts-expect-error untyped local import * as workpadService from './workpad_service'; import { CanvasRenderedWorkpad } from '../../shareable_runtime/types'; @@ -21,7 +24,8 @@ export const downloadWorkpad = async (workpadId: string) => { const jsonBlob = new Blob([JSON.stringify(workpad)], { type: 'application/json' }); fileSaver.saveAs(jsonBlob, `canvas-workpad-${workpad.name}-${workpad.id}.json`); } catch (err) { - notifyService.getService().error(err, { title: strings.getDownloadFailureErrorMessage() }); + const notifyService = pluginServices.getServices().notify; + notifyService.error(err, { title: strings.getDownloadFailureErrorMessage() }); } }; @@ -33,9 +37,8 @@ export const downloadRenderedWorkpad = async (renderedWorkpad: CanvasRenderedWor `canvas-embed-workpad-${renderedWorkpad.name}-${renderedWorkpad.id}.json` ); } catch (err) { - notifyService - .getService() - .error(err, { title: strings.getDownloadRenderedWorkpadFailureErrorMessage() }); + const notifyService = pluginServices.getServices().notify; + notifyService.error(err, { title: strings.getDownloadRenderedWorkpadFailureErrorMessage() }); } }; @@ -45,9 +48,8 @@ export const downloadRuntime = async (basePath: string) => { window.open(path); return; } catch (err) { - notifyService - .getService() - .error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() }); + const notifyService = pluginServices.getServices().notify; + notifyService.error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() }); } }; @@ -56,8 +58,7 @@ export const downloadZippedRuntime = async (data: any) => { const zip = new Blob([data], { type: 'octet/stream' }); fileSaver.saveAs(zip, 'canvas-workpad-embed.zip'); } catch (err) { - notifyService - .getService() - .error(err, { title: strings.getDownloadZippedRuntimeFailureErrorMessage() }); + const notifyService = pluginServices.getServices().notify; + notifyService.error(err, { title: strings.getDownloadZippedRuntimeFailureErrorMessage() }); } }; diff --git a/x-pack/plugins/canvas/public/lib/elastic_outline.js b/x-pack/plugins/canvas/public/lib/elastic_outline.js deleted file mode 100644 index 7271f5b32d547..0000000000000 --- a/x-pack/plugins/canvas/public/lib/elastic_outline.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -export const elasticOutline = 'data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3Csvg%20viewBox%3D%22-3.948730230331421%20-1.7549896240234375%20245.25946044921875%20241.40370178222656%22%20width%3D%22245.25946044921875%22%20height%3D%22241.40370178222656%22%20style%3D%22enable-background%3Anew%200%200%20686.2%20235.7%3B%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%232D2D2D%3B%7D%0A%3C%2Fstyle%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Cg%20transform%3D%22matrix%281%2C%200%2C%200%2C%201%2C%200%2C%200%29%22%3E%0A%20%20%20%20%3Cg%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M329.4%2C160.3l4.7-0.5l0.3%2C9.6c-12.4%2C1.7-23%2C2.6-31.8%2C2.6c-11.7%2C0-20-3.4-24.9-10.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-4.9-6.8-7.3-17.4-7.3-31.7c0-28.6%2C11.4-42.9%2C34.1-42.9c11%2C0%2C19.2%2C3.1%2C24.6%2C9.2c5.4%2C6.1%2C8.1%2C15.8%2C8.1%2C28.9l-0.7%2C9.3h-53.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0%2C9%2C1.6%2C15.7%2C4.9%2C20c3.3%2C4.3%2C8.9%2C6.5%2C17%2C6.5C312.8%2C161.2%2C321.1%2C160.9%2C329.4%2C160.3z%20M325%2C124.9c0-10-1.6-17.1-4.8-21.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.2-4.1-8.4-6.2-15.6-6.2c-7.2%2C0-12.7%2C2.2-16.3%2C6.5c-3.6%2C4.3-5.5%2C11.3-5.6%2C20.9H325z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M354.3%2C171.4V64h12.2v107.4H354.3z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M443.5%2C113.5v41.1c0%2C4.1%2C10.1%2C3.9%2C10.1%2C3.9l-0.6%2C10.8c-8.6%2C0-15.7%2C0.7-20-3.4c-9.8%2C4.3-19.5%2C6.1-29.3%2C6.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.5%2C0-13.2-2.1-17.1-6.4c-3.9-4.2-5.9-10.3-5.9-18.3c0-7.9%2C2-13.8%2C6-17.5c4-3.7%2C10.3-6.1%2C18.9-6.9l25.6-2.4v-7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0-5.5-1.2-9.5-3.6-11.9c-2.4-2.4-5.7-3.6-9.8-3.6l-32.1%2C0V87.2h31.3c9.2%2C0%2C15.9%2C2.1%2C20.1%2C6.4C441.4%2C97.8%2C443.5%2C104.5%2C443.5%2C113.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bz%20M393.3%2C146.7c0%2C10%2C4.1%2C15%2C12.4%2C15c7.4%2C0%2C14.7-1.2%2C21.8-3.7l3.7-1.3v-26.9l-24.1%2C2.3c-4.9%2C0.4-8.4%2C1.8-10.6%2C4.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3BC394.4%2C138.7%2C393.3%2C142.2%2C393.3%2C146.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M491.2%2C98.2c-11.8%2C0-17.8%2C4.1-17.8%2C12.4c0%2C3.8%2C1.4%2C6.5%2C4.1%2C8.1c2.7%2C1.6%2C8.9%2C3.2%2C18.6%2C4.9%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc9.7%2C1.7%2C16.5%2C4%2C20.5%2C7.1c4%2C3%2C6%2C8.7%2C6%2C17.1c0%2C8.4-2.7%2C14.5-8.1%2C18.4c-5.4%2C3.9-13.2%2C5.9-23.6%2C5.9c-6.7%2C0-29.2-2.5-29.2-2.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bl0.7-10.6c12.9%2C1.2%2C22.3%2C2.2%2C28.6%2C2.2c6.3%2C0%2C11.1-1%2C14.4-3c3.3-2%2C5-5.4%2C5-10.1c0-4.7-1.4-7.9-4.2-9.6c-2.8-1.7-9-3.3-18.6-4.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-9.6-1.5-16.4-3.7-20.4-6.7c-4-2.9-6-8.4-6-16.3c0-7.9%2C2.8-13.8%2C8.4-17.6c5.6-3.8%2C12.6-5.7%2C20.9-5.7c6.6%2C0%2C29.6%2C1.7%2C29.6%2C1.7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bv10.7C508.1%2C99%2C498.2%2C98.2%2C491.2%2C98.2z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M581.7%2C99.5h-25.9v39c0%2C9.3%2C0.7%2C15.5%2C2%2C18.4c1.4%2C2.9%2C4.6%2C4.4%2C9.7%2C4.4l14.5-1l0.8%2C10.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.3%2C1.2-12.8%2C1.8-16.6%2C1.8c-8.5%2C0-14.3-2.1-17.6-6.2c-3.3-4.1-4.9-12-4.9-23.6V99.5h-11.6V88.9h11.6V63.9h12.1v24.9h25.9V99.5z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M598.7%2C78.4V64.3h12.2v14.2H598.7z%20M598.7%2C171.4V88.9h12.2v82.5H598.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M663.8%2C87.2c3.6%2C0%2C9.7%2C0.7%2C18.3%2C2l3.9%2C0.5l-0.5%2C9.9c-8.7-1-15.1-1.5-19.2-1.5c-9.2%2C0-15.5%2C2.2-18.8%2C6.6%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.3%2C4.4-5%2C12.6-5%2C24.5c0%2C11.9%2C1.5%2C20.2%2C4.6%2C24.9c3.1%2C4.7%2C9.5%2C7%2C19.3%2C7l19.2-1.5l0.5%2C10.1c-10.1%2C1.5-17.7%2C2.3-22.7%2C2.3%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-12.7%2C0-21.5-3.3-26.3-9.8c-4.8-6.5-7.3-17.5-7.3-33c0-15.5%2C2.6-26.4%2C7.8-32.6C643%2C90.4%2C651.7%2C87.2%2C663.8%2C87.2z%22%2F%3E%0A%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M236.6%2C123.5c0-19.8-12.3-37.2-30.8-43.9c0.8-4.2%2C1.2-8.4%2C1.2-12.7C207%2C30%2C177%2C0%2C140.2%2C0%26%2310%3B%26%239%3B%26%239%3BC118.6%2C0%2C98.6%2C10.3%2C86%2C27.7c-6.2-4.8-13.8-7.4-21.7-7.4c-19.6%2C0-35.5%2C15.9-35.5%2C35.5c0%2C4.3%2C0.8%2C8.5%2C2.2%2C12.4%26%2310%3B%26%239%3B%26%239%3BC12.6%2C74.8%2C0%2C92.5%2C0%2C112.2c0%2C19.9%2C12.4%2C37.3%2C30.9%2C44c-0.8%2C4.1-1.2%2C8.4-1.2%2C12.7c0%2C36.8%2C29.9%2C66.7%2C66.7%2C66.7%26%2310%3B%26%239%3B%26%239%3Bc21.6%2C0%2C41.6-10.4%2C54.1-27.8c6.2%2C4.9%2C13.8%2C7.6%2C21.7%2C7.6c19.6%2C0%2C35.5-15.9%2C35.5-35.5c0-4.3-0.8-8.5-2.2-12.4%26%2310%3B%26%239%3B%26%239%3BC223.9%2C160.9%2C236.6%2C143.2%2C236.6%2C123.5z%20M91.6%2C34.8c10.9-15.9%2C28.9-25.4%2C48.1-25.4c32.2%2C0%2C58.4%2C26.2%2C58.4%2C58.4%26%2310%3B%26%239%3B%26%239%3Bc0%2C3.9-0.4%2C7.7-1.1%2C11.5l-52.2%2C45.8L93%2C101.5L82.9%2C79.9L91.6%2C34.8z%20M65.4%2C29c6.2%2C0%2C12.1%2C2%2C17%2C5.7l-7.8%2C40.3l-35.5-8.4%26%2310%3B%26%239%3B%26%239%3Bc-1.1-3.1-1.7-6.3-1.7-9.7C37.4%2C41.6%2C49.9%2C29%2C65.4%2C29z%20M9.1%2C112.3c0-16.7%2C11-31.9%2C26.9-37.2L75%2C84.4l9.1%2C19.5l-49.8%2C45%26%2310%3B%26%239%3B%26%239%3BC19.2%2C143.1%2C9.1%2C128.6%2C9.1%2C112.3z%20M145.2%2C200.9c-10.9%2C16.1-29%2C25.6-48.4%2C25.6c-32.3%2C0-58.6-26.3-58.6-58.5c0-4%2C0.4-7.9%2C1.1-11.7%26%2310%3B%26%239%3B%26%239%3Bl50.9-46l52%2C23.7l11.5%2C22L145.2%2C200.9z%20M171.2%2C206.6c-6.1%2C0-12-2-16.9-5.8l7.7-40.2l35.4%2C8.3c1.1%2C3.1%2C1.7%2C6.3%2C1.7%2C9.7%26%2310%3B%26%239%3B%26%239%3BC199.2%2C194.1%2C186.6%2C206.6%2C171.2%2C206.6z%20M200.5%2C160.5l-39-9.1l-10.4-19.8l51-44.7c15.1%2C5.7%2C25.2%2C20.2%2C25.2%2C36.5%26%2310%3B%26%239%3B%26%239%3BC227.4%2C140.1%2C216.4%2C155.3%2C200.5%2C160.5z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E'; diff --git a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts index cdf9324e947da..a46252081e672 100644 --- a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts +++ b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts @@ -8,7 +8,7 @@ import { camelCase } from 'lodash'; import { getClipboardData, setClipboardData } from './clipboard'; import { cloneSubgraphs } from './clone_subgraphs'; -import { notifyService } from '../services'; +import { pluginServices } from '../services'; import * as customElementService from './custom_element_service'; import { getId } from './get_id'; import { PositionedElement } from '../../types'; @@ -70,6 +70,8 @@ export const basicHandlerCreators = { description = '', image = '' ): void => { + const notifyService = pluginServices.getServices().notify; + if (selectedNodes.length) { const content = JSON.stringify({ selectedNodes }); const customElement = { @@ -83,17 +85,15 @@ export const basicHandlerCreators = { customElementService .create(customElement) .then(() => - notifyService - .getService() - .success( - `Custom element '${customElement.displayName || customElement.id}' was saved`, - { - 'data-test-subj': 'canvasCustomElementCreate-success', - } - ) + notifyService.success( + `Custom element '${customElement.displayName || customElement.id}' was saved`, + { + 'data-test-subj': 'canvasCustomElementCreate-success', + } + ) ) .catch((error: Error) => - notifyService.getService().warning(error, { + notifyService.warning(error, { title: `Custom element '${ customElement.displayName || customElement.id }' was not saved`, @@ -135,16 +135,20 @@ export const groupHandlerCreators = { // handlers for cut/copy/paste export const clipboardHandlerCreators = { cutNodes: ({ pageId, removeNodes, selectedNodes }: Props) => (): void => { + const notifyService = pluginServices.getServices().notify; + if (selectedNodes.length) { setClipboardData({ selectedNodes }); removeNodes(selectedNodes.map(extractId), pageId); - notifyService.getService().success('Cut element to clipboard'); + notifyService.success('Cut element to clipboard'); } }, copyNodes: ({ selectedNodes }: Props) => (): void => { + const notifyService = pluginServices.getServices().notify; + if (selectedNodes.length) { setClipboardData({ selectedNodes }); - notifyService.getService().success('Copied element to clipboard'); + notifyService.success('Copied element to clipboard'); } }, pasteNodes: ({ insertNodes, pageId, selectToplevelNodes }: Props) => (): void => { diff --git a/x-pack/plugins/canvas/public/lib/es_service.ts b/x-pack/plugins/canvas/public/lib/es_service.ts index 25b63bf26c5bb..0ff6c9e5d110f 100644 --- a/x-pack/plugins/canvas/public/lib/es_service.ts +++ b/x-pack/plugins/canvas/public/lib/es_service.ts @@ -5,27 +5,30 @@ * 2.0. */ +// TODO - clint: convert to service abstraction import { IndexPatternAttributes } from 'src/plugins/data/public'; import { API_ROUTE } from '../../common/lib/constants'; import { fetch } from '../../common/lib/fetch'; import { ErrorStrings } from '../../i18n'; -import { notifyService } from '../services'; -import { platformService } from '../services'; +import { pluginServices } from '../services'; const { esService: strings } = ErrorStrings; const getApiPath = function () { - const basePath = platformService.getService().getBasePath(); + const platformService = pluginServices.getServices().platform; + const basePath = platformService.getBasePath(); return basePath + API_ROUTE; }; const getSavedObjectsClient = function () { - return platformService.getService().getSavedObjectsClient(); + const platformService = pluginServices.getServices().platform; + return platformService.getSavedObjectsClient(); }; const getAdvancedSettings = function () { - return platformService.getService().getUISettings(); + const platformService = pluginServices.getServices().platform; + return platformService.getUISettings(); }; export const getFields = (index = '_all') => { @@ -36,11 +39,12 @@ export const getFields = (index = '_all') => { .filter((field) => !field.startsWith('_')) // filters out meta fields .sort() ) - .catch((err: Error) => - notifyService.getService().error(err, { + .catch((err: Error) => { + const notifyService = pluginServices.getServices().notify; + notifyService.error(err, { title: strings.getFieldsFetchErrorMessage(index), - }) - ); + }); + }); }; export const getIndices = () => @@ -56,9 +60,10 @@ export const getIndices = () => return savedObject.attributes.title; }); }) - .catch((err: Error) => - notifyService.getService().error(err, { title: strings.getIndicesFetchErrorMessage() }) - ); + .catch((err: Error) => { + const notifyService = pluginServices.getServices().notify; + notifyService.error(err, { title: strings.getIndicesFetchErrorMessage() }); + }); export const getDefaultIndex = () => { const defaultIndexId = getAdvancedSettings().get('defaultIndex'); @@ -67,10 +72,9 @@ export const getDefaultIndex = () => { ? getSavedObjectsClient() .get('index-pattern', defaultIndexId) .then((defaultIndex) => defaultIndex.attributes.title) - .catch((err) => - notifyService - .getService() - .error(err, { title: strings.getDefaultIndexFetchErrorMessage() }) - ) + .catch((err) => { + const notifyService = pluginServices.getServices().notify; + notifyService.error(err, { title: strings.getDefaultIndexFetchErrorMessage() }); + }) : Promise.resolve(''); }; diff --git a/x-pack/plugins/canvas/public/lib/fullscreen.js b/x-pack/plugins/canvas/public/lib/fullscreen.js index f3f6e029696ea..fd4e0b65785b9 100644 --- a/x-pack/plugins/canvas/public/lib/fullscreen.js +++ b/x-pack/plugins/canvas/public/lib/fullscreen.js @@ -5,21 +5,22 @@ * 2.0. */ -import { platformService } from '../services'; +import { pluginServices } from '../services'; export const fullscreenClass = 'canvas-isFullscreen'; export function setFullscreen(fullscreen, doc = document) { + const platformService = pluginServices.getServices().platform; const enabled = Boolean(fullscreen); const body = doc.querySelector('body'); const bodyClassList = body.classList; const isFullscreen = bodyClassList.contains(fullscreenClass); if (enabled && !isFullscreen) { - platformService.getService().setFullscreen(false); + platformService.setFullscreen(false); bodyClassList.add(fullscreenClass); } else if (!enabled && isFullscreen) { bodyClassList.remove(fullscreenClass); - platformService.getService().setFullscreen(true); + platformService.setFullscreen(true); } } diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/plugins/canvas/public/lib/run_interpreter.ts index eb9be96c5367b..d69f89566cfc9 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.ts @@ -6,8 +6,9 @@ */ import { fromExpression, getType } from '@kbn/interpreter/common'; +import { pluck } from 'rxjs/operators'; import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public'; -import { notifyService, expressionsService } from '../services'; +import { pluginServices, expressionsService } from '../services'; interface Options { castToRender?: boolean; @@ -21,7 +22,12 @@ export async function interpretAst( variables: Record ): Promise { const context = { variables }; - return await expressionsService.getService().execute(ast, null, context).getData(); + return await expressionsService + .getService() + .execute(ast, null, context) + .getData() + .pipe(pluck('result')) + .toPromise(); } /** @@ -43,7 +49,12 @@ export async function runInterpreter( const context = { variables }; try { - const renderable = await expressionsService.getService().execute(ast, input, context).getData(); + const renderable = await expressionsService + .getService() + .execute(ast, input, context) + .getData() + .pipe(pluck('result')) + .toPromise(); if (getType(renderable) === 'render') { return renderable; @@ -57,7 +68,8 @@ export async function runInterpreter( throw new Error(`Ack! I don't know how to render a '${getType(renderable)}'`); } catch (err) { - notifyService.getService().error(err); + const { error: displayError } = pluginServices.getServices().notify; + displayError(err); throw err; } } diff --git a/x-pack/plugins/canvas/public/lib/template_service.ts b/x-pack/plugins/canvas/public/lib/template_service.ts index 6a262d57672e4..d5ec467f18740 100644 --- a/x-pack/plugins/canvas/public/lib/template_service.ts +++ b/x-pack/plugins/canvas/public/lib/template_service.ts @@ -5,13 +5,16 @@ * 2.0. */ +// TODO - clint: convert to service abstraction + import { API_ROUTE_TEMPLATES } from '../../common/lib/constants'; import { fetch } from '../../common/lib/fetch'; -import { platformService } from '../services'; +import { pluginServices } from '../services'; import { CanvasTemplate } from '../../types'; const getApiPath = function () { - const basePath = platformService.getService().getBasePath(); + const platformService = pluginServices.getServices().platform; + const basePath = platformService.getBasePath(); return `${basePath}${API_ROUTE_TEMPLATES}`; }; @@ -21,6 +24,5 @@ interface ListResponse { export async function list() { const templateResponse = await fetch.get(`${getApiPath()}`); - return templateResponse.data.templates; } diff --git a/x-pack/plugins/canvas/public/lib/workpad_service.js b/x-pack/plugins/canvas/public/lib/workpad_service.js index 70aa1c3f1f816..20ad82860f1fa 100644 --- a/x-pack/plugins/canvas/public/lib/workpad_service.js +++ b/x-pack/plugins/canvas/public/lib/workpad_service.js @@ -5,6 +5,7 @@ * 2.0. */ +// TODO: clint - move to workpad service. import { API_ROUTE_WORKPAD, API_ROUTE_WORKPAD_ASSETS, @@ -12,7 +13,7 @@ import { DEFAULT_WORKPAD_CSS, } from '../../common/lib/constants'; import { fetch } from '../../common/lib/fetch'; -import { platformService } from '../services'; +import { pluginServices } from '../services'; /* Remove any top level keys from the workpad which will be rejected by validation @@ -46,17 +47,20 @@ const sanitizeWorkpad = function (workpad) { }; const getApiPath = function () { - const basePath = platformService.getService().getBasePath(); + const platformService = pluginServices.getServices().platform; + const basePath = platformService.getBasePath(); return `${basePath}${API_ROUTE_WORKPAD}`; }; const getApiPathStructures = function () { - const basePath = platformService.getService().getBasePath(); + const platformService = pluginServices.getServices().platform; + const basePath = platformService.getBasePath(); return `${basePath}${API_ROUTE_WORKPAD_STRUCTURES}`; }; const getApiPathAssets = function () { - const basePath = platformService.getService().getBasePath(); + const platformService = pluginServices.getServices().platform; + const basePath = platformService.getBasePath(); return `${basePath}${API_ROUTE_WORKPAD_ASSETS}`; }; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts index ab26625038bc5..9021c6d6c2753 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts @@ -5,15 +5,14 @@ * 2.0. */ import { useContext, useEffect } from 'react'; -import { useServices } from '../../../services'; +import { usePlatformService } from '../../../services'; import { WorkpadRoutingContext } from '..'; const fullscreenClass = 'canvas-isFullscreen'; export const useFullscreenPresentationHelper = () => { const { isFullscreen } = useContext(WorkpadRoutingContext); - const services = useServices(); - const { setFullscreen } = services.platform; + const { setFullscreen } = usePlatformService(); useEffect(() => { const body = document.querySelector('body'); diff --git a/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx b/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx index ccb38cd1a1e0f..bdf84de7a47bd 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx @@ -14,21 +14,21 @@ import { getWorkpad } from '../../state/selectors/workpad'; import { useFullscreenPresentationHelper } from './hooks/use_fullscreen_presentation_helper'; import { useAutoplayHelper } from './hooks/use_autoplay_helper'; import { useRefreshHelper } from './hooks/use_refresh_helper'; -import { useServices } from '../../services'; +import { usePlatformService } from '../../services'; export const WorkpadPresentationHelper: FC = ({ children }) => { - const services = useServices(); + const platformService = usePlatformService(); const workpad = useSelector(getWorkpad); useFullscreenPresentationHelper(); useAutoplayHelper(); useRefreshHelper(); useEffect(() => { - services.platform.setBreadcrumbs([ + platformService.setBreadcrumbs([ getBaseBreadcrumb(), getWorkpadBreadcrumb({ name: workpad.name }), ]); - }, [workpad.name, workpad.id, services.platform]); + }, [workpad.name, workpad.id, platformService]); useEffect(() => { setDocTitle(workpad.name); diff --git a/x-pack/plugins/canvas/public/routes/workpad/workpad_route.tsx b/x-pack/plugins/canvas/public/routes/workpad/workpad_route.tsx index 7683b3413f681..95caba08517ee 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/workpad_route.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/workpad_route.tsx @@ -13,7 +13,7 @@ import { ExportApp } from '../../components/export_app'; import { CanvasLoading } from '../../components/canvas_loading'; // @ts-expect-error import { fetchAllRenderables } from '../../state/actions/elements'; -import { useServices } from '../../services'; +import { useNotifyService } from '../../services'; import { CanvasWorkpad } from '../../../types'; import { ErrorStrings } from '../../../i18n'; import { useWorkpad } from './hooks/use_workpad'; @@ -98,13 +98,13 @@ const WorkpadLoaderComponent: FC<{ children: (workpad: CanvasWorkpad) => JSX.Element; }> = ({ params, children, loadPages }) => { const [workpad, error] = useWorkpad(params.id, loadPages); - const services = useServices(); + const notifyService = useNotifyService(); useEffect(() => { if (error) { - services.notify.error(error, { title: strings.getLoadFailureErrorMessage() }); + notifyService.error(error, { title: strings.getLoadFailureErrorMessage() }); } - }, [error, services.notify]); + }, [error, notifyService]); if (error) { return ; diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts index 49408fcec1ec4..0aaef4ef280f0 100644 --- a/x-pack/plugins/canvas/public/services/index.ts +++ b/x-pack/plugins/canvas/public/services/index.ts @@ -9,11 +9,17 @@ export * from './legacy'; import { PluginServices } from '../../../../../src/plugins/presentation_util/public'; import { CanvasWorkpadService } from './workpad'; +import { CanvasNotifyService } from './notify'; +import { CanvasPlatformService } from './platform'; export interface CanvasPluginServices { workpad: CanvasWorkpadService; + notify: CanvasNotifyService; + platform: CanvasPlatformService; } export const pluginServices = new PluginServices(); export const useWorkpadService = () => (() => pluginServices.getHooks().workpad.useService())(); +export const useNotifyService = () => (() => pluginServices.getHooks().notify.useService())(); +export const usePlatformService = () => (() => pluginServices.getHooks().platform.useService())(); diff --git a/x-pack/plugins/canvas/public/services/kibana/index.ts b/x-pack/plugins/canvas/public/services/kibana/index.ts index 99012003b3a15..bb0095a3c525c 100644 --- a/x-pack/plugins/canvas/public/services/kibana/index.ts +++ b/x-pack/plugins/canvas/public/services/kibana/index.ts @@ -13,16 +13,22 @@ import { } from '../../../../../../src/plugins/presentation_util/public'; import { workpadServiceFactory } from './workpad'; +import { notifyServiceFactory } from './notify'; +import { platformServiceFactory } from './platform'; import { CanvasPluginServices } from '..'; import { CanvasStartDeps } from '../../plugin'; export { workpadServiceFactory } from './workpad'; +export { notifyServiceFactory } from './notify'; +export { platformServiceFactory } from './platform'; export const pluginServiceProviders: PluginServiceProviders< CanvasPluginServices, KibanaPluginServiceParams > = { workpad: new PluginServiceProvider(workpadServiceFactory), + notify: new PluginServiceProvider(notifyServiceFactory), + platform: new PluginServiceProvider(platformServiceFactory), }; export const pluginServiceRegistry = new PluginServiceRegistry< diff --git a/x-pack/plugins/canvas/public/services/legacy/notify.ts b/x-pack/plugins/canvas/public/services/kibana/notify.ts similarity index 74% rename from x-pack/plugins/canvas/public/services/legacy/notify.ts rename to x-pack/plugins/canvas/public/services/kibana/notify.ts index 22dcfa671d0b5..0082b523d050e 100644 --- a/x-pack/plugins/canvas/public/services/legacy/notify.ts +++ b/x-pack/plugins/canvas/public/services/kibana/notify.ts @@ -6,9 +6,17 @@ */ import { get } from 'lodash'; -import { CanvasServiceFactory } from '.'; +import { KibanaPluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public'; + import { formatMsg } from '../../../../../../src/plugins/kibana_legacy/public'; import { ToastInputFields } from '../../../../../../src/core/public'; +import { CanvasStartDeps } from '../../plugin'; +import { CanvasNotifyService } from '../notify'; + +export type CanvasNotifyServiceFactory = KibanaPluginServiceFactory< + CanvasNotifyService, + CanvasStartDeps +>; const getToast = (err: Error | string, opts: ToastInputFields = {}) => { const errData = (get(err, 'response') || err) as Error | string; @@ -28,15 +36,8 @@ const getToast = (err: Error | string, opts: ToastInputFields = {}) => { }; }; -export interface NotifyService { - error: (err: string | Error, opts?: ToastInputFields) => void; - warning: (err: string | Error, opts?: ToastInputFields) => void; - info: (err: string | Error, opts?: ToastInputFields) => void; - success: (err: string | Error, opts?: ToastInputFields) => void; -} - -export const notifyServiceFactory: CanvasServiceFactory = (setup, start) => { - const toasts = start.notifications.toasts; +export const notifyServiceFactory: CanvasNotifyServiceFactory = ({ coreStart }) => { + const toasts = coreStart.notifications.toasts; return { /* diff --git a/x-pack/plugins/canvas/public/services/legacy/platform.ts b/x-pack/plugins/canvas/public/services/kibana/platform.ts similarity index 54% rename from x-pack/plugins/canvas/public/services/legacy/platform.ts rename to x-pack/plugins/canvas/public/services/kibana/platform.ts index b867622f5d302..79eae8d8081bb 100644 --- a/x-pack/plugins/canvas/public/services/legacy/platform.ts +++ b/x-pack/plugins/canvas/public/services/kibana/platform.ts @@ -5,38 +5,17 @@ * 2.0. */ -import { - SavedObjectsStart, - SavedObjectsClientContract, - IUiSettingsClient, - ChromeBreadcrumb, - IBasePath, - ChromeStart, -} from '../../../../../../src/core/public'; -import { CanvasServiceFactory } from '.'; +import { KibanaPluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public'; -export interface PlatformService { - getBasePath: () => string; - getBasePathInterface: () => IBasePath; - getDocLinkVersion: () => string; - getElasticWebsiteUrl: () => string; - getHasWriteAccess: () => boolean; - getUISetting: (key: string, defaultValue?: any) => any; - setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; - setRecentlyAccessed: (link: string, label: string, id: string) => void; - setFullscreen: ChromeStart['setIsVisible']; +import { CanvasStartDeps } from '../../plugin'; +import { CanvasPlatformService } from '../platform'; - // TODO: these should go away. We want thin accessors, not entire objects. - // Entire objects are hard to mock, and hide our dependency on the external service. - getSavedObjects: () => SavedObjectsStart; - getSavedObjectsClient: () => SavedObjectsClientContract; - getUISettings: () => IUiSettingsClient; -} +export type CanvaPlatformServiceFactory = KibanaPluginServiceFactory< + CanvasPlatformService, + CanvasStartDeps +>; -export const platformServiceFactory: CanvasServiceFactory = ( - _coreSetup, - coreStart -) => { +export const platformServiceFactory: CanvaPlatformServiceFactory = ({ coreStart }) => { return { getBasePath: coreStart.http.basePath.get, getBasePathInterface: () => coreStart.http.basePath, diff --git a/x-pack/plugins/canvas/public/services/legacy/context.tsx b/x-pack/plugins/canvas/public/services/legacy/context.tsx index 7a90c6870df4a..2f472afd7d3c1 100644 --- a/x-pack/plugins/canvas/public/services/legacy/context.tsx +++ b/x-pack/plugins/canvas/public/services/legacy/context.tsx @@ -22,8 +22,6 @@ export interface WithServicesProps { const defaultContextValue = { embeddables: {}, expressions: {}, - notify: {}, - platform: {}, navLink: {}, search: {}, }; @@ -31,10 +29,8 @@ const defaultContextValue = { const context = createContext(defaultContextValue as CanvasServices); export const useServices = () => useContext(context); -export const usePlatformService = () => useServices().platform; export const useEmbeddablesService = () => useServices().embeddables; export const useExpressionsService = () => useServices().expressions; -export const useNotifyService = () => useServices().notify; export const useNavLinkService = () => useServices().navLink; export const useLabsService = () => useServices().labs; @@ -52,8 +48,6 @@ export const LegacyServicesProvider: FC<{ const value = { embeddables: specifiedProviders.embeddables.getService(), expressions: specifiedProviders.expressions.getService(), - notify: specifiedProviders.notify.getService(), - platform: specifiedProviders.platform.getService(), navLink: specifiedProviders.navLink.getService(), search: specifiedProviders.search.getService(), reporting: specifiedProviders.reporting.getService(), diff --git a/x-pack/plugins/canvas/public/services/legacy/index.ts b/x-pack/plugins/canvas/public/services/legacy/index.ts index e23057daa7359..01f252c8eb0d6 100644 --- a/x-pack/plugins/canvas/public/services/legacy/index.ts +++ b/x-pack/plugins/canvas/public/services/legacy/index.ts @@ -8,8 +8,6 @@ import { BehaviorSubject } from 'rxjs'; import { CoreSetup, CoreStart, AppUpdater } from '../../../../../../src/core/public'; import { CanvasSetupDeps, CanvasStartDeps } from '../../plugin'; -import { notifyServiceFactory } from './notify'; -import { platformServiceFactory } from './platform'; import { navLinkServiceFactory } from './nav_link'; import { embeddablesServiceFactory } from './embeddables'; import { expressionsServiceFactory } from './expressions'; @@ -17,9 +15,7 @@ import { searchServiceFactory } from './search'; import { labsServiceFactory } from './labs'; import { reportingServiceFactory } from './reporting'; -export { NotifyService } from './notify'; export { SearchService } from './search'; -export { PlatformService } from './platform'; export { NavLinkService } from './nav_link'; export { EmbeddablesService } from './embeddables'; export { ExpressionsService } from '../../../../../../src/plugins/expressions/common'; @@ -79,8 +75,6 @@ export type ServiceFromProvider

= P extends CanvasServiceProvider ? export const services = { embeddables: new CanvasServiceProvider(embeddablesServiceFactory), expressions: new CanvasServiceProvider(expressionsServiceFactory), - notify: new CanvasServiceProvider(notifyServiceFactory), - platform: new CanvasServiceProvider(platformServiceFactory), navLink: new CanvasServiceProvider(navLinkServiceFactory), search: new CanvasServiceProvider(searchServiceFactory), reporting: new CanvasServiceProvider(reportingServiceFactory), @@ -92,8 +86,6 @@ export type CanvasServiceProviders = typeof services; export interface CanvasServices { embeddables: ServiceFromProvider; expressions: ServiceFromProvider; - notify: ServiceFromProvider; - platform: ServiceFromProvider; navLink: ServiceFromProvider; search: ServiceFromProvider; reporting: ServiceFromProvider; @@ -120,8 +112,6 @@ export const stopServices = () => { export const { embeddables: embeddableService, - notify: notifyService, - platform: platformService, navLink: navLinkService, expressions: expressionsService, search: searchService, diff --git a/x-pack/plugins/canvas/public/services/legacy/stubs/index.ts b/x-pack/plugins/canvas/public/services/legacy/stubs/index.ts index 7246a34d7f491..9857e27d8a3cf 100644 --- a/x-pack/plugins/canvas/public/services/legacy/stubs/index.ts +++ b/x-pack/plugins/canvas/public/services/legacy/stubs/index.ts @@ -10,9 +10,7 @@ import { embeddablesService } from './embeddables'; import { expressionsService } from './expressions'; import { reportingService } from './reporting'; import { navLinkService } from './nav_link'; -import { notifyService } from './notify'; import { labsService } from './labs'; -import { platformService } from './platform'; import { searchService } from './search'; export const stubs: CanvasServices = { @@ -20,8 +18,6 @@ export const stubs: CanvasServices = { expressions: expressionsService, reporting: reportingService, navLink: navLinkService, - notify: notifyService, - platform: platformService, search: searchService, labs: labsService, }; diff --git a/x-pack/plugins/canvas/public/services/notify.ts b/x-pack/plugins/canvas/public/services/notify.ts new file mode 100644 index 0000000000000..67c5cb6bf79c4 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/notify.ts @@ -0,0 +1,15 @@ +/* + * 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 { ToastInputFields } from '../../../../../src/core/public'; + +export interface CanvasNotifyService { + error: (err: string | Error, opts?: ToastInputFields) => void; + warning: (err: string | Error, opts?: ToastInputFields) => void; + info: (err: string | Error, opts?: ToastInputFields) => void; + success: (err: string | Error, opts?: ToastInputFields) => void; +} diff --git a/x-pack/plugins/canvas/public/services/platform.ts b/x-pack/plugins/canvas/public/services/platform.ts new file mode 100644 index 0000000000000..7a452d809a614 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/platform.ts @@ -0,0 +1,33 @@ +/* + * 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 { + SavedObjectsStart, + SavedObjectsClientContract, + IUiSettingsClient, + ChromeBreadcrumb, + IBasePath, + ChromeStart, +} from '../../../../../src/core/public'; + +export interface CanvasPlatformService { + getBasePath: () => string; + getBasePathInterface: () => IBasePath; + getDocLinkVersion: () => string; + getElasticWebsiteUrl: () => string; + getHasWriteAccess: () => boolean; + getUISetting: (key: string, defaultValue?: any) => any; + setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; + setRecentlyAccessed: (link: string, label: string, id: string) => void; + setFullscreen: ChromeStart['setIsVisible']; + + // TODO: these should go away. We want thin accessors, not entire objects. + // Entire objects are hard to mock, and hide our dependency on the external service. + getSavedObjects: () => SavedObjectsStart; + getSavedObjectsClient: () => SavedObjectsClientContract; + getUISettings: () => IUiSettingsClient; +} diff --git a/x-pack/plugins/canvas/public/services/storybook/index.ts b/x-pack/plugins/canvas/public/services/storybook/index.ts index de231f730faf5..86ff52155a0bf 100644 --- a/x-pack/plugins/canvas/public/services/storybook/index.ts +++ b/x-pack/plugins/canvas/public/services/storybook/index.ts @@ -13,6 +13,7 @@ import { import { CanvasPluginServices } from '..'; import { pluginServiceProviders as stubProviders } from '../stubs'; import { workpadServiceFactory } from './workpad'; +import { notifyServiceFactory } from './notify'; export interface StorybookParams { hasTemplates?: boolean; @@ -26,6 +27,7 @@ export const pluginServiceProviders: PluginServiceProviders< > = { ...stubProviders, workpad: new PluginServiceProvider(workpadServiceFactory), + notify: new PluginServiceProvider(notifyServiceFactory), }; export const argTypes = { diff --git a/x-pack/plugins/canvas/public/services/storybook/notify.ts b/x-pack/plugins/canvas/public/services/storybook/notify.ts new file mode 100644 index 0000000000000..7ffd2ef9d1453 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/storybook/notify.ts @@ -0,0 +1,22 @@ +/* + * 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 { action } from '@storybook/addon-actions'; + +import { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public'; + +import { StorybookParams } from '.'; +import { CanvasNotifyService } from '../notify'; + +type CanvasNotifyServiceFactory = PluginServiceFactory; + +export const notifyServiceFactory: CanvasNotifyServiceFactory = () => ({ + success: (message) => action(`success: ${message}`)(), + error: (message) => action(`error: ${message}`)(), + info: (message) => action(`info: ${message}`)(), + warning: (message) => action(`warning: ${message}`)(), +}); diff --git a/x-pack/plugins/canvas/public/services/stubs/index.ts b/x-pack/plugins/canvas/public/services/stubs/index.ts index 586007201db81..1aa05647f7e9e 100644 --- a/x-pack/plugins/canvas/public/services/stubs/index.ts +++ b/x-pack/plugins/canvas/public/services/stubs/index.ts @@ -15,11 +15,17 @@ import { import { CanvasPluginServices } from '..'; import { workpadServiceFactory } from './workpad'; +import { notifyServiceFactory } from './notify'; +import { platformServiceFactory } from './platform'; export { workpadServiceFactory } from './workpad'; +export { notifyServiceFactory } from './notify'; +export { platformServiceFactory } from './platform'; export const pluginServiceProviders: PluginServiceProviders = { workpad: new PluginServiceProvider(workpadServiceFactory), + notify: new PluginServiceProvider(notifyServiceFactory), + platform: new PluginServiceProvider(platformServiceFactory), }; export const pluginServiceRegistry = new PluginServiceRegistry( diff --git a/x-pack/plugins/canvas/public/services/stubs/notify.ts b/x-pack/plugins/canvas/public/services/stubs/notify.ts new file mode 100644 index 0000000000000..0ad322a414f0d --- /dev/null +++ b/x-pack/plugins/canvas/public/services/stubs/notify.ts @@ -0,0 +1,21 @@ +/* + * 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 { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public'; + +import { CanvasNotifyService } from '../notify'; + +type CanvasNotifyServiceFactory = PluginServiceFactory; + +const noop = (..._args: any[]): any => {}; + +export const notifyServiceFactory: CanvasNotifyServiceFactory = () => ({ + error: noop, + info: noop, + success: noop, + warning: noop, +}); diff --git a/x-pack/plugins/canvas/public/services/legacy/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts similarity index 72% rename from x-pack/plugins/canvas/public/services/legacy/stubs/platform.ts rename to x-pack/plugins/canvas/public/services/stubs/platform.ts index 5776a1d0d6983..181d355df8a1c 100644 --- a/x-pack/plugins/canvas/public/services/legacy/stubs/platform.ts +++ b/x-pack/plugins/canvas/public/services/stubs/platform.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { PlatformService } from '../platform'; +import { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public'; + +import { CanvasPlatformService } from '../platform'; + +type CanvasPlatformServiceFactory = PluginServiceFactory; const noop = (..._args: any[]): any => {}; @@ -15,7 +19,7 @@ const uiSettings: Record = { const getUISetting = (setting: string) => uiSettings[setting]; -export const platformService: PlatformService = { +export const platformServiceFactory: CanvasPlatformServiceFactory = () => ({ getBasePath: () => '/base/path', getBasePathInterface: noop, getDocLinkVersion: () => 'dockLinkVersion', @@ -28,4 +32,4 @@ export const platformService: PlatformService = { getSavedObjectsClient: noop, getUISettings: noop, setFullscreen: noop, -}; +}); diff --git a/x-pack/plugins/canvas/public/services/workpad.ts b/x-pack/plugins/canvas/public/services/workpad.ts index 37664244b2d55..6b90cc346834b 100644 --- a/x-pack/plugins/canvas/public/services/workpad.ts +++ b/x-pack/plugins/canvas/public/services/workpad.ts @@ -17,7 +17,6 @@ export interface WorkpadFindResponse { export interface TemplateFindResponse { templates: CanvasTemplate[]; } - export interface CanvasWorkpadService { get: (id: string) => Promise; create: (workpad: CanvasWorkpad) => Promise; diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js index ac5d768de53b9..a8302cf094016 100644 --- a/x-pack/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/plugins/canvas/public/state/actions/elements.js @@ -22,7 +22,7 @@ import { getDefaultElement } from '../defaults'; import { ErrorStrings } from '../../../i18n'; import { runInterpreter, interpretAst } from '../../lib/run_interpreter'; import { subMultitree } from '../../lib/aeroelastic/functional'; -import { services } from '../../services'; +import { pluginServices } from '../../services'; import { selectToplevelNodes } from './transient'; import * as args from './resolved_args'; @@ -144,7 +144,8 @@ const fetchRenderableWithContextFn = ({ dispatch, getState }, element, ast, cont dispatch(getAction(renderable)); }) .catch((err) => { - services.notify.getService().error(err); + const notifyService = pluginServices.getServices().notify; + notifyService.error(err); dispatch(getAction(err)); }); }; @@ -188,7 +189,8 @@ export const fetchAllRenderables = createThunk( return runInterpreter(ast, null, variables, { castToRender: true }) .then((renderable) => ({ path: argumentPath, value: renderable })) .catch((err) => { - services.notify.getService().error(err); + const notifyService = pluginServices.getServices().notify; + notifyService.error(err); return { path: argumentPath, value: err }; }); }); @@ -307,7 +309,8 @@ const setAst = createThunk('setAst', ({ dispatch }, ast, element, pageId, doRend const expression = toExpression(ast); dispatch(setExpression(expression, element.id, pageId, doRender)); } catch (err) { - services.notify.getService().error(err); + const notifyService = pluginServices.getServices().notify; + notifyService.error(err); // TODO: remove this, may have been added just to cause a re-render, but why? dispatch(setExpression(element.expression, element.id, pageId, doRender)); diff --git a/x-pack/plugins/canvas/public/state/initial_state.js b/x-pack/plugins/canvas/public/state/initial_state.js index e4909bdb95081..c652cc573abe9 100644 --- a/x-pack/plugins/canvas/public/state/initial_state.js +++ b/x-pack/plugins/canvas/public/state/initial_state.js @@ -6,11 +6,12 @@ */ import { get } from 'lodash'; -import { platformService } from '../services'; +import { pluginServices } from '../services'; import { getDefaultWorkpad } from './defaults'; export const getInitialState = (path) => { - const { getHasWriteAccess } = platformService.getService(); + const platformService = pluginServices.getServices().platform; + const { getHasWriteAccess } = platformService; const state = { app: {}, // Kibana stuff in here diff --git a/x-pack/plugins/canvas/public/state/middleware/es_persist.js b/x-pack/plugins/canvas/public/state/middleware/es_persist.js index 61a2e612215b5..17d0c9649b912 100644 --- a/x-pack/plugins/canvas/public/state/middleware/es_persist.js +++ b/x-pack/plugins/canvas/public/state/middleware/es_persist.js @@ -15,7 +15,7 @@ import { setAssets, resetAssets } from '../actions/assets'; import * as transientActions from '../actions/transient'; import * as resolvedArgsActions from '../actions/resolved_args'; import { update, updateAssets, updateWorkpad } from '../../lib/workpad_service'; -import { services } from '../../services'; +import { pluginServices } from '../../services'; import { canUserWrite } from '../selectors/app'; const { esPersist: strings } = ErrorStrings; @@ -61,17 +61,19 @@ export const esPersistMiddleware = ({ getState }) => { const notifyError = (err) => { const statusCode = err.response && err.response.status; + const notifyService = pluginServices.getServices().notify; + switch (statusCode) { case 400: - return services.notify.getService().error(err.response, { + return notifyService.error(err.response, { title: strings.getSaveFailureTitle(), }); case 413: - return services.notify.getService().error(strings.getTooLargeErrorMessage(), { + return notifyService.error(strings.getTooLargeErrorMessage(), { title: strings.getSaveFailureTitle(), }); default: - return services.notify.getService().error(err, { + return notifyService.error(err, { title: strings.getUpdateFailureTitle(), }); } diff --git a/x-pack/plugins/canvas/public/state/reducers/workpad.js b/x-pack/plugins/canvas/public/state/reducers/workpad.js index acd371e9490fb..ebde0106f9c01 100644 --- a/x-pack/plugins/canvas/public/state/reducers/workpad.js +++ b/x-pack/plugins/canvas/public/state/reducers/workpad.js @@ -6,7 +6,7 @@ */ import { handleActions } from 'redux-actions'; -import { platformService } from '../../services'; +import { pluginServices } from '../../services'; import { getDefaultWorkpad } from '../defaults'; import { setWorkpad, @@ -24,9 +24,13 @@ import { APP_ROUTE_WORKPAD } from '../../../common/lib/constants'; export const workpadReducer = handleActions( { [setWorkpad]: (workpadState, { payload }) => { - platformService - .getService() - .setRecentlyAccessed(`${APP_ROUTE_WORKPAD}/${payload.id}`, payload.name, payload.id); + pluginServices + .getServices() + .platform.setRecentlyAccessed( + `${APP_ROUTE_WORKPAD}/${payload.id}`, + payload.name, + payload.id + ); return payload; }, @@ -39,9 +43,13 @@ export const workpadReducer = handleActions( }, [setName]: (workpadState, { payload }) => { - platformService - .getService() - .setRecentlyAccessed(`${APP_ROUTE_WORKPAD}/${workpadState.id}`, payload, workpadState.id); + pluginServices + .getServices() + .platform.setRecentlyAccessed( + `${APP_ROUTE_WORKPAD}/${workpadState.id}`, + payload, + workpadState.id + ); return { ...workpadState, name: payload }; }, diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss index e866eada1f85f..aac898c3dd374 100644 --- a/x-pack/plugins/canvas/public/style/index.scss +++ b/x-pack/plugins/canvas/public/style/index.scss @@ -45,16 +45,13 @@ @import '../components/workpad_page/workpad_static_page/workpad_static_page'; @import '../components/var_config/edit_var'; @import '../components/var_config/var_config'; - @import '../transitions/fade/fade'; @import '../transitions/rotate/rotate'; @import '../transitions/slide/slide'; @import '../transitions/zoom/zoom'; - @import '../../canvas_plugin_src/renderers/filters/advanced_filter/component/advanced_filter.scss'; @import '../../canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.scss'; @import '../../canvas_plugin_src/renderers/embeddable/embeddable.scss'; @import '../../canvas_plugin_src/renderers/plot/plot.scss'; -@import '../../canvas_plugin_src/renderers/reveal_image/reveal_image.scss'; @import '../../canvas_plugin_src/renderers/filters/time_filter/time_filter.scss'; @import '../../canvas_plugin_src/uis/arguments/image_upload/image_upload.scss'; diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js index 8ee96aeec2951..60987e987f63a 100644 --- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js +++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js @@ -9,7 +9,6 @@ import { debug } from '../canvas_plugin_src/renderers/debug'; import { error } from '../canvas_plugin_src/renderers/error'; import { image } from '../canvas_plugin_src/renderers/image'; import { repeatImage } from '../canvas_plugin_src/renderers/repeat_image'; -import { revealImage } from '../canvas_plugin_src/renderers/reveal_image'; import { markdown } from '../canvas_plugin_src/renderers/markdown'; import { metric } from '../canvas_plugin_src/renderers/metric'; import { pie } from '../canvas_plugin_src/renderers/pie'; @@ -18,6 +17,7 @@ import { progress } from '../canvas_plugin_src/renderers/progress'; import { shape } from '../canvas_plugin_src/renderers/shape'; import { table } from '../canvas_plugin_src/renderers/table'; import { text } from '../canvas_plugin_src/renderers/text'; +import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public'; /** * This is a collection of renderers which are bundled with the runtime. If diff --git a/x-pack/plugins/canvas/storybook/preview.ts b/x-pack/plugins/canvas/storybook/preview.ts index 266ff767c566a..8eae76abaf415 100644 --- a/x-pack/plugins/canvas/storybook/preview.ts +++ b/x-pack/plugins/canvas/storybook/preview.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { action } from '@storybook/addon-actions'; import { addParameters } from '@storybook/react'; import { startServices } from '../public/services/stubs'; @@ -14,14 +13,7 @@ import { addDecorators } from './decorators'; // Import Canvas CSS import '../public/style/index.scss'; -startServices({ - notify: { - success: (message) => action(`success: ${message}`)(), - error: (message) => action(`error: ${message}`)(), - info: (message) => action(`info: ${message}`)(), - warning: (message) => action(`warning: ${message}`)(), - }, -}); +startServices(); addDecorators(); addParameters({ diff --git a/x-pack/plugins/canvas/test_helpers/function_wrapper.js b/x-pack/plugins/canvas/test_helpers/function_wrapper.js deleted file mode 100644 index d20cac18cbb54..0000000000000 --- a/x-pack/plugins/canvas/test_helpers/function_wrapper.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mapValues } from 'lodash'; - -// It takes a function spec and passes in default args into the spec fn -export const functionWrapper = (fnSpec, mockReduxStore) => { - const spec = fnSpec(); - const defaultArgs = mapValues(spec.args, (argSpec) => { - return argSpec.default; - }); - - return (context, args, handlers) => - spec.fn(context, { ...defaultArgs, ...args }, handlers, mockReduxStore); -}; diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index 487b68ba3542b..84581d7be85a3 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -31,6 +31,7 @@ { "path": "../../../src/plugins/discover/tsconfig.json" }, { "path": "../../../src/plugins/embeddable/tsconfig.json" }, { "path": "../../../src/plugins/expressions/tsconfig.json" }, + { "path": "../../../src/plugins/expression_reveal_image/tsconfig.json" }, { "path": "../../../src/plugins/home/tsconfig.json" }, { "path": "../../../src/plugins/inspector/tsconfig.json" }, { "path": "../../../src/plugins/kibana_legacy/tsconfig.json" }, diff --git a/x-pack/plugins/canvas/types/renderers.ts b/x-pack/plugins/canvas/types/renderers.ts index e840ebee43ed3..2c3931485757d 100644 --- a/x-pack/plugins/canvas/types/renderers.ts +++ b/x-pack/plugins/canvas/types/renderers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; type GenericRendererCallback = (callback: () => void) => void; @@ -35,9 +35,9 @@ export interface RendererSpec { /** The render type */ name: string; /** The name to display */ - displayName: string; + displayName?: string; /** A description of what is rendered */ - help: string; + help?: string; /** Indicate whether the element should reuse the existing DOM element when re-rendering */ reuseDomNode: boolean; /** The default width of the element in pixels */ @@ -50,5 +50,7 @@ export interface RendererSpec { export type RendererFactory = () => RendererSpec; -export type AnyRendererFactory = RendererFactory; +export type AnyRendererFactory = + | RendererFactory + | Array<() => ExpressionRenderDefinition>; export type AnyRendererSpec = RendererSpec; diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index 5d7ee47bb8ea0..fb3a0475d627a 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -94,3 +94,9 @@ if (ENABLE_CASE_CONNECTOR) { export const MAX_DOCS_PER_PAGE = 10000; export const MAX_CONCURRENT_SEARCHES = 10; + +/** + * Validation + */ + +export const MAX_TITLE_LENGTH = 64; diff --git a/x-pack/plugins/cases/public/common/translations.ts b/x-pack/plugins/cases/public/common/translations.ts index 020d301c8e30e..c81ec1c25d84f 100644 --- a/x-pack/plugins/cases/public/common/translations.ts +++ b/x-pack/plugins/cases/public/common/translations.ts @@ -228,3 +228,9 @@ export const SELECTABLE_MESSAGE_COLLECTIONS = i18n.translate( export const SELECT_CASE_TITLE = i18n.translate('xpack.cases.common.allCases.caseModal.title', { defaultMessage: 'Select case', }); + +export const MAX_LENGTH_ERROR = (field: string, length: number) => + i18n.translate('xpack.cases.createCase.maxLengthError', { + values: { field, length }, + defaultMessage: 'The length of the {field} is too long. The maximum length is {length}.', + }); diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx index ad4447223837c..140dbf2f53c25 100644 --- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx @@ -34,6 +34,7 @@ import { useDeleteCases } from '../../containers/use_delete_cases'; import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; import { useKibana } from '../../common/lib/kibana'; import { StatusContextMenu } from '../case_action_bar/status_context_menu'; +import { TruncatedText } from '../truncated_text'; export type CasesColumns = | EuiTableActionsColumnType @@ -145,10 +146,10 @@ export const useCasesColumns = ({ subCaseId={isSubCase(theCase) ? theCase.id : undefined} title={theCase.title} > - {theCase.title} + ) : ( - {theCase.title} + ); return theCase.status !== CaseStatuses.closed ? ( caseDetailsLinkComponent diff --git a/x-pack/plugins/cases/public/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx index e083f11ced777..0ddab55c621d3 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx @@ -183,6 +183,36 @@ describe('Create case', () => { await waitFor(() => expect(postCase).toBeCalledWith(sampleData)); }); + it('it does not submits the title when the length is longer than 64 characters', async () => { + const longTitle = + 'This is a title that should not be saved as it is longer than 64 characters.'; + + const wrapper = mount( + + + + + + + ); + + act(() => { + wrapper + .find(`[data-test-subj="caseTitle"] input`) + .first() + .simulate('change', { target: { value: longTitle } }); + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('[data-test-subj="caseTitle"] .euiFormErrorText').text()).toBe( + 'The length of the title is too long. The maximum length is 64.' + ); + }); + expect(postCase).not.toHaveBeenCalled(); + }); + it('should toggle sync settings', async () => { useConnectorsMock.mockReturnValue({ ...sampleConnectorData, diff --git a/x-pack/plugins/cases/public/components/create/schema.tsx b/x-pack/plugins/cases/public/components/create/schema.tsx index bea1a46d93760..41709a74d2fa5 100644 --- a/x-pack/plugins/cases/public/components/create/schema.tsx +++ b/x-pack/plugins/cases/public/components/create/schema.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import { CasePostRequest, ConnectorTypeFields } from '../../../common'; +import { CasePostRequest, ConnectorTypeFields, MAX_TITLE_LENGTH } from '../../../common'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../common/shared_imports'; import * as i18n from './translations'; import { OptionalFieldLabel } from './optional_field_label'; -const { emptyField } = fieldValidators; +const { emptyField, maxLengthField } = fieldValidators; export const schemaTags = { type: FIELD_TYPES.COMBO_BOX, @@ -33,6 +33,12 @@ export const schema: FormSchema = { { validator: emptyField(i18n.TITLE_REQUIRED), }, + { + validator: maxLengthField({ + length: MAX_TITLE_LENGTH, + message: i18n.MAX_LENGTH_ERROR('title', MAX_TITLE_LENGTH), + }), + }, ], }, description: { diff --git a/x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap b/x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap index 05af2fee2c2a2..9ff9b0616c57e 100644 --- a/x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap +++ b/x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap @@ -7,7 +7,9 @@ exports[`Title it renders 1`] = `

- Test title + `; + +exports[`Title it renders the title if is not a string 1`] = ` + +

+ + Test title + +

+
+`; diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx index babfeb584677b..19aea39f1f793 100644 --- a/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx @@ -187,4 +187,33 @@ describe('EditableTitle', () => { expect(submitTitle.mock.calls[0][0]).toEqual(newTitle); expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe(true); }); + + test('it does not submits the title when the length is longer than 64 characters', () => { + const longTitle = + 'This is a title that should not be saved as it is longer than 64 characters.'; + + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + wrapper + .find('input[data-test-subj="editable-title-input-field"]') + .simulate('change', { target: { value: longTitle } }); + + wrapper.find('button[data-test-subj="editable-title-submit-btn"]').simulate('click'); + wrapper.update(); + expect(wrapper.find('.euiFormErrorText').text()).toBe( + 'The length of the title is too long. The maximum length is 64.' + ); + + expect(submitTitle).not.toHaveBeenCalled(); + expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe( + false + ); + }); }); diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx index 7856a77332275..4dcfa9ad98fde 100644 --- a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx @@ -16,10 +16,11 @@ import { EuiFieldText, EuiButtonIcon, EuiLoadingSpinner, + EuiFormRow, } from '@elastic/eui'; +import { MAX_TITLE_LENGTH } from '../../../common'; import * as i18n from './translations'; - import { Title } from './title'; const MyEuiButtonIcon = styled(EuiButtonIcon)` @@ -37,7 +38,7 @@ const MySpinner = styled(EuiLoadingSpinner)` export interface EditableTitleProps { userCanCrud: boolean; isLoading: boolean; - title: string | React.ReactNode; + title: string; onSubmit: (title: string) => void; } @@ -48,57 +49,72 @@ const EditableTitleComponent: React.FC = ({ title, }) => { const [editMode, setEditMode] = useState(false); - const [changedTitle, onTitleChange] = useState(typeof title === 'string' ? title : ''); + const [errors, setErrors] = useState([]); + const [newTitle, setNewTitle] = useState(title); - const onCancel = useCallback(() => setEditMode(false), []); - const onClickEditIcon = useCallback(() => setEditMode(true), []); + const onCancel = useCallback(() => { + setEditMode(false); + setErrors([]); + setNewTitle(title); + }, [title]); + const onClickEditIcon = useCallback(() => setEditMode(true), []); const onClickSubmit = useCallback((): void => { - if (changedTitle !== title) { - onSubmit(changedTitle); + if (newTitle.length > MAX_TITLE_LENGTH) { + setErrors([i18n.MAX_LENGTH_ERROR('title', MAX_TITLE_LENGTH)]); + return; + } + + if (newTitle !== title) { + onSubmit(newTitle); } setEditMode(false); - }, [changedTitle, onSubmit, title]); + }, [newTitle, onSubmit, title]); const handleOnChange = useCallback( - (e: ChangeEvent) => onTitleChange(e.target.value), + (e: ChangeEvent) => setNewTitle(e.target.value), [] ); + + const hasErrors = errors.length > 0; + return editMode ? ( - - - - - + + - - {i18n.SAVE} - - - - - {i18n.CANCEL} - + + + + + {i18n.SAVE} + + + + + {i18n.CANCEL} + + + + - - + ) : ( diff --git a/x-pack/plugins/cases/public/components/header_page/title.test.tsx b/x-pack/plugins/cases/public/components/header_page/title.test.tsx index 2423104eb8819..063b21e4d8906 100644 --- a/x-pack/plugins/cases/public/components/header_page/title.test.tsx +++ b/x-pack/plugins/cases/public/components/header_page/title.test.tsx @@ -36,4 +36,10 @@ describe('Title', () => { expect(wrapper.find('[data-test-subj="header-page-title"]').first().exists()).toBe(true); }); + + test('it renders the title if is not a string', () => { + const wrapper = shallow({'Test title'}</span>} />); + + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/x-pack/plugins/cases/public/components/header_page/title.tsx b/x-pack/plugins/cases/public/components/header_page/title.tsx index 3a0390a436e1c..629aa612610ee 100644 --- a/x-pack/plugins/cases/public/components/header_page/title.tsx +++ b/x-pack/plugins/cases/public/components/header_page/title.tsx @@ -6,10 +6,12 @@ */ import React from 'react'; +import { isString } from 'lodash'; import { EuiBetaBadge, EuiBadge, EuiTitle } from '@elastic/eui'; import styled from 'styled-components'; import { BadgeOptions, TitleProp } from './types'; +import { TruncatedText } from '../truncated_text'; const StyledEuiBetaBadge = styled(EuiBetaBadge)` vertical-align: middle; @@ -30,7 +32,7 @@ interface Props { const TitleComponent: React.FC<Props> = ({ title, badgeOptions }) => ( <EuiTitle size="l"> <h1 data-test-subj="header-page-title"> - {title} + {isString(title) ? <TruncatedText text={title} /> : title} {badgeOptions && ( <> {' '} diff --git a/x-pack/plugins/cases/public/components/header_page/translations.ts b/x-pack/plugins/cases/public/components/header_page/translations.ts index b24c347857a6c..ba987d1f45f15 100644 --- a/x-pack/plugins/cases/public/components/header_page/translations.ts +++ b/x-pack/plugins/cases/public/components/header_page/translations.ts @@ -7,6 +7,8 @@ import { i18n } from '@kbn/i18n'; +export * from '../../common/translations'; + export const SAVE = i18n.translate('xpack.cases.header.editableTitle.save', { defaultMessage: 'Save', }); diff --git a/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx index bfe44dda6c6ef..e08c629913258 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx @@ -19,6 +19,7 @@ import { NoCases } from './no_cases'; import { isSubCase } from '../all_cases/helpers'; import { MarkdownRenderer } from '../markdown_editor'; import { FilterOptions } from '../../containers/types'; +import { TruncatedText } from '../truncated_text'; const MarkdownContainer = styled.div` max-height: 150px; @@ -80,7 +81,7 @@ export const RecentCasesComp = ({ title={c.title} subCaseId={isSubCase(c) ? c.id : undefined} > - {c.title} + <TruncatedText text={c.title} /> </CaseDetailsLink> </EuiText> diff --git a/x-pack/plugins/cases/public/components/truncated_text/index.tsx b/x-pack/plugins/cases/public/components/truncated_text/index.tsx new file mode 100644 index 0000000000000..8a480ed9dbdd1 --- /dev/null +++ b/x-pack/plugins/cases/public/components/truncated_text/index.tsx @@ -0,0 +1,29 @@ +/* + * 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'; + +const LINE_CLAMP = 3; + +const Text = styled.span` + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: ${LINE_CLAMP}; + -webkit-box-orient: vertical; + overflow: hidden; +`; + +interface Props { + text: string; +} + +const TruncatedTextComponent: React.FC<Props> = ({ text }) => { + return <Text title={text}>{text}</Text>; +}; + +export const TruncatedText = React.memo(TruncatedTextComponent); diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 0eebeb343e814..03ea76ede5c2e 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -22,6 +22,7 @@ import { CaseType, OWNER_FIELD, ENABLE_CASE_CONNECTOR, + MAX_TITLE_LENGTH, } from '../../../common'; import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; import { getConnectorFromConfiguration } from '../utils'; @@ -72,6 +73,12 @@ export const create = async ( fold(throwErrors(Boom.badRequest), identity) ); + if (query.title.length > MAX_TITLE_LENGTH) { + throw Boom.badRequest( + `The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}.` + ); + } + try { const savedObjectID = SavedObjectsUtils.generateId(); diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index e5d9e1cddeee6..afe43171563ce 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -40,6 +40,7 @@ import { MAX_CONCURRENT_SEARCHES, SUB_CASE_SAVED_OBJECT, throwErrors, + MAX_TITLE_LENGTH, } from '../../../common'; import { buildCaseUserActions } from '../../services/user_actions/helpers'; import { getCaseToUpdate } from '../utils'; @@ -181,6 +182,24 @@ async function throwIfInvalidUpdateOfTypeWithAlerts({ } } +/** + * Throws an error if any of the requests updates a title and the length is over MAX_TITLE_LENGTH. + */ +function throwIfTitleIsInvalid(requests: ESCasePatchRequest[]) { + const requestsInvalidTitle = requests.filter( + (req) => req.title !== undefined && req.title.length > MAX_TITLE_LENGTH + ); + + if (requestsInvalidTitle.length > 0) { + const ids = requestsInvalidTitle.map((req) => req.id); + throw Boom.badRequest( + `The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}, ids: [${ids.join( + ', ' + )}]` + ); + } +} + /** * Get the id from a reference in a comment for a specific type. */ @@ -477,6 +496,7 @@ export const update = async ( } throwIfUpdateOwner(updateFilterCases); + throwIfTitleIsInvalid(updateFilterCases); throwIfUpdateStatusOfCollection(updateFilterCases, casesMap); throwIfUpdateTypeCollectionToIndividual(updateFilterCases, casesMap); await throwIfInvalidUpdateOfTypeWithAlerts({ diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/_index.scss b/x-pack/plugins/data_visualizer/public/application/common/components/_index.scss index f57abbbe6396b..02a8766b3d24c 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/_index.scss +++ b/x-pack/plugins/data_visualizer/public/application/common/components/_index.scss @@ -1,4 +1,3 @@ @import 'embedded_map/index'; -@import 'experimental_badge/index'; @import 'stats_table/index'; @import 'top_values/top_values'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/_experimental_badge.scss b/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/_experimental_badge.scss deleted file mode 100644 index 8b21620542ff7..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/_experimental_badge.scss +++ /dev/null @@ -1,7 +0,0 @@ -.experimental-badge.euiBetaBadge { - font-size: 10px; - vertical-align: middle; - margin-bottom: 5px; - padding: 0 20px; - line-height: 20px; -} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/_index.scss b/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/_index.scss deleted file mode 100644 index 9e25affd5e5f6..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'experimental_badge' diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/experimental_badge.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/experimental_badge.tsx deleted file mode 100644 index 9c39ee54a2a86..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/experimental_badge.tsx +++ /dev/null @@ -1,28 +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 { FormattedMessage } from '@kbn/i18n/react'; -import React, { FC } from 'react'; - -import { EuiBetaBadge } from '@elastic/eui'; - -export const ExperimentalBadge: FC<{ tooltipContent: string }> = ({ tooltipContent }) => { - return ( - <span> - <EuiBetaBadge - className="experimental-badge" - label={ - <FormattedMessage - id="xpack.dataVisualizer.experimentalBadge.experimentalLabel" - defaultMessage="Experimental" - /> - } - tooltipContent={tooltipContent} - /> - </span> - ); -}; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx index 86b869fe06fa1..7b091e699b617 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx @@ -7,30 +7,12 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC } from 'react'; -import { i18n } from '@kbn/i18n'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiLink, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; - -import { ExperimentalBadge } from '../../../common/components/experimental_badge'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { useDataVisualizerKibana } from '../../../kibana_context'; export const WelcomeContent: FC = () => { - const toolTipContent = i18n.translate( - 'xpack.dataVisualizer.file.welcomeContent.experimentalFeatureTooltip', - { - defaultMessage: "Experimental feature. We'd love to hear your feedback.", - } - ); - const { services: { fileUpload: { getMaxBytesFormatted }, @@ -48,10 +30,7 @@ export const WelcomeContent: FC = () => { <h1> <FormattedMessage id="xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileTitle" - defaultMessage="Visualize data from a log file {experimentalBadge}" - values={{ - experimentalBadge: <ExperimentalBadge tooltipContent={toolTipContent} />, - }} + defaultMessage="Visualize data from a log file" /> </h1> </EuiTitle> @@ -132,25 +111,6 @@ export const WelcomeContent: FC = () => { /> </p> </EuiText> - <EuiSpacer size="s" /> - <EuiText> - <p> - <FormattedMessage - id="xpack.dataVisualizer.file.welcomeContent.experimentalFeatureDescription" - defaultMessage="This feature is experimental. Got feedback? Please create an issue in {githubLink}." - values={{ - githubLink: ( - <EuiLink - href="https://github.com/elastic/kibana/issues/new/choose" - target="_blank" - > - GitHub - </EuiLink> - ), - }} - /> - </p> - </EuiText> </EuiFlexItem> </EuiFlexGroup> ); diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js index 74a3638f555d0..232a32c75dc29 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js @@ -31,7 +31,6 @@ import { addCombinedFieldsToMappings, getDefaultCombinedFields, } from '../../../common/components/combined_fields'; -import { ExperimentalBadge } from '../../../common/components/experimental_badge'; const DEFAULT_TIME_FIELD = '@timestamp'; const DEFAULT_INDEX_SETTINGS = { number_of_shards: 1 }; @@ -510,15 +509,6 @@ export class ImportView extends Component { id="xpack.dataVisualizer.file.importView.importDataTitle" defaultMessage="Import data" /> -   - <ExperimentalBadge - tooltipContent={ - <FormattedMessage - id="xpack.dataVisualizer.file.importView.experimentalFeatureTooltip" - defaultMessage="Experimental feature. We'd love to hear your feedback." - /> - } - /> </h2> </EuiTitle> diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts index 11003d0fcc171..1b5dab0839663 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; + import { HttpResponse } from 'src/core/public'; import { FlashMessagesLogic } from './flash_messages_logic'; @@ -31,12 +33,17 @@ interface Options { isQueued?: boolean; } +export const defaultErrorMessage = i18n.translate( + 'xpack.enterpriseSearch.shared.flashMessages.defaultErrorMessage', + { + defaultMessage: 'An unexpected error occurred', + } +); + /** * Converts API/HTTP errors into user-facing Flash Messages */ export const flashAPIErrors = (error: HttpResponse<ErrorResponse>, { isQueued }: Options = {}) => { - const defaultErrorMessage = 'An unexpected error occurred'; - const errorFlashMessages: IFlashMessage[] = Array.isArray(error?.body?.attributes?.errors) ? error.body!.attributes.errors.map((message) => ({ type: 'error', message })) : [{ type: 'error', message: error?.body?.message || defaultErrorMessage }]; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts index fd1a574b3438f..3e8322145dad6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts @@ -27,6 +27,7 @@ const spyScrollTo = jest.fn(); Object.defineProperty(global.window, 'scrollTo', { value: spyScrollTo }); import { ADD, UPDATE } from '../../../../../shared/constants/operations'; +import { defaultErrorMessage } from '../../../../../shared/flash_messages/handle_api_errors'; import { SchemaType } from '../../../../../shared/schema/types'; import { AppLogic } from '../../../../app_logic'; @@ -390,13 +391,25 @@ describe('SchemaLogic', () => { expect(onSchemaSetSuccessSpy).toHaveBeenCalledWith(serverResponse); }); - it('handles error', async () => { + it('handles error with message', async () => { + const onSchemaSetFormErrorsSpy = jest.spyOn(SchemaLogic.actions, 'onSchemaSetFormErrors'); + // We expect body.message to be a string[] when it is present + http.post.mockReturnValue(Promise.reject({ body: { message: ['this is an error'] } })); + SchemaLogic.actions.setServerField(schema, ADD); + await nextTick(); + + expect(onSchemaSetFormErrorsSpy).toHaveBeenCalledWith(['this is an error']); + expect(spyScrollTo).toHaveBeenCalledWith(0, 0); + }); + + it('handles error with no message', async () => { const onSchemaSetFormErrorsSpy = jest.spyOn(SchemaLogic.actions, 'onSchemaSetFormErrors'); - http.post.mockReturnValue(Promise.reject({ message: 'this is an error' })); + http.post.mockReturnValue(Promise.reject()); SchemaLogic.actions.setServerField(schema, ADD); await nextTick(); - expect(onSchemaSetFormErrorsSpy).toHaveBeenCalledWith('this is an error'); + expect(onSchemaSetFormErrorsSpy).toHaveBeenCalledWith([defaultErrorMessage]); + expect(spyScrollTo).toHaveBeenCalledWith(0, 0); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts index b2c329f0544fd..7af074d412a60 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts @@ -17,6 +17,7 @@ import { setErrorMessage, clearFlashMessages, } from '../../../../../shared/flash_messages'; +import { defaultErrorMessage } from '../../../../../shared/flash_messages/handle_api_errors'; import { HttpLogic } from '../../../../../shared/http'; import { IndexJob, @@ -349,7 +350,9 @@ export const SchemaLogic = kea<MakeLogicType<SchemaValues, SchemaActions>>({ } catch (e) { window.scrollTo(0, 0); if (isAdding) { - actions.onSchemaSetFormErrors(e?.message); + // We expect body.message to be a string[] for actions.onSchemaSetFormErrors + const message: string[] = e?.body?.message || [defaultErrorMessage]; + actions.onSchemaSetFormErrors(message); } else { flashAPIErrors(e); } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index 1363af573b86d..5075398d2d6b0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -425,7 +425,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { [params, updatePackageInfo, agentPolicy, updateAgentPolicy, queryParamsPolicyId] ); - const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); + const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); const stepConfigurePackagePolicy = useMemo( () => @@ -444,7 +444,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { /> {/* Only show the out-of-box configuration step if a UI extension is NOT registered */} - {!ExtensionView && ( + {!extensionView && ( <StepConfigurePackagePolicy packageInfo={packageInfo} showOnlyIntegration={integrationInfo?.name} @@ -456,9 +456,12 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { )} {/* If an Agent Policy and a package has been selected, then show UI extension (if any) */} - {ExtensionView && packagePolicy.policy_id && packagePolicy.package?.name && ( + {extensionView && packagePolicy.policy_id && packagePolicy.package?.name && ( <ExtensionWrapper> - <ExtensionView newPolicy={packagePolicy} onChange={handleExtensionViewOnChange} /> + <extensionView.Component + newPolicy={packagePolicy} + onChange={handleExtensionViewOnChange} + /> </ExtensionWrapper> )} </> @@ -474,7 +477,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { validationResults, formState, integrationInfo?.name, - ExtensionView, + extensionView, handleExtensionViewOnChange, ] ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index fb3603aaef7f4..b07d76dc6bd8e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -338,7 +338,7 @@ export const EditPackagePolicyForm = memo<{ packageInfo, }; - const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit'); + const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit'); const configurePackage = useMemo( () => @@ -354,7 +354,7 @@ export const EditPackagePolicyForm = memo<{ /> {/* Only show the out-of-box configuration step if a UI extension is NOT registered */} - {!ExtensionView && ( + {!extensionView && ( <StepConfigurePackagePolicy packageInfo={packageInfo} packagePolicy={packagePolicy} @@ -364,12 +364,12 @@ export const EditPackagePolicyForm = memo<{ /> )} - {ExtensionView && + {extensionView && packagePolicy.policy_id && packagePolicy.package?.name && originalPackagePolicy && ( <ExtensionWrapper> - <ExtensionView + <extensionView.Component policy={originalPackagePolicy} newPolicy={packagePolicy} onChange={handleExtensionViewOnChange} @@ -386,7 +386,7 @@ export const EditPackagePolicyForm = memo<{ validationResults, formState, originalPackagePolicy, - ExtensionView, + extensionView, handleExtensionViewOnChange, ] ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx index 43db5657b0615..fc2966697418a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx @@ -5,19 +5,20 @@ * 2.0. */ -import { Search as LocalSearch } from 'js-search'; +import { Search as LocalSearch, AllSubstringsIndexStrategy } from 'js-search'; import { useEffect, useRef } from 'react'; import type { PackageList } from '../../../types'; export const searchIdField = 'id'; -export const fieldsToSearch = ['description', 'name', 'title']; +export const fieldsToSearch = ['name', 'title']; export function useLocalSearch(packageList: PackageList) { const localSearchRef = useRef<LocalSearch | null>(null); useEffect(() => { const localSearch = new LocalSearch(searchIdField); + localSearch.indexStrategy = new AllSubstringsIndexStrategy(); fieldsToSearch.forEach((field) => localSearch.addIndex(field)); localSearch.addDocuments(packageList); localSearchRef.current = localSearch; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx index 41db09b0538b9..f8e4c9994e570 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx @@ -19,7 +19,7 @@ export const DisplayedAssets: ServiceNameToAssetTypes = { elasticsearch: Object.values(ElasticsearchAssetType), }; -export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType; +export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType | 'view'; export const AssetTitleMap: Record<DisplayedAssetType, string> = { dashboard: 'Dashboard', @@ -36,6 +36,7 @@ export const AssetTitleMap: Record<DisplayedAssetType, string> = { lens: 'Lens', security_rule: 'Security Rule', ml_module: 'ML Module', + view: 'Views', }; export const ServiceTitleMap: Record<ServiceName, string> = { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx index e6dce1bc51367..6d075faeef308 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx @@ -10,12 +10,17 @@ import { Redirect } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; -import { Loading, Error } from '../../../../../components'; +import { Loading, Error, ExtensionWrapper } from '../../../../../components'; import type { PackageInfo } from '../../../../../types'; import { InstallStatus } from '../../../../../types'; -import { useGetPackageInstallStatus, useLink, useStartServices } from '../../../../../hooks'; +import { + useGetPackageInstallStatus, + useLink, + useStartServices, + useUIExtension, +} from '../../../../../hooks'; import type { AssetSavedObject } from './types'; import { allowedAssetTypes } from './constants'; @@ -27,9 +32,12 @@ interface AssetsPanelProps { export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { const { name, version } = packageInfo; + const pkgkey = `${name}-${version}`; + const { savedObjects: { client: savedObjectsClient }, } = useStartServices(); + const customAssetsExtension = useUIExtension(packageInfo.name, 'package-detail-assets'); const { getPath } = useLink(); const getPackageInstallStatus = useGetPackageInstallStatus(); @@ -76,13 +84,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { // if they arrive at this page and the package is not installed, send them to overview // this happens if they arrive with a direct url or they uninstall while on this tab if (packageInstallStatus.status !== InstallStatus.installed) { - return ( - <Redirect - to={getPath('integration_details_overview', { - pkgkey: `${name}-${version}`, - })} - /> - ); + return <Redirect to={getPath('integration_details_overview', { pkgkey })} />; } let content: JSX.Element | Array<JSX.Element | null>; @@ -102,31 +104,49 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { /> ); } else if (assetSavedObjects === undefined) { - content = ( - <EuiTitle> - <h2> - <FormattedMessage - id="xpack.fleet.epm.packageDetails.assets.noAssetsFoundLabel" - defaultMessage="No assets found" - /> - </h2> - </EuiTitle> - ); + if (customAssetsExtension) { + // If a UI extension for custom asset entries is defined, render the custom component here depisite + // there being no saved objects found + content = ( + <ExtensionWrapper> + <customAssetsExtension.Component /> + </ExtensionWrapper> + ); + } else { + content = ( + <EuiTitle> + <h2> + <FormattedMessage + id="xpack.fleet.epm.packageDetails.assets.noAssetsFoundLabel" + defaultMessage="No assets found" + /> + </h2> + </EuiTitle> + ); + } } else { - content = allowedAssetTypes.map((assetType) => { - const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType); + content = [ + ...allowedAssetTypes.map((assetType) => { + const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType); - if (!sectionAssetSavedObjects.length) { - return null; - } + if (!sectionAssetSavedObjects.length) { + return null; + } - return ( - <> - <AssetsAccordion savedObjects={sectionAssetSavedObjects} type={assetType} /> - <EuiSpacer size="l" /> - </> - ); - }); + return ( + <> + <AssetsAccordion savedObjects={sectionAssetSavedObjects} type={assetType} /> + <EuiSpacer size="l" /> + </> + ); + }), + // Ensure we add any custom assets provided via UI extension to the end of the list of other assets + customAssetsExtension ? ( + <ExtensionWrapper> + <customAssetsExtension.Component /> + </ExtensionWrapper> + ) : null, + ]; } return ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx index 12d4a0014b976..91c6b68c66221 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx @@ -55,6 +55,11 @@ export const AssetsAccordion: FunctionComponent<Props> = ({ savedObjects, type } <EuiSpacer size="m" /> <EuiSplitPanel.Outer hasBorder hasShadow={false}> {savedObjects.map(({ id, attributes: { title, description } }, idx) => { + // Ignore custom asset views + if (type === 'view') { + return; + } + const pathToObjectInApp = getHrefToObjectInKibanaApp({ http, id, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts index 21efd1cd562e8..26a6729be3623 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts @@ -17,4 +17,4 @@ export type AllowedAssetTypes = [ KibanaAssetType.visualization ]; -export type AllowedAssetType = AllowedAssetTypes[number]; +export type AllowedAssetType = AllowedAssetTypes[number] | 'view'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx index ef8d21236c670..b59804cea1733 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx @@ -18,16 +18,16 @@ interface Props { } export const CustomViewPage: React.FC<Props> = memo(({ packageInfo }) => { - const CustomView = useUIExtension(packageInfo.name, 'package-detail-custom'); + const customViewExtension = useUIExtension(packageInfo.name, 'package-detail-custom'); const { getPath } = useLink(); const pkgkey = useMemo(() => pkgKeyFromPackageInfo(packageInfo), [packageInfo]); - return CustomView ? ( + return customViewExtension ? ( <EuiFlexGroup alignItems="flexStart"> <EuiFlexItem grow={1} /> <EuiFlexItem grow={6}> <ExtensionWrapper> - <CustomView pkgkey={pkgkey} packageInfo={packageInfo} /> + <customViewExtension.Component pkgkey={pkgkey} packageInfo={packageInfo} /> </ExtensionWrapper> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx index 04253994d3875..f436c248abd3c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx @@ -113,7 +113,7 @@ describe('when on integration detail', () => { }); }); - describe('and a custom UI extension is registered', () => { + describe('and a custom tab UI extension is registered', () => { // Because React Lazy components are loaded async (Promise), we setup this "watcher" Promise // that is `resolved` once the lazy components actually renders. let lazyComponentWasRendered: Promise<void>; @@ -136,7 +136,7 @@ describe('when on integration detail', () => { testRenderer.startInterface.registerExtension({ package: 'nginx', view: 'package-detail-custom', - component: CustomComponent, + Component: CustomComponent, }); render(); @@ -162,6 +162,53 @@ describe('when on integration detail', () => { }); }); + describe('and a custom assets UI extension is registered', () => { + let lazyComponentWasRendered: Promise<void>; + + beforeEach(() => { + let setWasRendered: () => void; + lazyComponentWasRendered = new Promise((resolve) => { + setWasRendered = resolve; + }); + + const CustomComponent = lazy(async () => { + return { + default: memo(() => { + setWasRendered(); + return <div data-test-subj="custom-hello">hello</div>; + }), + }; + }); + + testRenderer.startInterface.registerExtension({ + package: 'nginx', + view: 'package-detail-assets', + Component: CustomComponent, + }); + + render(); + }); + + afterEach(() => { + // @ts-ignore + lazyComponentWasRendered = undefined; + }); + + it('should display "assets" tab in navigation', () => { + expect(renderResult.getByTestId('tab-assets')); + }); + + it('should display custom assets when tab is clicked', async () => { + act(() => { + testRenderer.history.push( + pagePathGetters.integration_details_assets({ pkgkey: 'nginx-0.3.7' })[1] + ); + }); + await lazyComponentWasRendered; + expect(renderResult.getByTestId('custom-hello')); + }); + }); + describe('and the Add integration button is clicked', () => { beforeEach(() => render()); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 2102c5055503b..21a139ad11baa 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -101,6 +101,8 @@ export function Detail() { const setPackageInstallStatus = useSetPackageInstallStatus(); const getPackageInstallStatus = useGetPackageInstallStatus(); + const CustomAssets = useUIExtension(packageInfo?.name ?? '', 'package-detail-assets'); + const packageInstallStatus = useMemo(() => { if (packageInfo === null || !packageInfo.name) { return undefined; @@ -418,7 +420,7 @@ export function Detail() { }); } - if (packageInstallStatus === InstallStatus.installed && packageInfo.assets) { + if (packageInstallStatus === InstallStatus.installed && (packageInfo.assets || CustomAssets)) { tabs.push({ id: 'assets', name: ( @@ -471,7 +473,7 @@ export function Detail() { } return tabs; - }, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab]); + }, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab, CustomAssets]); return ( <WithHeaderLayout diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index c672abeb1c903..79eea3441643a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -7,7 +7,11 @@ import { stringify, parse } from 'query-string'; import React, { memo, useCallback, useMemo, useState } from 'react'; import { Redirect, useLocation, useHistory } from 'react-router-dom'; -import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui'; +import type { + CriteriaWithPagination, + EuiStepProps, + EuiTableFieldDataColumnType, +} from '@elastic/eui'; import { EuiButtonIcon, EuiBasicTable, @@ -29,6 +33,7 @@ import { useUrlPagination, useGetPackageInstallStatus, AgentPolicyRefreshContext, + useUIExtension, } from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; import { @@ -88,6 +93,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`, }); + const agentEnrollmentFlyoutExtension = useUIExtension(name, 'agent-enrollment-flyout'); + const handleTableOnChange = useCallback( ({ page }: CriteriaWithPagination<PackagePolicyAndAgentPolicy>) => { setPagination({ @@ -98,35 +105,46 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps [setPagination] ); - const renderViewDataStepContent = useCallback( - () => ( - <> - <EuiText> - <FormattedMessage - id="xpack.fleet.agentEnrollment.viewDataDescription" - defaultMessage="After your agent starts, you can view your data in Kibana by using the integration's installed assets. {pleaseNote}: it may take a few minutes for the initial data to arrive." - values={{ - pleaseNote: ( - <strong> - {i18n.translate( - 'xpack.fleet.epm.agentEnrollment.viewDataDescription.pleaseNoteLabel', - { defaultMessage: 'Please note' } - )} - </strong> - ), - }} - /> - </EuiText> - <EuiSpacer size="l" /> - <EuiButton href={getHref('integration_details_assets', { pkgkey: `${name}-${version}` })}> - {i18n.translate('xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel', { - defaultMessage: 'View assets', - })} - </EuiButton> - </> - ), - [name, version, getHref] - ); + const viewDataStep = useMemo<EuiStepProps>(() => { + if (agentEnrollmentFlyoutExtension) { + return { + title: agentEnrollmentFlyoutExtension.title, + children: <agentEnrollmentFlyoutExtension.Component />, + }; + } + + return { + title: i18n.translate('xpack.fleet.agentEnrollment.stepViewDataTitle', { + defaultMessage: 'View your data', + }), + children: ( + <> + <EuiText> + <FormattedMessage + id="xpack.fleet.agentEnrollment.viewDataDescription" + defaultMessage="After your agent starts, you can view your data in Kibana by using the integration's installed assets. {pleaseNote}: it may take a few minutes for the initial data to arrive." + values={{ + pleaseNote: ( + <strong> + {i18n.translate( + 'xpack.fleet.epm.agentEnrollment.viewDataDescription.pleaseNoteLabel', + { defaultMessage: 'Please note' } + )} + </strong> + ), + }} + /> + </EuiText> + <EuiSpacer size="l" /> + <EuiButton href={getHref('integration_details_assets', { pkgkey: `${name}-${version}` })}> + {i18n.translate('xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel', { + defaultMessage: 'View assets', + })} + </EuiButton> + </> + ), + }; + }, [name, version, getHref, agentEnrollmentFlyoutExtension]); const columns: Array<EuiTableFieldDataColumnType<PackagePolicyAndAgentPolicy>> = useMemo( () => [ @@ -230,13 +248,13 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps <PackagePolicyActionsMenu agentPolicy={agentPolicy} packagePolicy={packagePolicy} - viewDataStepContent={renderViewDataStepContent()} + viewDataStep={viewDataStep} /> ); }, }, ], - [renderViewDataStepContent] + [viewDataStep] ); const noItemsMessage = useMemo(() => { @@ -292,7 +310,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps data?.items.find(({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId) ?.agentPolicy } - viewDataStepContent={renderViewDataStepContent()} + viewDataStep={viewDataStep} /> )} </AgentPolicyRefreshContext.Provider> diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index f6d62d45a4e56..f68b1b878c51c 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -21,7 +21,7 @@ import { FleetStatusProvider, ConfigContext } from '../../hooks'; import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components'; -import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep, ViewDataStep } from './steps'; +import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep } from './steps'; import type { Props } from '.'; import { AgentEnrollmentFlyout } from '.'; @@ -129,24 +129,26 @@ describe('<AgentEnrollmentFlyout />', () => { }); }); - describe('"View data" extension point', () => { - it('calls the "View data" step when UI extension is provided', async () => { + // Skipped due to implementation details in the step components. See https://github.com/elastic/kibana/issues/103894 + describe.skip('"View data" extension point', () => { + it('shows the "View data" step when UI extension is provided', async () => { jest.clearAllMocks(); await act(async () => { testBed = await setup({ agentPolicies: [], onClose: jest.fn(), - viewDataStepContent: <div />, + viewDataStep: { title: 'View Data', children: <div /> }, }); testBed.component.update(); }); const { exists, actions } = testBed; expect(exists('agentEnrollmentFlyout')).toBe(true); - expect(ViewDataStep).toHaveBeenCalled(); + expect(exists('view-data-step')).toBe(true); jest.clearAllMocks(); actions.goToStandaloneTab(); - expect(ViewDataStep).not.toHaveBeenCalled(); + expect(exists('agentEnrollmentFlyout')).toBe(true); + expect(exists('view-data-step')).toBe(false); }); it('does not call the "View data" step when UI extension is not provided', async () => { @@ -155,17 +157,17 @@ describe('<AgentEnrollmentFlyout />', () => { testBed = await setup({ agentPolicies: [], onClose: jest.fn(), - viewDataStepContent: undefined, + viewDataStep: undefined, }); testBed.component.update(); }); const { exists, actions } = testBed; expect(exists('agentEnrollmentFlyout')).toBe(true); - expect(ViewDataStep).not.toHaveBeenCalled(); + expect(exists('view-data-step')).toBe(false); jest.clearAllMocks(); actions.goToStandaloneTab(); - expect(ViewDataStep).not.toHaveBeenCalled(); + expect(exists('view-data-step')).toBe(false); }); }); }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx index 08d78154941e9..9b82b2a80b5e1 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx @@ -45,7 +45,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({ onClose, agentPolicy, agentPolicies, - viewDataStepContent, + viewDataStep, defaultMode = 'managed', }) => { const [mode, setMode] = useState<FlyoutMode>(defaultMode); @@ -119,14 +119,10 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({ <ManagedInstructions agentPolicy={agentPolicy} agentPolicies={agentPolicies} - viewDataStepContent={viewDataStepContent} + viewDataStep={viewDataStep} /> ) : ( - <StandaloneInstructions - agentPolicy={agentPolicy} - agentPolicies={agentPolicies} - viewDataStepContent={viewDataStepContent} - /> + <StandaloneInstructions agentPolicy={agentPolicy} agentPolicies={agentPolicies} /> )} </EuiFlyoutBody> <EuiFlyoutFooter> diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx index c739725b79739..61f86335cd7f9 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx @@ -23,12 +23,7 @@ import { } from '../../applications/fleet/sections/agents/agent_requirements_page/components'; import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page'; -import { - DownloadStep, - AgentPolicySelectionStep, - AgentEnrollmentKeySelectionStep, - ViewDataStep, -} from './steps'; +import { DownloadStep, AgentPolicySelectionStep, AgentEnrollmentKeySelectionStep } from './steps'; import type { BaseProps } from './types'; type Props = BaseProps; @@ -61,7 +56,7 @@ const FleetServerMissingRequirements = () => { }; export const ManagedInstructions = React.memo<Props>( - ({ agentPolicy, agentPolicies, viewDataStepContent }) => { + ({ agentPolicy, agentPolicies, viewDataStep }) => { const fleetStatus = useFleetStatus(); const [selectedApiKeyId, setSelectedAPIKeyId] = useState<string | undefined>(); @@ -118,8 +113,8 @@ export const ManagedInstructions = React.memo<Props>( }); } - if (viewDataStepContent) { - baseSteps.push(ViewDataStep(viewDataStepContent)); + if (viewDataStep) { + baseSteps.push({ 'data-test-subj': 'view-data-step', ...viewDataStep }); } return baseSteps; @@ -127,12 +122,12 @@ export const ManagedInstructions = React.memo<Props>( agentPolicy, selectedApiKeyId, setSelectedAPIKeyId, - viewDataStepContent, agentPolicies, apiKey.data, fleetServerSteps, isFleetServerPolicySelected, settings.data?.item?.fleet_server_hosts, + viewDataStep, ]); return ( diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx index f77cba754e909..8eeb5fac4b0df 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx @@ -144,16 +144,3 @@ export const AgentEnrollmentKeySelectionStep = ({ ), }; }; - -/** - * Send users to assets installed by the package in Kibana so they can - * view their data. - */ -export const ViewDataStep = (content: JSX.Element) => { - return { - title: i18n.translate('xpack.fleet.agentEnrollment.stepViewDataTitle', { - defaultMessage: 'View your data', - }), - children: content, - }; -}; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts index e0c5b040a61fb..9ee514c634655 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { EuiStepProps } from '@elastic/eui'; + import type { AgentPolicy } from '../../types'; export interface BaseProps { @@ -24,5 +26,5 @@ export interface BaseProps { * There is a step in the agent enrollment process that allows users to see the data from an integration represented in the UI * in some way. This is an area for consumers to render a button and text explaining how data can be viewed. */ - viewDataStepContent?: JSX.Element; + viewDataStep?: EuiStepProps; } diff --git a/x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx b/x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx new file mode 100644 index 0000000000000..f4dd3a7deaaab --- /dev/null +++ b/x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx @@ -0,0 +1,86 @@ +/* + * 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 type { FunctionComponent } from 'react'; +import { + EuiAccordion, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiNotificationBadge, + EuiSpacer, + EuiSplitPanel, + EuiLink, + EuiHorizontalRule, +} from '@elastic/eui'; + +import { AssetTitleMap } from '../applications/integrations/sections/epm/constants'; +import { useStartServices } from '../hooks'; +import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public'; + +export interface CustomAssetsAccordionProps { + views: Array<{ + name: string; + url: string; + description: string; + }>; + initialIsOpen?: boolean; +} + +export const CustomAssetsAccordion: FunctionComponent<CustomAssetsAccordionProps> = ({ + views, + initialIsOpen = false, +}) => { + const { application } = useStartServices(); + + return ( + <EuiAccordion + initialIsOpen={initialIsOpen} + buttonContent={ + <EuiFlexGroup justifyContent="center" alignItems="center" gutterSize="s" responsive={false}> + <EuiFlexItem grow={false}> + <EuiText size="m"> + <h3>{AssetTitleMap.view}</h3> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiNotificationBadge color="subdued" size="m"> + <h3>{views.length}</h3> + </EuiNotificationBadge> + </EuiFlexItem> + </EuiFlexGroup> + } + id="custom-assets" + > + <> + <EuiSpacer size="m" /> + <EuiSplitPanel.Outer hasBorder hasShadow={false}> + {views.map((view, index) => ( + <> + <EuiSplitPanel.Inner grow={false} key={index}> + <EuiText size="m"> + <p> + <RedirectAppLinks application={application}> + <EuiLink href={view.url}>{view.name}</EuiLink> + </RedirectAppLinks> + </p> + </EuiText> + + <EuiSpacer size="s" /> + <EuiText size="s" color="subdued"> + <p>{view.description}</p> + </EuiText> + </EuiSplitPanel.Inner> + {index + 1 < views.length && <EuiHorizontalRule margin="none" />} + </> + ))} + </EuiSplitPanel.Outer> + </> + </EuiAccordion> + ); +}; diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx index 1f64de27fce39..9743135d5f1c1 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx @@ -7,6 +7,7 @@ import React, { useMemo, useState } from 'react'; import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; +import type { EuiStepProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import type { AgentPolicy, PackagePolicy } from '../types'; @@ -21,8 +22,8 @@ import { PackagePolicyDeleteProvider } from './package_policy_delete_provider'; export const PackagePolicyActionsMenu: React.FunctionComponent<{ agentPolicy: AgentPolicy; packagePolicy: PackagePolicy; - viewDataStepContent?: JSX.Element; -}> = ({ agentPolicy, packagePolicy, viewDataStepContent }) => { + viewDataStep?: EuiStepProps; +}> = ({ agentPolicy, packagePolicy, viewDataStep }) => { const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); const { getHref } = useLink(); const hasWriteCapabilities = useCapabilities().write; @@ -106,7 +107,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ <EuiPortal> <AgentEnrollmentFlyout agentPolicy={agentPolicy} - viewDataStepContent={viewDataStepContent} + viewDataStep={viewDataStep} onClose={onEnrollmentFlyoutClose} /> </EuiPortal> diff --git a/x-pack/plugins/fleet/public/constants/index.ts b/x-pack/plugins/fleet/public/constants/index.ts index a7cc2726eace1..a0e88bc58726a 100644 --- a/x-pack/plugins/fleet/public/constants/index.ts +++ b/x-pack/plugins/fleet/public/constants/index.ts @@ -24,3 +24,5 @@ export { export * from './page_paths'; export const INDEX_NAME = '.kibana'; + +export const CUSTOM_LOGS_INTEGRATION_NAME = 'log'; diff --git a/x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx b/x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx new file mode 100644 index 0000000000000..4090c4520bf2a --- /dev/null +++ b/x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx @@ -0,0 +1,31 @@ +/* + * 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'; + +import { CustomAssetsAccordion } from './components/custom_assets_accordion'; +import type { CustomAssetsAccordionProps } from './components/custom_assets_accordion'; +import { useStartServices } from './hooks'; +import type { PackageAssetsComponent } from './types'; + +export const CustomLogsAssetsExtension: PackageAssetsComponent = () => { + const { http } = useStartServices(); + const logStreamUrl = http.basePath.prepend('/app/logs/stream'); + + const views: CustomAssetsAccordionProps['views'] = [ + { + name: i18n.translate('xpack.fleet.assets.customLogs.name', { defaultMessage: 'Logs' }), + url: logStreamUrl, + description: i18n.translate('xpack.fleet.assets.customLogs.description', { + defaultMessage: 'View Custom logs data in Logs app', + }), + }, + ]; + + return <CustomAssetsAccordion views={views} initialIsOpen />; +}; diff --git a/x-pack/plugins/fleet/public/hooks/use_ui_extension.ts b/x-pack/plugins/fleet/public/hooks/use_ui_extension.ts index 3880a08a7ec03..93e316be241cb 100644 --- a/x-pack/plugins/fleet/public/hooks/use_ui_extension.ts +++ b/x-pack/plugins/fleet/public/hooks/use_ui_extension.ts @@ -20,7 +20,7 @@ type NarrowExtensionPoint<V extends UIExtensionPoint['view'], A = UIExtensionPoi export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionPoint['view']>( packageName: UIExtensionPoint['package'], view: V -): NarrowExtensionPoint<V>['component'] | undefined => { +): NarrowExtensionPoint<V> | undefined => { const registeredExtensions = useContext(UIExtensionsContext); if (!registeredExtensions) { @@ -32,6 +32,6 @@ export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionP if (extension) { // FIXME:PT Revisit ignore below and see if TS error can be addressed // @ts-ignore - return extension.component; + return extension; } }; diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index f563f008dbeb6..585dc70ce2c42 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -21,3 +21,7 @@ export * from './types/ui_extensions'; export { pagePathGetters } from './constants'; export { pkgKeyFromPackageInfo } from './services'; +export { + CustomAssetsAccordion, + CustomAssetsAccordionProps, +} from './components/custom_assets_accordion'; diff --git a/x-pack/plugins/fleet/public/lazy_custom_logs_assets_extension.tsx b/x-pack/plugins/fleet/public/lazy_custom_logs_assets_extension.tsx new file mode 100644 index 0000000000000..984532250a84f --- /dev/null +++ b/x-pack/plugins/fleet/public/lazy_custom_logs_assets_extension.tsx @@ -0,0 +1,17 @@ +/* + * 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 { lazy } from 'react'; + +import type { PackageAssetsComponent } from './types'; +import { CustomLogsAssetsExtension } from './custom_logs_assets_extension'; + +export const LazyCustomLogsAssetsExtension = lazy<PackageAssetsComponent>(async () => { + return { + default: CustomLogsAssetsExtension, + }; +}); diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index c092d5914637c..9b3aefa488f7b 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -32,7 +32,7 @@ import type { CheckPermissionsResponse, PostIngestSetupResponse } from '../commo import type { FleetConfigType } from '../common/types'; -import { FLEET_BASE_PATH } from './constants'; +import { CUSTOM_LOGS_INTEGRATION_NAME, FLEET_BASE_PATH } from './constants'; import { licenseService } from './hooks'; import { setHttpClient } from './hooks/use_request'; import { createPackageSearchProvider } from './search_provider'; @@ -43,6 +43,7 @@ import { } from './components/home_integration'; import { createExtensionRegistrationCallback } from './services/ui_extensions'; import type { UIExtensionRegistrationCallback, UIExtensionsStorage } from './types'; +import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension'; export { FleetConfigType } from '../common/types'; @@ -204,6 +205,13 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep public start(core: CoreStart): FleetStart { let successPromise: ReturnType<FleetStart['isInitialized']>; + const registerExtension = createExtensionRegistrationCallback(this.extensions); + + registerExtension({ + package: CUSTOM_LOGS_INTEGRATION_NAME, + view: 'package-detail-assets', + Component: LazyCustomLogsAssetsExtension, + }); return { isInitialized: () => { @@ -229,8 +237,7 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep return successPromise; }, - - registerExtension: createExtensionRegistrationCallback(this.extensions), + registerExtension, }; } diff --git a/x-pack/plugins/fleet/public/services/ui_extensions.test.ts b/x-pack/plugins/fleet/public/services/ui_extensions.test.ts index 1ca135538d2d2..d23219c1f653b 100644 --- a/x-pack/plugins/fleet/public/services/ui_extensions.test.ts +++ b/x-pack/plugins/fleet/public/services/ui_extensions.test.ts @@ -36,13 +36,13 @@ describe('UI Extension services', () => { register({ view: 'package-policy-edit', package: 'endpoint', - component: LazyCustomView, + Component: LazyCustomView, }); expect(storage.endpoint['package-policy-edit']).toEqual({ view: 'package-policy-edit', package: 'endpoint', - component: LazyCustomView, + Component: LazyCustomView, }); }); @@ -57,21 +57,21 @@ describe('UI Extension services', () => { register({ view: 'package-policy-edit', package: 'endpoint', - component: LazyCustomView, + Component: LazyCustomView, }); expect(() => { register({ view: 'package-policy-edit', package: 'endpoint', - component: LazyCustomView2, + Component: LazyCustomView2, }); }).toThrow(); expect(storage.endpoint['package-policy-edit']).toEqual({ view: 'package-policy-edit', package: 'endpoint', - component: LazyCustomView, + Component: LazyCustomView, }); }); }); diff --git a/x-pack/plugins/fleet/public/types/ui_extensions.ts b/x-pack/plugins/fleet/public/types/ui_extensions.ts index c79b676a26b33..bc692fe1caa7d 100644 --- a/x-pack/plugins/fleet/public/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/types/ui_extensions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { EuiStepProps } from '@elastic/eui'; import type { ComponentType, LazyExoticComponent } from 'react'; import type { NewPackagePolicy, PackageInfo, PackagePolicy } from './index'; @@ -48,7 +49,7 @@ export interface PackagePolicyEditExtensionComponentProps { export interface PackagePolicyEditExtension { package: string; view: 'package-policy-edit'; - component: LazyExoticComponent<PackagePolicyEditExtensionComponent>; + Component: LazyExoticComponent<PackagePolicyEditExtensionComponent>; } /** @@ -76,7 +77,7 @@ export interface PackagePolicyCreateExtensionComponentProps { export interface PackagePolicyCreateExtension { package: string; view: 'package-policy-create'; - component: LazyExoticComponent<PackagePolicyCreateExtensionComponent>; + Component: LazyExoticComponent<PackagePolicyCreateExtensionComponent>; } /** @@ -94,11 +95,32 @@ export interface PackageCustomExtensionComponentProps { export interface PackageCustomExtension { package: string; view: 'package-detail-custom'; - component: LazyExoticComponent<PackageCustomExtensionComponent>; + Component: LazyExoticComponent<PackageCustomExtensionComponent>; +} + +/** + * UI Component Extension for displaying custom views under the Assets tab for a given Integration + */ +export type PackageAssetsComponent = ComponentType<{}>; + +/** Extension point registration contract for Integration details Assets view */ +export interface PackageAssetsExtension { + package: string; + view: 'package-detail-assets'; + Component: LazyExoticComponent<PackageAssetsComponent>; +} + +export interface AgentEnrollmentFlyoutFinalStepExtension { + package: string; + view: 'agent-enrollment-flyout'; + title: EuiStepProps['title']; + Component: ComponentType<{}>; } /** Fleet UI Extension Point */ export type UIExtensionPoint = | PackagePolicyEditExtension | PackageCustomExtension - | PackagePolicyCreateExtension; + | PackagePolicyCreateExtension + | PackageAssetsExtension + | AgentEnrollmentFlyoutFinalStepExtension; diff --git a/x-pack/plugins/index_management/server/lib/fetch_indices.ts b/x-pack/plugins/index_management/server/lib/fetch_indices.ts index a6f499ede9038..b83843f0c615d 100644 --- a/x-pack/plugins/index_management/server/lib/fetch_indices.ts +++ b/x-pack/plugins/index_management/server/lib/fetch_indices.ts @@ -76,26 +76,33 @@ async function fetchIndicesCall( query: catQuery, }); - // The two responses should be equal in the number of indices returned - return catHits.map((hit) => { + // System indices may show up in _cat APIs, as these APIs are primarily used for troubleshooting + // For now, we filter them out and only return index information for the indices we have + // In the future, we should migrate away from using cat APIs (https://github.com/elastic/kibana/issues/57286) + return catHits.reduce((decoratedIndices, hit) => { const index = indices[hit.index]; - const aliases = Object.keys(index.aliases); - return { - health: hit.health, - status: hit.status, - name: hit.index, - uuid: hit.uuid, - primary: hit.pri, - replica: hit.rep, - documents: hit['docs.count'], - size: hit['store.size'], - isFrozen: hit.sth === 'true', // sth value coming back as a string from ES - aliases: aliases.length ? aliases : 'none', - hidden: index.settings.index.hidden === 'true', - data_stream: index.data_stream, - }; - }); + if (typeof index !== 'undefined') { + const aliases = Object.keys(index.aliases); + + decoratedIndices.push({ + health: hit.health, + status: hit.status, + name: hit.index, + uuid: hit.uuid, + primary: hit.pri, + replica: hit.rep, + documents: hit['docs.count'], + size: hit['store.size'], + isFrozen: hit.sth === 'true', // sth value coming back as a string from ES + aliases: aliases.length ? aliases : 'none', + hidden: index.settings.index.hidden === 'true', + data_stream: index.data_stream, + }); + } + + return decoratedIndices; + }, [] as Index[]); } export const fetchIndices = async ( diff --git a/x-pack/plugins/infra/common/constants.ts b/x-pack/plugins/infra/common/constants.ts index 5361434302a7d..9362293fce82f 100644 --- a/x-pack/plugins/infra/common/constants.ts +++ b/x-pack/plugins/infra/common/constants.ts @@ -9,3 +9,5 @@ export const DEFAULT_SOURCE_ID = 'default'; export const METRICS_INDEX_PATTERN = 'metrics-*,metricbeat-*'; export const LOGS_INDEX_PATTERN = 'logs-*,filebeat-*,kibana_sample_data_logs*'; export const TIMESTAMP_FIELD = '@timestamp'; +export const METRICS_APP = 'metrics'; +export const LOGS_APP = 'logs'; diff --git a/x-pack/plugins/infra/public/components/header/header.tsx b/x-pack/plugins/infra/public/components/header/header.tsx deleted file mode 100644 index 6196a0b117879..0000000000000 --- a/x-pack/plugins/infra/public/components/header/header.tsx +++ /dev/null @@ -1,56 +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 { useCallback, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { ChromeBreadcrumb } from 'src/core/public'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; - -interface HeaderProps { - breadcrumbs?: ChromeBreadcrumb[]; - readOnlyBadge?: boolean; -} - -export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) => { - const chrome = useKibana().services.chrome; - - // eslint-disable-next-line react-hooks/exhaustive-deps - const badge = readOnlyBadge - ? { - text: i18n.translate('xpack.infra.header.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('xpack.infra.header.badge.readOnly.tooltip', { - defaultMessage: 'Unable to change source configuration', - }), - iconType: 'glasses', - } - : undefined; - - const setBreadcrumbs = useCallback(() => { - return chrome?.setBreadcrumbs(breadcrumbs || []); - }, [breadcrumbs, chrome]); - - const setBadge = useCallback(() => { - return chrome?.setBadge(badge); - }, [badge, chrome]); - - useEffect(() => { - setBreadcrumbs(); - setBadge(); - }, [setBreadcrumbs, setBadge]); - - useEffect(() => { - setBreadcrumbs(); - }, [breadcrumbs, setBreadcrumbs]); - - useEffect(() => { - setBadge(); - }, [badge, setBadge]); - - return null; -}; diff --git a/x-pack/plugins/infra/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/infra/public/hooks/use_breadcrumbs.ts new file mode 100644 index 0000000000000..32127f21b75f5 --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_breadcrumbs.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ChromeBreadcrumb } from 'kibana/public'; +import { useEffect } from 'react'; +import { observabilityTitle } from '../translations'; +import { useKibanaContextForPlugin } from './use_kibana'; +import { useLinkProps } from './use_link_props'; + +type AppId = 'logs' | 'metrics'; + +export const useBreadcrumbs = (app: AppId, appTitle: string, extraCrumbs: ChromeBreadcrumb[]) => { + const { + services: { chrome }, + } = useKibanaContextForPlugin(); + + const observabilityLinkProps = useLinkProps({ app: 'observability-overview' }); + const appLinkProps = useLinkProps({ app }); + + useEffect(() => { + chrome?.setBreadcrumbs?.([ + { + ...observabilityLinkProps, + text: observabilityTitle, + }, + { + ...appLinkProps, + text: appTitle, + }, + ...extraCrumbs, + ]); + }, [appLinkProps, appTitle, chrome, extraCrumbs, observabilityLinkProps]); +}; diff --git a/x-pack/plugins/infra/public/hooks/use_logs_breadcrumbs.tsx b/x-pack/plugins/infra/public/hooks/use_logs_breadcrumbs.tsx new file mode 100644 index 0000000000000..e00e5b4818ba6 --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_logs_breadcrumbs.tsx @@ -0,0 +1,15 @@ +/* + * 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 { ChromeBreadcrumb } from 'kibana/public'; +import { useBreadcrumbs } from './use_breadcrumbs'; +import { LOGS_APP } from '../../common/constants'; +import { logsTitle } from '../translations'; + +export const useLogsBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { + useBreadcrumbs(LOGS_APP, logsTitle, extraCrumbs); +}; diff --git a/x-pack/plugins/infra/public/hooks/use_metrics_breadcrumbs.tsx b/x-pack/plugins/infra/public/hooks/use_metrics_breadcrumbs.tsx new file mode 100644 index 0000000000000..e55f65a97b63e --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_metrics_breadcrumbs.tsx @@ -0,0 +1,15 @@ +/* + * 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 { ChromeBreadcrumb } from 'kibana/public'; +import { useBreadcrumbs } from './use_breadcrumbs'; +import { METRICS_APP } from '../../common/constants'; +import { metricsTitle } from '../translations'; + +export const useMetricsBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { + useBreadcrumbs(METRICS_APP, metricsTitle, extraCrumbs); +}; diff --git a/x-pack/plugins/infra/public/hooks/use_readonly_badge.tsx b/x-pack/plugins/infra/public/hooks/use_readonly_badge.tsx new file mode 100644 index 0000000000000..a0b0558e0393d --- /dev/null +++ b/x-pack/plugins/infra/public/hooks/use_readonly_badge.tsx @@ -0,0 +1,32 @@ +/* + * 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 { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; + +export const useReadOnlyBadge = (isReadOnly = false) => { + const chrome = useKibana().services.chrome; + + useEffect(() => { + chrome?.setBadge( + isReadOnly + ? { + text: i18n.translate('xpack.infra.header.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('xpack.infra.header.badge.readOnly.tooltip', { + defaultMessage: 'Unable to change source configuration', + }), + iconType: 'glasses', + } + : undefined + ); + }, [chrome, isReadOnly]); + + return null; +}; diff --git a/x-pack/plugins/infra/public/pages/error.tsx b/x-pack/plugins/infra/public/pages/error.tsx index 6b6eaf98b1db6..18cb2a14a9214 100644 --- a/x-pack/plugins/infra/public/pages/error.tsx +++ b/x-pack/plugins/infra/public/pages/error.tsx @@ -18,7 +18,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { euiStyled } from '../../../../../src/plugins/kibana_react/common'; -import { Header } from '../components/header'; import { ColumnarPage, PageContent } from '../components/page'; const DetailPageContent = euiStyled(PageContent)` @@ -33,7 +32,6 @@ interface Props { export const Error: React.FC<Props> = ({ message }) => { return ( <ColumnarPage> - <Header /> <DetailPageContent> <ErrorPageBody message={message} /> </DetailPageContent> diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx index 64dbcbdfe2258..34634b194cb85 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx @@ -7,10 +7,18 @@ import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; +import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; import { LogEntryCategoriesPageContent } from './page_content'; import { LogEntryCategoriesPageProviders } from './page_providers'; +import { logCategoriesTitle } from '../../../translations'; export const LogEntryCategoriesPage = () => { + useLogsBreadcrumbs([ + { + text: logCategoriesTitle, + }, + ]); + return ( <EuiErrorBoundary> <LogEntryCategoriesPageProviders> diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx index ff4cba731b616..94950b24b1a94 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx @@ -9,8 +9,15 @@ import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; import { LogEntryRatePageContent } from './page_content'; import { LogEntryRatePageProviders } from './page_providers'; +import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; +import { anomaliesTitle } from '../../../translations'; export const LogEntryRatePage = () => { + useLogsBreadcrumbs([ + { + text: anomaliesTitle, + }, + ]); return ( <EuiErrorBoundary> <LogEntryRatePageProviders> diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index c7b145b4b0143..8175a95f6a064 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -14,7 +14,6 @@ import useMount from 'react-use/lib/useMount'; import { AlertDropdown } from '../../alerting/log_threshold'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { DocumentTitle } from '../../components/document_title'; -import { Header } from '../../components/header'; import { HelpCenterContent } from '../../components/help_center_content'; import { useLogSourceContext } from '../../containers/logs/log_source'; import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params'; @@ -25,6 +24,7 @@ import { StreamPage } from './stream'; import { HeaderMenuPortal } from '../../../../observability/public'; import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider'; import { useLinkProps } from '../../hooks/use_link_props'; +import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; export const LogsPageContent: React.FunctionComponent = () => { const uiCapabilities = useKibana().services.application?.capabilities; @@ -34,6 +34,8 @@ export const LogsPageContent: React.FunctionComponent = () => { const kibana = useKibana(); + useReadOnlyBadge(!uiCapabilities?.logs?.save); + useMount(() => { initialize(); }); @@ -101,14 +103,6 @@ export const LogsPageContent: React.FunctionComponent = () => { </HeaderMenuPortal> )} - <Header - breadcrumbs={[ - { - text: pageTitle, - }, - ]} - readOnlyBadge={!uiCapabilities?.logs?.save} - /> <Switch> <Route path={streamTab.pathname} component={StreamPage} /> <Route path={anomaliesTab.pathname} component={LogEntryRatePage} /> diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx index 180949572b086..a765cf074271c 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx @@ -18,6 +18,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { useCallback, useMemo } from 'react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { useTrackPageview } from '../../../../../observability/public'; +import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { useLogSourceContext } from '../../../containers/logs/log_source'; import { Prompt } from '../../../utils/navigation_warning_prompt'; @@ -27,10 +28,7 @@ import { NameConfigurationPanel } from './name_configuration_panel'; import { LogSourceConfigurationFormErrors } from './source_configuration_form_errors'; import { useLogSourceConfigurationFormState } from './source_configuration_form_state'; import { LogsPageTemplate } from '../page_template'; - -const settingsTitle = i18n.translate('xpack.infra.logs.settingsTitle', { - defaultMessage: 'Settings', -}); +import { settingsTitle } from '../../../translations'; export const LogsSettingsPage = () => { const uiCapabilities = useKibana().services.application?.capabilities; @@ -43,6 +41,12 @@ export const LogsSettingsPage = () => { delay: 15000, }); + useLogsBreadcrumbs([ + { + text: settingsTitle, + }, + ]); + const { sourceConfiguration: source, hasFailedLoadingSource, diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx index 99b66d2d4ab7b..2ac307570cc97 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx @@ -8,13 +8,21 @@ import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; import { useTrackPageview } from '../../../../../observability/public'; +import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; import { StreamPageContent } from './page_content'; import { StreamPageHeader } from './page_header'; import { LogsPageProviders } from './page_providers'; +import { streamTitle } from '../../../translations'; export const StreamPage = () => { useTrackPageview({ app: 'infra_logs', path: 'stream' }); useTrackPageview({ app: 'infra_logs', path: 'stream', delay: 15000 }); + + useLogsBreadcrumbs([ + { + text: streamTitle, + }, + ]); return ( <EuiErrorBoundary> <LogsPageProviders> diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index e52d1e90d7efd..045fcb57ae943 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -15,7 +15,7 @@ import { IIndexPattern } from 'src/plugins/data/common'; import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources'; import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; -import { Header } from '../../components/header'; +import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; import { MetricsExplorerOptionsContainer, DEFAULT_METRICS_EXPLORER_VIEW_STATE, @@ -56,6 +56,8 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { const kibana = useKibana(); + useReadOnlyBadge(!uiCapabilities?.infrastructure?.save); + const settingsLinkProps = useLinkProps({ app: 'metrics', pathname: 'settings', @@ -111,17 +113,6 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { </EuiFlexGroup> </HeaderMenuPortal> )} - - <Header - breadcrumbs={[ - { - text: i18n.translate('xpack.infra.header.infrastructureTitle', { - defaultMessage: 'Metrics', - }), - }, - ]} - readOnlyBadge={!uiCapabilities?.infrastructure?.save} - /> <Switch> <Route path={'/inventory'} component={SnapshotPage} /> <Route diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx index 4fb4b4d4eb0a6..9671699dadbad 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx @@ -8,7 +8,6 @@ import { EuiButton, EuiErrorBoundary, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; - import { FilterBar } from './components/filter_bar'; import { DocumentTitle } from '../../../components/document_title'; @@ -19,6 +18,7 @@ import { SourceLoadingPage } from '../../../components/source_loading_page'; import { ViewSourceConfigurationButton } from '../../../components/source_configuration/view_source_configuration_button'; import { Source } from '../../../containers/metrics_source'; import { useTrackPageview } from '../../../../../observability/public'; +import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { LayoutView } from './components/layout_view'; import { useLinkProps } from '../../../hooks/use_link_props'; @@ -28,10 +28,7 @@ import { useWaffleOptionsContext } from './hooks/use_waffle_options'; import { MetricsPageTemplate } from '../page_template'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { APP_WRAPPER_CLASS } from '../../../../../../../src/core/public'; - -const inventoryTitle = i18n.translate('xpack.infra.metrics.inventoryPageTitle', { - defaultMessage: 'Inventory', -}); +import { inventoryTitle } from '../../../translations'; export const SnapshotPage = () => { const uiCapabilities = useKibana().services.application?.capabilities; @@ -52,6 +49,12 @@ export const SnapshotPage = () => { hash: '/tutorial_directory/metrics', }); + useMetricsBreadcrumbs([ + { + text: inventoryTitle, + }, + ]); + return ( <EuiErrorBoundary> <DocumentTitle diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx index a447989530727..7ec43ef64f4a0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx @@ -9,19 +9,19 @@ import { i18n } from '@kbn/i18n'; import React, { useContext, useState } from 'react'; import { EuiTheme, withTheme } from '../../../../../../../src/plugins/kibana_react/common'; import { DocumentTitle } from '../../../components/document_title'; -import { Header } from '../../../components/header'; import { withMetricPageProviders } from './page_providers'; import { useMetadata } from './hooks/use_metadata'; +import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { Source } from '../../../containers/metrics_source'; import { InfraLoadingPanel } from '../../../components/loading'; import { findInventoryModel } from '../../../../common/inventory_models'; import { NavItem } from './lib/side_nav_context'; import { NodeDetailsPage } from './components/node_details_page'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { InventoryItemType } from '../../../../common/inventory_models/types'; import { useMetricsTimeContext } from './hooks/use_metrics_time'; import { useLinkProps } from '../../../hooks/use_link_props'; import { MetricsPageTemplate } from '../page_template'; +import { inventoryTitle } from '../../../translations'; interface Props { theme: EuiTheme | undefined; @@ -35,7 +35,6 @@ interface Props { export const MetricDetail = withMetricPageProviders( withTheme(({ match }: Props) => { - const uiCapabilities = useKibana().services.application?.capabilities; const nodeId = match.params.node; const nodeType = match.params.type as InventoryItemType; const inventoryModel = findInventoryModel(nodeType); @@ -70,20 +69,20 @@ export const MetricDetail = withMetricPageProviders( [sideNav] ); - const metricsLinkProps = useLinkProps({ + const inventoryLinkProps = useLinkProps({ app: 'metrics', - pathname: '/', + pathname: '/inventory', }); - const breadcrumbs = [ + useMetricsBreadcrumbs([ { - ...metricsLinkProps, - text: i18n.translate('xpack.infra.header.infrastructureTitle', { - defaultMessage: 'Metrics', - }), + ...inventoryLinkProps, + text: inventoryTitle, }, - { text: name }, - ]; + { + text: name, + }, + ]); if (metadataLoading && !filteredRequiredMetrics.length) { return ( @@ -101,7 +100,6 @@ export const MetricDetail = withMetricPageProviders( return ( <> - <Header breadcrumbs={breadcrumbs} readOnlyBadge={!uiCapabilities?.infrastructure?.save} /> <DocumentTitle title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', { defaultMessage: 'Infrastructure | Metrics | {name}', diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index 1ecadcac4e287..28e56c8337bf8 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -11,6 +11,8 @@ import React, { useEffect } from 'react'; import { IIndexPattern } from 'src/plugins/data/public'; import { MetricsSourceConfigurationProperties } from '../../../../common/metrics_sources'; import { useTrackPageview } from '../../../../../observability/public'; +import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; + import { DocumentTitle } from '../../../components/document_title'; import { NoData } from '../../../components/empty_states'; import { MetricsExplorerCharts } from './components/charts'; @@ -18,16 +20,13 @@ import { MetricsExplorerToolbar } from './components/toolbar'; import { useMetricsExplorerState } from './hooks/use_metric_explorer_state'; import { useSavedViewContext } from '../../../containers/saved_view/saved_view'; import { MetricsPageTemplate } from '../page_template'; +import { metricsExplorerTitle } from '../../../translations'; interface MetricsExplorerPageProps { source: MetricsSourceConfigurationProperties; derivedIndexPattern: IIndexPattern; } -const metricsExplorerTitle = i18n.translate('xpack.infra.metrics.metricsExplorerTitle', { - defaultMessage: 'Metrics Explorer', -}); - export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExplorerPageProps) => { const { loading, @@ -66,6 +65,12 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [loadData, shouldLoadDefault]); + useMetricsBreadcrumbs([ + { + text: metricsExplorerTitle, + }, + ]); + return ( <EuiErrorBoundary> <DocumentTitle diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx index 1066dddad6b5f..7224da4429a36 100644 --- a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx @@ -25,18 +25,23 @@ import { IndicesConfigurationPanel } from './indices_configuration_panel'; import { MLConfigurationPanel } from './ml_configuration_panel'; import { NameConfigurationPanel } from './name_configuration_panel'; import { useSourceConfigurationFormState } from './source_configuration_form_state'; +import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; +import { settingsTitle } from '../../../translations'; + import { MetricsPageTemplate } from '../page_template'; interface SourceConfigurationSettingsProps { shouldAllowEdit: boolean; } -const settingsTitle = i18n.translate('xpack.infra.metrics.settingsTitle', { - defaultMessage: 'Settings', -}); - export const SourceConfigurationSettings = ({ shouldAllowEdit, }: SourceConfigurationSettingsProps) => { + useMetricsBreadcrumbs([ + { + text: settingsTitle, + }, + ]); + const { createSourceConfiguration, source, diff --git a/x-pack/plugins/infra/public/translations.ts b/x-pack/plugins/infra/public/translations.ts new file mode 100644 index 0000000000000..4a9b19fde6ef2 --- /dev/null +++ b/x-pack/plugins/infra/public/translations.ts @@ -0,0 +1,47 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const observabilityTitle = i18n.translate('xpack.infra.header.observabilityTitle', { + defaultMessage: 'Observability', +}); + +export const logsTitle = i18n.translate('xpack.infra.header.logsTitle', { + defaultMessage: 'Logs', +}); + +export const streamTitle = i18n.translate('xpack.infra.logs.index.streamTabTitle', { + defaultMessage: 'Stream', +}); + +export const anomaliesTitle = i18n.translate('xpack.infra.logs.index.anomaliesTabTitle', { + defaultMessage: 'Anomalies', +}); + +export const logCategoriesTitle = i18n.translate( + 'xpack.infra.logs.index.logCategoriesBetaBadgeTitle', + { + defaultMessage: 'Categories', + } +); + +export const settingsTitle = i18n.translate('xpack.infra.logs.index.settingsTabTitle', { + defaultMessage: 'Settings', +}); + +export const metricsTitle = i18n.translate('xpack.infra.header.infrastructureTitle', { + defaultMessage: 'Metrics', +}); + +export const inventoryTitle = i18n.translate('xpack.infra.metrics.inventoryPageTitle', { + defaultMessage: 'Inventory', +}); + +export const metricsExplorerTitle = i18n.translate('xpack.infra.metrics.metricsExplorerTitle', { + defaultMessage: 'Metrics Explorer', +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx index 555ed7a09fe4f..390f8e0191ce9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx @@ -95,22 +95,19 @@ describe('Processor: Date', () => { component, } = testBed; + // Set required parameters form.setInputValue('fieldNameField.input', 'field_1'); - // Set optional parameteres await act(async () => { find('formatsValueField.input').simulate('change', [{ label: 'ISO8601' }]); }); component.update(); - // Set target field + // Set optional parameters form.setInputValue('targetField.input', 'target_field'); - - // Set locale field form.setInputValue('localeField.input', 'SPANISH'); - - // Set timezone field. form.setInputValue('timezoneField.input', 'EST'); + form.setInputValue('outputFormatField.input', 'yyyy-MM-dd'); // Save the field with new changes await saveNewProcessor(); @@ -122,6 +119,7 @@ describe('Processor: Date', () => { target_field: 'target_field', locale: 'SPANISH', timezone: 'EST', + output_format: 'yyyy-MM-dd', }); }); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index d50189167a2ff..24e1ddce008ea 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -140,6 +140,7 @@ type TestSubject = | 'appendValueField.input' | 'formatsValueField.input' | 'timezoneField.input' + | 'outputFormatField.input' | 'localeField.input' | 'processorTypeSelector.input' | 'fieldNameField.input' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx index b1e42d067e56e..90138757c97aa 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx @@ -32,10 +32,20 @@ const fieldsConfig: FieldsConfig = { label: i18n.translate('xpack.ingestPipelines.pipelineEditor.dateForm.formatsFieldLabel', { defaultMessage: 'Formats', }), - helpText: i18n.translate('xpack.ingestPipelines.pipelineEditor.dateForm.formatsFieldHelpText', { - defaultMessage: - 'Expected date formats. Provided formats are applied sequentially. Accepts a Java time pattern, ISO8601, UNIX, UNIX_MS, or TAI64N formats.', - }), + helpText: ( + <FormattedMessage + id="xpack.ingestPipelines.pipelineEditor.dateForm.formatsFieldHelpText" + defaultMessage="Expected date formats. Provided formats are applied sequentially. Accepts a Java time pattern or one of the following formats: {allowedFormats}." + values={{ + allowedFormats: ( + <> + <EuiCode>{'ISO8601'}</EuiCode>,<EuiCode>{'UNIX'}</EuiCode>, + <EuiCode>{'UNIX_MS'}</EuiCode>,<EuiCode>{'TAI64N'}</EuiCode> + </> + ), + }} + /> + ), validations: [ { validator: minLengthField({ @@ -79,6 +89,29 @@ const fieldsConfig: FieldsConfig = { /> ), }, + output_format: { + type: FIELD_TYPES.TEXT, + serializer: from.emptyStringToUndefined, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.dateForm.outputFormatFieldLabel', { + defaultMessage: 'Output format (optional)', + }), + helpText: ( + <FormattedMessage + id="xpack.ingestPipelines.pipelineEditor.dateForm.outputFormatHelpText" + defaultMessage="Format to use when writing the date to {targetField}. Accepts a Java time pattern or one of the following formats: {allowedFormats}. Defaults to {defaultFormat}." + values={{ + targetField: <EuiCode>{'target_field'}</EuiCode>, + allowedFormats: ( + <> + <EuiCode>{'ISO8601'}</EuiCode>,<EuiCode>{'UNIX'}</EuiCode>, + <EuiCode>{'UNIX_MS'}</EuiCode>,<EuiCode>{'TAI64N'}</EuiCode> + </> + ), + defaultFormat: <EuiCode>{`yyyy-MM-dd'T'HH:mm:ss.SSSXXX`}</EuiCode>, + }} + /> + ), + }, }; /** @@ -126,6 +159,13 @@ export const DateProcessor: FunctionComponent = () => { component={Field} path="fields.locale" /> + + <UseField + data-test-subj="outputFormatField" + config={fieldsConfig.output_format} + component={Field} + path="fields.output_format" + /> </> ); }; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 1fd12460ba3b6..12522402116c9 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -161,6 +161,7 @@ export async function mountApp( embeddableId: isCopied ? undefined : embeddableEditorIncomingState.embeddableId, type: LENS_EMBEDDABLE_TYPE, input, + searchSessionId: data.search.session.getSessionId(), }, }); } else { @@ -178,6 +179,10 @@ export async function mountApp( if (!initialContext) { data.query.filterManager.setAppFilters([]); } + + if (embeddableEditorIncomingState?.searchSessionId) { + data.search.session.continue(embeddableEditorIncomingState.searchSessionId); + } const { datasourceMap, visualizationMap } = instance; const initialDatasourceId = getInitialDatasourceId(datasourceMap); @@ -298,6 +303,7 @@ export async function mountApp( params.element ); return () => { + data.search.session.clear(); unmountComponentAtNode(params.element); unlistenParentHistory(); lensStore.dispatch(navigateAway()); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx index 3f539b18896c4..e6cba7ac9dce0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx @@ -126,7 +126,7 @@ export const counterRateOperation: OperationDefinition< signature: i18n.translate('xpack.lens.indexPattern.counterRate.signature', { defaultMessage: 'metric: number', }), - description: i18n.translate('xpack.lens.indexPattern.counterRate.documentation', { + description: i18n.translate('xpack.lens.indexPattern.counterRate.documentation.markdown', { defaultMessage: ` Calculates the rate of an ever increasing counter. This function will only yield helpful results on counter metric fields which contain a measurement of some kind monotonically growing over time. If the value does get smaller, it will interpret this as a counter reset. To get most precise results, \`counter_rate\` should be calculated on the \`max\` of a field. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx index 0bdef4cc7678d..9c8437140f793 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx @@ -122,7 +122,7 @@ export const cumulativeSumOperation: OperationDefinition< signature: i18n.translate('xpack.lens.indexPattern.cumulative_sum.signature', { defaultMessage: 'metric: number', }), - description: i18n.translate('xpack.lens.indexPattern.cumulativeSum.documentation', { + description: i18n.translate('xpack.lens.indexPattern.cumulativeSum.documentation.markdown', { defaultMessage: ` Calculates the cumulative sum of a metric over time, adding all previous values of a series to each value. To use this function, you need to configure a date histogram dimension as well. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx index 857b719b0f411..8890390378d21 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx @@ -114,7 +114,7 @@ export const derivativeOperation: OperationDefinition< signature: i18n.translate('xpack.lens.indexPattern.differences.signature', { defaultMessage: 'metric: number', }), - description: i18n.translate('xpack.lens.indexPattern.differences.documentation', { + description: i18n.translate('xpack.lens.indexPattern.differences.documentation.markdown', { defaultMessage: ` Calculates the difference to the last value of a metric over time. To use this function, you need to configure a date histogram dimension as well. Differences requires the data to be sequential. If your data is empty when using differences, try increasing the date histogram interval. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index aea203c70a928..72e14cc2ea016 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -137,7 +137,7 @@ export const movingAverageOperation: OperationDefinition< signature: i18n.translate('xpack.lens.indexPattern.moving_average.signature', { defaultMessage: 'metric: number, [window]: number', }), - description: i18n.translate('xpack.lens.indexPattern.movingAverage.documentation', { + description: i18n.translate('xpack.lens.indexPattern.movingAverage.documentation.markdown', { defaultMessage: ` Calculates the moving average of a metric over time, averaging the last n-th values to calculate the current value. To use this function, you need to configure a date histogram dimension as well. The default window value is {defaultValue}. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx index 21ec5387b3853..4250811e11452 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx @@ -115,7 +115,7 @@ export const overallSumOperation = buildOverallMetricOperation<OverallSumIndexPa }); }, metric: 'sum', - description: i18n.translate('xpack.lens.indexPattern.overall_sum.documentation', { + description: i18n.translate('xpack.lens.indexPattern.overall_sum.documentation.markdown', { defaultMessage: ` Calculates the sum of a metric of all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function. Other dimensions breaking down the data like top values or filter are treated as separate series. @@ -146,7 +146,7 @@ export const overallMinOperation = buildOverallMetricOperation<OverallMinIndexPa }); }, metric: 'min', - description: i18n.translate('xpack.lens.indexPattern.overall_min.documentation', { + description: i18n.translate('xpack.lens.indexPattern.overall_min.documentation.markdown', { defaultMessage: ` Calculates the minimum of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function. Other dimensions breaking down the data like top values or filter are treated as separate series. @@ -177,7 +177,7 @@ export const overallMaxOperation = buildOverallMetricOperation<OverallMaxIndexPa }); }, metric: 'max', - description: i18n.translate('xpack.lens.indexPattern.overall_max.documentation', { + description: i18n.translate('xpack.lens.indexPattern.overall_max.documentation.markdown', { defaultMessage: ` Calculates the maximum of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function. Other dimensions breaking down the data like top values or filter are treated as separate series. @@ -209,7 +209,7 @@ export const overallAverageOperation = buildOverallMetricOperation<OverallAverag }); }, metric: 'average', - description: i18n.translate('xpack.lens.indexPattern.overall_average.documentation', { + description: i18n.translate('xpack.lens.indexPattern.overall_average.documentation.markdown', { defaultMessage: ` Calculates the average of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function. Other dimensions breaking down the data like top values or filter are treated as separate series. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index 41b90cfc6173a..72c1362896ac0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -121,7 +121,7 @@ export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternCo signature: i18n.translate('xpack.lens.indexPattern.cardinality.signature', { defaultMessage: 'field: string', }), - description: i18n.translate('xpack.lens.indexPattern.cardinality.documentation', { + description: i18n.translate('xpack.lens.indexPattern.cardinality.documentation.markdown', { defaultMessage: ` Calculates the number of unique values of a specified field. Works for number, string, date and boolean values. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index 3e5eee480a685..a6134ddc67bc0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -114,7 +114,7 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field documentation: { section: 'elasticsearch', signature: '', - description: i18n.translate('xpack.lens.indexPattern.count.documentation', { + description: i18n.translate('xpack.lens.indexPattern.count.documentation.markdown', { defaultMessage: ` Calculates the number of documents. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx index dbdbcf226d9bb..3c3068a595bc0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx @@ -79,8 +79,10 @@ function FormulaHelp({ }), description: ( <Markdown - markdown={i18n.translate('xpack.lens.formulaDocumentation.filterRatioDescription', { - defaultMessage: `### Filter ratio: + markdown={i18n.translate( + 'xpack.lens.formulaDocumentation.filterRatioDescription.markdown', + { + defaultMessage: `### Filter ratio: Use \`kql=''\` to filter one set of documents and compare it to other documents within the same grouping. For example, to see how the error rate changes over time: @@ -90,9 +92,10 @@ count(kql='response.status_code > 400') / count() \`\`\` `, - description: - 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', - })} + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} /> ), }, @@ -102,8 +105,10 @@ count(kql='response.status_code > 400') / count() }), description: ( <Markdown - markdown={i18n.translate('xpack.lens.formulaDocumentation.weekOverWeekDescription', { - defaultMessage: `### Week over week: + markdown={i18n.translate( + 'xpack.lens.formulaDocumentation.weekOverWeekDescription.markdown', + { + defaultMessage: `### Week over week: Use \`shift='1w'\` to get the value of each grouping from the previous week. Time shift should not be used with the *Top values* function. @@ -114,9 +119,10 @@ percentile(system.network.in.bytes, percentile=99, shift='1w') \`\`\` `, - description: - 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', - })} + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} /> ), }, @@ -126,8 +132,10 @@ percentile(system.network.in.bytes, percentile=99, shift='1w') }), description: ( <Markdown - markdown={i18n.translate('xpack.lens.formulaDocumentation.percentOfTotalDescription', { - defaultMessage: `### Percent of total + markdown={i18n.translate( + 'xpack.lens.formulaDocumentation.percentOfTotalDescription.markdown', + { + defaultMessage: `### Percent of total Formulas can calculate \`overall_sum\` for all the groupings, which lets you convert each grouping into a percent of total: @@ -137,9 +145,10 @@ sum(products.base_price) / overall_sum(sum(products.base_price)) \`\`\` `, - description: - 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', - })} + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} /> ), }, @@ -372,9 +381,8 @@ sum(products.base_price) / overall_sum(sum(products.base_price)) }} > <Markdown - markdown={i18n.translate('xpack.lens.formulaDocumentation', { - defaultMessage: ` -## How it works + markdown={i18n.translate('xpack.lens.formulaDocumentation.markdown', { + defaultMessage: `## How it works Lens formulas let you do math using a combination of Elasticsearch aggregations and math functions. There are three main types of functions: diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 4914ca0bd0f88..267b8e1f66a1d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -282,7 +282,7 @@ export const lastValueOperation: OperationDefinition<LastValueIndexPatternColumn signature: i18n.translate('xpack.lens.indexPattern.lastValue.signature', { defaultMessage: 'field: string', }), - description: i18n.translate('xpack.lens.indexPattern.lastValue.documentation', { + description: i18n.translate('xpack.lens.indexPattern.lastValue.documentation.markdown', { defaultMessage: ` Returns the value of a field from the last document, ordered by the default time field of the index pattern. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index 46e099820ba3d..7596a05c921f2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -139,7 +139,7 @@ function buildMetricOperation<T extends MetricColumn<string>>({ signature: i18n.translate('xpack.lens.indexPattern.metric.signature', { defaultMessage: 'field: string', }), - description: i18n.translate('xpack.lens.indexPattern.metric.documentation', { + description: i18n.translate('xpack.lens.indexPattern.metric.documentation.markdown', { defaultMessage: ` Returns the {metric} of a field. This function only works for number fields. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index bdc4743e4f56d..9c9f7e8b66a1f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -220,7 +220,7 @@ export const percentileOperation: OperationDefinition<PercentileIndexPatternColu signature: i18n.translate('xpack.lens.indexPattern.percentile.signature', { defaultMessage: 'field: string, [percentile]: number', }), - description: i18n.translate('xpack.lens.indexPattern.percentile.documentation', { + description: i18n.translate('xpack.lens.indexPattern.percentile.documentation.markdown', { defaultMessage: ` Returns the specified percentile of the values of a field. This is the value n percent of the values occuring in documents are smaller. diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.js b/x-pack/plugins/maps/public/actions/map_actions.test.js index fa69dad616747..763ce459dcc25 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.test.js +++ b/x-pack/plugins/maps/public/actions/map_actions.test.js @@ -77,8 +77,8 @@ describe('map_actions', () => { minLon: 95, }, buffer: { - maxLat: 21.94305, - maxLon: 112.5, + maxLat: 11.1784, + maxLon: 101.25, minLat: 0, minLon: 90, }, @@ -158,10 +158,10 @@ describe('map_actions', () => { minLon: 85, }, buffer: { - maxLat: 7.71099, - maxLon: 92.8125, - minLat: -2.81137, - minLon: 82.26563, + maxLat: 5.26601, + maxLon: 90.35156, + minLat: -0.35156, + minLon: 84.72656, }, }, type: 'MAP_EXTENT_CHANGED', @@ -190,10 +190,10 @@ describe('map_actions', () => { minLon: 96, }, buffer: { - maxLat: 13.58192, - maxLon: 103.53516, - minLat: 3.33795, - minLon: 93.33984, + maxLat: 11.0059, + maxLon: 101.07422, + minLat: 5.96575, + minLon: 95.97656, }, }, type: 'MAP_EXTENT_CHANGED', diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index a1c7e2f7b453c..af8d6cc6bedc3 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -60,7 +60,6 @@ import { Timeslice, } from '../../common/descriptor_types'; import { INITIAL_LOCATION } from '../../common/constants'; -import { scaleBounds } from '../../common/elasticsearch_util'; import { cleanTooltipStateForLayer } from './tooltip_actions'; import { VectorLayer } from '../classes/layers/vector_layer'; import { SET_DRAW_MODE } from './ui_actions'; @@ -167,9 +166,8 @@ export function mapExtentChanged(mapExtentState: MapExtentState) { } if (!doesBufferContainExtent || currentZoom !== newZoom) { - const expandedExtent = scaleBounds(extent, 0.5); // snap to the smallest tile-bounds, to avoid jitter in the bounds - dataFilters.buffer = expandToTileBoundaries(expandedExtent, Math.ceil(newZoom)); + dataFilters.buffer = expandToTileBoundaries(extent, Math.ceil(newZoom)); } } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index 3b3b1af30610d..f9df1b452f475 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -20,7 +20,6 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { isFullLicense } from '../license'; @@ -122,18 +121,6 @@ export const DatavisualizerSelector: FC = () => { values={{ maxFileSize }} /> } - betaBadgeLabel={i18n.translate( - 'xpack.ml.datavisualizer.selector.experimentalBadgeLabel', - { - defaultMessage: 'Experimental', - } - )} - betaBadgeTooltipContent={ - <FormattedMessage - id="xpack.ml.datavisualizer.selector.experimentalBadgeTooltipLabel" - defaultMessage="Experimental feature. We'd love to hear your feedback." - /> - } footer={ <EuiButton target="_self" diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx index a78a832fdb6e9..9a4d6036428f8 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx @@ -102,7 +102,7 @@ export const JobMessagesPane: FC<JobMessagesPaneProps> = React.memo( return ( <> - <EuiSpacer /> + {canCreateJob && showClearButton ? <EuiSpacer /> : null} <EuiFlexGroup direction="column"> {canCreateJob && showClearButton ? ( <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js index 318c103b39636..137df3a6f3151 100644 --- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js +++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js @@ -39,7 +39,7 @@ const anomalyDetectorTypeFilter = { }, }; -export function jobAuditMessagesProvider({ asInternalUser, asCurrentUser }, mlClient) { +export function jobAuditMessagesProvider({ asInternalUser }, mlClient) { // search for audit messages, // jobId is optional. without it, all jobs will be listed. // from is optional and should be a string formatted in ES time units. e.g. 12h, 1d, 7d @@ -310,10 +310,10 @@ export function jobAuditMessagesProvider({ asInternalUser, asCurrentUser }, mlCl }; await Promise.all([ - asCurrentUser.updateByQuery({ + asInternalUser.updateByQuery({ index: ML_NOTIFICATION_INDEX_02, ignore_unavailable: true, - refresh: true, + refresh: false, conflicts: 'proceed', body: { query, @@ -323,7 +323,7 @@ export function jobAuditMessagesProvider({ asInternalUser, asCurrentUser }, mlCl }, }, }), - asCurrentUser.index({ + asInternalUser.index({ index: ML_NOTIFICATION_INDEX_02, body: newClearedMessage, refresh: 'wait_for', diff --git a/x-pack/plugins/osquery/public/plugin.ts b/x-pack/plugins/osquery/public/plugin.ts index d1e7154fb0dc0..abdea2df3b2b5 100644 --- a/x-pack/plugins/osquery/public/plugin.ts +++ b/x-pack/plugins/osquery/public/plugin.ts @@ -58,7 +58,7 @@ export function toggleOsqueryPlugin( registerExtension({ package: OSQUERY_INTEGRATION_NAME, view: 'package-detail-custom', - component: LazyOsqueryManagedCustomButtonExtension, + Component: LazyOsqueryManagedCustomButtonExtension, }); } @@ -146,13 +146,13 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt registerExtension({ package: OSQUERY_INTEGRATION_NAME, view: 'package-policy-create', - component: LazyOsqueryManagedPolicyCreateImportExtension, + Component: LazyOsqueryManagedPolicyCreateImportExtension, }); registerExtension({ package: OSQUERY_INTEGRATION_NAME, view: 'package-policy-edit', - component: LazyOsqueryManagedPolicyEditExtension, + Component: LazyOsqueryManagedPolicyEditExtension, }); } } else { diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts index f20c6c2d52fdd..c95c837c4959f 100644 --- a/x-pack/plugins/reporting/common/constants.ts +++ b/x-pack/plugins/reporting/common/constants.ts @@ -91,6 +91,12 @@ export const API_BASE_GENERATE = `${API_BASE_URL}/generate`; export const API_LIST_URL = `${API_BASE_URL}/jobs`; export const API_DIAGNOSE_URL = `${API_BASE_URL}/diagnose`; +export const API_GET_ILM_POLICY_STATUS = `${API_BASE_URL}/ilm_policy_status`; +export const API_CREATE_ILM_POLICY_URL = `${API_BASE_URL}/ilm_policy`; +export const API_MIGRATE_ILM_POLICY_URL = `${API_BASE_URL}/deprecations/migrate_ilm_policy`; + +export const ILM_POLICY_NAME = 'kibana-reporting'; + // hacky endpoint: download CSV without queueing a report export const API_BASE_URL_V1 = '/api/reporting/v1'; // export const API_GENERATE_IMMEDIATE = `${API_BASE_URL_V1}/generate/immediate/csv_searchsource`; diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types.ts index 8205b4f13a320..6efaf42a5ad14 100644 --- a/x-pack/plugins/reporting/common/types.ts +++ b/x-pack/plugins/reporting/common/types.ts @@ -164,3 +164,9 @@ export type DownloadReportFn = (jobId: JobId) => DownloadLink; type ManagementLink = string; export type ManagementLinkFn = () => ManagementLink; + +export type IlmPolicyMigrationStatus = 'policy-not-found' | 'indices-not-managed-by-policy' | 'ok'; + +export interface IlmPolicyStatusResponse { + status: IlmPolicyMigrationStatus; +} diff --git a/x-pack/plugins/reporting/kibana.json b/x-pack/plugins/reporting/kibana.json index ddba61e9a0b8d..6a8f3a3e4e5ec 100644 --- a/x-pack/plugins/reporting/kibana.json +++ b/x-pack/plugins/reporting/kibana.json @@ -6,6 +6,7 @@ "configPath": ["xpack", "reporting"], "requiredPlugins": [ "data", + "esUiShared", "home", "management", "licensing", diff --git a/x-pack/plugins/reporting/public/lib/ilm_policy_status_context.tsx b/x-pack/plugins/reporting/public/lib/ilm_policy_status_context.tsx new file mode 100644 index 0000000000000..78b2e77d09aee --- /dev/null +++ b/x-pack/plugins/reporting/public/lib/ilm_policy_status_context.tsx @@ -0,0 +1,43 @@ +/* + * 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 type { FunctionComponent } from 'react'; +import React, { createContext, useContext } from 'react'; + +import { IlmPolicyStatusResponse } from '../../common/types'; + +import { useCheckIlmPolicyStatus } from './reporting_api_client'; + +type UseCheckIlmPolicyStatus = ReturnType<typeof useCheckIlmPolicyStatus>; + +interface ContextValue { + status: undefined | IlmPolicyStatusResponse['status']; + isLoading: UseCheckIlmPolicyStatus['isLoading']; + recheckStatus: UseCheckIlmPolicyStatus['resendRequest']; +} + +const IlmPolicyStatusContext = createContext<undefined | ContextValue>(undefined); + +export const IlmPolicyStatusContextProvider: FunctionComponent = ({ children }) => { + const { isLoading, data, resendRequest: recheckStatus } = useCheckIlmPolicyStatus(); + + return ( + <IlmPolicyStatusContext.Provider value={{ isLoading, status: data?.status, recheckStatus }}> + {children} + </IlmPolicyStatusContext.Provider> + ); +}; + +export type UseIlmPolicyStatusReturn = ReturnType<typeof useIlmPolicyStatus>; + +export const useIlmPolicyStatus = (): ContextValue => { + const ctx = useContext(IlmPolicyStatusContext); + if (!ctx) { + throw new Error('"useIlmPolicyStatus" can only be used inside of "IlmPolicyStatusContext"'); + } + return ctx; +}; diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/context.tsx b/x-pack/plugins/reporting/public/lib/reporting_api_client/context.tsx new file mode 100644 index 0000000000000..37857943774d4 --- /dev/null +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/context.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 type { HttpSetup } from 'src/core/public'; +import type { FunctionComponent } from 'react'; +import React, { createContext, useContext } from 'react'; + +import type { ReportingAPIClient } from './reporting_api_client'; + +interface ContextValue { + http: HttpSetup; + apiClient: ReportingAPIClient; +} + +const InternalApiClientContext = createContext<undefined | ContextValue>(undefined); + +export const InternalApiClientClientProvider: FunctionComponent<{ + http: HttpSetup; + apiClient: ReportingAPIClient; +}> = ({ http, apiClient, children }) => { + return ( + <InternalApiClientContext.Provider value={{ http, apiClient }}> + {children} + </InternalApiClientContext.Provider> + ); +}; + +export const useInternalApiClient = (): ContextValue => { + const ctx = useContext(InternalApiClientContext); + if (!ctx) { + throw new Error('"useInternalApiClient" can only be used inside of "InternalApiClientContext"'); + } + return ctx; +}; diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/hooks.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/hooks.ts new file mode 100644 index 0000000000000..afd8222fd3831 --- /dev/null +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/hooks.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 { useRequest, UseRequestResponse } from '../../shared_imports'; +import { IlmPolicyStatusResponse } from '../../../common/types'; + +import { API_GET_ILM_POLICY_STATUS } from '../../../common/constants'; + +import { useInternalApiClient } from './context'; + +export const useCheckIlmPolicyStatus = (): UseRequestResponse<IlmPolicyStatusResponse> => { + const { http } = useInternalApiClient(); + return useRequest(http, { path: API_GET_ILM_POLICY_STATUS, method: 'get' }); +}; diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/index.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/index.ts new file mode 100644 index 0000000000000..b32d675a1d209 --- /dev/null +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/index.ts @@ -0,0 +1,12 @@ +/* + * 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 * from './reporting_api_client'; + +export * from './hooks'; + +export { InternalApiClientClientProvider, useInternalApiClient } from './context'; diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts similarity index 93% rename from x-pack/plugins/reporting/public/lib/reporting_api_client.ts rename to x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts index 4ce9e8760f21f..64caac0e27bdd 100644 --- a/x-pack/plugins/reporting/public/lib/reporting_api_client.ts +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts @@ -12,8 +12,9 @@ import { API_BASE_GENERATE, API_BASE_URL, API_LIST_URL, + API_MIGRATE_ILM_POLICY_URL, REPORTING_MANAGEMENT_HOME, -} from '../../common/constants'; +} from '../../../common/constants'; import { DownloadReportFn, JobId, @@ -21,8 +22,8 @@ import { ReportApiJSON, ReportDocument, ReportSource, -} from '../../common/types'; -import { add } from '../notifier/job_completion_notifications'; +} from '../../../common/types'; +import { add } from '../../notifier/job_completion_notifications'; export interface JobQueueEntry { _id: string; @@ -167,4 +168,8 @@ export class ReportingAPIClient { this.http.post(`${API_BASE_URL}/diagnose/screenshot`, { asSystemRequest: true, }); + + public migrateReportingIndicesIlmPolicy = (): Promise<void> => { + return this.http.put(`${API_MIGRATE_ILM_POLICY_URL}`); + }; } diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap index 93df3c8d99935..9ce249aa32a1d 100644 --- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap @@ -1,82 +1,116 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ReportListing Report job listing with some items 1`] = ` -Array [ - <EuiBasicTable - columns={ - Array [ - Object { - "field": "object_title", - "name": "Report", - "render": [Function], - }, - Object { - "field": "created_at", - "name": "Created at", - "render": [Function], - }, - Object { - "field": "status", - "name": "Status", - "render": [Function], - }, - Object { - "actions": Array [ - Object { - "render": [Function], - }, - ], - "name": "Actions", - }, - ] - } - data-test-subj="reportJobListing" - isSelectable={true} - itemId="id" - items={Array []} - loading={true} - noItemsMessage="Loading reports" - onChange={[Function]} - pagination={ - Object { - "hidePerPageOptions": true, - "pageIndex": 0, - "pageSize": 10, - "totalItemCount": 0, - } - } - responsive={true} - selection={ - Object { - "itemId": "id", - "onSelectionChange": [Function], - } - } - tableCaption="Reports generated in Kibana applications" - tableLayout="fixed" - > - <div - className="euiBasicTable euiBasicTable-loading" - data-test-subj="reportJobListing" - > - <div> - <EuiTableHeaderMobile> +<div + className="euiBasicTable" + data-test-subj="reportJobListing" +> + <div> + <EuiTableHeaderMobile> + <div + className="euiTableHeaderMobile" + > + <EuiFlexGroup + alignItems="baseline" + justifyContent="spaceBetween" + responsive={false} + > <div - className="euiTableHeaderMobile" + className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow" > - <EuiFlexGroup - alignItems="baseline" - justifyContent="spaceBetween" - responsive={false} + <EuiFlexItem + grow={false} + > + <div + className="euiFlexItem euiFlexItem--flexGrowZero" + > + <EuiI18n + default="Select all rows" + token="euiBasicTable.selectAllRows" + > + <EuiCheckbox + aria-label="Select all rows" + checked={false} + compressed={false} + disabled={false} + id="_selection_column-checkbox_generated-id" + indeterminate={false} + label="Select all rows" + onChange={[Function]} + > + <div + className="euiCheckbox" + > + <input + aria-label="Select all rows" + checked={false} + className="euiCheckbox__input" + disabled={false} + id="_selection_column-checkbox_generated-id" + onChange={[Function]} + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + <label + className="euiCheckbox__label" + htmlFor="_selection_column-checkbox_generated-id" + > + Select all rows + </label> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </EuiFlexItem> + <EuiFlexItem + grow={false} > <div - className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow" + className="euiFlexItem euiFlexItem--flexGrowZero" + /> + </EuiFlexItem> + </div> + </EuiFlexGroup> + </div> + </EuiTableHeaderMobile> + <EuiTable + id="generated-id" + responsive={true} + tableLayout="fixed" + > + <table + className="euiTable euiTable--responsive" + id="generated-id" + tabIndex={-1} + > + <EuiScreenReaderOnly> + <caption + className="euiScreenReaderOnly euiTableCaption" + > + <EuiDelayRender + delay={500} + /> + </caption> + </EuiScreenReaderOnly> + <EuiTableHeader> + <thead> + <tr> + <EuiTableHeaderCellCheckbox + key="_selection_column_h" > - <EuiFlexItem - grow={false} + <th + className="euiTableHeaderCellCheckbox" + scope="col" + style={ + Object { + "width": undefined, + } + } > <div - className="euiFlexItem euiFlexItem--flexGrowZero" + className="euiTableCellContent" > <EuiI18n default="Select all rows" @@ -86,20 +120,23 @@ Array [ aria-label="Select all rows" checked={false} compressed={false} - disabled={true} + data-test-subj="checkboxSelectAll" + disabled={false} id="_selection_column-checkbox_generated-id" indeterminate={false} - label="Select all rows" + label={null} onChange={[Function]} + type="inList" > <div - className="euiCheckbox" + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" > <input aria-label="Select all rows" checked={false} className="euiCheckbox__input" - disabled={true} + data-test-subj="checkboxSelectAll" + disabled={false} id="_selection_column-checkbox_generated-id" onChange={[Function]} type="checkbox" @@ -107,466 +144,246 @@ Array [ <div className="euiCheckbox__square" /> - <label - className="euiCheckbox__label" - htmlFor="_selection_column-checkbox_generated-id" - > - Select all rows - </label> </div> </EuiCheckbox> </EuiI18n> </div> - </EuiFlexItem> - <EuiFlexItem - grow={false} - > - <div - className="euiFlexItem euiFlexItem--flexGrowZero" - /> - </EuiFlexItem> - </div> - </EuiFlexGroup> - </div> - </EuiTableHeaderMobile> - <EuiTable - id="generated-id" - responsive={true} - tableLayout="fixed" - > - <table - className="euiTable euiTable--responsive" - id="generated-id" - tabIndex={-1} - > - <EuiScreenReaderOnly> - <caption - className="euiScreenReaderOnly euiTableCaption" + </th> + </EuiTableHeaderCellCheckbox> + <EuiTableHeaderCell + align="left" + data-test-subj="tableHeaderCell_object_title_0" + key="_data_h_object_title_0" > - <EuiDelayRender - delay={500} - /> - </caption> - </EuiScreenReaderOnly> - <EuiTableHeader> - <thead> - <tr> - <EuiTableHeaderCellCheckbox - key="_selection_column_h" - > - <th - className="euiTableHeaderCellCheckbox" - scope="col" - style={ - Object { - "width": undefined, - } - } + <th + className="euiTableHeaderCell" + data-test-subj="tableHeaderCell_object_title_0" + role="columnheader" + scope="col" + style={ + Object { + "width": undefined, + } + } + > + <CellContents + className="euiTableCellContent" + showSortMsg={false} + > + <span + className="euiTableCellContent" > - <div - className="euiTableCellContent" - > + <EuiInnerText> <EuiI18n - default="Select all rows" - token="euiBasicTable.selectAllRows" + default="{innerText}; {description}" + token="euiTableHeaderCell.titleTextWithDesc" + values={ + Object { + "description": undefined, + "innerText": "Report", + } + } > - <EuiCheckbox - aria-label="Select all rows" - checked={false} - compressed={false} - data-test-subj="checkboxSelectAll" - disabled={true} - id="_selection_column-checkbox_generated-id" - indeterminate={false} - label={null} - onChange={[Function]} - type="inList" + <span + className="euiTableCellContent__text" + title="Report" > - <div - className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" - > - <input - aria-label="Select all rows" - checked={false} - className="euiCheckbox__input" - data-test-subj="checkboxSelectAll" - disabled={true} - id="_selection_column-checkbox_generated-id" - onChange={[Function]} - type="checkbox" - /> - <div - className="euiCheckbox__square" - /> - </div> - </EuiCheckbox> + Report + </span> </EuiI18n> - </div> - </th> - </EuiTableHeaderCellCheckbox> - <EuiTableHeaderCell - align="left" - data-test-subj="tableHeaderCell_object_title_0" - key="_data_h_object_title_0" - > - <th - className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_object_title_0" - role="columnheader" - scope="col" - style={ - Object { - "width": undefined, - } - } - > - <CellContents - className="euiTableCellContent" - showSortMsg={false} - > - <span - className="euiTableCellContent" - > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Report", - } - } - > - <span - className="euiTableCellContent__text" - title="Report" - > - Report - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> - <EuiTableHeaderCell - align="left" - data-test-subj="tableHeaderCell_created_at_1" - key="_data_h_created_at_1" - > - <th - className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_created_at_1" - role="columnheader" - scope="col" - style={ - Object { - "width": undefined, - } - } + </EuiInnerText> + </span> + </CellContents> + </th> + </EuiTableHeaderCell> + <EuiTableHeaderCell + align="left" + data-test-subj="tableHeaderCell_created_at_1" + key="_data_h_created_at_1" + > + <th + className="euiTableHeaderCell" + data-test-subj="tableHeaderCell_created_at_1" + role="columnheader" + scope="col" + style={ + Object { + "width": undefined, + } + } + > + <CellContents + className="euiTableCellContent" + showSortMsg={false} + > + <span + className="euiTableCellContent" > - <CellContents - className="euiTableCellContent" - showSortMsg={false} - > - <span - className="euiTableCellContent" + <EuiInnerText> + <EuiI18n + default="{innerText}; {description}" + token="euiTableHeaderCell.titleTextWithDesc" + values={ + Object { + "description": undefined, + "innerText": "Created at", + } + } > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Created at", - } - } - > - <span - className="euiTableCellContent__text" - title="Created at" - > - Created at - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> - <EuiTableHeaderCell - align="left" - data-test-subj="tableHeaderCell_status_2" - key="_data_h_status_2" - > - <th - className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_status_2" - role="columnheader" - scope="col" - style={ - Object { - "width": undefined, - } - } + <span + className="euiTableCellContent__text" + title="Created at" + > + Created at + </span> + </EuiI18n> + </EuiInnerText> + </span> + </CellContents> + </th> + </EuiTableHeaderCell> + <EuiTableHeaderCell + align="left" + data-test-subj="tableHeaderCell_status_2" + key="_data_h_status_2" + > + <th + className="euiTableHeaderCell" + data-test-subj="tableHeaderCell_status_2" + role="columnheader" + scope="col" + style={ + Object { + "width": undefined, + } + } + > + <CellContents + className="euiTableCellContent" + showSortMsg={false} + > + <span + className="euiTableCellContent" > - <CellContents - className="euiTableCellContent" - showSortMsg={false} - > - <span - className="euiTableCellContent" + <EuiInnerText> + <EuiI18n + default="{innerText}; {description}" + token="euiTableHeaderCell.titleTextWithDesc" + values={ + Object { + "description": undefined, + "innerText": "Status", + } + } > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Status", - } - } - > - <span - className="euiTableCellContent__text" - title="Status" - > - Status - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> - <EuiTableHeaderCell - align="right" - key="_actions_h_3" - > - <th - className="euiTableHeaderCell" - role="columnheader" - scope="col" - style={ - Object { - "width": undefined, - } - } + <span + className="euiTableCellContent__text" + title="Status" + > + Status + </span> + </EuiI18n> + </EuiInnerText> + </span> + </CellContents> + </th> + </EuiTableHeaderCell> + <EuiTableHeaderCell + align="right" + key="_actions_h_3" + > + <th + className="euiTableHeaderCell" + role="columnheader" + scope="col" + style={ + Object { + "width": undefined, + } + } + > + <CellContents + className="euiTableCellContent euiTableCellContent--alignRight" + showSortMsg={false} + > + <span + className="euiTableCellContent euiTableCellContent--alignRight" > - <CellContents - className="euiTableCellContent euiTableCellContent--alignRight" - showSortMsg={false} - > - <span - className="euiTableCellContent euiTableCellContent--alignRight" - > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Actions", - } - } - > - <span - className="euiTableCellContent__text" - title="Actions" - > - Actions - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> - </tr> - </thead> - </EuiTableHeader> - <EuiTableBody> - <tbody> - <EuiTableRow> - <tr - className="euiTableRow" - > - <EuiTableRowCell - align="center" - colSpan={5} - isMobileFullWidth={true} - > - <td - className="euiTableRowCell euiTableRowCell--isMobileFullWidth" - colSpan={5} - style={ - Object { - "width": undefined, + <EuiInnerText> + <EuiI18n + default="{innerText}; {description}" + token="euiTableHeaderCell.titleTextWithDesc" + values={ + Object { + "description": undefined, + "innerText": "Actions", + } } - } - > - <div - className="euiTableCellContent euiTableCellContent--alignCenter" > <span className="euiTableCellContent__text" + title="Actions" > - Loading reports + Actions </span> - </div> - </td> - </EuiTableRowCell> - </tr> - </EuiTableRow> - </tbody> - </EuiTableBody> - </table> - </EuiTable> - </div> - </div> - </EuiBasicTable>, - <div - className="euiBasicTable euiBasicTable-loading" - data-test-subj="reportJobListing" - > - <div> - <EuiTableHeaderMobile> - <div - className="euiTableHeaderMobile" + </EuiI18n> + </EuiInnerText> + </span> + </CellContents> + </th> + </EuiTableHeaderCell> + </tr> + </thead> + </EuiTableHeader> + <EuiTableBody + bodyRef={[Function]} > - <EuiFlexGroup - alignItems="baseline" - justifyContent="spaceBetween" - responsive={false} - > - <div - className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow" + <tbody> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} > - <EuiFlexItem - grow={false} - > - <div - className="euiFlexItem euiFlexItem--flexGrowZero" - > - <EuiI18n - default="Select all rows" - token="euiBasicTable.selectAllRows" - > - <EuiCheckbox - aria-label="Select all rows" - checked={false} - compressed={false} - disabled={true} - id="_selection_column-checkbox_generated-id" - indeterminate={false} - label="Select all rows" - onChange={[Function]} - > - <div - className="euiCheckbox" - > - <input - aria-label="Select all rows" - checked={false} - className="euiCheckbox__input" - disabled={true} - id="_selection_column-checkbox_generated-id" - onChange={[Function]} - type="checkbox" - /> - <div - className="euiCheckbox__square" - /> - <label - className="euiCheckbox__label" - htmlFor="_selection_column-checkbox_generated-id" - > - Select all rows - </label> - </div> - </EuiCheckbox> - </EuiI18n> - </div> - </EuiFlexItem> - <EuiFlexItem - grow={false} + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" > - <div - className="euiFlexItem euiFlexItem--flexGrowZero" - /> - </EuiFlexItem> - </div> - </EuiFlexGroup> - </div> - </EuiTableHeaderMobile> - <EuiTable - id="generated-id" - responsive={true} - tableLayout="fixed" - > - <table - className="euiTable euiTable--responsive" - id="generated-id" - tabIndex={-1} - > - <EuiScreenReaderOnly> - <caption - className="euiScreenReaderOnly euiTableCaption" - > - <EuiDelayRender - delay={500} - /> - </caption> - </EuiScreenReaderOnly> - <EuiTableHeader> - <thead> - <tr> - <EuiTableHeaderCellCheckbox - key="_selection_column_h" + <EuiTableRowCellCheckbox + key="_selection_column_k90e51pk1ieucbae0c3t8wo2" > - <th - className="euiTableHeaderCellCheckbox" - scope="col" - style={ - Object { - "width": undefined, - } - } + <td + className="euiTableRowCellCheckbox" > <div className="euiTableCellContent" > <EuiI18n - default="Select all rows" - token="euiBasicTable.selectAllRows" + default="Select this row" + token="euiBasicTable.selectThisRow" > <EuiCheckbox - aria-label="Select all rows" + aria-label="Select this row" checked={false} compressed={false} - data-test-subj="checkboxSelectAll" - disabled={true} - id="_selection_column-checkbox_generated-id" + data-test-subj="checkboxSelectRow-k90e51pk1ieucbae0c3t8wo2" + disabled={false} + id="_selection_column_k90e51pk1ieucbae0c3t8wo2-checkbox" indeterminate={false} - label={null} onChange={[Function]} + title="Select this row" type="inList" > <div className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" > <input - aria-label="Select all rows" + aria-label="Select this row" checked={false} className="euiCheckbox__input" - data-test-subj="checkboxSelectAll" - disabled={true} - id="_selection_column-checkbox_generated-id" + data-test-subj="checkboxSelectRow-k90e51pk1ieucbae0c3t8wo2" + disabled={false} + id="_selection_column_k90e51pk1ieucbae0c3t8wo2-checkbox" onChange={[Function]} + title="Select this row" type="checkbox" /> <div @@ -576,231 +393,9864 @@ Array [ </EuiCheckbox> </EuiI18n> </div> - </th> - </EuiTableHeaderCellCheckbox> - <EuiTableHeaderCell + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell align="left" - data-test-subj="tableHeaderCell_object_title_0" - key="_data_h_object_title_0" + key="_data_column_object_title_k90e51pk1ieucbae0c3t8wo2_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} > - <th - className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_object_title_0" - role="columnheader" - scope="col" + <td + className="euiTableRowCell" style={ Object { "width": undefined, } } > - <CellContents - className="euiTableCellContent" - showSortMsg={false} - > - <span - className="euiTableCellContent" - > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Report", - } - } + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + My Canvas Workpad + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" > - <span - className="euiTableCellContent__text" - title="Report" + <EuiTextColor + color="subdued" > - Report - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> - <EuiTableHeaderCell + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell align="left" - data-test-subj="tableHeaderCell_created_at_1" - key="_data_h_created_at_1" + key="_data_column_created_at_k90e51pk1ieucbae0c3t8wo2_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} > - <th - className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_created_at_1" - role="columnheader" - scope="col" + <td + className="euiTableRowCell" style={ Object { "width": undefined, } } > - <CellContents - className="euiTableCellContent" - showSortMsg={false} - > - <span - className="euiTableCellContent" - > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Created at", - } - } - > - <span - className="euiTableCellContent__text" - title="Created at" - > - Created at - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> - <EuiTableHeaderCell + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 05:01 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell align="left" - data-test-subj="tableHeaderCell_status_2" - key="_data_h_status_2" - > - <th - className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_status_2" - role="columnheader" - scope="col" - style={ - Object { - "width": undefined, - } + key="_data_column_status_k90e51pk1ieucbae0c3t8wo2_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, } - > - <CellContents - className="euiTableCellContent" - showSortMsg={false} - > - <span - className="euiTableCellContent" - > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Status", - } - } - > - <span - className="euiTableCellContent__text" - title="Status" - > - Status - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> - <EuiTableHeaderCell - align="right" - key="_actions_h_3" + } + setScopeRow={false} + textOnly={false} > - <th - className="euiTableHeaderCell" - role="columnheader" - scope="col" + <td + className="euiTableRowCell" style={ Object { "width": undefined, } } > - <CellContents - className="euiTableCellContent euiTableCellContent--alignRight" - showSortMsg={false} - > - <span - className="euiTableCellContent euiTableCellContent--alignRight" - > - <EuiInnerText> - <EuiI18n - default="{innerText}; {description}" - token="euiTableHeaderCell.titleTextWithDesc" - values={ - Object { - "description": undefined, - "innerText": "Actions", - } + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="Pending - waiting for job to be processed" + id="xpack.reporting.listing.tableValue.statusDetail.pendingStatusReachedText" + values={Object {}} + > + Pending - waiting for job to be processed + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k90e51pk1ieucbae0c3t8wo2_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 0, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo2", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": undefined, + "status": "pending", + "statusLabel": "Pending", + "type": "printable_pdf", + "warnings": undefined, + } + } + itemId="k90e51pk1ieucbae0c3t8wo2" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 0, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo2", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": undefined, + "status": "pending", + "statusLabel": "Pending", + "type": "printable_pdf", + "warnings": undefined, } + } + key="item_action_k90e51pk1ieucbae0c3t8wo2_0" + > + <div + className="euiTableCellContent__hoverItem" > - <span - className="euiTableCellContent__text" - title="Actions" + <div + onBlur={[Function]} + onFocus={[Function]} > - Actions - </span> - </EuiI18n> - </EuiInnerText> - </span> - </CellContents> - </th> - </EuiTableHeaderCell> + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 0, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo2", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": undefined, + "status": "pending", + "statusLabel": "Pending", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 0, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo2", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": undefined, + "status": "pending", + "statusLabel": "Pending", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 0, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo2", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": undefined, + "status": "pending", + "statusLabel": "Pending", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k90e51pk1ieucbae0c3t8wo2" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> </tr> - </thead> - </EuiTableHeader> - <EuiTableBody> - <tbody> - <EuiTableRow> - <tr - className="euiTableRow" - > - <EuiTableRowCell - align="center" - colSpan={5} - isMobileFullWidth={true} - > - <td - className="euiTableRowCell euiTableRowCell--isMobileFullWidth" - colSpan={5} - style={ - Object { - "width": undefined, - } + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k90e51pk1ieucbae0c3t8wo1" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k90e51pk1ieucbae0c3t8wo1" + disabled={false} + id="_selection_column_k90e51pk1ieucbae0c3t8wo1-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k90e51pk1ieucbae0c3t8wo1" + disabled={false} + id="_selection_column_k90e51pk1ieucbae0c3t8wo1-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k90e51pk1ieucbae0c3t8wo1_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" > <div - className="euiTableCellContent euiTableCellContent--alignCenter" + className="" + key=".0" > - <span - className="euiTableCellContent__text" + <div> + My Canvas Workpad + </div> + <EuiText + size="s" > - Loading reports + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k90e51pk1ieucbae0c3t8wo1_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 05:01 PM + </div> + <span> + elastic </span> </div> - </td> - </EuiTableRowCell> - </tr> - </EuiTableRow> - </tbody> - </EuiTableBody> - </table> - </EuiTable> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k90e51pk1ieucbae0c3t8wo1_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Processing (attempt 1 of 1)", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-14 @ 05:01 PM + </span>, + } + } + > + Processing (attempt 1 of 1) at + <span + className="eui-textNoWrap" + > + 2020-04-14 @ 05:01 PM + </span> + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k90e51pk1ieucbae0c3t8wo1_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo1", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T21:01:14.526Z", + "status": "processing", + "statusLabel": "Processing", + "type": "printable_pdf", + "warnings": undefined, + } + } + itemId="k90e51pk1ieucbae0c3t8wo1" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo1", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T21:01:14.526Z", + "status": "processing", + "statusLabel": "Processing", + "type": "printable_pdf", + "warnings": undefined, + } + } + key="item_action_k90e51pk1ieucbae0c3t8wo1_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo1", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T21:01:14.526Z", + "status": "processing", + "statusLabel": "Processing", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo1", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T21:01:14.526Z", + "status": "processing", + "statusLabel": "Processing", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": undefined, + "created_at": "2020-04-14T21:01:13.064Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90e51pk1ieucbae0c3t8wo1", + "max_attempts": 1, + "max_size_reached": false, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T21:01:14.526Z", + "status": "processing", + "statusLabel": "Processing", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k90e51pk1ieucbae0c3t8wo1" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k90cmthd1gv8cbae0c2le8bo" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k90cmthd1gv8cbae0c2le8bo" + disabled={false} + id="_selection_column_k90cmthd1gv8cbae0c2le8bo-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k90cmthd1gv8cbae0c2le8bo" + disabled={false} + id="_selection_column_k90cmthd1gv8cbae0c2le8bo-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k90cmthd1gv8cbae0c2le8bo_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + My Canvas Workpad + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k90cmthd1gv8cbae0c2le8bo_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 04:19 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k90cmthd1gv8cbae0c2le8bo_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Completed", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-14 @ 04:19 PM + </span>, + } + } + > + Completed at + <span + className="eui-textNoWrap" + > + 2020-04-14 @ 04:19 PM + </span> + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k90cmthd1gv8cbae0c2le8bo_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T20:19:14.748Z", + "created_at": "2020-04-14T20:19:02.977Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90cmthd1gv8cbae0c2le8bo", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T20:19:04.073Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + itemId="k90cmthd1gv8cbae0c2le8bo" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T20:19:14.748Z", + "created_at": "2020-04-14T20:19:02.977Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90cmthd1gv8cbae0c2le8bo", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T20:19:04.073Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + key="item_action_k90cmthd1gv8cbae0c2le8bo_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T20:19:14.748Z", + "created_at": "2020-04-14T20:19:02.977Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90cmthd1gv8cbae0c2le8bo", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T20:19:04.073Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiToolTip + content="Download report" + delay="regular" + position="top" + > + <span + className="euiToolTipAnchor" + onKeyUp={[Function]} + onMouseOut={[Function]} + onMouseOver={[Function]} + > + <EuiButtonIcon + aria-label="Download report" + iconType="importAction" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + <button + aria-label="Download report" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="importAction" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="importAction" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </span> + </EuiToolTip> + </ReportDownloadButton> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T20:19:14.748Z", + "created_at": "2020-04-14T20:19:02.977Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90cmthd1gv8cbae0c2le8bo", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T20:19:04.073Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T20:19:14.748Z", + "created_at": "2020-04-14T20:19:02.977Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k90cmthd1gv8cbae0c2le8bo", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T20:19:04.073Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k90cmthd1gv8cbae0c2le8bo" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k906958e1d4wcbae0c9hip1a" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k906958e1d4wcbae0c9hip1a" + disabled={false} + id="_selection_column_k906958e1d4wcbae0c9hip1a-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k906958e1d4wcbae0c9hip1a" + disabled={false} + id="_selection_column_k906958e1d4wcbae0c9hip1a-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k906958e1d4wcbae0c9hip1a_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + My Canvas Workpad + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k906958e1d4wcbae0c9hip1a_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 01:20 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k906958e1d4wcbae0c9hip1a_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Completed with warnings", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:21 PM + </span>, + } + } + > + Completed with warnings at + <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:21 PM + </span> + </FormattedMessage> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + <FormattedMessage + defaultMessage="Errors occurred: see job info for details." + id="xpack.reporting.listing.tableValue.statusDetail.warningsText" + values={Object {}} + > + Errors occurred: see job info for details. + </FormattedMessage> + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k906958e1d4wcbae0c9hip1a_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:21:08.223Z", + "created_at": "2020-04-14T17:20:27.326Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k906958e1d4wcbae0c9hip1a", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:20:29.444Z", + "status": "completed_with_warnings", + "statusLabel": "Completed with warnings", + "type": "printable_pdf", + "warnings": Array [ + "An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. TimeoutError: waiting for selector \\"[data-shared-item],[data-shared-items-count]\\" failed: timeout 30000ms exceeded", + ], + } + } + itemId="k906958e1d4wcbae0c9hip1a" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:21:08.223Z", + "created_at": "2020-04-14T17:20:27.326Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k906958e1d4wcbae0c9hip1a", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:20:29.444Z", + "status": "completed_with_warnings", + "statusLabel": "Completed with warnings", + "type": "printable_pdf", + "warnings": Array [ + "An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. TimeoutError: waiting for selector \\"[data-shared-item],[data-shared-items-count]\\" failed: timeout 30000ms exceeded", + ], + } + } + key="item_action_k906958e1d4wcbae0c9hip1a_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:21:08.223Z", + "created_at": "2020-04-14T17:20:27.326Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k906958e1d4wcbae0c9hip1a", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:20:29.444Z", + "status": "completed_with_warnings", + "statusLabel": "Completed with warnings", + "type": "printable_pdf", + "warnings": Array [ + "An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. TimeoutError: waiting for selector \\"[data-shared-item],[data-shared-items-count]\\" failed: timeout 30000ms exceeded", + ], + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiToolTip + content="Download report" + delay="regular" + position="top" + > + <span + className="euiToolTipAnchor" + onKeyUp={[Function]} + onMouseOut={[Function]} + onMouseOver={[Function]} + > + <EuiButtonIcon + aria-label="Download report" + iconType="importAction" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + <button + aria-label="Download report" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="importAction" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="importAction" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </span> + </EuiToolTip> + </ReportDownloadButton> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:21:08.223Z", + "created_at": "2020-04-14T17:20:27.326Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k906958e1d4wcbae0c9hip1a", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:20:29.444Z", + "status": "completed_with_warnings", + "statusLabel": "Completed with warnings", + "type": "printable_pdf", + "warnings": Array [ + "An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. TimeoutError: waiting for selector \\"[data-shared-item],[data-shared-items-count]\\" failed: timeout 30000ms exceeded", + ], + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:21:08.223Z", + "created_at": "2020-04-14T17:20:27.326Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k906958e1d4wcbae0c9hip1a", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:20:29.444Z", + "status": "completed_with_warnings", + "statusLabel": "Completed with warnings", + "type": "printable_pdf", + "warnings": Array [ + "An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. TimeoutError: waiting for selector \\"[data-shared-item],[data-shared-items-count]\\" failed: timeout 30000ms exceeded", + ], + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k906958e1d4wcbae0c9hip1a" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k9067y2a1d4wcbae0cad38n0" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k9067y2a1d4wcbae0cad38n0" + disabled={false} + id="_selection_column_k9067y2a1d4wcbae0cad38n0-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k9067y2a1d4wcbae0cad38n0" + disabled={false} + id="_selection_column_k9067y2a1d4wcbae0cad38n0-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k9067y2a1d4wcbae0cad38n0_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + My Canvas Workpad + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k9067y2a1d4wcbae0cad38n0_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 01:19 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k9067y2a1d4wcbae0cad38n0_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Completed", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:19 PM + </span>, + } + } + > + Completed at + <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:19 PM + </span> + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k9067y2a1d4wcbae0cad38n0_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:53.244Z", + "created_at": "2020-04-14T17:19:31.379Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067y2a1d4wcbae0cad38n0", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:39.883Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + itemId="k9067y2a1d4wcbae0cad38n0" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:53.244Z", + "created_at": "2020-04-14T17:19:31.379Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067y2a1d4wcbae0cad38n0", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:39.883Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + key="item_action_k9067y2a1d4wcbae0cad38n0_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:53.244Z", + "created_at": "2020-04-14T17:19:31.379Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067y2a1d4wcbae0cad38n0", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:39.883Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiToolTip + content="Download report" + delay="regular" + position="top" + > + <span + className="euiToolTipAnchor" + onKeyUp={[Function]} + onMouseOut={[Function]} + onMouseOver={[Function]} + > + <EuiButtonIcon + aria-label="Download report" + iconType="importAction" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + <button + aria-label="Download report" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="importAction" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="importAction" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </span> + </EuiToolTip> + </ReportDownloadButton> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:53.244Z", + "created_at": "2020-04-14T17:19:31.379Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067y2a1d4wcbae0cad38n0", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:39.883Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:53.244Z", + "created_at": "2020-04-14T17:19:31.379Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067y2a1d4wcbae0cad38n0", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:39.883Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k9067y2a1d4wcbae0cad38n0" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k9067s1m1d4wcbae0cdnvcms" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k9067s1m1d4wcbae0cdnvcms" + disabled={false} + id="_selection_column_k9067s1m1d4wcbae0cdnvcms-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k9067s1m1d4wcbae0cdnvcms" + disabled={false} + id="_selection_column_k9067s1m1d4wcbae0cdnvcms-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k9067s1m1d4wcbae0cdnvcms_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + My Canvas Workpad + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k9067s1m1d4wcbae0cdnvcms_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 01:19 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k9067s1m1d4wcbae0cdnvcms_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Completed", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:19 PM + </span>, + } + } + > + Completed at + <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:19 PM + </span> + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k9067s1m1d4wcbae0cdnvcms_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:36.822Z", + "created_at": "2020-04-14T17:19:23.578Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067s1m1d4wcbae0cdnvcms", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:25.247Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + itemId="k9067s1m1d4wcbae0cdnvcms" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:36.822Z", + "created_at": "2020-04-14T17:19:23.578Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067s1m1d4wcbae0cdnvcms", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:25.247Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + key="item_action_k9067s1m1d4wcbae0cdnvcms_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:36.822Z", + "created_at": "2020-04-14T17:19:23.578Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067s1m1d4wcbae0cdnvcms", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:25.247Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiToolTip + content="Download report" + delay="regular" + position="top" + > + <span + className="euiToolTipAnchor" + onKeyUp={[Function]} + onMouseOut={[Function]} + onMouseOver={[Function]} + > + <EuiButtonIcon + aria-label="Download report" + iconType="importAction" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + <button + aria-label="Download report" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="importAction" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="importAction" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </span> + </EuiToolTip> + </ReportDownloadButton> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:36.822Z", + "created_at": "2020-04-14T17:19:23.578Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067s1m1d4wcbae0cdnvcms", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:25.247Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:19:36.822Z", + "created_at": "2020-04-14T17:19:23.578Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9067s1m1d4wcbae0cdnvcms", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:19:25.247Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k9067s1m1d4wcbae0cdnvcms" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k9065q3s1d4wcbae0c00fxlh" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k9065q3s1d4wcbae0c00fxlh" + disabled={false} + id="_selection_column_k9065q3s1d4wcbae0c00fxlh-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k9065q3s1d4wcbae0c00fxlh" + disabled={false} + id="_selection_column_k9065q3s1d4wcbae0c00fxlh-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k9065q3s1d4wcbae0c00fxlh_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + My Canvas Workpad + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k9065q3s1d4wcbae0c00fxlh_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 01:17 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k9065q3s1d4wcbae0c00fxlh_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Completed", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:18 PM + </span>, + } + } + > + Completed at + <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:18 PM + </span> + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k9065q3s1d4wcbae0c00fxlh_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:18:03.910Z", + "created_at": "2020-04-14T17:17:47.752Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9065q3s1d4wcbae0c00fxlh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:17:50.379Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + itemId="k9065q3s1d4wcbae0c00fxlh" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:18:03.910Z", + "created_at": "2020-04-14T17:17:47.752Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9065q3s1d4wcbae0c00fxlh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:17:50.379Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + key="item_action_k9065q3s1d4wcbae0c00fxlh_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:18:03.910Z", + "created_at": "2020-04-14T17:17:47.752Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9065q3s1d4wcbae0c00fxlh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:17:50.379Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiToolTip + content="Download report" + delay="regular" + position="top" + > + <span + className="euiToolTipAnchor" + onKeyUp={[Function]} + onMouseOut={[Function]} + onMouseOver={[Function]} + > + <EuiButtonIcon + aria-label="Download report" + iconType="importAction" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + <button + aria-label="Download report" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="importAction" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="importAction" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </span> + </EuiToolTip> + </ReportDownloadButton> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:18:03.910Z", + "created_at": "2020-04-14T17:17:47.752Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9065q3s1d4wcbae0c00fxlh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:17:50.379Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:18:03.910Z", + "created_at": "2020-04-14T17:17:47.752Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k9065q3s1d4wcbae0c00fxlh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:17:50.379Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k9065q3s1d4wcbae0c00fxlh" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k905zdw11d34cbae0c3y6tzh" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k905zdw11d34cbae0c3y6tzh" + disabled={false} + id="_selection_column_k905zdw11d34cbae0c3y6tzh-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k905zdw11d34cbae0c3y6tzh" + disabled={false} + id="_selection_column_k905zdw11d34cbae0c3y6tzh-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k905zdw11d34cbae0c3y6tzh_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + My Canvas Workpad + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + canvas workpad + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k905zdw11d34cbae0c3y6tzh_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-14 @ 01:12 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k905zdw11d34cbae0c3y6tzh_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Completed", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:13 PM + </span>, + } + } + > + Completed at + <span + className="eui-textNoWrap" + > + 2020-04-14 @ 01:13 PM + </span> + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k905zdw11d34cbae0c3y6tzh_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:13:03.719Z", + "created_at": "2020-04-14T17:12:51.985Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k905zdw11d34cbae0c3y6tzh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:12:52.431Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + itemId="k905zdw11d34cbae0c3y6tzh" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:13:03.719Z", + "created_at": "2020-04-14T17:12:51.985Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k905zdw11d34cbae0c3y6tzh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:12:52.431Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + key="item_action_k905zdw11d34cbae0c3y6tzh_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:13:03.719Z", + "created_at": "2020-04-14T17:12:51.985Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k905zdw11d34cbae0c3y6tzh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:12:52.431Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiToolTip + content="Download report" + delay="regular" + position="top" + > + <span + className="euiToolTipAnchor" + onKeyUp={[Function]} + onMouseOut={[Function]} + onMouseOver={[Function]} + > + <EuiButtonIcon + aria-label="Download report" + iconType="importAction" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + <button + aria-label="Download report" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="importAction" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="importAction" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </span> + </EuiToolTip> + </ReportDownloadButton> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:13:03.719Z", + "created_at": "2020-04-14T17:12:51.985Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k905zdw11d34cbae0c3y6tzh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:12:52.431Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-14T17:13:03.719Z", + "created_at": "2020-04-14T17:12:51.985Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k905zdw11d34cbae0c3y6tzh", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "My Canvas Workpad", + "object_type": "canvas workpad", + "started_at": "2020-04-14T17:12:52.431Z", + "status": "completed", + "statusLabel": "Completed", + "type": "printable_pdf", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k905zdw11d34cbae0c3y6tzh" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + <EuiTableRow + hasActions={true} + isSelectable={true} + isSelected={false} + > + <tr + className="euiTableRow euiTableRow-isSelectable euiTableRow-hasActions" + > + <EuiTableRowCellCheckbox + key="_selection_column_k8t4ylcb07mi9d006214ifyg" + > + <td + className="euiTableRowCellCheckbox" + > + <div + className="euiTableCellContent" + > + <EuiI18n + default="Select this row" + token="euiBasicTable.selectThisRow" + > + <EuiCheckbox + aria-label="Select this row" + checked={false} + compressed={false} + data-test-subj="checkboxSelectRow-k8t4ylcb07mi9d006214ifyg" + disabled={false} + id="_selection_column_k8t4ylcb07mi9d006214ifyg-checkbox" + indeterminate={false} + onChange={[Function]} + title="Select this row" + type="inList" + > + <div + className="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel" + > + <input + aria-label="Select this row" + checked={false} + className="euiCheckbox__input" + data-test-subj="checkboxSelectRow-k8t4ylcb07mi9d006214ifyg" + disabled={false} + id="_selection_column_k8t4ylcb07mi9d006214ifyg-checkbox" + onChange={[Function]} + title="Select this row" + type="checkbox" + /> + <div + className="euiCheckbox__square" + /> + </div> + </EuiCheckbox> + </EuiI18n> + </div> + </td> + </EuiTableRowCellCheckbox> + <EuiTableRowCell + align="left" + key="_data_column_object_title_k8t4ylcb07mi9d006214ifyg_0" + mobileOptions={ + Object { + "header": "Report", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Report + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + count + </div> + <EuiText + size="s" + > + <div + className="euiText euiText--small" + > + <EuiTextColor + color="subdued" + > + <span + className="euiTextColor euiTextColor--subdued" + > + visualization + </span> + </EuiTextColor> + </div> + </EuiText> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_created_at_k8t4ylcb07mi9d006214ifyg_1" + mobileOptions={ + Object { + "header": "Created at", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Created at + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <div> + 2020-04-09 @ 03:09 PM + </div> + <span> + elastic + </span> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="left" + key="_data_column_status_k8t4ylcb07mi9d006214ifyg_2" + mobileOptions={ + Object { + "header": "Status", + "render": undefined, + } + } + setScopeRow={false} + textOnly={false} + > + <td + className="euiTableRowCell" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop" + > + Status + </div> + <div + className="euiTableCellContent euiTableCellContent--overflowingContent" + > + <div + className="" + key=".0" + > + <FormattedMessage + defaultMessage="{statusLabel} at {statusTimestamp}" + id="xpack.reporting.listing.tableValue.statusDetail.statusTimestampText" + values={ + Object { + "statusLabel": "Completed", + "statusTimestamp": <span + className="eui-textNoWrap" + > + 2020-04-09 @ 03:10 PM + </span>, + } + } + > + Completed at + <span + className="eui-textNoWrap" + > + 2020-04-09 @ 03:10 PM + </span> + </FormattedMessage> + </div> + </div> + </td> + </EuiTableRowCell> + <EuiTableRowCell + align="right" + hasActions={true} + key="record_actions_k8t4ylcb07mi9d006214ifyg_3" + showOnHover={true} + textOnly={false} + > + <td + className="euiTableRowCell euiTableRowCell--hasActions" + style={ + Object { + "width": undefined, + } + } + > + <div + className="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--showOnHover euiTableCellContent--overflowingContent" + > + <ExpandedItemActions + actionEnabled={[Function]} + actions={ + Array [ + Object { + "render": [Function], + }, + ] + } + className="euiTableCellContent__hoverItem" + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-09T19:10:10.049Z", + "created_at": "2020-04-09T19:09:52.139Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k8t4ylcb07mi9d006214ifyg", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "count", + "object_type": "visualization", + "started_at": "2020-04-09T19:09:54.570Z", + "status": "completed", + "statusLabel": "Completed", + "type": "PNG", + "warnings": undefined, + } + } + itemId="k8t4ylcb07mi9d006214ifyg" + key=".0" + > + <CustomItemAction + action={ + Object { + "render": [Function], + } + } + className="euiTableCellContent__hoverItem" + enabled={true} + index={0} + item={ + Object { + "attempts": 1, + "completed_at": "2020-04-09T19:10:10.049Z", + "created_at": "2020-04-09T19:09:52.139Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k8t4ylcb07mi9d006214ifyg", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "count", + "object_type": "visualization", + "started_at": "2020-04-09T19:09:54.570Z", + "status": "completed", + "statusLabel": "Completed", + "type": "PNG", + "warnings": undefined, + } + } + key="item_action_k8t4ylcb07mi9d006214ifyg_0" + > + <div + className="euiTableCellContent__hoverItem" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <ReportDownloadButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-09T19:10:10.049Z", + "created_at": "2020-04-09T19:09:52.139Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k8t4ylcb07mi9d006214ifyg", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "count", + "object_type": "visualization", + "started_at": "2020-04-09T19:09:54.570Z", + "status": "completed", + "statusLabel": "Completed", + "type": "PNG", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiToolTip + content="Download report" + delay="regular" + position="top" + > + <span + className="euiToolTipAnchor" + onKeyUp={[Function]} + onMouseOut={[Function]} + onMouseOver={[Function]} + > + <EuiButtonIcon + aria-label="Download report" + iconType="importAction" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + <button + aria-label="Download report" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="importAction" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="importAction" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </span> + </EuiToolTip> + </ReportDownloadButton> + <InjectIntl(ReportErrorButtonUi) + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-09T19:10:10.049Z", + "created_at": "2020-04-09T19:09:52.139Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k8t4ylcb07mi9d006214ifyg", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "count", + "object_type": "visualization", + "started_at": "2020-04-09T19:09:54.570Z", + "status": "completed", + "statusLabel": "Completed", + "type": "PNG", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <ReportErrorButtonUi + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + record={ + Object { + "attempts": 1, + "completed_at": "2020-04-09T19:10:10.049Z", + "created_at": "2020-04-09T19:09:52.139Z", + "created_by": "elastic", + "csv_contains_formulas": undefined, + "id": "k8t4ylcb07mi9d006214ifyg", + "max_attempts": 1, + "max_size_reached": undefined, + "object_title": "count", + "object_type": "visualization", + "started_at": "2020-04-09T19:09:54.570Z", + "status": "completed", + "statusLabel": "Completed", + "type": "PNG", + "warnings": undefined, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + /> + </InjectIntl(ReportErrorButtonUi)> + <ReportInfoButton + apiClient={ + Object { + "list": [Function], + "migrateReportingIndicesIlmPolicy": [MockFunction], + "total": [Function], + } + } + ilmPolicyContextValue={ + Object { + "isLoading": false, + "recheckStatus": [Function], + "status": undefined, + } + } + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object { + "date": Object { + "full": Object { + "day": "numeric", + "month": "long", + "weekday": "long", + "year": "numeric", + }, + "long": Object { + "day": "numeric", + "month": "long", + "year": "numeric", + }, + "medium": Object { + "day": "numeric", + "month": "short", + "year": "numeric", + }, + "short": Object { + "day": "numeric", + "month": "numeric", + "year": "2-digit", + }, + }, + "number": Object { + "currency": Object { + "style": "currency", + }, + "percent": Object { + "style": "percent", + }, + }, + "relative": Object { + "days": Object { + "units": "day", + }, + "hours": Object { + "units": "hour", + }, + "minutes": Object { + "units": "minute", + }, + "months": Object { + "units": "month", + }, + "seconds": Object { + "units": "second", + }, + "years": Object { + "units": "year", + }, + }, + "time": Object { + "full": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "long": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short", + }, + "medium": Object { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + }, + "short": Object { + "hour": "numeric", + "minute": "numeric", + }, + }, + }, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } + } + jobId="k8t4ylcb07mi9d006214ifyg" + license$={ + Object { + "subscribe": [Function], + } + } + navigateToUrl={[MockFunction]} + pollConfig={ + Object { + "jobCompletionNotifier": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + "jobsRefresh": Object { + "interval": 5000, + "intervalErrorMultiplier": 3, + }, + } + } + redirect={[MockFunction]} + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + urlService={ + Object { + "locators": Object { + "get": [Function], + }, + } + } + > + <EuiButtonIcon + aria-label="Show report info" + color="primary" + data-test-subj="reportInfoButton" + iconType="iInCircle" + onClick={[Function]} + > + <button + aria-label="Show report info" + className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="reportInfoButton" + disabled={false} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="iInCircle" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="iInCircle" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </ReportInfoButton> + </div> + </div> + </CustomItemAction> + </ExpandedItemActions> + </div> + </td> + </EuiTableRowCell> + </tr> + </EuiTableRow> + </tbody> + </EuiTableBody> + </table> + </EuiTable> + </div> + <PaginationBar + aria-controls="generated-id" + aria-label={ + <EuiI18n + default="Pagination for preceding table: {tableCaption}" + token="euiBasicTable.tablePagination" + values={ + Object { + "tableCaption": "Reports generated in Kibana applications", + } + } + /> + } + onPageChange={[Function]} + onPageSizeChange={[Function]} + pagination={ + Object { + "hidePerPageOptions": true, + "pageIndex": 0, + "pageSize": 10, + "totalItemCount": 18, + } + } + > + <div> + <EuiSpacer + size="m" + > + <div + className="euiSpacer euiSpacer--m" + /> + </EuiSpacer> + <EuiTablePagination + activePage={0} + aria-controls="generated-id" + hidePerPageOptions={true} + itemsPerPage={10} + itemsPerPageOptions={ + Array [ + 10, + 25, + 50, + ] + } + onChangeItemsPerPage={[Function]} + onChangePage={[Function]} + pageCount={2} + > + <EuiFlexGroup + alignItems="center" + justifyContent="spaceBetween" + responsive={false} + > + <div + className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow" + > + <EuiFlexItem + grow={false} + > + <div + className="euiFlexItem euiFlexItem--flexGrowZero" + /> + </EuiFlexItem> + <EuiFlexItem + grow={false} + > + <div + className="euiFlexItem euiFlexItem--flexGrowZero" + > + <EuiPagination + activePage={0} + aria-controls="generated-id" + onPageClick={[Function]} + pageCount={2} + > + <nav + className="euiPagination" + > + <EuiI18n + default="Previous page, {page}" + token="euiPagination.previousPage" + values={ + Object { + "page": 0, + } + } + > + <EuiI18n + default="Previous page" + token="euiPagination.disabledPreviousPage" + > + <EuiButtonIcon + aria-label="Previous page" + color="text" + data-test-subj="pagination-button-previous" + disabled={true} + iconType="arrowLeft" + onClick={[Function]} + > + <button + aria-label="Previous page" + className="euiButtonIcon euiButtonIcon--text euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="pagination-button-previous" + disabled={true} + onClick={[Function]} + type="button" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="arrowLeft" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="arrowLeft" + size="m" + /> + </EuiIcon> + </button> + </EuiButtonIcon> + </EuiI18n> + </EuiI18n> + <ul + className="euiPagination__list" + > + <PaginationButton + key="0" + pageIndex={0} + > + <li + className="euiPagination__item" + > + <EuiPaginationButton + aria-controls="generated-id" + hideOnMobile={true} + isActive={true} + onClick={[Function]} + pageIndex={0} + totalPages={2} + > + <EuiI18n + default="Page {page} of {totalPages}" + token="euiPaginationButton.longPageString" + values={ + Object { + "page": 1, + "totalPages": 2, + } + } + > + <EuiI18n + default="Page {page}" + token="euiPaginationButton.shortPageString" + values={ + Object { + "page": 1, + } + } + > + <EuiButtonEmpty + aria-controls="generated-id" + aria-current={true} + aria-label="Page 1 of 2" + className="euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile" + color="text" + data-test-subj="pagination-button-0" + href="#generated-id" + isDisabled={true} + onClick={[Function]} + size="s" + > + <button + aria-controls="generated-id" + aria-current={true} + aria-label="Page 1 of 2" + className="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--small euiButtonEmpty-isDisabled euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile" + data-test-subj="pagination-button-0" + disabled={true} + onClick={[Function]} + type="button" + > + <EuiButtonContent + className="euiButtonEmpty__content" + iconSide="left" + iconSize="m" + textProps={ + Object { + "className": "euiButtonEmpty__text", + } + } + > + <span + className="euiButtonContent euiButtonEmpty__content" + > + <span + className="euiButtonEmpty__text" + > + 1 + </span> + </span> + </EuiButtonContent> + </button> + </EuiButtonEmpty> + </EuiI18n> + </EuiI18n> + </EuiPaginationButton> + </li> + </PaginationButton> + <PaginationButton + key="1" + pageIndex={1} + > + <li + className="euiPagination__item" + > + <EuiPaginationButton + aria-controls="generated-id" + hideOnMobile={true} + isActive={false} + onClick={[Function]} + pageIndex={1} + totalPages={2} + > + <EuiI18n + default="Page {page} of {totalPages}" + token="euiPaginationButton.longPageString" + values={ + Object { + "page": 2, + "totalPages": 2, + } + } + > + <EuiI18n + default="Page {page}" + token="euiPaginationButton.shortPageString" + values={ + Object { + "page": 2, + } + } + > + <EuiButtonEmpty + aria-controls="generated-id" + aria-label="Page 2 of 2" + className="euiPaginationButton euiPaginationButton--hideOnMobile" + color="text" + data-test-subj="pagination-button-1" + href="#generated-id" + isDisabled={false} + onClick={[Function]} + size="s" + > + <a + aria-controls="generated-id" + aria-label="Page 2 of 2" + className="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--small euiPaginationButton euiPaginationButton--hideOnMobile" + data-test-subj="pagination-button-1" + href="#generated-id" + onClick={[Function]} + rel="noreferrer" + > + <EuiButtonContent + className="euiButtonEmpty__content" + iconSide="left" + iconSize="m" + textProps={ + Object { + "className": "euiButtonEmpty__text", + } + } + > + <span + className="euiButtonContent euiButtonEmpty__content" + > + <span + className="euiButtonEmpty__text" + > + 2 + </span> + </span> + </EuiButtonContent> + </a> + </EuiButtonEmpty> + </EuiI18n> + </EuiI18n> + </EuiPaginationButton> + </li> + </PaginationButton> + </ul> + <EuiI18n + default="Next page, {page}" + token="euiPagination.nextPage" + values={ + Object { + "page": 2, + } + } + > + <EuiI18n + default="Next page" + token="euiPagination.disabledNextPage" + > + <EuiButtonIcon + aria-controls="generated-id" + aria-label="Next page, 2" + color="text" + data-test-subj="pagination-button-next" + href="#generated-id" + iconType="arrowRight" + onClick={[Function]} + > + <a + aria-controls="generated-id" + aria-label="Next page, 2" + className="euiButtonIcon euiButtonIcon--text euiButtonIcon--empty euiButtonIcon--xSmall" + data-test-subj="pagination-button-next" + href="#generated-id" + onClick={[Function]} + rel="noreferrer" + > + <EuiIcon + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + size="m" + type="arrowRight" + > + <span + aria-hidden="true" + className="euiButtonIcon__icon" + color="inherit" + data-euiicon-type="arrowRight" + size="m" + /> + </EuiIcon> + </a> + </EuiButtonIcon> + </EuiI18n> + </EuiI18n> + </nav> + </EuiPagination> + </div> + </EuiFlexItem> + </div> + </EuiFlexGroup> + </EuiTablePagination> </div> - </div>, -] + </PaginationBar> +</div> `; diff --git a/x-pack/plugins/reporting/public/management/ilm_policy_link.tsx b/x-pack/plugins/reporting/public/management/ilm_policy_link.tsx new file mode 100644 index 0000000000000..3945ec5be9fa7 --- /dev/null +++ b/x-pack/plugins/reporting/public/management/ilm_policy_link.tsx @@ -0,0 +1,47 @@ +/* + * 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 type { FunctionComponent } from 'react'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty } from '@elastic/eui'; +import type { ApplicationStart } from 'src/core/public'; + +import { ILM_POLICY_NAME } from '../../common/constants'; +import { LocatorPublic, SerializableState } from '../shared_imports'; + +interface Props { + navigateToUrl: ApplicationStart['navigateToUrl']; + locator: LocatorPublic<SerializableState>; +} + +const i18nTexts = { + buttonLabel: i18n.translate('xpack.reporting.listing.reports.ilmPolicyLinkText', { + defaultMessage: 'Edit ILM policy', + }), +}; + +export const IlmPolicyLink: FunctionComponent<Props> = ({ locator, navigateToUrl }) => { + return ( + <EuiButtonEmpty + data-test-subj="ilmPolicyLink" + size="xs" + onClick={() => { + locator + .getUrl({ + page: 'policy_edit', + policyName: ILM_POLICY_NAME, + }) + .then((url) => { + navigateToUrl(url); + }); + }} + > + {i18nTexts.buttonLabel} + </EuiButtonEmpty> + ); +}; diff --git a/x-pack/plugins/reporting/public/management/migrate_ilm_policy_callout/ilm_policy_migration_needed_callout.tsx b/x-pack/plugins/reporting/public/management/migrate_ilm_policy_callout/ilm_policy_migration_needed_callout.tsx new file mode 100644 index 0000000000000..5bb3ac524e130 --- /dev/null +++ b/x-pack/plugins/reporting/public/management/migrate_ilm_policy_callout/ilm_policy_migration_needed_callout.tsx @@ -0,0 +1,94 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { FunctionComponent } from 'react'; +import React, { useState } from 'react'; +import { EuiCallOut, EuiButton, EuiCode } from '@elastic/eui'; + +import type { NotificationsSetup } from 'src/core/public'; + +import { ILM_POLICY_NAME } from '../../../common/constants'; + +import { useInternalApiClient } from '../../lib/reporting_api_client'; + +const i18nTexts = { + title: i18n.translate('xpack.reporting.listing.ilmPolicyCallout.migrationNeededTitle', { + defaultMessage: 'Migrate reporting indices', + }), + description: ( + <FormattedMessage + id="xpack.reporting.listing.ilmPolicyCallout.migrationNeededDescription" + defaultMessage="Reporting indices are not managed by the same ILM policy. This can lead to unexpected results for the lifecycle of reports stored in Elasticsearch indices. Reporting indices should all be managed by the {ilmPolicyName} policy." + values={{ + ilmPolicyName: <EuiCode>{ILM_POLICY_NAME}</EuiCode>, + }} + /> + ), + buttonLabel: i18n.translate( + 'xpack.reporting.listing.ilmPolicyCallout.migrateIndicesButtonLabel', + { + defaultMessage: 'Migrate indices', + } + ), + migrateErrorTitle: i18n.translate( + 'xpack.reporting.listing.ilmPolicyCallout.migrateIndicesErrorTitle', + { + defaultMessage: 'Could not migrate reporting indices', + } + ), + migrateSuccessTitle: i18n.translate( + 'xpack.reporting.listing.ilmPolicyCallout.migrateIndicesSuccessTitle', + { + defaultMessage: 'Successfully migrated reporting indices', + } + ), +}; + +interface Props { + toasts: NotificationsSetup['toasts']; + onMigrationDone: () => void; +} + +export const IlmPolicyMigrationNeededCallOut: FunctionComponent<Props> = ({ + toasts, + onMigrationDone, +}) => { + const [isMigratingIndices, setIsMigratingIndices] = useState(false); + + const { apiClient } = useInternalApiClient(); + + const migrateReportingIndices = async () => { + try { + setIsMigratingIndices(true); + await apiClient.migrateReportingIndicesIlmPolicy(); + onMigrationDone(); + toasts.addSuccess({ title: i18nTexts.migrateSuccessTitle }); + } catch (e) { + toasts.addError(e, { + title: i18nTexts.migrateErrorTitle, + toastMessage: e.body?.message, + }); + } finally { + setIsMigratingIndices(false); + } + }; + + return ( + <EuiCallOut data-test-subj="migrateReportingIndicesPolicyCallOut" title={i18nTexts.title}> + <p>{i18nTexts.description}</p> + <EuiButton + data-test-subj="migrateReportingIndicesButton" + isLoading={isMigratingIndices} + onClick={migrateReportingIndices} + > + {i18nTexts.buttonLabel} + </EuiButton> + </EuiCallOut> + ); +}; diff --git a/x-pack/plugins/reporting/public/management/migrate_ilm_policy_callout/index.tsx b/x-pack/plugins/reporting/public/management/migrate_ilm_policy_callout/index.tsx new file mode 100644 index 0000000000000..892cbcdde5ede --- /dev/null +++ b/x-pack/plugins/reporting/public/management/migrate_ilm_policy_callout/index.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FunctionComponent } from 'react'; +import React from 'react'; +import { EuiSpacer, EuiFlexItem } from '@elastic/eui'; + +import { NotificationsSetup } from 'src/core/public'; + +import { useIlmPolicyStatus } from '../../lib/ilm_policy_status_context'; + +import { IlmPolicyMigrationNeededCallOut } from './ilm_policy_migration_needed_callout'; + +interface Props { + toasts: NotificationsSetup['toasts']; +} + +export const MigrateIlmPolicyCallOut: FunctionComponent<Props> = ({ toasts }) => { + const { isLoading, recheckStatus, status } = useIlmPolicyStatus(); + + if (isLoading || !status || status === 'ok') { + return null; + } + + return ( + <> + <EuiSpacer size="m" /> + <EuiFlexItem> + <IlmPolicyMigrationNeededCallOut toasts={toasts} onMigrationDone={recheckStatus} /> + </EuiFlexItem> + </> + ); +}; diff --git a/x-pack/plugins/reporting/public/management/mount_management_section.tsx b/x-pack/plugins/reporting/public/management/mount_management_section.tsx index eb1057a9bdfc7..8d147628c6662 100644 --- a/x-pack/plugins/reporting/public/management/mount_management_section.tsx +++ b/x-pack/plugins/reporting/public/management/mount_management_section.tsx @@ -10,10 +10,11 @@ import * as React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Observable } from 'rxjs'; import { CoreSetup, CoreStart } from 'src/core/public'; -import { ManagementAppMountParams } from '../../../../../src/plugins/management/public'; import { ILicense } from '../../../licensing/public'; -import { ReportingAPIClient } from '../lib/reporting_api_client'; +import { ReportingAPIClient, InternalApiClientClientProvider } from '../lib/reporting_api_client'; +import { IlmPolicyStatusContextProvider } from '../lib/ilm_policy_status_context'; import { ClientConfigType } from '../plugin'; +import type { ManagementAppMountParams, SharePluginSetup } from '../shared_imports'; import { ReportListing } from './report_listing'; export async function mountManagementSection( @@ -22,17 +23,23 @@ export async function mountManagementSection( license$: Observable<ILicense>, pollConfig: ClientConfigType['poll'], apiClient: ReportingAPIClient, + urlService: SharePluginSetup['url'], params: ManagementAppMountParams ) { render( <I18nProvider> - <ReportListing - toasts={coreSetup.notifications.toasts} - license$={license$} - pollConfig={pollConfig} - redirect={coreStart.application.navigateToApp} - apiClient={apiClient} - /> + <InternalApiClientClientProvider http={coreSetup.http} apiClient={apiClient}> + <IlmPolicyStatusContextProvider> + <ReportListing + toasts={coreSetup.notifications.toasts} + license$={license$} + pollConfig={pollConfig} + redirect={coreStart.application.navigateToApp} + navigateToUrl={coreStart.application.navigateToUrl} + urlService={urlService} + /> + </IlmPolicyStatusContextProvider> + </InternalApiClientClientProvider> </I18nProvider>, params.element ); diff --git a/x-pack/plugins/reporting/public/management/report_listing.test.tsx b/x-pack/plugins/reporting/public/management/report_listing.test.tsx index efc1a7dfe3b20..0b278cbaa0449 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.test.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.test.tsx @@ -7,9 +7,22 @@ import React from 'react'; import { Observable } from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; -import { ILicense } from '../../../licensing/public'; -import { ReportingAPIClient } from '../lib/reporting_api_client'; +import { UnwrapPromise } from '@kbn/utility-types'; + +import { act } from 'react-dom/test-utils'; + +import { registerTestBed } from '@kbn/test/jest'; + +import type { SharePluginSetup, LocatorPublic } from '../../../../../src/plugins/share/public'; +import type { NotificationsSetup } from '../../../../../src/core/public'; +import { httpServiceMock, notificationServiceMock } from '../../../../../src/core/public/mocks'; + +import type { ILicense } from '../../../licensing/public'; + +import { IlmPolicyMigrationStatus } from '../../common/types'; + +import { ReportingAPIClient, InternalApiClientClientProvider } from '../lib/reporting_api_client'; +import { IlmPolicyStatusContextProvider } from '../lib/ilm_policy_status_context'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { return { @@ -17,7 +30,7 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { }; }); -import { ReportListing } from './report_listing'; +import { ReportListing, Props } from './report_listing'; const reportingAPIClient = { list: () => @@ -33,6 +46,7 @@ const reportingAPIClient = { { _id: 'k8t4ylcb07mi9d006214ifyg', _index: '.reporting-2020.04.05', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-09T19:10:10.049Z', created_at: '2020-04-09T19:09:52.139Z', created_by: 'elastic', jobtype: 'PNG', kibana_id: 'f2e59b4e-f79b-4a48-8a7d-6d50a3c1d914', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'png', objectType: 'visualization', }, output: { content_type: 'image/png', }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-09T19:09:52.137Z', layout: { dimensions: { height: 1575, width: 1423, }, id: 'png', }, objectType: 'visualization', relativeUrl: "/s/hsyjklk/app/visualize#/edit/94d1fe40-7a94-11ea-b373-0749f92ad295?_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!((enabled:!t,id:'1',params:(),schema:metric,type:count)),params:(addLegend:!f,addTooltip:!t,metric:(colorSchema:'Green%20to%20Red',colorsRange:!((from:0,to:10000)),invertColors:!f,labels:(show:!t),metricColorMode:None,percentageMode:!f,style:(bgColor:!f,bgFill:%23000,fontSize:60,labelColor:!f,subText:''),useRanges:!f),type:metric),title:count,type:metric))&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15y,to:now))&indexPattern=d81752b0-7434-11ea-be36-1f978cda44d4&type=metric", title: 'count', }, priority: 10, process_expiration: '2020-04-09T19:14:54.570Z', started_at: '2020-04-09T19:09:54.570Z', status: 'completed', timeout: 300000, }, sort: [1586459392139], }, ]), // prettier-ignore total: () => Promise.resolve(18), + migrateReportingIndicesIlmPolicy: jest.fn(), } as any; const validCheck = { @@ -48,10 +62,6 @@ const license$ = { }, } as Observable<ILicense>; -const toasts = { - addDanger: jest.fn(), -} as any; - const mockPollConfig = { jobCompletionNotifier: { interval: 5000, @@ -64,22 +74,87 @@ const mockPollConfig = { }; describe('ReportListing', () => { - it('Report job listing with some items', () => { - const wrapper = mountWithIntl( - <ReportListing + let httpService: ReturnType<typeof httpServiceMock.createSetupContract>; + let ilmLocator: undefined | LocatorPublic<any>; + let urlService: SharePluginSetup['url']; + let testBed: UnwrapPromise<ReturnType<typeof setup>>; + let toasts: NotificationsSetup['toasts']; + + const createTestBed = registerTestBed( + (props?: Partial<Props>) => ( + <InternalApiClientClientProvider apiClient={reportingAPIClient as ReportingAPIClient} - license$={license$} - pollConfig={mockPollConfig} - redirect={jest.fn()} - toasts={toasts} - /> - ); - wrapper.update(); - const input = wrapper.find('[data-test-subj="reportJobListing"]'); - expect(input).toMatchSnapshot(); + http={httpService} + > + <IlmPolicyStatusContextProvider> + <ReportListing + license$={license$} + pollConfig={mockPollConfig} + redirect={jest.fn()} + navigateToUrl={jest.fn()} + urlService={urlService} + toasts={toasts} + {...props} + /> + </IlmPolicyStatusContextProvider> + </InternalApiClientClientProvider> + ), + { memoryRouter: { wrapComponent: false } } + ); + + const setup = async (props?: Partial<Props>) => { + const tb = await createTestBed(props); + const { find, exists, component } = tb; + + return { + ...tb, + actions: { + findListTable: () => find('reportJobListing'), + hasIlmMigrationBanner: () => exists('migrateReportingIndicesPolicyCallOut'), + hasIlmPolicyLink: () => exists('ilmPolicyLink'), + migrateIndices: async () => { + await act(async () => { + find('migrateReportingIndicesButton').simulate('click'); + }); + component.update(); + }, + }, + }; + }; + + const runSetup = async (props?: Partial<Props>) => { + await act(async () => { + testBed = await setup(props); + }); + testBed.component.update(); + }; + + beforeEach(async () => { + toasts = notificationServiceMock.createSetupContract().toasts; + httpService = httpServiceMock.createSetupContract(); + ilmLocator = ({ + getUrl: jest.fn(), + } as unknown) as LocatorPublic<any>; + + urlService = ({ + locators: { + get: () => ilmLocator, + }, + } as unknown) as SharePluginSetup['url']; + await runSetup(); + }); + + afterEach(() => { + jest.clearAllMocks(); }); - it('subscribes to license changes, and unsubscribes on dismount', () => { + it('Report job listing with some items', () => { + const { actions } = testBed; + const table = actions.findListTable(); + expect(table).toMatchSnapshot(); + }); + + it('subscribes to license changes, and unsubscribes on dismount', async () => { const unsubscribeMock = jest.fn(); const subMock = { subscribe: jest.fn().mockReturnValue({ @@ -87,19 +162,103 @@ describe('ReportListing', () => { }), } as any; - const wrapper = mountWithIntl( - <ReportListing - apiClient={reportingAPIClient as ReportingAPIClient} - license$={subMock as Observable<ILicense>} - pollConfig={mockPollConfig} - redirect={jest.fn()} - toasts={toasts} - /> - ); - wrapper.update(); + await runSetup({ license$: subMock }); + expect(subMock.subscribe).toHaveBeenCalled(); expect(unsubscribeMock).not.toHaveBeenCalled(); - wrapper.unmount(); + testBed.component.unmount(); expect(unsubscribeMock).toHaveBeenCalled(); }); + + describe('ILM policy', () => { + beforeEach(async () => { + httpService = httpServiceMock.createSetupContract(); + ilmLocator = ({ + getUrl: jest.fn(), + } as unknown) as LocatorPublic<any>; + + urlService = ({ + locators: { + get: () => ilmLocator, + }, + } as unknown) as SharePluginSetup['url']; + + await runSetup(); + }); + + it('shows the migrate banner when migration status is not "OK"', async () => { + const status: IlmPolicyMigrationStatus = 'indices-not-managed-by-policy'; + httpService.get.mockResolvedValue({ status }); + await runSetup(); + const { actions } = testBed; + expect(actions.hasIlmMigrationBanner()).toBe(true); + }); + + it('does not show the migrate banner when migration status is "OK"', async () => { + const status: IlmPolicyMigrationStatus = 'ok'; + httpService.get.mockResolvedValue({ status }); + await runSetup(); + const { actions } = testBed; + expect(actions.hasIlmMigrationBanner()).toBe(false); + }); + + it('hides the ILM policy link if there is no ILM policy', async () => { + const status: IlmPolicyMigrationStatus = 'policy-not-found'; + httpService.get.mockResolvedValue({ status }); + await runSetup(); + const { actions } = testBed; + expect(actions.hasIlmPolicyLink()).toBe(false); + }); + + it('hides the ILM policy link if there is no ILM policy locator', async () => { + ilmLocator = undefined; + const status: IlmPolicyMigrationStatus = 'ok'; // should never happen, but need to test that when the locator is missing we don't render the link + httpService.get.mockResolvedValue({ status }); + await runSetup(); + const { actions } = testBed; + expect(actions.hasIlmPolicyLink()).toBe(false); + }); + + it('always shows the ILM policy link if there is an ILM policy', async () => { + const status: IlmPolicyMigrationStatus = 'ok'; + httpService.get.mockResolvedValue({ status }); + await runSetup(); + const { actions } = testBed; + expect(actions.hasIlmPolicyLink()).toBe(true); + + const status2: IlmPolicyMigrationStatus = 'indices-not-managed-by-policy'; + httpService.get.mockResolvedValue({ status: status2 }); + await runSetup(); + expect(actions.hasIlmPolicyLink()).toBe(true); + }); + + it('hides the banner after migrating indices', async () => { + const status: IlmPolicyMigrationStatus = 'indices-not-managed-by-policy'; + const status2: IlmPolicyMigrationStatus = 'ok'; + httpService.get.mockResolvedValueOnce({ status }); + httpService.get.mockResolvedValueOnce({ status: status2 }); + await runSetup(); + const { actions } = testBed; + + expect(actions.hasIlmMigrationBanner()).toBe(true); + await actions.migrateIndices(); + expect(actions.hasIlmMigrationBanner()).toBe(false); + expect(actions.hasIlmPolicyLink()).toBe(true); + expect(toasts.addSuccess).toHaveBeenCalledTimes(1); + }); + + it('informs users when migrations failed', async () => { + const status: IlmPolicyMigrationStatus = 'indices-not-managed-by-policy'; + httpService.get.mockResolvedValueOnce({ status }); + reportingAPIClient.migrateReportingIndicesIlmPolicy.mockRejectedValueOnce(new Error('oops!')); + await runSetup(); + const { actions } = testBed; + + expect(actions.hasIlmMigrationBanner()).toBe(true); + await actions.migrateIndices(); + expect(toasts.addError).toHaveBeenCalledTimes(1); + expect(actions.hasIlmMigrationBanner()).toBe(true); + expect(actions.hasIlmPolicyLink()).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx index 0b6ece4d8bd02..749e42de526d3 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -13,6 +13,7 @@ import { EuiSpacer, EuiText, EuiTextColor, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; @@ -26,10 +27,18 @@ import { JOB_STATUSES as JobStatuses } from '../../common/constants'; import { Poller } from '../../common/poller'; import { durationToNumber } from '../../common/schema_utils'; import { checkLicense } from '../lib/license_check'; -import { JobQueueEntry, ReportingAPIClient } from '../lib/reporting_api_client'; +import { + JobQueueEntry, + ReportingAPIClient, + useInternalApiClient, +} from '../lib/reporting_api_client'; +import { useIlmPolicyStatus, UseIlmPolicyStatusReturn } from '../lib/ilm_policy_status_context'; +import type { SharePluginSetup } from '../shared_imports'; import { ClientConfigType } from '../plugin'; import { ReportDeleteButton, ReportDownloadButton, ReportErrorButton, ReportInfoButton } from './'; import { ReportDiagnostic } from './report_diagnostic'; +import { MigrateIlmPolicyCallOut } from './migrate_ilm_policy_callout'; +import { IlmPolicyLink } from './ilm_policy_link'; export interface Job { id: string; @@ -55,7 +64,10 @@ export interface Props { license$: LicensingPluginSetup['license$']; pollConfig: ClientConfigType['poll']; redirect: ApplicationStart['navigateToApp']; + navigateToUrl: ApplicationStart['navigateToUrl']; toasts: ToastsSetup; + urlService: SharePluginSetup['url']; + ilmPolicyContextValue: UseIlmPolicyStatusReturn; } interface State { @@ -132,6 +144,10 @@ class ReportListingUi extends Component<Props, State> { } public render() { + const { ilmPolicyContextValue, urlService, navigateToUrl } = this.props; + const ilmLocator = urlService.locators.get('ILM_LOCATOR_ID'); + const hasIlmPolicy = ilmPolicyContextValue.status !== 'policy-not-found'; + const showIlmPolicyLink = Boolean(ilmLocator && hasIlmPolicy); return ( <> <EuiPageHeader @@ -147,11 +163,22 @@ class ReportListingUi extends Component<Props, State> { } /> + <MigrateIlmPolicyCallOut toasts={this.props.toasts} /> + <EuiSpacer size={'l'} /> {this.renderTable()} <EuiSpacer size="s" /> - <EuiFlexGroup justifyContent="spaceBetween" direction="rowReverse"> + <EuiFlexGroup justifyContent="flexEnd"> + <EuiFlexItem grow={false}> + {ilmPolicyContextValue.isLoading ? ( + <EuiLoadingSpinner /> + ) : ( + showIlmPolicyLink && ( + <IlmPolicyLink navigateToUrl={navigateToUrl} locator={ilmLocator!} /> + ) + )} + </EuiFlexItem> <EuiFlexItem grow={false}> <ReportDiagnostic apiClient={this.props.apiClient} /> </EuiFlexItem> @@ -531,4 +558,18 @@ class ReportListingUi extends Component<Props, State> { } } -export const ReportListing = injectI18n(ReportListingUi); +const PrivateReportListing = injectI18n(ReportListingUi); + +export const ReportListing = ( + props: Omit<Props, 'ilmPolicyContextValue' | 'intl' | 'apiClient'> +) => { + const ilmPolicyStatusValue = useIlmPolicyStatus(); + const { apiClient } = useInternalApiClient(); + return ( + <PrivateReportListing + {...props} + apiClient={apiClient} + ilmPolicyContextValue={ilmPolicyStatusValue} + /> + ); +}; diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index a2881af902072..fcbc4662c6e59 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -15,7 +15,6 @@ import { Plugin, PluginInitializerContext, } from 'src/core/public'; -import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public'; import { CONTEXT_MENU_TRIGGER } from '../../../../src/plugins/embeddable/public'; import { FeatureCatalogueCategory, @@ -23,7 +22,6 @@ import { HomePublicPluginStart, } from '../../../../src/plugins/home/public'; import { ManagementSetup, ManagementStart } from '../../../../src/plugins/management/public'; -import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public'; import { constants, getDefaultLayoutSelectors } from '../common'; import { durationToNumber } from '../common/schema_utils'; @@ -37,6 +35,13 @@ import { getSharedComponents } from './shared'; import { ReportingCsvShareProvider } from './share_context_menu/register_csv_reporting'; import { reportingScreenshotShareProvider } from './share_context_menu/register_pdf_png_reporting'; +import type { + SharePluginSetup, + SharePluginStart, + UiActionsSetup, + UiActionsStart, +} from './shared_imports'; + export interface ClientConfigType { poll: { jobsRefresh: { interval: number; intervalErrorMultiplier: number } }; roles: { enabled: boolean }; @@ -159,6 +164,7 @@ export class ReportingPublicPlugin license$, this.config.poll, apiClient, + share.url, params ); }, diff --git a/x-pack/plugins/reporting/public/shared_imports.ts b/x-pack/plugins/reporting/public/shared_imports.ts new file mode 100644 index 0000000000000..010da46c07401 --- /dev/null +++ b/x-pack/plugins/reporting/public/shared_imports.ts @@ -0,0 +1,20 @@ +/* + * 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 type { + SharePluginSetup, + SharePluginStart, + LocatorPublic, +} from '../../../../src/plugins/share/public'; + +export { useRequest, UseRequestResponse } from '../../../../src/plugins/es_ui_shared/public'; + +export type { SerializableState } from 'src/plugins/kibana_utils/common'; + +export type { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public'; + +export type { ManagementAppMountParams } from 'src/plugins/management/public'; diff --git a/x-pack/plugins/reporting/server/lib/deprecations/check_ilm_migration_status.ts b/x-pack/plugins/reporting/server/lib/deprecations/check_ilm_migration_status.ts new file mode 100644 index 0000000000000..dc20f92f38c94 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/deprecations/check_ilm_migration_status.ts @@ -0,0 +1,41 @@ +/* + * 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 type { + IndicesIndexStatePrefixedSettings, + IndicesIndexSettings, +} from '@elastic/elasticsearch/api/types'; +import { ILM_POLICY_NAME } from '../../../common/constants'; +import { IlmPolicyMigrationStatus } from '../../../common/types'; +import { IlmPolicyManager } from '../../lib/store/ilm_policy_manager'; +import type { DeprecationsDependencies } from './types'; + +export const checkIlmMigrationStatus = async ({ + reportingCore, + elasticsearchClient, +}: DeprecationsDependencies): Promise<IlmPolicyMigrationStatus> => { + const ilmPolicyManager = IlmPolicyManager.create({ client: elasticsearchClient }); + if (!(await ilmPolicyManager.doesIlmPolicyExist())) { + return 'policy-not-found'; + } + + const store = await reportingCore.getStore(); + const indexPattern = store.getReportingIndexPattern(); + + const { body: reportingIndicesSettings } = await elasticsearchClient.indices.getSettings({ + index: indexPattern, + }); + + const hasUnmanagedIndices = Object.values(reportingIndicesSettings).some((settings) => { + return ( + (settings?.settings as IndicesIndexStatePrefixedSettings)?.index?.lifecycle?.name !== + ILM_POLICY_NAME && + (settings?.settings as IndicesIndexSettings)?.['index.lifecycle']?.name !== ILM_POLICY_NAME + ); + }); + + return hasUnmanagedIndices ? 'indices-not-managed-by-policy' : 'ok'; +}; diff --git a/x-pack/plugins/canvas/public/services/legacy/stubs/notify.ts b/x-pack/plugins/reporting/server/lib/deprecations/index.ts similarity index 56% rename from x-pack/plugins/canvas/public/services/legacy/stubs/notify.ts rename to x-pack/plugins/reporting/server/lib/deprecations/index.ts index 866da3d459ed3..95594940e07e2 100644 --- a/x-pack/plugins/canvas/public/services/legacy/stubs/notify.ts +++ b/x-pack/plugins/reporting/server/lib/deprecations/index.ts @@ -5,13 +5,8 @@ * 2.0. */ -import { NotifyService } from '../notify'; +import { checkIlmMigrationStatus } from './check_ilm_migration_status'; -const noop = (..._args: any[]): any => {}; - -export const notifyService: NotifyService = { - error: noop, - info: noop, - success: noop, - warning: noop, +export const deprecations = { + checkIlmMigrationStatus, }; diff --git a/x-pack/plugins/canvas/common/lib/url.ts b/x-pack/plugins/reporting/server/lib/deprecations/types.ts similarity index 52% rename from x-pack/plugins/canvas/common/lib/url.ts rename to x-pack/plugins/reporting/server/lib/deprecations/types.ts index 5018abc027713..c6e9e3b7ad920 100644 --- a/x-pack/plugins/canvas/common/lib/url.ts +++ b/x-pack/plugins/reporting/server/lib/deprecations/types.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { isValidDataUrl } from '../../common/lib/dataurl'; -import { isValidHttpUrl } from '../../common/lib/httpurl'; +import type { ElasticsearchClient } from 'src/core/server'; +import type { ReportingCore } from '../../core'; -export function isValidUrl(url: string) { - return isValidDataUrl(url) || isValidHttpUrl(url); +export interface DeprecationsDependencies { + reportingCore: ReportingCore; + elasticsearchClient: ElasticsearchClient; } diff --git a/x-pack/plugins/reporting/server/lib/index.ts b/x-pack/plugins/reporting/server/lib/index.ts index e66f72f88f8ea..b2a2a1edcd6a5 100644 --- a/x-pack/plugins/reporting/server/lib/index.ts +++ b/x-pack/plugins/reporting/server/lib/index.ts @@ -10,5 +10,5 @@ export { cryptoFactory } from './crypto'; export { ExportTypesRegistry, getExportTypesRegistry } from './export_types_registry'; export { LevelLogger } from './level_logger'; export { statuses } from './statuses'; -export { ReportingStore } from './store'; +export { ReportingStore, IlmPolicyManager } from './store'; export { startTrace } from './trace'; diff --git a/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts b/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/constants.ts similarity index 83% rename from x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts rename to x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/constants.ts index 90636e3c523a3..bea2ba21c0846 100644 --- a/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts +++ b/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IlmPutLifecycleRequest } from '@elastic/elasticsearch/api/types'; +import type { IlmPutLifecycleRequest } from '@elastic/elasticsearch/api/types'; export const reportingIlmPolicy: IlmPutLifecycleRequest['body'] = { policy: { diff --git a/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/ilm_policy_manager.ts b/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/ilm_policy_manager.ts new file mode 100644 index 0000000000000..ca0a74cae8726 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/ilm_policy_manager.ts @@ -0,0 +1,46 @@ +/* + * 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 type { ElasticsearchClient } from 'src/core/server'; +import { ILM_POLICY_NAME } from '../../../../common/constants'; + +import { reportingIlmPolicy } from './constants'; + +/** + * Responsible for detecting and provisioning the reporting ILM policy. + * + * Uses the provided {@link ElasticsearchClient} to scope request privileges. + */ +export class IlmPolicyManager { + constructor(private readonly client: ElasticsearchClient) {} + + public static create(opts: { client: ElasticsearchClient }) { + return new IlmPolicyManager(opts.client); + } + + public async doesIlmPolicyExist(): Promise<boolean> { + try { + await this.client.ilm.getLifecycle({ policy: ILM_POLICY_NAME }); + return true; + } catch (e) { + if (e.statusCode === 404) { + return false; + } + throw e; + } + } + + /** + * Create the Reporting ILM policy + */ + public async createIlmPolicy(): Promise<void> { + await this.client.ilm.putLifecycle({ + policy: ILM_POLICY_NAME, + body: reportingIlmPolicy, + }); + } +} diff --git a/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/index.ts b/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/index.ts new file mode 100644 index 0000000000000..045a9ecb59997 --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/store/ilm_policy_manager/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { reportingIlmPolicy } from './constants'; +export { IlmPolicyManager } from './ilm_policy_manager'; diff --git a/x-pack/plugins/reporting/server/lib/store/index.ts b/x-pack/plugins/reporting/server/lib/store/index.ts index 6b979325921a6..888918abbc344 100644 --- a/x-pack/plugins/reporting/server/lib/store/index.ts +++ b/x-pack/plugins/reporting/server/lib/store/index.ts @@ -8,3 +8,4 @@ export { ReportDocument } from '../../../common/types'; export { Report } from './report'; export { ReportingStore } from './store'; +export { IlmPolicyManager } from './ilm_policy_manager'; diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index 17c067a255b38..7a7dd20e1b25c 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -10,12 +10,15 @@ import { ElasticsearchClient } from 'src/core/server'; import { LevelLogger, statuses } from '../'; import { ReportingCore } from '../../'; import { JobStatus } from '../../../common/types'; + +import { ILM_POLICY_NAME } from '../../../common/constants'; + import { ReportTaskParams } from '../tasks'; + +import { MIGRATION_VERSION, Report, ReportDocument, ReportSource } from './report'; import { indexTimestamp } from './index_timestamp'; import { mapping } from './mapping'; -import { MIGRATION_VERSION, Report, ReportDocument, ReportSource } from './report'; - -import { reportingIlmPolicy } from './report_ilm_policy'; +import { IlmPolicyManager } from './ilm_policy_manager'; /* * When an instance of Kibana claims a report job, this information tells us about that instance @@ -92,6 +95,7 @@ export class ReportingStore { private readonly indexPrefix: string; // config setting of index prefix in system index name private readonly indexInterval: string; // config setting of index prefix: how often to poll for pending work private client?: ElasticsearchClient; + private ilmPolicyManager?: IlmPolicyManager; constructor(private reportingCore: ReportingCore, private logger: LevelLogger) { const config = reportingCore.getConfig(); @@ -109,6 +113,15 @@ export class ReportingStore { return this.client; } + private async getIlmPolicyManager() { + if (!this.ilmPolicyManager) { + const client = await this.getClient(); + this.ilmPolicyManager = IlmPolicyManager.create({ client }); + } + + return this.ilmPolicyManager; + } + private async createIndex(indexName: string) { const client = await this.getClient(); const { body: exists } = await client.indices.exists({ index: indexName }); @@ -125,7 +138,7 @@ export class ReportingStore { number_of_shards: 1, auto_expand_replicas: '0-1', lifecycle: { - name: this.ilmPolicyName, + name: ILM_POLICY_NAME, }, }, mappings: { @@ -181,37 +194,19 @@ export class ReportingStore { return client.indices.refresh({ index }); } - private readonly ilmPolicyName = 'kibana-reporting'; - - private async doesIlmPolicyExist(): Promise<boolean> { - const client = await this.getClient(); - try { - await client.ilm.getLifecycle({ policy: this.ilmPolicyName }); - return true; - } catch (e) { - if (e.statusCode === 404) { - return false; - } - throw e; - } - } - /** * Function to be called during plugin start phase. This ensures the environment is correctly * configured for storage of reports. */ public async start() { - const client = await this.getClient(); + const ilmPolicyManager = await this.getIlmPolicyManager(); try { - if (await this.doesIlmPolicyExist()) { - this.logger.debug(`Found ILM policy ${this.ilmPolicyName}; skipping creation.`); + if (await ilmPolicyManager.doesIlmPolicyExist()) { + this.logger.debug(`Found ILM policy ${ILM_POLICY_NAME}; skipping creation.`); return; } - this.logger.info(`Creating ILM policy for managing reporting indices: ${this.ilmPolicyName}`); - await client.ilm.putLifecycle({ - policy: this.ilmPolicyName, - body: reportingIlmPolicy, - }); + this.logger.info(`Creating ILM policy for managing reporting indices: ${ILM_POLICY_NAME}`); + await ilmPolicyManager.createIlmPolicy(); } catch (e) { this.logger.error('Error in start phase'); this.logger.error(e.body.error); @@ -446,4 +441,8 @@ export class ReportingStore { return body.hits?.hits[0] as ReportRecordTimeout; } + + public getReportingIndexPattern(): string { + return `${this.indexPrefix}-*`; + } } diff --git a/x-pack/plugins/reporting/server/routes/deprecations.ts b/x-pack/plugins/reporting/server/routes/deprecations.ts new file mode 100644 index 0000000000000..7a38faf60f6bb --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/deprecations.ts @@ -0,0 +1,110 @@ +/* + * 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 { errors } from '@elastic/elasticsearch'; +import { + API_MIGRATE_ILM_POLICY_URL, + API_GET_ILM_POLICY_STATUS, + ILM_POLICY_NAME, +} from '../../common/constants'; +import { IlmPolicyStatusResponse } from '../../common/types'; +import { deprecations } from '../lib/deprecations'; +import { ReportingCore } from '../core'; +import { IlmPolicyManager, LevelLogger as Logger } from '../lib'; + +export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Logger) => { + const { router } = reporting.getPluginSetupDeps(); + + router.get( + { + path: API_GET_ILM_POLICY_STATUS, + validate: false, + }, + async ( + { + core: { + elasticsearch: { client: scopedClient }, + }, + }, + req, + res + ) => { + const checkIlmMigrationStatus = () => { + return deprecations.checkIlmMigrationStatus({ + reportingCore: reporting, + // We want to make the current status visible to all reporting users + elasticsearchClient: scopedClient.asInternalUser, + }); + }; + + try { + const response: IlmPolicyStatusResponse = { + status: await checkIlmMigrationStatus(), + }; + return res.ok({ body: response }); + } catch (e) { + return res.customError({ statusCode: e?.statusCode ?? 500, body: { message: e.message } }); + } + } + ); + + router.put( + { path: API_MIGRATE_ILM_POLICY_URL, validate: false }, + async ({ core: { elasticsearch } }, req, res) => { + const store = await reporting.getStore(); + const { + client: { asCurrentUser: client }, + } = elasticsearch; + + const scopedIlmPolicyManager = IlmPolicyManager.create({ + client, + }); + + // First we ensure that the reporting ILM policy exists in the cluster + try { + // We don't want to overwrite an existing reporting policy because it may contain alterations made by users + if (!(await scopedIlmPolicyManager.doesIlmPolicyExist())) { + await scopedIlmPolicyManager.createIlmPolicy(); + } + } catch (e) { + return res.customError({ statusCode: e?.statusCode ?? 500, body: { message: e.message } }); + } + + const indexPattern = store.getReportingIndexPattern(); + + // Second we migrate all of the existing indices to be managed by the reporting ILM policy + try { + await client.indices.putSettings({ + index: indexPattern, + body: { + 'index.lifecycle': { + name: ILM_POLICY_NAME, + }, + }, + }); + return res.ok(); + } catch (err) { + logger.error(err); + + if (err instanceof errors.ResponseError) { + // If there were no reporting indices to update, that's OK because then there is nothing to migrate + if (err.statusCode === 404) { + return res.ok(); + } + return res.customError({ + statusCode: err.statusCode ?? 500, + body: { + message: err.message, + name: err.name, + }, + }); + } + + throw err; + } + } + ); +}; diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts index e061bd4f7d66c..a462da3849083 100644 --- a/x-pack/plugins/reporting/server/routes/index.ts +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -6,15 +6,17 @@ */ import { LevelLogger as Logger } from '../lib'; +import { registerDeprecationsRoutes } from './deprecations'; +import { registerDiagnosticRoutes } from './diagnostic'; import { registerJobGenerationRoutes } from './generation'; import { registerJobInfoRoutes } from './jobs'; import { ReportingCore } from '../core'; -import { registerDiagnosticRoutes } from './diagnostic'; export function registerRoutes(reporting: ReportingCore, logger: Logger) { + registerDeprecationsRoutes(reporting, logger); + registerDiagnosticRoutes(reporting, logger); registerJobGenerationRoutes(reporting, logger); registerJobInfoRoutes(reporting); - registerDiagnosticRoutes(reporting, logger); } export interface ReportingRequestPre { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 3fb32856a1ef1..abfcb4014a79f 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -78,13 +78,6 @@ export enum SecurityPageName { eventFilters = 'event_filters', } -export enum SecurityPageGroupName { - detect = 'detect', - explore = 'explore', - investigate = 'investigate', - manage = 'manage', -} - export const TIMELINES_PATH = '/timelines'; export const CASES_PATH = '/cases'; export const OVERVIEW_PATH = '/overview'; diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/integration/hosts/inspect.spec.ts index 28240bd12e4be..4a729ab5044ba 100644 --- a/x-pack/plugins/security_solution/cypress/integration/hosts/inspect.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/hosts/inspect.spec.ts @@ -6,17 +6,20 @@ */ import { INSPECT_HOSTS_BUTTONS_IN_SECURITY, INSPECT_MODAL } from '../../screens/inspect'; +import { HOST_OVERVIEW } from '../../screens/hosts/main'; import { cleanKibana } from '../../tasks/common'; -import { closesModal, openStatsAndTables } from '../../tasks/inspect'; -import { loginAndWaitForPage } from '../../tasks/login'; +import { clickInspectButton, closesModal, openStatsAndTables } from '../../tasks/inspect'; +import { loginAndWaitForHostDetailsPage, loginAndWaitForPage } from '../../tasks/login'; import { HOSTS_URL } from '../../urls/navigation'; describe('Inspect', () => { + before(() => { + cleanKibana(); + }); context('Hosts stats and tables', () => { before(() => { - cleanKibana(); loginAndWaitForPage(HOSTS_URL); }); afterEach(() => { @@ -30,4 +33,18 @@ describe('Inspect', () => { }) ); }); + + context('Hosts details', () => { + before(() => { + loginAndWaitForHostDetailsPage(); + }); + afterEach(() => { + closesModal(); + }); + + it(`inspects the host details`, () => { + clickInspectButton(HOST_OVERVIEW); + cy.get(INSPECT_MODAL).should('be.visible'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts index 77a1775494e6a..c3e04aaaf6a1f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts @@ -78,10 +78,22 @@ describe('Row renderers', () => { }); it('Selected renderer can be disabled with one click', () => { + // Ensure these elements are visible before continuing since sometimes it takes a second for the modal to show up + // and it gives the click handlers a bit of time to be initialized as well to reduce chances of flake but you still + // have to use pipe() below as an additional measure. cy.get(TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN).should('exist'); - cy.get(TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN) - .pipe(($el) => $el.trigger('click')) - .should('not.be.visible'); + cy.get(TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX).should('be.checked'); + + // Keep clicking on the disable all button until the first element of all the elements are no longer checked. + // In cases where the click handler is not present on the page just yet, this will cause the button to be clicked + // multiple times until it sees that the click took effect. You could go through the whole list but I just check + // for the first to be unchecked and then assume the click was successful + cy.root() + .pipe(($el) => { + $el.find(TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN).trigger('click'); + return $el.find(TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX).first(); + }) + .should('not.be.checked'); cy.intercept('PATCH', '/api/timeline').as('updateTimeline'); cy.wait('@updateTimeline').its('response.statusCode').should('eq', 200); diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/not_found.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/not_found.spec.ts index 3b1df67bec29c..cdccb6d75d18c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/not_found.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/not_found.spec.ts @@ -71,7 +71,7 @@ describe('Display not found page', () => { cy.get(NOT_FOUND).should('exist'); }); - it('navigates to the trusted applications page with incorrect link', () => { + it('navigates to the event filters page with incorrect link', () => { loginAndWaitForPage(`${EVENT_FILTERS_URL}/randomUrl`); cy.get(NOT_FOUND).should('exist'); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts index f8b20393ba110..95381b06f44e9 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts @@ -16,3 +16,5 @@ export const EVENTS_TAB = '[data-test-subj="navigation-events"]'; export const KQL_SEARCH_BAR = '[data-test-subj="queryInput"]'; export const UNCOMMON_PROCESSES_TAB = '[data-test-subj="navigation-uncommonProcesses"]'; + +export const HOST_OVERVIEW = `[data-test-subj="host-overview"]`; diff --git a/x-pack/plugins/security_solution/cypress/tasks/inspect.ts b/x-pack/plugins/security_solution/cypress/tasks/inspect.ts index d056e012bb7f8..112bf9a208b39 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/inspect.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/inspect.ts @@ -11,6 +11,10 @@ export const closesModal = () => { cy.get('[data-test-subj="modal-inspect-close"]').click(); }; +export const clickInspectButton = (container: string) => { + cy.get(`${container} ${INSPECT_BUTTON_ICON}`).trigger('click', { force: true }); +}; + export const openStatsAndTables = (table: InspectButtonMetadata) => { if (table.tabId) { cy.get(table.tabId).click({ force: true }); @@ -21,6 +25,6 @@ export const openStatsAndTables = (table: InspectButtonMetadata) => { force: true, }); } else { - cy.get(`${table.id} ${INSPECT_BUTTON_ICON}`).trigger('click', { force: true }); + clickInspectButton(table.id); } }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts index be447993273fb..243bfd113bfd2 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts @@ -10,6 +10,7 @@ import Url, { UrlObject } from 'url'; import { ROLES } from '../../common/test'; import { TIMELINE_FLYOUT_BODY } from '../screens/timeline'; +import { hostDetailsUrl } from '../urls/navigation'; /** * Credentials in the `kibana.dev.yml` config file will be used to authenticate @@ -312,6 +313,11 @@ export const loginAndWaitForTimeline = (timelineId: string, role?: ROLES) => { cy.get(TIMELINE_FLYOUT_BODY).should('be.visible'); }; +export const loginAndWaitForHostDetailsPage = () => { + loginAndWaitForPage(hostDetailsUrl('suricata-iowa')); + cy.get('[data-test-subj="loading-spinner"]', { timeout: 12000 }).should('not.exist'); +}; + export const waitForPageWithoutDateRange = (url: string, role?: ROLES) => { cy.visit(role ? getUrlWithRoute(role, url) : url); cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts index 304db7e93e2cb..a9fad865f506c 100644 --- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -23,6 +23,8 @@ export const SECURITY_DETECTIONS_RULES_CREATION_URL = '/app/security/detections/ export const EXCEPTIONS_URL = 'app/security/exceptions'; export const HOSTS_URL = '/app/security/hosts/allHosts'; +export const hostDetailsUrl = (hostName: string) => + `/app/security/hosts/${hostName}/authentications`; export const HOSTS_PAGE_TAB_URLS = { allHosts: '/app/security/hosts/allHosts', anomalies: '/app/security/hosts/anomalies', diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index cbaa789d47489..e1c14f2a86380 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -26,7 +26,7 @@ import { NETWORK, TIMELINES, CASE, - ADMINISTRATION, + MANAGE, } from '../translations'; import { OVERVIEW_PATH, @@ -116,12 +116,12 @@ export const topDeepLinks: AppDeepLink[] = [ }, { id: SecurityPageName.administration, - title: ADMINISTRATION, + title: MANAGE, path: ENDPOINTS_PATH, navLinkStatus: AppNavLinkStatus.hidden, keywords: [ - i18n.translate('xpack.securitySolution.search.administration', { - defaultMessage: 'Administration', + i18n.translate('xpack.securitySolution.search.manage', { + defaultMessage: 'Manage', }), ], }, @@ -165,7 +165,7 @@ const nestedDeepLinks: SecurityDeepLinks = { navLinkStatus: AppNavLinkStatus.hidden, keywords: [ i18n.translate('xpack.securitySolution.search.exceptions', { - defaultMessage: 'Exceptions', + defaultMessage: 'Exception list', }), ], searchable: true, @@ -179,28 +179,28 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.hosts.authentications', { defaultMessage: 'Authentications', }), - path: '/authentications', + path: `${HOSTS_PATH}/authentications`, }, { id: 'uncommonProcesses', title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', { defaultMessage: 'Uncommon Processes', }), - path: '/uncommonProcesses', + path: `${HOSTS_PATH}/uncommonProcesses`, }, { id: 'events', title: i18n.translate('xpack.securitySolution.search.hosts.events', { defaultMessage: 'Events', }), - path: '/events', + path: `${HOSTS_PATH}/events`, }, { id: 'externalAlerts', title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', { defaultMessage: 'External Alerts', }), - path: '/alerts', + path: `${HOSTS_PATH}/alerts`, }, ], premium: [ @@ -209,7 +209,7 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { defaultMessage: 'Anomalies', }), - path: '/anomalies', + path: `${HOSTS_PATH}/anomalies`, }, ], }, @@ -220,28 +220,28 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.network.dns', { defaultMessage: 'DNS', }), - path: '/dns', + path: `${NETWORK_PATH}/dns`, }, { id: 'http', title: i18n.translate('xpack.securitySolution.search.network.http', { defaultMessage: 'HTTP', }), - path: '/http', + path: `${NETWORK_PATH}/http`, }, { id: 'tls', title: i18n.translate('xpack.securitySolution.search.network.tls', { defaultMessage: 'TLS', }), - path: '/tls', + path: `${NETWORK_PATH}/tls`, }, { id: 'externalAlertsNetwork', title: i18n.translate('xpack.securitySolution.search.network.externalAlerts', { defaultMessage: 'External Alerts', }), - path: '/external-alerts', + path: `${NETWORK_PATH}/external-alerts`, }, ], premium: [ @@ -250,7 +250,7 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { defaultMessage: 'Anomalies', }), - path: '/anomalies', + path: `${NETWORK_PATH}/anomalies`, }, ], }, @@ -261,7 +261,7 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.timeline.templates', { defaultMessage: 'Templates', }), - path: '/template', + path: `${TIMELINES_PATH}/template`, }, ], }, @@ -272,7 +272,7 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.cases.create', { defaultMessage: 'Create New Case', }), - path: '/create', + path: `${CASES_PATH}/create`, }, ], premium: [ @@ -281,7 +281,7 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.cases.configure', { defaultMessage: 'Configure Cases', }), - path: '/configure', + path: `${CASES_PATH}/configure`, }, ], }, @@ -299,14 +299,14 @@ const nestedDeepLinks: SecurityDeepLinks = { { id: SecurityPageName.trustedApps, title: i18n.translate('xpack.securitySolution.search.administration.trustedApps', { - defaultMessage: 'Trusted Applications', + defaultMessage: 'Trusted applications', }), path: TRUSTED_APPS_PATH, }, { id: SecurityPageName.eventFilters, title: i18n.translate('xpack.securitySolution.search.administration.eventFilters', { - defaultMessage: 'Event Filters', + defaultMessage: 'Event filters', }), path: EVENT_FILTERS_PATH, }, @@ -377,12 +377,13 @@ export function updateGlobalNavigation({ const deepLinks = getDeepLinks(undefined, capabilities); const updatedDeepLinks = deepLinks.map((link) => { switch (link.id) { - case 'case': + case SecurityPageName.case: return { ...link, navLinkStatus: capabilities.siem.read_cases ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden, + searchable: capabilities.siem.read_cases === true, }; default: return link; @@ -390,6 +391,7 @@ export function updateGlobalNavigation({ }); updater$.next(() => ({ + navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent showing main nav link deepLinks: updatedDeepLinks, })); } diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts index 271eea47840dc..d6f8516d43a72 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts @@ -6,8 +6,11 @@ */ import * as i18n from '../translations'; -import { SecurityPageName, SecurityPageGroupName } from '../types'; -import { SiemNavTab, NavTabGroups } from '../../common/components/navigation/types'; +import { + SecurityNav, + SecurityNavGroup, + SecurityNavGroupKey, +} from '../../common/components/navigation/types'; import { APP_OVERVIEW_PATH, APP_RULES_PATH, @@ -21,9 +24,10 @@ import { APP_ENDPOINTS_PATH, APP_TRUSTED_APPS_PATH, APP_EVENT_FILTERS_PATH, + SecurityPageName, } from '../../../common/constants'; -export const navTabs: SiemNavTab = { +export const navTabs: SecurityNav = { [SecurityPageName.overview]: { id: SecurityPageName.overview, name: i18n.OVERVIEW, @@ -36,21 +40,21 @@ export const navTabs: SiemNavTab = { name: i18n.ALERTS, href: APP_ALERTS_PATH, disabled: false, - urlKey: SecurityPageName.alerts, + urlKey: 'alerts', }, [SecurityPageName.rules]: { id: SecurityPageName.rules, name: i18n.RULES, href: APP_RULES_PATH, disabled: false, - urlKey: SecurityPageName.rules, + urlKey: 'rules', }, [SecurityPageName.exceptions]: { id: SecurityPageName.exceptions, name: i18n.EXCEPTIONS, href: APP_EXCEPTIONS_PATH, disabled: false, - urlKey: SecurityPageName.exceptions, + urlKey: 'exceptions', }, [SecurityPageName.hosts]: { id: SecurityPageName.hosts, @@ -85,46 +89,46 @@ export const navTabs: SiemNavTab = { name: i18n.ADMINISTRATION, href: APP_MANAGEMENT_PATH, disabled: false, - urlKey: SecurityPageName.administration, + urlKey: 'administration', }, [SecurityPageName.endpoints]: { id: SecurityPageName.endpoints, name: i18n.ENDPOINTS, href: APP_ENDPOINTS_PATH, disabled: false, - urlKey: SecurityPageName.administration, + urlKey: 'administration', }, [SecurityPageName.trustedApps]: { id: SecurityPageName.trustedApps, name: i18n.TRUSTED_APPLICATIONS, href: APP_TRUSTED_APPS_PATH, disabled: false, - urlKey: SecurityPageName.administration, + urlKey: 'administration', }, [SecurityPageName.eventFilters]: { id: SecurityPageName.eventFilters, name: i18n.EVENT_FILTERS, href: APP_EVENT_FILTERS_PATH, disabled: false, - urlKey: SecurityPageName.administration, + urlKey: 'administration', }, }; -export const navTabGroups: NavTabGroups = { - [SecurityPageGroupName.detect]: { - id: SecurityPageGroupName.detect, +export const securityNavGroup: SecurityNavGroup = { + [SecurityNavGroupKey.detect]: { + id: SecurityNavGroupKey.detect, name: i18n.DETECT, }, - [SecurityPageGroupName.explore]: { - id: SecurityPageGroupName.explore, + [SecurityNavGroupKey.explore]: { + id: SecurityNavGroupKey.explore, name: i18n.EXPLORE, }, - [SecurityPageGroupName.investigate]: { - id: SecurityPageGroupName.investigate, + [SecurityNavGroupKey.investigate]: { + id: SecurityNavGroupKey.investigate, name: i18n.INVESTIGATE, }, - [SecurityPageGroupName.manage]: { - id: SecurityPageGroupName.manage, + [SecurityNavGroupKey.manage]: { + id: SecurityNavGroupKey.manage, name: i18n.MANAGE, }, }; diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index d16c35a832e6b..e7f825691c58d 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -14,12 +14,14 @@ import { SecuritySolutionAppWrapper } from '../../common/components/page'; import { HelpMenu } from '../../common/components/help_menu'; import { UseUrlState } from '../../common/components/url_state'; import { navTabs } from './home_navigations'; -import { useInitSourcerer, useSourcererScope } from '../../common/containers/sourcerer'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; +import { + useInitSourcerer, + useSourcererScope, + getScopeFromPath, +} from '../../common/containers/sourcerer'; import { useUpgradeSecurityPackages } from '../../common/hooks/use_upgrade_security_packages'; import { GlobalHeader } from './global_header'; import { SecuritySolutionTemplateWrapper } from './template_wrapper'; -import { isDetectionsPath } from '../../helpers'; interface HomePageProps { children: React.ReactNode; @@ -34,13 +36,9 @@ const HomePageComponent: React.FC<HomePageProps> = ({ }) => { const { pathname } = useLocation(); - useInitSourcerer( - isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default - ); + useInitSourcerer(getScopeFromPath(pathname)); - const { browserFields, indexPattern } = useSourcererScope( - isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default - ); + const { browserFields, indexPattern } = useSourcererScope(getScopeFromPath(pathname)); // side effect: this will attempt to upgrade the endpoint package if it is not up to date // this will run when a user navigates to the Security Solution app and when they navigate between diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx index a2f1ed8c115d6..eb606cd8ff583 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx @@ -12,12 +12,10 @@ import { useLocation } from 'react-router-dom'; import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; import { AppLeaveHandler } from '../../../../../../../../src/core/public'; import { useShowTimeline } from '../../../../common/utils/timeline/use_show_timeline'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; -import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { useSourcererScope, getScopeFromPath } from '../../../../common/containers/sourcerer'; import { TimelineId } from '../../../../../common/types/timeline'; import { AutoSaveWarningMsg } from '../../../../timelines/components/timeline/auto_save_warning'; import { Flyout } from '../../../../timelines/components/flyout'; -import { isDetectionsPath } from '../../../../../public/helpers'; export const BOTTOM_BAR_CLASSNAME = 'timeline-bottom-bar'; @@ -27,9 +25,7 @@ export const SecuritySolutionBottomBar = React.memo( const [showTimeline] = useShowTimeline(); - const { indicesExist } = useSourcererScope( - isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default - ); + const { indicesExist } = useSourcererScope(getScopeFromPath(pathname)); return indicesExist && showTimeline ? ( <> diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index edbab928a5c69..847a9114d94bd 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -24,7 +24,7 @@ export const RULES = i18n.translate('xpack.securitySolution.navigation.rules', { }); export const EXCEPTIONS = i18n.translate('xpack.securitySolution.navigation.exceptions', { - defaultMessage: 'Exceptions', + defaultMessage: 'Exception list', }); export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts', { @@ -48,13 +48,13 @@ export const ENDPOINTS = i18n.translate('xpack.securitySolution.search.administr export const TRUSTED_APPLICATIONS = i18n.translate( 'xpack.securitySolution.search.administration.trustedApps', { - defaultMessage: 'Trusted Applications', + defaultMessage: 'Trusted applications', } ); export const EVENT_FILTERS = i18n.translate( 'xpack.securitySolution.search.administration.eventFilters', { - defaultMessage: 'Event Filters', + defaultMessage: 'Event filters', } ); diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 62a61828830be..8056c4092091c 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -36,7 +36,7 @@ import { Immutable } from '../../common/endpoint/types'; import { AppAction } from '../common/store/actions'; import { TimelineState } from '../timelines/store/timeline/types'; import { SecurityPageName } from '../../common/constants'; -export { SecurityPageName, SecurityPageGroupName } from '../../common/constants'; +export { SecurityPageName } from '../../common/constants'; export interface SecuritySubPluginStore<K extends SecuritySubPluginKeyStore, T> { initialState: Record<K, T | undefined>; diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx index 9722447b96ad5..3e0aa17a3830e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx @@ -5,6 +5,9 @@ * 2.0. */ +import React from 'react'; +import { mount } from 'enzyme'; +import 'jest-styled-components'; import { createUpdateSuccessToaster } from './helpers'; import { Case } from '../../../../../cases/common'; @@ -23,12 +26,30 @@ describe('helpers', () => { it('creates the correct toast when the sync alerts is on', () => { // We remove the id as is randomly generated and the text as it is a React component // which is being test on toaster_content.test.tsx - const { id, text, ...toast } = createUpdateSuccessToaster(theCase, onViewCaseClick); + const { id, text, title, ...toast } = createUpdateSuccessToaster(theCase, onViewCaseClick); + const mountedTitle = mount(<>{title}</>); + expect(toast).toEqual({ color: 'success', iconType: 'check', - title: 'An alert has been added to "My case"', }); + expect(mountedTitle).toMatchInlineSnapshot(` + .c0 { + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + } + + <styled.span> + <span + className="c0" + > + An alert has been added to "My case" + </span> + </styled.span> + `); }); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx index 8682b6680830d..93e1f0499893e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx @@ -7,11 +7,22 @@ import React from 'react'; import uuid from 'uuid'; +import styled from 'styled-components'; import { AppToast } from '../../../common/components/toasters'; import { ToasterContent } from './toaster_content'; import * as i18n from './translations'; import { Case } from '../../../../../cases/common'; +const LINE_CLAMP = 3; + +const Title = styled.span` + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: ${LINE_CLAMP}; + -webkit-box-orient: vertical; + overflow: hidden; +`; + export const createUpdateSuccessToaster = ( theCase: Case, onViewCaseClick: (id: string) => void @@ -20,7 +31,7 @@ export const createUpdateSuccessToaster = ( id: uuid.v4(), color: 'success', iconType: 'check', - title: i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title), + title: <Title>{i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title)}, text: ( { const { @@ -31,9 +30,9 @@ const ConfigureCasesPageComponent: React.FC = () => { const backOptions = useMemo( () => ({ - href: getCaseUrl(search), + path: getCaseUrl(search), text: i18n.BACK_TO_ALL, - pageId: SecurityPageName.case as SiemNavTabKey, + pageId: SecurityPageName.case, }), [search] ); diff --git a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx index e46e5c2074f05..4a59fe3fdcabd 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx @@ -18,7 +18,6 @@ import { CaseHeaderPage } from '../components/case_header_page'; import { Create } from '../components/create'; import * as i18n from './translations'; import { APP_ID } from '../../../common/constants'; -import { SiemNavTabKey } from '../../common/components/navigation/types'; export const CreateCasePage = React.memo(() => { const userPermissions = useGetUserCasesPermissions(); @@ -29,9 +28,9 @@ export const CreateCasePage = React.memo(() => { const backOptions = useMemo( () => ({ - href: getCaseUrl(search), + path: getCaseUrl(search), text: i18n.BACK_TO_ALL, - pageId: SecurityPageName.case as SiemNavTabKey, + pageId: SecurityPageName.case, }), [search] ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index d07cdd81aa5f4..9afaaef61b17a 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -104,7 +104,7 @@ const EventDetailsComponent: React.FC = ({ setSelectedTabId, ]); - const eventFields = useMemo(() => getEnrichmentFields(data ?? []), [data]); + const eventFields = useMemo(() => getEnrichmentFields(data), [data]); const existingEnrichments = useMemo( () => isAlert @@ -242,7 +242,7 @@ const EventDetailsComponent: React.FC = ({ ); }, [summaryTab, threatIntelTab, tableTab, jsonTab]); - const selectedTab = useMemo(() => tabs.find((tab) => tab.id === selectedTabId), [ + const selectedTab = useMemo(() => tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0], [ tabs, selectedTabId, ]); diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx index 2c42353daee75..47b0871229864 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx @@ -41,7 +41,7 @@ describe('HeaderPage', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx index dc8e19249b6be..dea19e1366875 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx @@ -12,7 +12,7 @@ import { EuiPageHeaderSection, EuiSpacer, } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import React from 'react'; import styled, { css } from 'styled-components'; import { LinkIcon, LinkIconProps } from '../link_icon'; @@ -24,8 +24,6 @@ import { SecurityPageName } from '../../../app/types'; import { Sourcerer } from '../sourcerer'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useKibana } from '../../lib/kibana'; -import { SiemNavTabKey } from '../navigation/types'; - interface HeaderProps { border?: boolean; isLoading?: boolean; @@ -65,10 +63,10 @@ const HeaderSection = styled(EuiPageHeaderSection)` HeaderSection.displayName = 'HeaderSection'; interface BackOptions { + pageId: SecurityPageName; text: LinkIconProps['children']; - href?: LinkIconProps['href']; + path?: string; dataTestSubj?: string; - pageId: SiemNavTabKey; } export interface HeaderPageProps extends HeaderProps { @@ -85,6 +83,29 @@ export interface HeaderPageProps extends HeaderProps { titleNode?: React.ReactElement; } +const HeaderLinkBack: React.FC<{ backOptions: BackOptions }> = React.memo(({ backOptions }) => { + const { navigateToUrl } = useKibana().services.application; + const { formatUrl } = useFormatUrl(backOptions.pageId); + + const backUrl = formatUrl(backOptions.path ?? ''); + return ( + + { + ev.preventDefault(); + navigateToUrl(backUrl); + }} + href={backUrl} + iconType="arrowLeft" + > + {backOptions.text} + + + ); +}); +HeaderLinkBack.displayName = 'HeaderLinkBack'; + const HeaderPageComponent: React.FC = ({ backOptions, backComponent, @@ -99,62 +120,36 @@ const HeaderPageComponent: React.FC = ({ title, titleNode, ...rest -}) => { - const { navigateToUrl } = useKibana().services.application; +}) => ( + <> + + + {backOptions && } + {!backOptions && backComponent && <>{backComponent}} - const { formatUrl } = useFormatUrl(backOptions?.pageId ?? SecurityPageName.overview); - const backUrl = formatUrl(backOptions?.href ?? ''); - const goTo = useCallback( - (ev) => { - ev.preventDefault(); - if (backOptions) { - navigateToUrl(backUrl); - } - }, - [backOptions, navigateToUrl, backUrl] - ); - return ( - <> - - - {backOptions && ( - - - {backOptions.text} - - - )} - - {!backOptions && backComponent && <>{backComponent}} - {titleNode || ( - - )} + {titleNode || ( + <Title + draggableArguments={draggableArguments} + title={title} + badgeOptions={badgeOptions} + /> + )} - {subtitle && <Subtitle data-test-subj="header-page-subtitle" items={subtitle} />} - {subtitle2 && <Subtitle data-test-subj="header-page-subtitle-2" items={subtitle2} />} - {border && isLoading && <EuiProgress size="xs" color="accent" />} - </HeaderSection> + {subtitle && <Subtitle data-test-subj="header-page-subtitle" items={subtitle} />} + {subtitle2 && <Subtitle data-test-subj="header-page-subtitle-2" items={subtitle2} />} + {border && isLoading && <EuiProgress size="xs" color="accent" />} + </HeaderSection> - {children && ( - <EuiPageHeaderSection data-test-subj="header-page-supplements"> - {children} - </EuiPageHeaderSection> - )} - {!hideSourcerer && <Sourcerer scope={SourcererScopeName.default} />} - </EuiPageHeader> - {/* Manually add a 'padding-bottom' to header */} - <EuiSpacer size="l" /> - </> - ); -}; + {children && ( + <EuiPageHeaderSection data-test-subj="header-page-supplements"> + {children} + </EuiPageHeaderSection> + )} + {!hideSourcerer && <Sourcerer scope={SourcererScopeName.default} />} + </EuiPageHeader> + {/* Manually add a 'padding-bottom' to header */} + <EuiSpacer size="l" /> + </> +); export const HeaderPage = React.memo(HeaderPageComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts index b7defcc8c2af9..6681ee2cb7e8f 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts @@ -9,9 +9,9 @@ import { isEmpty } from 'lodash/fp'; import { useCallback } from 'react'; import { useGetUrlSearch } from '../navigation/use_get_url_search'; import { navTabs } from '../../../app/home/home_navigations'; -import { APP_ID } from '../../../../common/constants'; -import { useKibana } from '../../lib/kibana'; -import { SiemNavTabKey } from '../navigation/types'; +import { useAppUrl } from '../../lib/kibana/hooks'; +import { SecurityNavKey } from '../navigation/types'; +import { SecurityPageName } from '../../../app/types'; export { getDetectionEngineUrl, getRuleDetailsUrl } from './redirect_to_detection_engine'; export { getAppOverviewUrl } from './redirect_to_overview'; @@ -33,9 +33,11 @@ interface FormatUrlOptions { export type FormatUrl = (path: string, options?: Partial<FormatUrlOptions>) => string; -export const useFormatUrl = (page: SiemNavTabKey) => { - const { getUrlForApp } = useKibana().services.application; - const search = useGetUrlSearch(navTabs[page]); +export const useFormatUrl = (page: SecurityPageName) => { + const { getAppUrl } = useAppUrl(); + const tab = page in navTabs ? navTabs[page as SecurityNavKey] : undefined; + const search = useGetUrlSearch(tab); + const formatUrl = useCallback<FormatUrl>( (path: string, { absolute = false, skipSearch = false } = {}) => { const pathArr = path.split('?'); @@ -48,9 +50,9 @@ export const useFormatUrl = (page: SiemNavTabKey) => { ? '' : `?${pathArr[1]}` }`; - return getUrlForApp(APP_ID, { deepLinkId: page, path: formattedPath, absolute }); + return getAppUrl({ deepLinkId: page, path: formattedPath, absolute }); }, - [getUrlForApp, page, search] + [getAppUrl, page, search] ); return { formatUrl, search }; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index 1b1b3c9af4bfc..e147e8b7fc958 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -6,7 +6,7 @@ */ import { UrlStateType } from '../url_state/constants'; -import { SecurityPageName, SecurityPageGroupName } from '../../../app/types'; +import { SecurityPageName } from '../../../app/types'; import { UrlState } from '../url_state/types'; import { SiemRouteType } from '../../utils/route/types'; @@ -27,15 +27,14 @@ export interface NavGroupTab { id: string; name: string; } +export enum SecurityNavGroupKey { + detect = 'detect', + explore = 'explore', + investigate = 'investigate', + manage = 'manage', +} -export type SecurityNavTabGroupKey = - | SecurityPageGroupName.detect - | SecurityPageGroupName.explore - | SecurityPageGroupName.investigate - | SecurityPageGroupName.manage; - -export type NavTabGroups = Record<SecurityNavTabGroupKey, NavGroupTab>; - +export type SecurityNavGroup = Record<SecurityNavGroupKey, NavGroupTab>; export interface NavTab { id: string; name: string; @@ -44,8 +43,7 @@ export interface NavTab { urlKey?: UrlStateType; pageId?: SecurityPageName; } - -export type SiemNavTabKey = +export type SecurityNavKey = | SecurityPageName.overview | SecurityPageName.hosts | SecurityPageName.network @@ -59,7 +57,7 @@ export type SiemNavTabKey = | SecurityPageName.trustedApps | SecurityPageName.eventFilters; -export type SiemNavTab = Record<SiemNavTabKey, NavTab>; +export type SecurityNav = Record<SecurityNavKey, NavTab>; export type GetUrlForApp = ( appId: string, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_get_url_search.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_get_url_search.tsx index 011378db1b31c..258dad531837a 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_get_url_search.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_get_url_search.tsx @@ -12,9 +12,9 @@ import { makeMapStateToProps } from '../url_state/helpers'; import { getSearch } from './helpers'; import { SearchNavTab } from './types'; -export const useGetUrlSearch = (tab: SearchNavTab) => { +export const useGetUrlSearch = (tab?: SearchNavTab) => { const mapState = makeMapStateToProps(); const { urlState } = useDeepEqualSelector(mapState); - const urlSearch = useMemo(() => getSearch(tab, urlState), [tab, urlState]); + const urlSearch = useMemo(() => (tab ? getSearch(tab, urlState) : ''), [tab, urlState]); return urlSearch; }; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index 7e211a2e95152..e3549aa6ec047 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -7,7 +7,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; -import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana'; +import { useKibana } from '../../../lib/kibana/kibana_react'; +import { useGetUserCasesPermissions } from '../../../lib/kibana'; import { SecurityPageName } from '../../../../app/types'; import { useSecuritySolutionNavigation } from '.'; import { CONSTANTS } from '../../url_state/constants'; @@ -16,6 +17,7 @@ import { useDeepEqualSelector } from '../../../hooks/use_selector'; import { UrlInputsModel } from '../../../store/inputs/model'; import { useRouteSpy } from '../../../utils/route/use_route_spy'; +jest.mock('../../../lib/kibana/kibana_react'); jest.mock('../../../lib/kibana'); jest.mock('../../../hooks/use_selector'); jest.mock('../../../utils/route/use_route_spy'); @@ -94,7 +96,7 @@ describe('useSecuritySolutionNavigation', () => { "icon": "logoSecurity", "items": Array [ Object { - "id": "securitySolution", + "id": "main", "items": Array [ Object { "data-href": "securitySolution/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", @@ -139,7 +141,7 @@ describe('useSecuritySolutionNavigation', () => { "href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "exceptions", "isSelected": false, - "name": "Exceptions", + "name": "Exception list", "onClick": [Function], }, ], @@ -207,7 +209,7 @@ describe('useSecuritySolutionNavigation', () => { "href": "securitySolution/trusted_apps", "id": "trusted_apps", "isSelected": false, - "name": "Trusted Applications", + "name": "Trusted applications", "onClick": [Function], }, Object { @@ -217,7 +219,7 @@ describe('useSecuritySolutionNavigation', () => { "href": "securitySolution/event_filters", "id": "event_filters", "isSelected": false, - "name": "Event Filters", + "name": "Event filters", "onClick": [Function], }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index e04ec7727a08f..fffe59fceff41 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -7,11 +7,11 @@ import React, { useCallback, useMemo } from 'react'; import { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_nav_types'; -import { navTabGroups } from '../../../../app/home/home_navigations'; -import { APP_ID } from '../../../../../common/constants'; +import { securityNavGroup } from '../../../../app/home/home_navigations'; import { getSearch } from '../helpers'; import { PrimaryNavigationItemsProps } from './types'; -import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana'; +import { useGetUserCasesPermissions } from '../../../lib/kibana'; +import { useNavigation } from '../../../lib/kibana/hooks'; import { NavTab } from '../types'; export const usePrimaryNavigationItems = ({ @@ -19,7 +19,7 @@ export const usePrimaryNavigationItems = ({ selectedTabId, ...urlStateProps }: PrimaryNavigationItemsProps): Array<EuiSideNavItemType<{}>> => { - const { navigateToApp, getUrlForApp } = useKibana().services.application; + const { navigateTo, getAppUrl } = useNavigation(); const getSideNav = useCallback( (tab: NavTab) => { @@ -29,10 +29,10 @@ export const usePrimaryNavigationItems = ({ const handleClick = (ev: React.MouseEvent) => { ev.preventDefault(); - navigateToApp(APP_ID, { deepLinkId: id, path: urlSearch }); + navigateTo({ deepLinkId: id, path: urlSearch }); }; - const appHref = getUrlForApp(APP_ID, { deepLinkId: id, path: urlSearch }); + const appHref = getAppUrl({ deepLinkId: id, path: urlSearch }); return { 'data-href': appHref, @@ -45,7 +45,7 @@ export const usePrimaryNavigationItems = ({ onClick: handleClick, }; }, - [getUrlForApp, navigateToApp, selectedTabId, urlStateProps] + [getAppUrl, navigateTo, selectedTabId, urlStateProps] ); const navItemsToDisplay = usePrimaryNavigationItemsToDisplay(navTabs); @@ -63,27 +63,30 @@ export const usePrimaryNavigationItems = ({ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) { const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; - return [ - { - id: APP_ID, - name: '', - items: [navTabs.overview], - }, - { - ...navTabGroups.detect, - items: [navTabs.alerts, navTabs.rules, navTabs.exceptions], - }, - { - ...navTabGroups.explore, - items: [navTabs.hosts, navTabs.network], - }, - { - ...navTabGroups.investigate, - items: hasCasesReadPermissions ? [navTabs.timelines, navTabs.case] : [navTabs.timelines], - }, - { - ...navTabGroups.manage, - items: [navTabs.endpoints, navTabs.trusted_apps, navTabs.event_filters], - }, - ]; + return useMemo( + () => [ + { + id: 'main', + name: '', + items: [navTabs.overview], + }, + { + ...securityNavGroup.detect, + items: [navTabs.alerts, navTabs.rules, navTabs.exceptions], + }, + { + ...securityNavGroup.explore, + items: [navTabs.hosts, navTabs.network], + }, + { + ...securityNavGroup.investigate, + items: hasCasesReadPermissions ? [navTabs.timelines, navTabs.case] : [navTabs.timelines], + }, + { + ...securityNavGroup.manage, + items: [navTabs.endpoints, navTabs.trusted_apps, navTabs.event_filters], + }, + ], + [navTabs, hasCasesReadPermissions] + ); } diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx index 702a532949428..ae2e509a7d94e 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; import { Provider } from 'react-redux'; -import { useInitSourcerer } from '.'; +import { getScopeFromPath, useInitSourcerer } from '.'; import { mockPatterns } from './mocks'; // import { SourcererScopeName } from '../../store/sourcerer/model'; import { RouteSpyState } from '../../utils/route/types'; @@ -180,3 +180,18 @@ describe('Sourcerer Hooks', () => { }); }); }); + +describe('getScopeFromPath', () => { + it('should return default scope', async () => { + expect(getScopeFromPath('/')).toBe(SourcererScopeName.default); + expect(getScopeFromPath('/exceptions')).toBe(SourcererScopeName.default); + expect(getScopeFromPath('/rules')).toBe(SourcererScopeName.default); + expect(getScopeFromPath('/rules/create')).toBe(SourcererScopeName.default); + }); + + it('should return detections scope', async () => { + expect(getScopeFromPath('/alerts')).toBe(SourcererScopeName.detections); + expect(getScopeFromPath('/rules/id/foo')).toBe(SourcererScopeName.detections); + expect(getScopeFromPath('/rules/id/foo/edit')).toBe(SourcererScopeName.detections); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index 8cc075de324a2..002c40fc9d428 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -8,11 +8,13 @@ import { useEffect, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; +import { matchPath } from 'react-router-dom'; import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useIndexFields } from '../source'; import { useUserInfo } from '../../../detections/components/user_info'; import { timelineSelectors } from '../../../timelines/store/timeline'; +import { ALERTS_PATH, RULES_PATH } from '../../../../common/constants'; import { TimelineId } from '../../../../common/types/timeline'; import { useDeepEqualSelector } from '../../hooks/use_selector'; @@ -125,3 +127,14 @@ export const useSourcererScope = (scope: SourcererScopeName = SourcererScopeName const SourcererScope = useDeepEqualSelector((state) => sourcererScopeSelector(state, scope)); return SourcererScope; }; + +export const getScopeFromPath = ( + pathname: string +): SourcererScopeName.default | SourcererScopeName.detections => { + return matchPath(pathname, { + path: [ALERTS_PATH, `${RULES_PATH}/id/:id`], + strict: false, + }) == null + ? SourcererScopeName.default + : SourcererScopeName.detections; +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts index 1b05c6a857263..b9bbf7afd3626 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts @@ -15,6 +15,7 @@ import { set } from '@elastic/safer-lodash-set'; import { APP_ID, DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants'; import { errorToToaster, useStateToaster } from '../../components/toasters'; import { AuthenticatedUser } from '../../../../../security/common/model'; +import { NavigateToAppOptions } from '../../../../../../../src/core/public'; import { StartServices } from '../../../types'; import { useUiSetting, useKibana } from './kibana_react'; @@ -169,8 +170,15 @@ export const useAppUrl = () => { const { getUrlForApp } = useKibana().services.application; const getAppUrl = useCallback( - ({ appId = APP_ID, ...options }: { appId?: string; deepLinkId?: string; path?: string }) => - getUrlForApp(appId, options), + ({ + appId = APP_ID, + ...options + }: { + appId?: string; + deepLinkId?: string; + path?: string; + absolute?: boolean; + }) => getUrlForApp(appId, options), [getUrlForApp] ); return { getAppUrl }; @@ -191,9 +199,7 @@ export const useNavigateTo = () => { }: { url?: string; appId?: string; - deepLinkId?: string; - path?: string; - }) => { + } & NavigateToAppOptions) => { if (url) { navigateToUrl(url); } else { diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 316f8b6214d1e..ffbfd1a5123ad 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -59,7 +59,7 @@ export const mockGlobalState: State = { events: { activePage: 0, limit: 10 }, uncommonProcesses: { activePage: 0, limit: 10 }, anomalies: null, - alerts: { activePage: 0, limit: 10 }, + externalAlerts: { activePage: 0, limit: 10 }, }, }, details: { @@ -74,7 +74,7 @@ export const mockGlobalState: State = { events: { activePage: 0, limit: 10 }, uncommonProcesses: { activePage: 0, limit: 10 }, anomalies: null, - alerts: { activePage: 0, limit: 10 }, + externalAlerts: { activePage: 0, limit: 10 }, }, }, }, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx index 31d1ce6d41153..70101021bc4f0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx @@ -27,8 +27,8 @@ jest.mock('react-router-dom', () => { }); const mockNavigateToApp = jest.fn(); -jest.mock('../../../common/lib/kibana', () => { - const original = jest.requireActual('../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana/kibana_react', () => { + const original = jest.requireActual('../../../common/lib/kibana/kibana_react'); return { ...original, @@ -43,6 +43,13 @@ jest.mock('../../../common/lib/kibana', () => { }, }, }), + }; +}); + +jest.mock('../../../common/lib/kibana', () => { + const original = jest.requireActual('../../../common/lib/kibana'); + return { + ...original, useUiSetting$: jest.fn().mockReturnValue([]), useGetUserSavedObjectPermissions: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index 880817af856f8..0595fd96d1377 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -27,7 +27,20 @@ jest.mock('react-router-dom', () => { }); jest.mock('../../../../common/components/link_to'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../common/lib/kibana/kibana_react', () => { + const original = jest.requireActual('../../../../common/lib/kibana/kibana_react'); + return { + ...original, + useKibana: () => ({ + services: { + application: { + navigateToApp: jest.fn(), + getUrlForApp: jest.fn(), + }, + }, + }), + }; +}); jest.mock('../../../containers/detection_engine/rules/api', () => ({ getPrePackagedRulesStatus: jest.fn().mockResolvedValue({ diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index 5688b4065ab76..90568e28793a3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -16,8 +16,7 @@ import { SecurityPageName } from '../../../../app/types'; import { useFormatUrl } from '../../../../common/components/link_to'; import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; import { useUserData } from '../../user_info'; -import { APP_ID } from '../../../../../common/constants'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useNavigateTo } from '../../../../common/lib/kibana/hooks'; const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ @@ -40,14 +39,14 @@ const PrePackagedRulesPromptComponent: React.FC<PrePackagedRulesPromptProps> = ( createPrePackagedRules(); }, [createPrePackagedRules]); const { formatUrl } = useFormatUrl(SecurityPageName.rules); - const { navigateToApp } = useKibana().services.application; + const { navigateTo } = useNavigateTo(); const goToCreateRule = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl() }); + navigateTo({ deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl() }); }, - [navigateToApp] + [navigateTo] ); const [ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx index 3d488f1f08c98..1f2bda768d19c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx @@ -305,7 +305,7 @@ const CreateRulePageComponent: React.FC = () => { <MaxWidthEuiFlexItem> <DetectionEngineHeaderPage backOptions={{ - href: getRulesUrl(), + path: getRulesUrl(), text: i18n.BACK_TO_RULES, pageId: SecurityPageName.rules, }} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index a7a9b31d1f408..66f62ad3ebeab 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -578,7 +578,7 @@ const RuleDetailsPageComponent = () => { <Display show={!globalFullScreen}> <DetectionEngineHeaderPage backOptions={{ - href: getRulesUrl(), + path: getRulesUrl(), text: i18n.BACK_TO_RULES, pageId: SecurityPageName.rules, dataTestSubj: 'ruleDetailsBackToAllRules', diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index 4786d7f2eae78..caec85f537d2b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -355,7 +355,7 @@ const EditRulePageComponent: FC = () => { <MaxWidthEuiFlexItem> <DetectionEngineHeaderPage backOptions={{ - href: getRuleDetailsUrl(ruleId ?? ''), + path: getRuleDetailsUrl(ruleId ?? ''), text: `${i18n.BACK_TO} ${rule?.name ?? ''}`, pageId: SecurityPageName.rules, dataTestSubj: 'ruleEditBackToRuleDetails', diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index 3b76ec8a0d13f..5be29a94b5330 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -18,6 +18,7 @@ import { hostDetailsPagePath } from '../types'; import { type } from './utils'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { getHostDetailsPageFilters } from './helpers'; +import { HostsTableType } from '../../store/model'; jest.mock('../../../common/lib/kibana'); @@ -51,12 +52,12 @@ mockUseResizeObserver.mockImplementation(() => ({})); describe('body', () => { const scenariosMap = { - authentications: 'AuthenticationsQueryTabBody', - allHosts: 'HostsQueryTabBody', - uncommonProcesses: 'UncommonProcessQueryTabBody', - anomalies: 'AnomaliesQueryTabBody', - events: 'EventsQueryTabBody', - alerts: 'HostAlertsQueryTabBody', + [HostsTableType.authentications]: 'AuthenticationsQueryTabBody', + [HostsTableType.hosts]: 'HostsQueryTabBody', + [HostsTableType.uncommonProcesses]: 'UncommonProcessQueryTabBody', + [HostsTableType.anomalies]: 'AnomaliesQueryTabBody', + [HostsTableType.events]: 'EventsQueryTabBody', + [HostsTableType.alerts]: 'HostAlertsQueryTabBody', }; const mockHostDetailsPageFilters = getHostDetailsPageFilters('host-1'); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 6e371bbf610e1..7c34e6f30b910 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -97,7 +97,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta ); const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); - const [loading, { hostDetails: hostOverview, id, refetch }] = useHostDetails({ + const [loading, { inspect, hostDetails: hostOverview, id, refetch }] = useHostDetails({ endDate: to, startDate: from, hostName: detailName, @@ -169,6 +169,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta }} setQuery={setQuery} refetch={refetch} + inspect={inspect} /> )} </AnomalyTableProvider> diff --git a/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts b/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts index c9dcc3a60b4a9..8c3a3e27ffb38 100644 --- a/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/hosts/store/helpers.test.ts @@ -71,26 +71,26 @@ describe('Hosts redux store', () => { describe('#setHostsQueriesActivePageToZero', () => { test('set activePage to zero for all queries in hosts page ', () => { expect(setHostsQueriesActivePageToZero(mockHostsState, HostsType.page)).toEqual({ - allHosts: { + [HostsTableType.hosts]: { activePage: 0, direction: 'desc', limit: 10, sortField: 'lastSeen', }, - anomalies: null, - authentications: { + [HostsTableType.anomalies]: null, + [HostsTableType.authentications]: { activePage: 0, limit: 10, }, - events: { + [HostsTableType.events]: { activePage: 0, limit: 10, }, - uncommonProcesses: { + [HostsTableType.uncommonProcesses]: { activePage: 0, limit: 10, }, - alerts: { + [HostsTableType.alerts]: { activePage: 0, limit: 10, }, @@ -99,26 +99,26 @@ describe('Hosts redux store', () => { test('set activePage to zero for all queries in host details ', () => { expect(setHostsQueriesActivePageToZero(mockHostsState, HostsType.details)).toEqual({ - allHosts: { + [HostsTableType.hosts]: { activePage: 0, direction: 'desc', limit: 10, sortField: 'lastSeen', }, - anomalies: null, - authentications: { + [HostsTableType.anomalies]: null, + [HostsTableType.authentications]: { activePage: 0, limit: 10, }, - events: { + [HostsTableType.events]: { activePage: 0, limit: 10, }, - uncommonProcesses: { + [HostsTableType.uncommonProcesses]: { activePage: 0, limit: 10, }, - alerts: { + [HostsTableType.alerts]: { activePage: 0, limit: 10, }, diff --git a/x-pack/plugins/security_solution/public/hosts/store/model.ts b/x-pack/plugins/security_solution/public/hosts/store/model.ts index 2060d46206723..ea168e965fa23 100644 --- a/x-pack/plugins/security_solution/public/hosts/store/model.ts +++ b/x-pack/plugins/security_solution/public/hosts/store/model.ts @@ -19,7 +19,7 @@ export enum HostsTableType { events = 'events', uncommonProcesses = 'uncommonProcesses', anomalies = 'anomalies', - alerts = 'alerts', + alerts = 'externalAlerts', } export interface BasicQueryPaginated { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts index 6cf5e989fb645..a123f06f62f96 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts @@ -304,7 +304,7 @@ describe('endpoint list middleware', () => { }); }); - describe('handle Endpoint Pending Actions state actions', () => { + describe.skip('handle Endpoint Pending Actions state actions', () => { let mockedApis: ReturnType<typeof endpointPageHttpMock>; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 1a431ea88ad6a..e34e9cf5a83f3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -36,8 +36,7 @@ import { getLastLoadedActivityLogData, detailsData, getEndpointDetailsFlyoutView, - getIsEndpointPackageInfoPending, - getIsEndpointPackageInfoSuccessful, + getIsEndpointPackageInfoUninitialized, } from './selectors'; import { AgentIdsPendingActions, EndpointState, PolicyIds } from '../types'; import { @@ -167,6 +166,8 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState }); } } catch (error) { + // TODO should handle the error instead of logging it to the browser + // Also this is an anti-pattern we shouldn't use // Ignore Errors, since this should not hinder the user's ability to use the UI logError(error); } @@ -287,6 +288,8 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState }); } } catch (error) { + // TODO should handle the error instead of logging it to the browser + // Also this is an anti-pattern we shouldn't use // Ignore Errors, since this should not hinder the user's ability to use the UI logError(error); } @@ -332,6 +335,8 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState }); } } catch (error) { + // TODO should handle the error instead of logging it to the browser + // Also this is an anti-pattern we shouldn't use // Ignore Errors, since this should not hinder the user's ability to use the UI logError(error); } @@ -538,6 +543,8 @@ const endpointsTotal = async (http: HttpStart): Promise<number> => { }) ).total; } catch (error) { + // TODO should handle the error instead of logging it to the browser + // Also this is an anti-pattern we shouldn't use logError(`error while trying to check for total endpoints`); logError(error); } @@ -548,6 +555,8 @@ const doEndpointsExist = async (http: HttpStart): Promise<boolean> => { try { return (await endpointsTotal(http)) > 0; } catch (error) { + // TODO should handle the error instead of logging it to the browser + // Also this is an anti-pattern we shouldn't use logError(`error while trying to check if endpoints exist`); logError(error); } @@ -598,7 +607,7 @@ async function getEndpointPackageInfo( dispatch: Dispatch<EndpointPackageInfoStateChanged>, coreStart: CoreStart ) { - if (getIsEndpointPackageInfoPending(state) || getIsEndpointPackageInfoSuccessful(state)) return; + if (!getIsEndpointPackageInfoUninitialized(state)) return; dispatch({ type: 'endpointPackageInfoStateChanged', @@ -614,6 +623,8 @@ async function getEndpointPackageInfo( payload: createLoadedResourceState(packageInfo), }); } catch (error) { + // TODO should handle the error instead of logging it to the browser + // Also this is an anti-pattern we shouldn't use // Ignore Errors, since this should not hinder the user's ability to use the UI logError(error); dispatch({ @@ -664,6 +675,8 @@ const loadEndpointsPendingActions = async ({ payload: createLoadedResourceState(agentIdToPendingActions), }); } catch (error) { + // TODO should handle the error instead of logging it to the browser + // Also this is an anti-pattern we shouldn't use logError(error); } }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index c09e4032d6222..5771fbac957d8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -33,6 +33,7 @@ import { isFailedResourceState, isLoadedResourceState, isLoadingResourceState, + isUninitialisedResourceState, } from '../../../state'; import { ServerApiError } from '../../../../common/types'; @@ -69,15 +70,10 @@ export const policyItemsLoading = (state: Immutable<EndpointState>) => state.pol export const selectedPolicyId = (state: Immutable<EndpointState>) => state.selectedPolicyId; export const endpointPackageInfo = (state: Immutable<EndpointState>) => state.endpointPackageInfo; -export const getIsEndpointPackageInfoPending: ( +export const getIsEndpointPackageInfoUninitialized: ( state: Immutable<EndpointState> ) => boolean = createSelector(endpointPackageInfo, (packageInfo) => - isLoadingResourceState(packageInfo) -); -export const getIsEndpointPackageInfoSuccessful: ( - state: Immutable<EndpointState> -) => boolean = createSelector(endpointPackageInfo, (packageInfo) => - isLoadedResourceState(packageInfo) + isUninitialisedResourceState(packageInfo) ); export const isAutoRefreshEnabled = (state: Immutable<EndpointState>) => state.isAutoRefreshEnabled; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index 166d3f3b98a85..62d51c3630db7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -658,4 +658,37 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'windows.advanced.memory_protection.shellcode_enhanced_pe_parsing', + first_supported_version: '7.15', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.memory_protection.shellcode_enhanced_pe_parsing', + { + defaultMessage: + "A value of 'false' disables enhanced parsing of PEs found within shellcode payloads. Default: true.", + } + ), + }, + { + key: 'windows.advanced.memory_protection.shellcode', + first_supported_version: '7.15', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.memory_protection.shellcode', + { + defaultMessage: + "A value of 'false' disables Shellcode Injection Protection, a feature of Memory Protection. Default: true.", + } + ), + }, + { + key: 'windows.advanced.memory_protection.memory_scan', + first_supported_version: '7.15', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.memory_protection.signature', + { + defaultMessage: + "A value of 'false' disables Memory Signature Scanning, a feature of Memory Protection. Default: true.", + } + ), + }, ]; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_custom_assets_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_custom_assets_extension.tsx new file mode 100644 index 0000000000000..781aa880d1483 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_custom_assets_extension.tsx @@ -0,0 +1,34 @@ +/* + * 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'; + +import { useKibana } from '../../../../../common/lib/kibana'; +import { APP_PATH } from '../../../../../../common/constants'; +import { + CustomAssetsAccordionProps, + CustomAssetsAccordion, + PackageAssetsComponent, +} from '../../../../../../../fleet/public'; + +export const EndpointCustomAssetsExtension: PackageAssetsComponent = () => { + const { http } = useKibana().services; + const views: CustomAssetsAccordionProps['views'] = [ + { + name: i18n.translate('xpack.securitySolution.fleetIntegration.assets.name', { + defaultMessage: 'Hosts', + }), + url: http.basePath.prepend(`${APP_PATH}/administration/endpoints`), + description: i18n.translate('xpack.securitySolution.fleetIntegration.assets.description', { + defaultMessage: 'View endpoints in Security app', + }), + }, + ]; + + return <CustomAssetsAccordion views={views} initialIsOpen />; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension.tsx new file mode 100644 index 0000000000000..05187d64a12e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension.tsx @@ -0,0 +1,16 @@ +/* + * 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 { lazy } from 'react'; + +export const LazyEndpointCustomAssetsExtension = lazy(async () => { + const { EndpointCustomAssetsExtension } = await import('./endpoint_custom_assets_extension'); + + return { + default: EndpointCustomAssetsExtension, + }; +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx index fa644d1cbcdac..75723e0d3af84 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx @@ -203,7 +203,10 @@ export const HostOverview = React.memo<HostSummaryProps>( return ( <> <InspectButtonContainer> - <OverviewWrapper direction={isInDetailsSidePanel ? 'column' : 'row'}> + <OverviewWrapper + direction={isInDetailsSidePanel ? 'column' : 'row'} + data-test-subj="host-overview" + > {!isInDetailsSidePanel && ( <InspectButton queryId={id} title={i18n.INSPECT_TITLE} inspectIndex={0} /> )} diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 1bf3edf1605d8..137fef1641501 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -60,6 +60,7 @@ import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/vie import { getLazyEndpointPackageCustomExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension'; import { parseExperimentalConfigValue } from '../common/experimental_features'; import type { TimelineState } from '../../timelines/public'; +import { LazyEndpointCustomAssetsExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension'; export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> { private kibanaVersion: string; @@ -199,19 +200,25 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S registerExtension({ package: 'endpoint', view: 'package-policy-edit', - component: getLazyEndpointPolicyEditExtension(core, plugins), + Component: getLazyEndpointPolicyEditExtension(core, plugins), }); registerExtension({ package: 'endpoint', view: 'package-policy-create', - component: LazyEndpointPolicyCreateExtension, + Component: LazyEndpointPolicyCreateExtension, }); registerExtension({ package: 'endpoint', view: 'package-detail-custom', - component: getLazyEndpointPackageCustomExtension(core, plugins), + Component: getLazyEndpointPackageCustomExtension(core, plugins), + }); + + registerExtension({ + package: 'endpoint', + view: 'package-detail-assets', + Component: LazyEndpointCustomAssetsExtension, }); } licenseService.start(plugins.licensing.license$); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 06db698a91a6d..edf1a50787a57 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -241,248 +241,14 @@ exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should render the Event Details view of the Details Panel in the flyout when the panelView is eventDetail and the eventId is set 1`] = ` Array [ - .c1 { - -webkit-flex: 0 1 auto; - -ms-flex: 0 1 auto; - flex: 0 1 auto; - margin-top: 8px; -} - -.c2 .euiFlyoutBody__overflow { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - overflow: hidden; -} - -.c2 .euiFlyoutBody__overflow .euiFlyoutBody__overflowContent { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - overflow: hidden; - padding: 4px 16px 50px; -} - -.c0 { - z-index: 7000; -} - -<Styled(EuiFlyout) - data-test-subj="timeline:details-panel:flyout" - onClose={[Function]} - ownFocus={false} - size="m" - > - <EuiFlyout - className="c0" - data-test-subj="timeline:details-panel:flyout" - onClose={[Function]} - ownFocus={false} - size="m" - > - <div - data-eui="EuiFlyout" - data-test-subj="timeline:details-panel:flyout" - role="dialog" - > - <button - data-test-subj="euiFlyoutCloseButton" - onClick={[Function]} - type="button" - /> - <EventDetailsPanelComponent - browserFields={Object {}} - docValueFields={Array []} - expandedEvent={ - Object { - "eventId": "my-id", - "indexName": "my-index", - } - } - handleOnEventClosed={[Function]} - isFlyoutView={true} - tabType="query" - timelineId="test" - > - <EuiFlyoutHeader - hasBorder={true} - > - <div - className="euiFlyoutHeader euiFlyoutHeader--hasBorder" - > - <ExpandableEventTitle - isAlert={false} - loading={true} - > - <Styled(EuiFlexGroup) - gutterSize="none" - justifyContent="spaceBetween" - wrap={true} - > - <EuiFlexGroup - className="c1" - gutterSize="none" - justifyContent="spaceBetween" - wrap={true} - > - <div - className="euiFlexGroup euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap c1" - > - <EuiFlexItem - grow={false} - > - <div - className="euiFlexItem euiFlexItem--flexGrowZero" - > - <EuiTitle - size="s" - /> - </div> - </EuiFlexItem> - </div> - </EuiFlexGroup> - </Styled(EuiFlexGroup)> - </ExpandableEventTitle> - </div> - </EuiFlyoutHeader> - <Styled(EuiFlyoutBody)> - <EuiFlyoutBody - className="c2" - > - <div - className="euiFlyoutBody c2" - > - <div - className="euiFlyoutBody__overflow" - tabIndex={0} - > - <div - className="euiFlyoutBody__overflowContent" - > - <ExpandableEvent - browserFields={Object {}} - detailsData={null} - event={ - Object { - "eventId": "my-id", - "indexName": "my-index", - } - } - isAlert={false} - loading={true} - timelineId="test" - timelineTabType="flyout" - > - <EuiLoadingContent - lines={10} - > - <span - className="euiLoadingContent" - > - <span - className="euiLoadingContent__singleLine" - key="0" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="1" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="2" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="3" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="4" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="5" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="6" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="7" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="8" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - <span - className="euiLoadingContent__singleLine" - key="9" - > - <span - className="euiLoadingContent__singleLineBackground" - /> - </span> - </span> - </EuiLoadingContent> - </ExpandableEvent> - </div> - </div> - </div> - </EuiFlyoutBody> - </Styled(EuiFlyoutBody)> - </EventDetailsPanelComponent> - </div> - </EuiFlyout> - </Styled(EuiFlyout)>, - .c1 { + .c0 { -webkit-flex: 0 1 auto; -ms-flex: 0 1 auto; flex: 0 1 auto; margin-top: 8px; } -.c2 .euiFlyoutBody__overflow { +.c1 .euiFlyoutBody__overflow { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -493,7 +259,7 @@ Array [ overflow: hidden; } -.c2 .euiFlyoutBody__overflow .euiFlyoutBody__overflowContent { +.c1 .euiFlyoutBody__overflow .euiFlyoutBody__overflowContent { -webkit-flex: 1; -ms-flex: 1; flex: 1; @@ -501,12 +267,7 @@ Array [ padding: 4px 16px 50px; } -.c0 { - z-index: 7000; -} - <EuiFlyout - className="c0" data-test-subj="timeline:details-panel:flyout" onClose={[Function]} ownFocus={false} @@ -552,13 +313,13 @@ Array [ wrap={true} > <EuiFlexGroup - className="c1" + className="c0" gutterSize="none" justifyContent="spaceBetween" wrap={true} > <div - className="euiFlexGroup euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap c1" + className="euiFlexGroup euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap c0" > <EuiFlexItem grow={false} @@ -579,10 +340,10 @@ Array [ </EuiFlyoutHeader> <Styled(EuiFlyoutBody)> <EuiFlyoutBody - className="c2" + className="c1" > <div - className="euiFlyoutBody c2" + className="euiFlyoutBody c1" > <div className="euiFlyoutBody__overflow" diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index fff2d4559c8df..47f6fe6606d3d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -122,7 +122,7 @@ export const ExpandableEvent = React.memo<Props>( <StyledEuiFlexItem grow={true}> <EventDetails browserFields={browserFields} - data={detailsData!} + data={detailsData ?? []} id={event.eventId!} isAlert={isAlert} timelineId={timelineId} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap index 84611e0b7f02c..2cd3d333798d4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap @@ -121,14 +121,17 @@ exports[`Expandable Host Component ExpandableHostDetails: rendering it should re className="c0" > <OverviewWrapper + data-test-subj="host-overview" direction="column" > <EuiFlexGroup className="c1" + data-test-subj="host-overview" direction="column" > <div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionColumn euiFlexGroup--responsive c1" + data-test-subj="host-overview" > <OverviewDescriptionList descriptionList={ diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx index ea408be7c8e9a..3e57ec2e039f5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import React, { useCallback, useMemo, ReactNode } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { EuiFlyout, EuiFlyoutProps } from '@elastic/eui'; -import styled, { StyledComponent } from 'styled-components'; import { timelineActions, timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; @@ -18,14 +17,6 @@ import { EventDetailsPanel } from './event_details'; import { HostDetailsPanel } from './host_details'; import { NetworkDetailsPanel } from './network_details'; -// TODO: EUI team follow up on complex types and styled-components `styled` -// https://github.com/elastic/eui/issues/4855 -const StyledEuiFlyout: StyledComponent<typeof EuiFlyout, {}, { children?: ReactNode }> = styled( - EuiFlyout -)` - z-index: ${({ theme }) => theme.eui.euiZLevel7}; -`; - interface DetailsPanelProps { browserFields: BrowserFields; docValueFields: DocValueFields[]; @@ -113,14 +104,14 @@ export const DetailsPanel = React.memo( } return isFlyoutView ? ( - <StyledEuiFlyout + <EuiFlyout data-test-subj="timeline:details-panel:flyout" size={panelSize} onClose={closePanel} ownFocus={false} > {visiblePanel} - </StyledEuiFlyout> + </EuiFlyout> ) : ( visiblePanel ); diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts index f5163f4ca5ed8..aca73a4b77434 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts @@ -360,7 +360,7 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { non_recurring: 20, owner_ids: 2, estimated_schedule_density: [], - capacity_requirments: { + capacity_requirements: { per_minute: 150, per_hour: 360, per_day: 820, diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts index c68e307dbec03..bd8ecf0cc6d93 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts @@ -17,7 +17,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 60, per_hour: 0, per_day: 0, @@ -72,7 +72,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 60, per_hour: 0, per_day: 0, @@ -129,7 +129,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 60, per_hour: 0, per_day: 0, @@ -165,7 +165,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 0, per_hour: 12000, per_day: 200, @@ -221,7 +221,7 @@ describe('estimateCapacity', () => { // 0 active tasks at this moment in time, so no owners identifiable owner_ids: 0, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 60, per_hour: 0, per_day: 0, @@ -276,7 +276,7 @@ describe('estimateCapacity', () => { { owner_ids: 3, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 150, per_hour: 60, per_day: 0, @@ -337,7 +337,7 @@ describe('estimateCapacity', () => { { owner_ids: provisionedKibanaInstances, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 150, per_hour: 60, per_day: 0, @@ -417,7 +417,7 @@ describe('estimateCapacity', () => { { owner_ids: provisionedKibanaInstances, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: recurringTasksPerMinute, per_hour: 0, per_day: 0, @@ -498,7 +498,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 170, per_hour: 0, per_day: 0, @@ -562,7 +562,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 175, per_hour: 0, per_day: 0, @@ -623,7 +623,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 210, per_hour: 0, per_day: 0, @@ -684,7 +684,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 28, per_hour: 27, per_day: 2, @@ -759,7 +759,7 @@ describe('estimateCapacity', () => { { owner_ids: 1, overdue_non_recurring: 0, - capacity_requirments: { + capacity_requirements: { per_minute: 210, per_hour: 0, per_day: 0, @@ -871,7 +871,7 @@ function mockStats( estimated_schedule_density: [], non_recurring: 20, owner_ids: 2, - capacity_requirments: { + capacity_requirements: { per_minute: 150, per_hour: 360, per_day: 820, diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts index 073112f94e049..90f564152c8c7 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts @@ -58,7 +58,7 @@ export function estimateCapacity( recurring: percentageOfExecutionsUsedByRecurringTasks, non_recurring: percentageOfExecutionsUsedByNonRecurringTasks, } = capacityStats.runtime.value.execution.persistence; - const { overdue, capacity_requirments: capacityRequirments } = workload; + const { overdue, capacity_requirements: capacityRequirements } = workload; const { poll_interval: pollInterval, max_workers: maxWorkers, @@ -130,9 +130,9 @@ export function estimateCapacity( * On average, how many tasks per minute does this cluster need to execute? */ const averageRecurringRequiredPerMinute = - capacityRequirments.per_minute + - capacityRequirments.per_hour / 60 + - capacityRequirments.per_day / 24 / 60; + capacityRequirements.per_minute + + capacityRequirements.per_hour / 60 + + capacityRequirements.per_day / 24 / 60; /** * how many Kibana are needed solely for the recurring tasks diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index 3fe003ebc6591..9125bca8f5b05 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -624,7 +624,7 @@ describe('Workload Statistics Aggregator', () => { expect(result.key).toEqual('workload'); expect(result.value).toMatchObject({ - capacity_requirments: { + capacity_requirements: { // these are buckets of required capacity, rather than aggregated requirmenets. per_minute: 150, per_hour: 360, diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index 64c1c66140196..5c4e7d6cbe2cf 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -36,7 +36,7 @@ interface RawWorkloadStat extends JsonObject { overdue: number; overdue_non_recurring: number; estimated_schedule_density: number[]; - capacity_requirments: CapacityRequirments; + capacity_requirements: CapacityRequirements; } export interface WorkloadStat extends RawWorkloadStat { @@ -45,7 +45,7 @@ export interface WorkloadStat extends RawWorkloadStat { export interface SummarizedWorkloadStat extends RawWorkloadStat { owner_ids: number; } -export interface CapacityRequirments extends JsonObject { +export interface CapacityRequirements extends JsonObject { per_minute: number; per_hour: number; per_day: number; @@ -277,7 +277,7 @@ export function createWorkloadAggregator( pollInterval, scheduleDensity ), - capacity_requirments: { + capacity_requirements: { per_minute: cadence.perMinute, per_hour: cadence.perHour, per_day: cadence.perDay, diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts index 735029e90c2d3..ece91ed571f88 100644 --- a/x-pack/plugins/task_manager/server/routes/health.test.ts +++ b/x-pack/plugins/task_manager/server/routes/health.test.ts @@ -464,7 +464,7 @@ function mockHealthStats(overrides = {}) { non_recurring: 20, owner_ids: [0, 0, 0, 1, 2, 0, 0, 2, 2, 2, 1, 2, 1, 1], estimated_schedule_density: [], - capacity_requirments: { + capacity_requirements: { per_minute: 150, per_hour: 360, per_day: 820, diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.test.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.test.ts new file mode 100644 index 0000000000000..aa8f5421184e5 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.test.ts @@ -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 { getCombinedProperties } from './use_pivot_data'; +import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/common'; + +describe('getCombinedProperties', () => { + test('extracts missing mappings from docs', () => { + const mappingProps = { + testProp: { + type: ES_FIELD_TYPES.STRING, + }, + }; + + const docs = [ + { + testProp: 'test_value1', + scriptProp: 1, + }, + { + testProp: 'test_value2', + scriptProp: 2, + }, + { + testProp: 'test_value3', + scriptProp: 3, + }, + ]; + + expect(getCombinedProperties(mappingProps, docs)).toEqual({ + testProp: { + type: 'string', + }, + scriptProp: { + type: 'number', + }, + }); + }); + + test('does not override defined mappings', () => { + const mappingProps = { + testProp: { + type: ES_FIELD_TYPES.STRING, + }, + scriptProp: { + type: ES_FIELD_TYPES.LONG, + }, + }; + + const docs = [ + { + testProp: 'test_value1', + scriptProp: 1, + }, + { + testProp: 'test_value2', + scriptProp: 2, + }, + { + testProp: 'test_value3', + scriptProp: 3, + }, + ]; + + expect(getCombinedProperties(mappingProps, docs)).toEqual({ + testProp: { + type: 'string', + }, + scriptProp: { + type: 'long', + }, + }); + }); +}); diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 9a49ed9480359..329e2d5f87131 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -13,6 +13,7 @@ import { EuiDataGridColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getFlattenedObject } from '@kbn/std'; +import { sample, difference } from 'lodash'; import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/common'; import type { PreviewMappingsProperties } from '../../../common/api_schemas/transforms'; @@ -71,6 +72,25 @@ function sortColumnsForLatest(sortField: string) { }; } +/** + * Extracts missing mappings from docs. + */ +export function getCombinedProperties( + populatedProperties: PreviewMappingsProperties, + docs: Array<Record<string, unknown>> +): PreviewMappingsProperties { + // Take a sample from docs and resolve missing mappings + const sampleDoc = sample(docs) ?? {}; + const missingMappings = difference(Object.keys(sampleDoc), Object.keys(populatedProperties)); + return { + ...populatedProperties, + ...missingMappings.reduce((acc, curr) => { + acc[curr] = { type: typeof sampleDoc[curr] as ES_FIELD_TYPES }; + return acc; + }, {} as PreviewMappingsProperties), + }; +} + export const usePivotData = ( indexPatternTitle: SearchItems['indexPattern']['title'], query: PivotQuery, @@ -170,7 +190,7 @@ export const usePivotData = ( const populatedFields = [...new Set(docs.map(Object.keys).flat(1))]; // 3. Filter mapping properties by populated fields - const populatedProperties: PreviewMappingsProperties = Object.entries( + let populatedProperties: PreviewMappingsProperties = Object.entries( resp.generated_dest_index.mappings.properties ) .filter(([key]) => populatedFields.includes(key)) @@ -182,6 +202,8 @@ export const usePivotData = ( {} ); + populatedProperties = getCombinedProperties(populatedProperties, docs); + setTableItems(docs); setRowCount(docs.length); setRowCountRelation(ES_CLIENT_TOTAL_HITS_RELATION.EQ); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 69553fd53ffc5..df454b21ee725 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6340,15 +6340,15 @@ "xpack.canvas.functions.repeatImage.args.maxHelpText": "画像が繰り返される最高回数です。", "xpack.canvas.functions.repeatImage.args.sizeHelpText": "画像の高さまたは幅のピクセル単位での最高値です。画像が縦長の場合、この関数は高さを制限します。", "xpack.canvas.functions.repeatImageHelpText": "繰り返し画像エレメントを構成します。", + "expressionRevealImage.functions.revealImage.args.emptyImageHelpText": "表示される背景画像です。画像アセットは「{BASE64}」データ {URL} として提供するか、部分式で渡します。", + "expressionRevealImage.functions.revealImage.args.imageHelpText": "表示する画像です。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。", + "expressionRevealImage.functions.revealImage.args.originHelpText": "画像で埋め始める位置です。たとえば、{list}、または {end}です。", + "expressionRevealImage.functions.revealImage.invalidPercentErrorMessage": "無効な値:「{percent}」。パーセンテージは 0 と 1 の間でなければなりません ", + "expressionRevealImage.functions.revealImageHelpText": "画像表示エレメントを構成します。", "xpack.canvas.functions.replace.args.flagsHelpText": "フラグを指定します。{url}を参照してください。", "xpack.canvas.functions.replace.args.patternHelpText": "{JS} 正規表現のテキストまたはパターンです。例:{example}。ここではキャプチャグループを使用できます。", "xpack.canvas.functions.replace.args.replacementHelpText": "文字列の一致する部分の代わりです。キャプチャグループはノードによってアクセス可能です。例:{example}。", "xpack.canvas.functions.replaceImageHelpText": "正規表現で文字列の一部を置き換えます。", - "xpack.canvas.functions.revealImage.args.emptyImageHelpText": "表示される背景画像です。画像アセットは「{BASE64}」データ {URL} として提供するか、部分式で渡します。", - "xpack.canvas.functions.revealImage.args.imageHelpText": "表示する画像です。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。", - "xpack.canvas.functions.revealImage.args.originHelpText": "画像で埋め始める位置です。たとえば、{list}、または {end}です。", - "xpack.canvas.functions.revealImage.invalidPercentErrorMessage": "無効な値:「{percent}」。パーセンテージは 0 と 1 の間でなければなりません ", - "xpack.canvas.functions.revealImageHelpText": "画像表示エレメントを構成します。", "xpack.canvas.functions.rounddate.args.formatHelpText": "バケットに使用する{MOMENTJS}フォーマットです。たとえば、{example}は月単位に端数処理されます。{url}を参照してください。", "xpack.canvas.functions.rounddateHelpText": "新世紀からのミリ秒の繰り上げ・繰り下げに {MOMENTJS} を使用し、新世紀からのミリ秒を戻します。", "xpack.canvas.functions.rowCountHelpText": "行数を返します。{plyFn}と組み合わせて、固有の列値の数、または固有の列値の組み合わせを求めます。", @@ -6543,8 +6543,8 @@ "xpack.canvas.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします", "xpack.canvas.renderer.repeatImage.displayName": "画像の繰り返し", "xpack.canvas.renderer.repeatImage.helpDescription": "画像を指定回数繰り返し表示します", - "xpack.canvas.renderer.revealImage.displayName": "画像の部分表示", - "xpack.canvas.renderer.revealImage.helpDescription": "カスタムゲージスタイルチャートを作成するため、画像のパーセンテージを表示します", + "expressionRevealImage.renderer.revealImage.displayName": "画像の部分表示", + "expressionRevealImage.renderer.revealImage.helpDescription": "カスタムゲージスタイルチャートを作成するため、画像のパーセンテージを表示します", "xpack.canvas.renderer.shape.displayName": "形状", "xpack.canvas.renderer.shape.helpDescription": "基本的な図形をレンダリングします", "xpack.canvas.renderer.table.displayName": "データテーブル", @@ -8485,7 +8485,6 @@ "xpack.dataVisualizer.file.editFlyout.overrides.timestampQuestionMarkValidationErrorMessage": "タイムスタンプフォーマット {timestampFormat} は、疑問符 ({fieldPlaceholder}) が含まれているためサポートされていません", "xpack.dataVisualizer.file.editFlyout.overrides.trimFieldsLabel": "フィールドを切り抜く", "xpack.dataVisualizer.file.editFlyout.overrideSettingsTitle": "上書き設定", - "xpack.dataVisualizer.experimentalBadge.experimentalLabel": "実験的", "xpack.dataVisualizer.file.explanationFlyout.closeButton": "閉じる", "xpack.dataVisualizer.file.explanationFlyout.content": "分析結果を生成した論理ステップ。", "xpack.dataVisualizer.file.explanationFlyout.title": "分析説明", @@ -8589,7 +8588,6 @@ "xpack.dataVisualizer.file.importSummary.indexPatternTitle": "インデックスパターン", "xpack.dataVisualizer.file.importSummary.indexTitle": "インデックス", "xpack.dataVisualizer.file.importSummary.ingestPipelineTitle": "パイプラインを投入", - "xpack.dataVisualizer.file.importView.experimentalFeatureTooltip": "実験的機能。フィードバックをお待ちしています。", "xpack.dataVisualizer.file.importView.importButtonLabel": "インポート", "xpack.dataVisualizer.file.importView.importDataTitle": "データのインポート", "xpack.dataVisualizer.file.importView.importPermissionError": "インデックス {index} にデータを作成またはインポートするパーミッションがありません。", @@ -8624,14 +8622,11 @@ "xpack.dataVisualizer.file.simpleImportSettings.indexNameFormRowLabel": "インデックス名", "xpack.dataVisualizer.file.simpleImportSettings.indexNamePlaceholder": "インデックス名", "xpack.dataVisualizer.file.welcomeContent.delimitedTextFilesDescription": "CSV や TSV などの区切られたテキストファイル", - "xpack.dataVisualizer.file.welcomeContent.experimentalFeatureDescription": "これは実験的な機能です。フィードバックがありますか?{githubLink}で問題を報告してください。", - "xpack.dataVisualizer.file.welcomeContent.experimentalFeatureTooltip": "実験的機能。フィードバックをお待ちしています。", "xpack.dataVisualizer.file.welcomeContent.logFilesWithCommonFormatDescription": "タイムスタンプの一般的フォーマットのログファイル", "xpack.dataVisualizer.file.welcomeContent.newlineDelimitedJsonDescription": "改行区切りの JSON", "xpack.dataVisualizer.file.welcomeContent.supportedFileFormatDescription": "ファイルデータビジュアライザーはこれらのファイル形式をサポートしています:", "xpack.dataVisualizer.file.welcomeContent.uploadedFilesAllowedSizeDescription": "最大{maxFileSize}のファイルをアップロードできます。", "xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileDescription": "ファイルデータビジュアライザーは、ログファイルのフィールドとメトリックの理解に役立ちます。ファイルをアップロードして、データを分析し、 Elasticsearch インデックスにインポートするか選択できます。", - "xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileTitle": "ログファイルのデータを可視化 {experimentalBadge}", "xpack.dataVisualizer.index.actionsPanel.discoverAppTitle": "Discover", "xpack.dataVisualizer.index.actionsPanel.exploreTitle": "データの調査", "xpack.dataVisualizer.index.actionsPanel.viewIndexInDiscoverDescription": "インデックスのドキュメントを調査します。", @@ -11946,7 +11941,6 @@ "xpack.ingestPipelines.pipelineEditor.customForm.optionsFieldAriaLabel": "構成JSONエディター", "xpack.ingestPipelines.pipelineEditor.customForm.optionsFieldLabel": "構成", "xpack.ingestPipelines.pipelineEditor.dateForm.fieldNameHelpText": "変換するフィールド。", - "xpack.ingestPipelines.pipelineEditor.dateForm.formatsFieldHelpText": "想定されるデータ形式。指定された形式は連続で適用されます。Java時刻パターン、ISO8601、UNIX、UNIX_MS、TAI64Nを使用できます。", "xpack.ingestPipelines.pipelineEditor.dateForm.formatsFieldLabel": "形式", "xpack.ingestPipelines.pipelineEditor.dateForm.formatsRequiredError": "形式の値は必須です。", "xpack.ingestPipelines.pipelineEditor.dateForm.localeFieldLabel": "ロケール (任意) ", @@ -14347,8 +14341,6 @@ "xpack.ml.dataVisualizer.fileBasedLabel": "ファイル", "xpack.ml.datavisualizer.selector.dataVisualizerDescription": "機械学習データビジュアライザーツールは、ログファイルのメトリックとフィールド、または既存の Elasticsearch インデックスを分析し、データの理解を助けます。", "xpack.ml.datavisualizer.selector.dataVisualizerTitle": "データビジュアライザー", - "xpack.ml.datavisualizer.selector.experimentalBadgeLabel": "実験的", - "xpack.ml.datavisualizer.selector.experimentalBadgeTooltipLabel": "実験的機能。フィードバックをお待ちしています。", "xpack.ml.datavisualizer.selector.importDataDescription": "ログファイルからデータをインポートします。最大{maxFileSize}のファイルをアップロードできます。", "xpack.ml.datavisualizer.selector.importDataTitle": "データのインポート", "xpack.ml.datavisualizer.selector.selectIndexButtonLabel": "インデックスパターンを選択", @@ -20819,7 +20811,6 @@ "xpack.securitySolution.rowRenderer.wasPreventedFromExecutingAMaliciousProcessDescription": "悪意のあるファイルの実行が防止されました", "xpack.securitySolution.rowRenderer.wasPreventedFromModifyingAMaliciousFileDescription": "悪意のあるファイルの修正が防止されました", "xpack.securitySolution.rowRenderer.wasPreventedFromRenamingAMaliciousFileDescription": "悪意のあるファイルの名前変更が防止されました", - "xpack.securitySolution.search.administration": "エンドポイント管理", "xpack.securitySolution.search.administration.trustedApps": "信頼できるアプリケーション", "xpack.securitySolution.search.cases": "ケース", "xpack.securitySolution.search.cases.configure": "ケースを構成", @@ -23600,7 +23591,6 @@ "xpack.uptime.monitorDetails.title.pingType.icmp": "ICMP ping", "xpack.uptime.monitorDetails.title.pingType.tcp": "TCP ping", "xpack.uptime.monitorList.anomalyColumn.label": "レスポンス異常スコア", - "xpack.uptime.monitorList.defineConnector.description": "アラートを有効にするには、デフォルトのアラートアクションコネクターを定義してください。", "xpack.uptime.monitorList.downLineSeries.downLabel": "ダウン", "xpack.uptime.monitorList.drawer.missingLocation": "一部の Heartbeat インスタンスには位置情報が定義されていません。Heartbeat 構成への{link}。", "xpack.uptime.monitorList.drawer.mostRecentRun": "直近のテスト実行", @@ -24224,4 +24214,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 261f68c2e629a..447ba99945edc 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6383,13 +6383,13 @@ "xpack.canvas.functions.replace.args.flagsHelpText": "指定标志。请参见 {url}。", "xpack.canvas.functions.replace.args.patternHelpText": "{JS} 正则表达式的文本或模式。例如,{example}。您可以在此处使用捕获组。", "xpack.canvas.functions.replace.args.replacementHelpText": "字符串匹配部分的替代。捕获组可以通过其索引进行访问。例如,{example}。", - "xpack.canvas.functions.replaceImageHelpText": "使用正则表达式替换字符串的各部分。", - "xpack.canvas.functions.revealImage.args.emptyImageHelpText": "要显示的可选背景图像。以 `{BASE64}` 数据 {URL} 的形式提供图像资产或传入子表达式。", - "xpack.canvas.functions.revealImage.args.imageHelpText": "要显示的图像。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。", - "xpack.canvas.functions.revealImage.args.originHelpText": "要开始图像填充的位置。例如 {list} 或 {end}。", - "xpack.canvas.functions.revealImage.invalidPercentErrorMessage": "无效值:“{percent}”。百分比必须介于 0 和 1 之间", - "xpack.canvas.functions.revealImageHelpText": "配置图像显示元素。", "xpack.canvas.functions.rounddate.args.formatHelpText": "用于存储桶存储的 {MOMENTJS} 格式。例如,{example} 四舍五入到月份。请参见 {url}。", + "xpack.canvas.functions.replaceImageHelpText": "使用正则表达式替换字符串的各部分。", + "expressionRevealImage.functions.revealImage.args.emptyImageHelpText": "要显示的可选背景图像。以 `{BASE64}` 数据 {URL} 的形式提供图像资产或传入子表达式。", + "expressionRevealImage.functions.revealImage.args.imageHelpText": "要显示的图像。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。", + "expressionRevealImage.functions.revealImage.args.originHelpText": "要开始图像填充的位置。例如 {list} 或 {end}。", + "expressionRevealImage.functions.revealImage.invalidPercentErrorMessage": "无效值:“{percent}”。百分比必须介于 0 和 1 之间", + "expressionRevealImage.functions.revealImageHelpText": "配置图像显示元素。", "xpack.canvas.functions.rounddateHelpText": "使用 {MOMENTJS} 格式字符串舍入自 Epoch 起毫秒数,并返回自 Epoch 起毫秒数。", "xpack.canvas.functions.rowCountHelpText": "返回行数。与 {plyFn} 搭配使用,可获取唯一列值的计数或唯一列值的组合。", "xpack.canvas.functions.savedLens.args.idHelpText": "已保存 Lens 可视化对象的 ID", @@ -6583,8 +6583,8 @@ "xpack.canvas.renderer.progress.helpDescription": "呈现显示元素百分比的进度指示", "xpack.canvas.renderer.repeatImage.displayName": "图像重复", "xpack.canvas.renderer.repeatImage.helpDescription": "重复图像给定次数", - "xpack.canvas.renderer.revealImage.displayName": "图像显示", - "xpack.canvas.renderer.revealImage.helpDescription": "显示一定百分比的图像,以制作定制的仪表样式图表", + "expressionRevealImage.renderer.revealImage.displayName": "图像显示", + "expressionRevealImage.renderer.revealImage.helpDescription": "显示一定百分比的图像,以制作定制的仪表样式图表", "xpack.canvas.renderer.shape.displayName": "形状", "xpack.canvas.renderer.shape.helpDescription": "呈现基本形状", "xpack.canvas.renderer.table.displayName": "数据表", @@ -8557,7 +8557,6 @@ "xpack.dataVisualizer.file.editFlyout.overrides.timestampQuestionMarkValidationErrorMessage": "时间戳格式 {timestampFormat} 不受支持,因为其包含问号字符 ({fieldPlaceholder})", "xpack.dataVisualizer.file.editFlyout.overrides.trimFieldsLabel": "应剪裁字段", "xpack.dataVisualizer.file.editFlyout.overrideSettingsTitle": "替代设置", - "xpack.dataVisualizer.experimentalBadge.experimentalLabel": "实验性", "xpack.dataVisualizer.file.explanationFlyout.closeButton": "关闭", "xpack.dataVisualizer.file.explanationFlyout.content": "产生分析结果的逻辑步骤。", "xpack.dataVisualizer.file.explanationFlyout.title": "分析说明", @@ -8662,7 +8661,6 @@ "xpack.dataVisualizer.file.importSummary.indexPatternTitle": "索引模式", "xpack.dataVisualizer.file.importSummary.indexTitle": "索引", "xpack.dataVisualizer.file.importSummary.ingestPipelineTitle": "采集管道", - "xpack.dataVisualizer.file.importView.experimentalFeatureTooltip": "实验性功能。我们很乐意听取您的反馈意见。", "xpack.dataVisualizer.file.importView.importButtonLabel": "导入", "xpack.dataVisualizer.file.importView.importDataTitle": "导入数据", "xpack.dataVisualizer.file.importView.importPermissionError": "您无权创建或将数据导入索引 {index}", @@ -8697,14 +8695,11 @@ "xpack.dataVisualizer.file.simpleImportSettings.indexNameFormRowLabel": "索引名称", "xpack.dataVisualizer.file.simpleImportSettings.indexNamePlaceholder": "索引名称", "xpack.dataVisualizer.file.welcomeContent.delimitedTextFilesDescription": "分隔的文本文件,例如 CSV 和 TSV", - "xpack.dataVisualizer.file.welcomeContent.experimentalFeatureDescription": "此功能为实验性功能。有反馈?如欲提供反馈,请在 {githubLink} 中创建问题。", - "xpack.dataVisualizer.file.welcomeContent.experimentalFeatureTooltip": "实验性功能。我们很乐意听取您的反馈意见。", "xpack.dataVisualizer.file.welcomeContent.logFilesWithCommonFormatDescription": "具有时间戳通用格式的日志文件", "xpack.dataVisualizer.file.welcomeContent.newlineDelimitedJsonDescription": "换行符分隔的 JSON", "xpack.dataVisualizer.file.welcomeContent.supportedFileFormatDescription": "File Data Visualizer 支持以下文件格式:", "xpack.dataVisualizer.file.welcomeContent.uploadedFilesAllowedSizeDescription": "您可以上传不超过 {maxFileSize} 的文件。", "xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileDescription": "File Data Visualizer 可帮助您理解日志文件中的字段和指标。上传文件、分析文件数据,然后选择是否将数据导入 Elasticsearch 索引。", - "xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileTitle": "可视化来自日志文件的数据 {experimentalBadge}", "xpack.dataVisualizer.index.actionsPanel.discoverAppTitle": "Discover", "xpack.dataVisualizer.index.actionsPanel.exploreTitle": "浏览您的数据", "xpack.dataVisualizer.index.actionsPanel.viewIndexInDiscoverDescription": "浏览您的索引中的文档。", @@ -12110,7 +12105,6 @@ "xpack.ingestPipelines.pipelineEditor.customForm.optionsFieldAriaLabel": "配置 JSON 编辑器", "xpack.ingestPipelines.pipelineEditor.customForm.optionsFieldLabel": "配置", "xpack.ingestPipelines.pipelineEditor.dateForm.fieldNameHelpText": "要转换的字段。", - "xpack.ingestPipelines.pipelineEditor.dateForm.formatsFieldHelpText": "预期的日期格式。提供的格式按顺序应用。接受 Java 时间模式、ISO8601、UNIX、UNIX_MS 或 TAI64N 格式。", "xpack.ingestPipelines.pipelineEditor.dateForm.formatsFieldLabel": "格式", "xpack.ingestPipelines.pipelineEditor.dateForm.formatsRequiredError": "需要格式的值。", "xpack.ingestPipelines.pipelineEditor.dateForm.localeFieldLabel": "区域设置 (可选) ", @@ -14535,8 +14529,6 @@ "xpack.ml.dataVisualizer.fileBasedLabel": "文件", "xpack.ml.datavisualizer.selector.dataVisualizerDescription": "Machine Learning 数据可视化工具通过分析日志文件或现有 Elasticsearch 索引中的指标和字段,帮助您理解数据。", "xpack.ml.datavisualizer.selector.dataVisualizerTitle": "数据可视化工具", - "xpack.ml.datavisualizer.selector.experimentalBadgeLabel": "实验性", - "xpack.ml.datavisualizer.selector.experimentalBadgeTooltipLabel": "实验性功能。我们很乐意听取您的反馈意见。", "xpack.ml.datavisualizer.selector.importDataDescription": "从日志文件导入数据。您可以上传不超过 {maxFileSize} 的文件。", "xpack.ml.datavisualizer.selector.importDataTitle": "导入数据", "xpack.ml.datavisualizer.selector.selectIndexButtonLabel": "选择索引模式", @@ -21151,7 +21143,6 @@ "xpack.securitySolution.rowRenderer.wasPreventedFromExecutingAMaliciousProcessDescription": "被阻止执行恶意进程", "xpack.securitySolution.rowRenderer.wasPreventedFromModifyingAMaliciousFileDescription": "被阻止修改恶意文件", "xpack.securitySolution.rowRenderer.wasPreventedFromRenamingAMaliciousFileDescription": "被阻止重命名恶意文件", - "xpack.securitySolution.search.administration": "终端管理", "xpack.securitySolution.search.administration.trustedApps": "受信任的应用程序", "xpack.securitySolution.search.cases": "案例", "xpack.securitySolution.search.cases.configure": "配置案例", @@ -23968,7 +23959,6 @@ "xpack.uptime.monitorDetails.title.pingType.icmp": "ICMP ping", "xpack.uptime.monitorDetails.title.pingType.tcp": "TCP ping", "xpack.uptime.monitorList.anomalyColumn.label": "响应异常分数", - "xpack.uptime.monitorList.defineConnector.description": "要开始启用告警,请在以下位置定义默认告警操作连接器", "xpack.uptime.monitorList.downLineSeries.downLabel": "关闭检查", "xpack.uptime.monitorList.drawer.missingLocation": "某些 Heartbeat 实例未定义位置。{link}到您的 Heartbeat 配置。", "xpack.uptime.monitorList.drawer.mostRecentRun": "最新测试运行", @@ -24602,4 +24592,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/cluster.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/cluster.test.ts new file mode 100644 index 0000000000000..412ce348d56e3 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/cluster.test.ts @@ -0,0 +1,360 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { MlAction, UpgradeAssistantStatus } from '../../common/types'; + +import { ClusterTestBed, setupClusterPage, setupEnvironment } from './helpers'; + +describe('Cluster tab', () => { + let testBed: ClusterTestBed; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + afterAll(() => { + server.restore(); + }); + + describe('with deprecations', () => { + const snapshotId = '1'; + const jobId = 'deprecation_check_job'; + const upgradeStatusMockResponse: UpgradeAssistantStatus = { + readyForUpgrade: false, + cluster: [ + { + level: 'critical', + message: + 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded', + details: + 'model snapshot [%s] for job [%s] supports minimum version [%s] and needs to be at least [%s]', + url: 'doc_url', + correctiveAction: { + type: 'mlSnapshot', + snapshotId, + jobId, + }, + }, + ], + indices: [], + }; + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(upgradeStatusMockResponse); + httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true }); + + await act(async () => { + testBed = await setupClusterPage({ isReadOnlyMode: false }); + }); + + const { actions, component } = testBed; + + component.update(); + + // Navigate to the cluster tab + await act(async () => { + actions.clickTab('cluster'); + }); + + component.update(); + }); + + test('renders deprecations', () => { + const { exists } = testBed; + expect(exists('clusterTabContent')).toBe(true); + expect(exists('deprecationsContainer')).toBe(true); + }); + + describe('fix ml snapshots button', () => { + let flyout: Element | null; + + beforeEach(async () => { + const { component, actions, exists, find } = testBed; + + expect(exists('deprecationsContainer')).toBe(true); + + // Open all deprecations + actions.clickExpandAll(); + + // The data-test-subj is derived from the deprecation message + const accordionTestSubj = `depgroup_${upgradeStatusMockResponse.cluster[0].message + .split(' ') + .join('_')}`; + + await act(async () => { + find(`${accordionTestSubj}.fixMlSnapshotsButton`).simulate('click'); + }); + + component.update(); + + // We need to read the document "body" as the flyout is added there and not inside + // the component DOM tree. + flyout = document.body.querySelector('[data-test-subj="fixSnapshotsFlyout"]'); + + expect(flyout).not.toBe(null); + expect(flyout!.textContent).toContain('Upgrade or delete model snapshot'); + }); + + test('upgrades snapshots', async () => { + const { component } = testBed; + + const upgradeButton: HTMLButtonElement | null = flyout!.querySelector( + '[data-test-subj="upgradeSnapshotButton"]' + ); + + httpRequestsMockHelpers.setUpgradeMlSnapshotResponse({ + nodeId: 'my_node', + snapshotId, + jobId, + status: 'in_progress', + }); + + await act(async () => { + upgradeButton!.click(); + }); + + component.update(); + + // First, we expect a POST request to upgrade the snapshot + const upgradeRequest = server.requests[server.requests.length - 2]; + expect(upgradeRequest.method).toBe('POST'); + expect(upgradeRequest.url).toBe('/api/upgrade_assistant/ml_snapshots'); + + // Next, we expect a GET request to check the status of the upgrade + const statusRequest = server.requests[server.requests.length - 1]; + expect(statusRequest.method).toBe('GET'); + expect(statusRequest.url).toBe( + `/api/upgrade_assistant/ml_snapshots/${jobId}/${snapshotId}` + ); + }); + + test('handles upgrade failure', async () => { + const { component, find } = testBed; + + const upgradeButton: HTMLButtonElement | null = flyout!.querySelector( + '[data-test-subj="upgradeSnapshotButton"]' + ); + + const error = { + statusCode: 500, + error: 'Upgrade snapshot error', + message: 'Upgrade snapshot error', + }; + + httpRequestsMockHelpers.setUpgradeMlSnapshotResponse(undefined, error); + + await act(async () => { + upgradeButton!.click(); + }); + + component.update(); + + const upgradeRequest = server.requests[server.requests.length - 1]; + expect(upgradeRequest.method).toBe('POST'); + expect(upgradeRequest.url).toBe('/api/upgrade_assistant/ml_snapshots'); + + const accordionTestSubj = `depgroup_${upgradeStatusMockResponse.cluster[0].message + .split(' ') + .join('_')}`; + + expect(find(`${accordionTestSubj}.fixMlSnapshotsButton`).text()).toEqual('Failed'); + }); + + test('deletes snapshots', async () => { + const { component } = testBed; + + const deleteButton: HTMLButtonElement | null = flyout!.querySelector( + '[data-test-subj="deleteSnapshotButton"]' + ); + + httpRequestsMockHelpers.setDeleteMlSnapshotResponse({ + acknowledged: true, + }); + + await act(async () => { + deleteButton!.click(); + }); + + component.update(); + + const request = server.requests[server.requests.length - 1]; + const mlDeprecation = upgradeStatusMockResponse.cluster[0]; + + expect(request.method).toBe('DELETE'); + expect(request.url).toBe( + `/api/upgrade_assistant/ml_snapshots/${ + (mlDeprecation.correctiveAction! as MlAction).jobId + }/${(mlDeprecation.correctiveAction! as MlAction).snapshotId}` + ); + }); + + test('handles delete failure', async () => { + const { component, find } = testBed; + + const deleteButton: HTMLButtonElement | null = flyout!.querySelector( + '[data-test-subj="deleteSnapshotButton"]' + ); + + const error = { + statusCode: 500, + error: 'Upgrade snapshot error', + message: 'Upgrade snapshot error', + }; + + httpRequestsMockHelpers.setDeleteMlSnapshotResponse(undefined, error); + + await act(async () => { + deleteButton!.click(); + }); + + component.update(); + + const request = server.requests[server.requests.length - 1]; + const mlDeprecation = upgradeStatusMockResponse.cluster[0]; + + expect(request.method).toBe('DELETE'); + expect(request.url).toBe( + `/api/upgrade_assistant/ml_snapshots/${ + (mlDeprecation.correctiveAction! as MlAction).jobId + }/${(mlDeprecation.correctiveAction! as MlAction).snapshotId}` + ); + + const accordionTestSubj = `depgroup_${upgradeStatusMockResponse.cluster[0].message + .split(' ') + .join('_')}`; + + expect(find(`${accordionTestSubj}.fixMlSnapshotsButton`).text()).toEqual('Failed'); + }); + }); + }); + + describe('no deprecations', () => { + beforeEach(async () => { + const noDeprecationsResponse = { + readyForUpgrade: false, + cluster: [], + indices: [], + }; + + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(noDeprecationsResponse); + + await act(async () => { + testBed = await setupClusterPage({ isReadOnlyMode: false }); + }); + + const { component } = testBed; + + component.update(); + }); + + test('renders prompt', () => { + const { exists, find } = testBed; + expect(exists('noDeprecationsPrompt')).toBe(true); + expect(find('noDeprecationsPrompt').text()).toContain('Ready to upgrade!'); + }); + }); + + describe('error handling', () => { + test('handles 403', async () => { + const error = { + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }; + + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); + + await act(async () => { + testBed = await setupClusterPage({ isReadOnlyMode: false }); + }); + + const { component, exists, find } = testBed; + + component.update(); + + expect(exists('permissionsError')).toBe(true); + expect(find('permissionsError').text()).toContain( + 'You are not authorized to view Elasticsearch deprecations.' + ); + }); + + test('shows upgraded message when all nodes have been upgraded', async () => { + const error = { + statusCode: 426, + error: 'Upgrade required', + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + // This is marked true in the scenario where none of the nodes have the same major version of Kibana, + // and therefore we assume all have been upgraded + allNodesUpgraded: true, + }, + }; + + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); + + await act(async () => { + testBed = await setupClusterPage({ isReadOnlyMode: false }); + }); + + const { component, exists, find } = testBed; + + component.update(); + + expect(exists('upgradedCallout')).toBe(true); + expect(find('upgradedCallout').text()).toContain( + 'Your configuration is up to date. Kibana and all Elasticsearch nodes are running the same version.' + ); + }); + + test('shows partially upgrade error when nodes are running different versions', async () => { + const error = { + statusCode: 426, + error: 'Upgrade required', + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: false, + }, + }; + + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); + + await act(async () => { + testBed = await setupClusterPage({ isReadOnlyMode: false }); + }); + + const { component, exists, find } = testBed; + + component.update(); + + expect(exists('partiallyUpgradedWarning')).toBe(true); + expect(find('partiallyUpgradedWarning').text()).toContain( + 'Upgrade Kibana to the same version as your Elasticsearch cluster. One or more nodes in the cluster is running a different version than Kibana.' + ); + }); + + test('handles generic error', async () => { + const error = { + statusCode: 500, + error: 'Internal server error', + message: 'Internal server error', + }; + + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); + + await act(async () => { + testBed = await setupClusterPage({ isReadOnlyMode: false }); + }); + + const { component, exists, find } = testBed; + + component.update(); + + expect(exists('requestError')).toBe(true); + expect(find('requestError').text()).toContain( + 'Could not retrieve Elasticsearch deprecations.' + ); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/cluster.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/cluster.helpers.ts new file mode 100644 index 0000000000000..2aedface1e32b --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/cluster.helpers.ts @@ -0,0 +1,67 @@ +/* + * 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 { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { EsDeprecationsContent } from '../../../public/application/components/es_deprecations'; +import { WithAppDependencies } from './setup_environment'; + +const testBedConfig: TestBedConfig = { + memoryRouter: { + initialEntries: ['/es_deprecations/cluster'], + componentRoutePath: '/es_deprecations/:tabName', + }, + doMountAsync: true, +}; + +export type ClusterTestBed = TestBed<ClusterTestSubjects> & { + actions: ReturnType<typeof createActions>; +}; + +const createActions = (testBed: TestBed) => { + /** + * User Actions + */ + const clickTab = (tabName: string) => { + const { find } = testBed; + const camelcaseTabName = tabName.charAt(0).toUpperCase() + tabName.slice(1); + + find(`upgradeAssistant${camelcaseTabName}Tab`).simulate('click'); + }; + + const clickExpandAll = () => { + const { find } = testBed; + find('expandAll').simulate('click'); + }; + + return { + clickTab, + clickExpandAll, + }; +}; + +export const setup = async (overrides?: Record<string, unknown>): Promise<ClusterTestBed> => { + const initTestBed = registerTestBed( + WithAppDependencies(EsDeprecationsContent, overrides), + testBedConfig + ); + const testBed = await initTestBed(); + + return { + ...testBed, + actions: createActions(testBed), + }; +}; + +export type ClusterTestSubjects = + | 'expandAll' + | 'deprecationsContainer' + | 'permissionsError' + | 'requestError' + | 'upgradedCallout' + | 'partiallyUpgradedWarning' + | 'noDeprecationsPrompt' + | string; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts index e3f6df54db60e..3fd8b7279c073 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts @@ -62,11 +62,35 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setUpgradeMlSnapshotResponse = (response?: object, error?: ResponseError) => { + const status = error ? error.statusCode || 400 : 200; + const body = error ? error : response; + + server.respondWith('POST', `${API_BASE_PATH}/ml_snapshots`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + + const setDeleteMlSnapshotResponse = (response?: object, error?: ResponseError) => { + const status = error ? error.statusCode || 400 : 200; + const body = error ? error : response; + + server.respondWith('DELETE', `${API_BASE_PATH}/ml_snapshots/:jobId/:snapshotId`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + return { setLoadEsDeprecationsResponse, setLoadDeprecationLoggingResponse, setUpdateDeprecationLoggingResponse, setUpdateIndexSettingsResponse, + setUpgradeMlSnapshotResponse, + setDeleteMlSnapshotResponse, }; }; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts index ddf5787af1037..8e256680253be 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/index.ts @@ -7,6 +7,7 @@ export { setup as setupOverviewPage, OverviewTestBed } from './overview.helpers'; export { setup as setupIndicesPage, IndicesTestBed } from './indices.helpers'; +export { setup as setupClusterPage, ClusterTestBed } from './cluster.helpers'; export { setup as setupKibanaPage, KibanaTestBed } from './kibana.helpers'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx index faeb0e4a40abd..aae5500403322 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx @@ -33,7 +33,6 @@ export const WithAppDependencies = (Comp: any, overrides: Record<string, unknown const contextValue = { http: (mockHttpClient as unknown) as HttpSetup, - isCloudEnabled: false, docLinks: docLinksServiceMock.createStartContract(), kibanaVersionInfo: { currentMajor: mockKibanaSemverVersion.major, diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts index 059980cb5671b..b44a04eb15d86 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; import { indexSettingDeprecations } from '../../common/constants'; -import { MIGRATION_DEPRECATION_LEVEL } from '../../common/types'; +import { UpgradeAssistantStatus } from '../../common/types'; import { IndicesTestBed, setupIndicesPage, setupEnvironment } from './helpers'; @@ -20,16 +20,19 @@ describe('Indices tab', () => { }); describe('with deprecations', () => { - const upgradeStatusMockResponse = { + const upgradeStatusMockResponse: UpgradeAssistantStatus = { readyForUpgrade: false, cluster: [], indices: [ { - level: 'warning' as MIGRATION_DEPRECATION_LEVEL, + level: 'warning', message: indexSettingDeprecations.translog.deprecationMessage, url: 'doc_url', index: 'my_index', - deprecatedIndexSettings: indexSettingDeprecations.translog.settings, + correctiveAction: { + type: 'indexSetting', + deprecatedSettings: indexSettingDeprecations.translog.settings, + }, }, ], }; @@ -56,6 +59,7 @@ describe('Indices tab', () => { test('renders deprecations', () => { const { exists, find } = testBed; + expect(exists('indexTabContent')).toBe(true); expect(exists('deprecationsContainer')).toBe(true); expect(find('indexCount').text()).toEqual('1'); }); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts index 85efaf38f32a7..9b65b493a74c4 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts @@ -38,7 +38,6 @@ describe('Overview page', () => { details: 'translog retention settings [index.translog.retention.size] and [index.translog.retention.age] are ignored because translog is no longer used in peer recoveries with soft-deletes enabled (default in 7.0 or later)', index: 'settings', - reindex: false, }, ], }; diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts index 0471fc30f28ea..88fa103bace89 100644 --- a/x-pack/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/plugins/upgrade_assistant/common/types.ts @@ -28,7 +28,6 @@ export enum ReindexStatus { } export const REINDEX_OP_TYPE = 'upgrade-assistant-reindex-operation'; - export interface QueueSettings extends SavedObjectAttributes { /** * A Unix timestamp of when the reindex operation was enqueued. @@ -190,11 +189,9 @@ export interface DeprecationAPIResponse { node_settings: DeprecationInfo[]; index_settings: IndexSettingsDeprecationInfo; } -export interface EnrichedDeprecationInfo extends DeprecationInfo { - index?: string; - node?: string; - reindex?: boolean; - deprecatedIndexSettings?: string[]; + +export interface ReindexAction { + type: 'reindex'; /** * Indicate what blockers have been detected for calling reindex * against this index. @@ -205,6 +202,21 @@ export interface EnrichedDeprecationInfo extends DeprecationInfo { blockerForReindexing?: 'index-closed'; // 'index-closed' can be handled automatically, but requires more resources, user should be warned } +export interface MlAction { + type: 'mlSnapshot'; + snapshotId: string; + jobId: string; +} + +export interface IndexSettingAction { + type: 'indexSetting'; + deprecatedSettings: string[]; +} +export interface EnrichedDeprecationInfo extends DeprecationInfo { + index?: string; + correctiveAction?: ReindexAction | MlAction | IndexSettingAction; +} + export interface UpgradeAssistantStatus { readyForUpgrade: boolean; cluster: EnrichedDeprecationInfo[]; @@ -225,3 +237,11 @@ export interface ResolveIndexResponseFromES { }>; data_streams: Array<{ name: string; backing_indices: string[]; timestamp_field: string }>; } + +export const ML_UPGRADE_OP_TYPE = 'upgrade-assistant-ml-upgrade-operation'; + +export interface MlOperation extends SavedObjectAttributes { + nodeId: string; + snapshotId: string; + jobId: string; +} diff --git a/x-pack/plugins/upgrade_assistant/kibana.json b/x-pack/plugins/upgrade_assistant/kibana.json index d9f4917fa0a6c..d013c16837b77 100644 --- a/x-pack/plugins/upgrade_assistant/kibana.json +++ b/x-pack/plugins/upgrade_assistant/kibana.json @@ -5,6 +5,6 @@ "ui": true, "configPath": ["xpack", "upgrade_assistant"], "requiredPlugins": ["management", "licensing", "features"], - "optionalPlugins": ["cloud", "usageCollection"], + "optionalPlugins": ["usageCollection"], "requiredBundles": ["esUiShared", "kibanaReact"] } diff --git a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx index 049318f5b78d9..88b5bd4721c36 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx @@ -24,7 +24,6 @@ export interface KibanaVersionContext { export interface ContextValue { http: HttpSetup; - isCloudEnabled: boolean; docLinks: DocLinksStart; kibanaVersionInfo: KibanaVersionContext; notifications: NotificationsStart; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx index b7d3247ffbf21..4324379f456ea 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx @@ -17,34 +17,84 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EnrichedDeprecationInfo } from '../../../../../common/types'; +import { + EnrichedDeprecationInfo, + MlAction, + ReindexAction, + IndexSettingAction, +} from '../../../../../common/types'; import { AppContext } from '../../../app_context'; import { ReindexButton } from './reindex'; import { FixIndexSettingsButton } from './index_settings'; +import { FixMlSnapshotsButton } from './ml_snapshots'; interface DeprecationCellProps { items?: Array<{ title?: string; body: string }>; - reindexIndexName?: string; - deprecatedIndexSettings?: string[]; docUrl?: string; headline?: string; healthColor?: string; children?: ReactNode; - reindexBlocker?: EnrichedDeprecationInfo['blockerForReindexing']; + correctiveAction?: EnrichedDeprecationInfo['correctiveAction']; + indexName?: string; +} + +interface CellActionProps { + correctiveAction: EnrichedDeprecationInfo['correctiveAction']; + indexName?: string; + items: Array<{ title?: string; body: string }>; } +const CellAction: FunctionComponent<CellActionProps> = ({ correctiveAction, indexName, items }) => { + const { type: correctiveActionType } = correctiveAction!; + switch (correctiveActionType) { + case 'mlSnapshot': + const { jobId, snapshotId } = correctiveAction as MlAction; + return ( + <FixMlSnapshotsButton + jobId={jobId} + snapshotId={snapshotId} + // There will only ever be a single item for the cluster deprecations list, so we can use the index to access the first one + description={items[0]?.body} + /> + ); + + case 'reindex': + const { blockerForReindexing } = correctiveAction as ReindexAction; + + return ( + <AppContext.Consumer> + {({ http, docLinks }) => ( + <ReindexButton + docLinks={docLinks} + reindexBlocker={blockerForReindexing} + indexName={indexName!} + http={http} + /> + )} + </AppContext.Consumer> + ); + + case 'indexSetting': + const { deprecatedSettings } = correctiveAction as IndexSettingAction; + + return <FixIndexSettingsButton settings={deprecatedSettings} index={indexName!} />; + + default: + throw new Error(`No UI defined for corrective action: ${correctiveActionType}`); + } +}; + /** * Used to display a deprecation with links to docs, a health indicator, and other descriptive information. */ export const DeprecationCell: FunctionComponent<DeprecationCellProps> = ({ headline, healthColor, - reindexIndexName, - deprecatedIndexSettings, + correctiveAction, + indexName, docUrl, items = [], children, - reindexBlocker, }) => ( <div className="upgDeprecationCell"> <EuiFlexGroup responsive={false} wrap alignItems="baseline"> @@ -82,24 +132,9 @@ export const DeprecationCell: FunctionComponent<DeprecationCellProps> = ({ )} </EuiFlexItem> - {reindexIndexName && ( - <EuiFlexItem grow={false}> - <AppContext.Consumer> - {({ http, docLinks }) => ( - <ReindexButton - docLinks={docLinks} - reindexBlocker={reindexBlocker} - indexName={reindexIndexName} - http={http} - /> - )} - </AppContext.Consumer> - </EuiFlexItem> - )} - - {deprecatedIndexSettings?.length && ( + {correctiveAction && ( <EuiFlexItem grow={false}> - <FixIndexSettingsButton settings={deprecatedIndexSettings} index={reindexIndexName!} /> + <CellAction correctiveAction={correctiveAction} indexName={indexName} items={items} /> </EuiFlexItem> )} </EuiFlexGroup> diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.test.tsx index 188e70b64ce6a..f4ac573d86b11 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.test.tsx @@ -13,9 +13,9 @@ import { IndexDeprecationTableProps, IndexDeprecationTable } from './index_table describe('IndexDeprecationTable', () => { const defaultProps = { indices: [ - { index: 'index1', details: 'Index 1 deets', reindex: true }, - { index: 'index2', details: 'Index 2 deets', reindex: true }, - { index: 'index3', details: 'Index 3 deets', reindex: true }, + { index: 'index1', details: 'Index 1 deets', correctiveAction: { type: 'reindex' } }, + { index: 'index2', details: 'Index 2 deets', correctiveAction: { type: 'reindex' } }, + { index: 'index3', details: 'Index 3 deets', correctiveAction: { type: 'reindex' } }, ], } as IndexDeprecationTableProps; @@ -49,19 +49,25 @@ describe('IndexDeprecationTable', () => { items={ Array [ Object { + "correctiveAction": Object { + "type": "reindex", + }, "details": "Index 1 deets", "index": "index1", - "reindex": true, }, Object { + "correctiveAction": Object { + "type": "reindex", + }, "details": "Index 2 deets", "index": "index2", - "reindex": true, }, Object { + "correctiveAction": Object { + "type": "reindex", + }, "details": "Index 3 deets", "index": "index3", - "reindex": true, }, ] } diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.tsx index 216884d547eeb..6b0f94ea24bc7 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index_table.tsx @@ -10,7 +10,11 @@ import React from 'react'; import { EuiBasicTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { EnrichedDeprecationInfo } from '../../../../../common/types'; +import { + EnrichedDeprecationInfo, + IndexSettingAction, + ReindexAction, +} from '../../../../../common/types'; import { AppContext } from '../../../app_context'; import { ReindexButton } from './reindex'; import { FixIndexSettingsButton } from './index_settings'; @@ -19,9 +23,7 @@ const PAGE_SIZES = [10, 25, 50, 100, 250, 500, 1000]; export interface IndexDeprecationDetails { index: string; - reindex: boolean; - deprecatedIndexSettings?: string[]; - blockerForReindexing?: EnrichedDeprecationInfo['blockerForReindexing']; + correctiveAction?: EnrichedDeprecationInfo['correctiveAction']; details?: string; } @@ -152,9 +154,9 @@ export class IndexDeprecationTable extends React.Component< // NOTE: this naive implementation assumes all indices in the table // should show the reindex button or fix indices button. This should work for known use cases. const { indices } = this.props; - const showReindexButton = Boolean(indices.find((i) => i.reindex === true)); + const showReindexButton = Boolean(indices.find((i) => i.correctiveAction?.type === 'reindex')); const showFixSettingsButton = Boolean( - indices.find((i) => i.deprecatedIndexSettings && i.deprecatedIndexSettings.length > 0) + indices.find((i) => i.correctiveAction?.type === 'indexSetting') ); if (showReindexButton === false && showFixSettingsButton === false) { @@ -172,7 +174,9 @@ export class IndexDeprecationTable extends React.Component< return ( <ReindexButton docLinks={docLinks} - reindexBlocker={indexDep.blockerForReindexing} + reindexBlocker={ + (indexDep.correctiveAction as ReindexAction).blockerForReindexing + } indexName={indexDep.index!} http={http} /> @@ -184,7 +188,7 @@ export class IndexDeprecationTable extends React.Component< return ( <FixIndexSettingsButton - settings={indexDep.deprecatedIndexSettings!} + settings={(indexDep.correctiveAction as IndexSettingAction).deprecatedSettings} index={indexDep.index} /> ); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx index 579cf1f4a55bb..2bfa8119e41bc 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx @@ -72,18 +72,14 @@ describe('EsDeprecationList', () => { indices={ Array [ Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": undefined, + "correctiveAction": undefined, "details": undefined, "index": "0", - "reindex": false, }, Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": undefined, + "correctiveAction": undefined, "details": undefined, "index": "1", - "reindex": false, }, ] } diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx index cb9f238d0e4dd..7b543a7e94b33 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx @@ -32,11 +32,10 @@ const MessageDeprecation: FunctionComponent<{ return ( <DeprecationCell - reindexBlocker={deprecation.blockerForReindexing} headline={deprecation.message} healthColor={COLOR_MAP[deprecation.level]} - reindexIndexName={deprecation.reindex ? deprecation.index! : undefined} - deprecatedIndexSettings={deprecation.deprecatedIndexSettings} + correctiveAction={deprecation.correctiveAction} + indexName={deprecation.index} docUrl={deprecation.url} items={items} /> @@ -57,10 +56,10 @@ const SimpleMessageDeprecation: FunctionComponent<{ deprecation: EnrichedDepreca return ( <DeprecationCell - reindexBlocker={deprecation.blockerForReindexing} + correctiveAction={deprecation.correctiveAction} + indexName={deprecation.index} items={items} docUrl={deprecation.url} - deprecatedIndexSettings={deprecation.deprecatedIndexSettings} /> ); }; @@ -94,12 +93,11 @@ export const EsDeprecationList: FunctionComponent<{ if (currentGroupBy === GroupByOption.message && deprecations[0].index !== undefined) { // We assume that every deprecation message is the same issue (since they have the same // message) and that each deprecation will have an index associated with it. + const indices = deprecations.map((dep) => ({ index: dep.index!, details: dep.details, - reindex: dep.reindex === true, - deprecatedIndexSettings: dep.deprecatedIndexSettings, - blockerForReindexing: dep.blockerForReindexing, + correctiveAction: dep.correctiveAction, })); return <IndexDeprecation indices={indices} deprecation={deprecations[0]} />; } else if (currentGroupBy === GroupByOption.index) { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/button.tsx new file mode 100644 index 0000000000000..13b7dacc3b598 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/button.tsx @@ -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 React, { useEffect, useState } from 'react'; + +import { ButtonSize, EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FixSnapshotsFlyout } from './fix_snapshots_flyout'; +import { useAppContext } from '../../../../app_context'; +import { useSnapshotState } from './use_snapshot_state'; + +const i18nTexts = { + fixButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.fixButtonLabel', + { + defaultMessage: 'Fix', + } + ), + upgradingButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradingButtonLabel', + { + defaultMessage: 'Upgrading…', + } + ), + deletingButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.deletingButtonLabel', + { + defaultMessage: 'Deleting…', + } + ), + doneButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.doneButtonLabel', + { + defaultMessage: 'Done', + } + ), + failedButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.failedButtonLabel', + { + defaultMessage: 'Failed', + } + ), +}; + +interface Props { + snapshotId: string; + jobId: string; + description: string; +} + +export const FixMlSnapshotsButton: React.FunctionComponent<Props> = ({ + snapshotId, + jobId, + description, +}) => { + const { api } = useAppContext(); + const { snapshotState, upgradeSnapshot, deleteSnapshot, updateSnapshotStatus } = useSnapshotState( + { + jobId, + snapshotId, + api, + } + ); + + const [showFlyout, setShowFlyout] = useState(false); + + useEffect(() => { + updateSnapshotStatus(); + }, [updateSnapshotStatus]); + + const commonButtonProps = { + size: 's' as ButtonSize, + onClick: () => setShowFlyout(true), + 'data-test-subj': 'fixMlSnapshotsButton', + }; + + let button = <EuiButton {...commonButtonProps}>{i18nTexts.fixButtonLabel}</EuiButton>; + + switch (snapshotState.status) { + case 'in_progress': + button = ( + <EuiButton color="secondary" {...commonButtonProps} isLoading> + {snapshotState.action === 'delete' + ? i18nTexts.deletingButtonLabel + : i18nTexts.upgradingButtonLabel} + </EuiButton> + ); + break; + case 'complete': + button = ( + <EuiButton color="secondary" iconType="check" {...commonButtonProps} disabled> + {i18nTexts.doneButtonLabel} + </EuiButton> + ); + break; + case 'error': + button = ( + <EuiButton color="danger" iconType="cross" {...commonButtonProps}> + {i18nTexts.failedButtonLabel} + </EuiButton> + ); + break; + } + + return ( + <> + {button} + + {showFlyout && ( + <FixSnapshotsFlyout + snapshotState={snapshotState} + upgradeSnapshot={upgradeSnapshot} + deleteSnapshot={deleteSnapshot} + description={description} + closeFlyout={() => setShowFlyout(false)} + /> + )} + </> + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/fix_snapshots_flyout.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/fix_snapshots_flyout.tsx new file mode 100644 index 0000000000000..7dafab011a69a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/fix_snapshots_flyout.tsx @@ -0,0 +1,181 @@ +/* + * 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'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiPortal, + EuiTitle, + EuiText, + EuiCallOut, + EuiSpacer, +} from '@elastic/eui'; +import { SnapshotStatus } from './use_snapshot_state'; +import { ResponseError } from '../../../../lib/api'; + +interface SnapshotState extends SnapshotStatus { + error?: ResponseError; +} +interface Props { + upgradeSnapshot: () => Promise<void>; + deleteSnapshot: () => Promise<void>; + description: string; + closeFlyout: () => void; + snapshotState: SnapshotState; +} + +const i18nTexts = { + upgradeButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.upgradeButtonLabel', + { + defaultMessage: 'Upgrade', + } + ), + retryUpgradeButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.retryUpgradeButtonLabel', + { + defaultMessage: 'Retry upgrade', + } + ), + closeButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.cancelButtonLabel', + { + defaultMessage: 'Close', + } + ), + deleteButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.deleteButtonLabel', + { + defaultMessage: 'Delete', + } + ), + retryDeleteButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.retryDeleteButtonLabel', + { + defaultMessage: 'Retry delete', + } + ), + flyoutTitle: i18n.translate('xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.title', { + defaultMessage: 'Upgrade or delete model snapshot', + }), + deleteSnapshotErrorTitle: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.deleteSnapshotErrorTitle', + { + defaultMessage: 'Error deleting snapshot', + } + ), + upgradeSnapshotErrorTitle: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.upgradeSnapshotErrorTitle', + { + defaultMessage: 'Error upgrading snapshot', + } + ), +}; + +export const FixSnapshotsFlyout = ({ + upgradeSnapshot, + deleteSnapshot, + description, + closeFlyout, + snapshotState, +}: Props) => { + const onUpgradeSnapshot = () => { + upgradeSnapshot(); + closeFlyout(); + }; + + const onDeleteSnapshot = () => { + deleteSnapshot(); + closeFlyout(); + }; + + return ( + <EuiPortal> + <EuiFlyout + onClose={closeFlyout} + ownFocus + size="m" + maxWidth + data-test-subj="fixSnapshotsFlyout" + > + <EuiFlyoutHeader hasBorder> + <EuiTitle size="s"> + <h2>{i18nTexts.flyoutTitle}</h2> + </EuiTitle> + </EuiFlyoutHeader> + <EuiFlyoutBody> + {snapshotState.error && ( + <> + <EuiCallOut + title={ + snapshotState.action === 'delete' + ? i18nTexts.deleteSnapshotErrorTitle + : i18nTexts.upgradeSnapshotErrorTitle + } + color="danger" + iconType="alert" + data-test-subj="upgradeSnapshotError" + > + {snapshotState.error.message} + </EuiCallOut> + <EuiSpacer /> + </> + )} + <EuiText> + <p>{description}</p> + </EuiText> + </EuiFlyoutBody> + <EuiFlyoutFooter> + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty iconType="cross" onClick={closeFlyout} flush="left"> + {i18nTexts.closeButtonLabel} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiFlexGroup> + <EuiFlexItem> + <EuiButtonEmpty + data-test-subj="deleteSnapshotButton" + color="danger" + onClick={onDeleteSnapshot} + isLoading={false} + > + {snapshotState.action === 'delete' && snapshotState.error + ? i18nTexts.retryDeleteButtonLabel + : i18nTexts.deleteButtonLabel} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem> + <EuiButton + fill + onClick={onUpgradeSnapshot} + isLoading={false} + data-test-subj="upgradeSnapshotButton" + > + {snapshotState.action === 'upgrade' && snapshotState.error + ? i18nTexts.retryUpgradeButtonLabel + : i18nTexts.upgradeButtonLabel} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlyoutFooter> + </EuiFlyout> + </EuiPortal> + ); +}; diff --git a/x-pack/plugins/infra/public/components/header/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/index.ts similarity index 83% rename from x-pack/plugins/infra/public/components/header/index.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/index.ts index 37156e1b6cacd..d537c94cf67ae 100644 --- a/x-pack/plugins/infra/public/components/header/index.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { Header } from './header'; +export { FixMlSnapshotsButton } from './button'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/use_snapshot_state.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/use_snapshot_state.tsx new file mode 100644 index 0000000000000..2dd4638c772b3 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/ml_snapshots/use_snapshot_state.tsx @@ -0,0 +1,151 @@ +/* + * 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 { useRef, useCallback, useState, useEffect } from 'react'; + +import { ApiService, ResponseError } from '../../../../lib/api'; + +const POLL_INTERVAL_MS = 1000; + +export interface SnapshotStatus { + snapshotId: string; + jobId: string; + status: 'complete' | 'in_progress' | 'error' | 'idle'; + action?: 'upgrade' | 'delete'; +} + +export const useSnapshotState = ({ + jobId, + snapshotId, + api, +}: { + jobId: string; + snapshotId: string; + api: ApiService; +}) => { + const [requestError, setRequestError] = useState<ResponseError | undefined>(undefined); + const [snapshotState, setSnapshotState] = useState<SnapshotStatus>({ + status: 'idle', + jobId, + snapshotId, + }); + + const pollIntervalIdRef = useRef<ReturnType<typeof setTimeout> | null>(null); + const isMounted = useRef(false); + + const clearPollInterval = useCallback(() => { + if (pollIntervalIdRef.current) { + clearTimeout(pollIntervalIdRef.current); + pollIntervalIdRef.current = null; + } + }, []); + + const updateSnapshotStatus = useCallback(async () => { + clearPollInterval(); + + const { data, error: updateStatusError } = await api.getMlSnapshotUpgradeStatus({ + jobId, + snapshotId, + }); + + if (updateStatusError) { + setSnapshotState({ + snapshotId, + jobId, + action: 'upgrade', + status: 'error', + }); + setRequestError(updateStatusError); + return; + } + + setSnapshotState(data); + + // Only keep polling if it exists and is in progress. + if (data?.status === 'in_progress') { + pollIntervalIdRef.current = setTimeout(updateSnapshotStatus, POLL_INTERVAL_MS); + } + }, [api, clearPollInterval, jobId, snapshotId]); + + const upgradeSnapshot = useCallback(async () => { + setSnapshotState({ + snapshotId, + jobId, + action: 'upgrade', + status: 'in_progress', + }); + + const { data, error: upgradeError } = await api.upgradeMlSnapshot({ jobId, snapshotId }); + + if (upgradeError) { + setRequestError(upgradeError); + setSnapshotState({ + snapshotId, + jobId, + action: 'upgrade', + status: 'error', + }); + return; + } + + setSnapshotState(data); + updateSnapshotStatus(); + }, [api, jobId, snapshotId, updateSnapshotStatus]); + + const deleteSnapshot = useCallback(async () => { + setSnapshotState({ + snapshotId, + jobId, + action: 'delete', + status: 'in_progress', + }); + + const { error: deleteError } = await api.deleteMlSnapshot({ + snapshotId, + jobId, + }); + + if (deleteError) { + setRequestError(deleteError); + setSnapshotState({ + snapshotId, + jobId, + action: 'delete', + status: 'error', + }); + return; + } + + setSnapshotState({ + snapshotId, + jobId, + action: 'delete', + status: 'complete', + }); + }, [api, jobId, snapshotId]); + + useEffect(() => { + isMounted.current = true; + + return () => { + isMounted.current = false; + + // Clean up on unmount. + clearPollInterval(); + }; + }, [clearPollInterval]); + + return { + snapshotState: { + ...snapshotState, + error: requestError, + }, + upgradeSnapshot, + updateSnapshotStatus, + deleteSnapshot, + }; +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/button.tsx index 34c1328459cdb..646f253931664 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/button.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/button.tsx @@ -14,11 +14,7 @@ import { EuiButton, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui' import { FormattedMessage } from '@kbn/i18n/react'; import { DocLinksStart, HttpSetup } from 'src/core/public'; import { API_BASE_PATH } from '../../../../../../common/constants'; -import { - EnrichedDeprecationInfo, - ReindexStatus, - UIReindexOption, -} from '../../../../../../common/types'; +import { ReindexAction, ReindexStatus, UIReindexOption } from '../../../../../../common/types'; import { LoadingState } from '../../../types'; import { ReindexFlyout } from './flyout'; import { ReindexPollingService, ReindexState } from './polling_service'; @@ -27,7 +23,7 @@ interface ReindexButtonProps { indexName: string; http: HttpSetup; docLinks: DocLinksStart; - reindexBlocker?: EnrichedDeprecationInfo['blockerForReindexing']; + reindexBlocker?: ReindexAction['blockerForReindexing']; } interface ReindexButtonState { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/container.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/container.tsx index 3e7b931452566..97031dd08ee2a 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/container.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/reindex/flyout/container.tsx @@ -19,7 +19,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { EnrichedDeprecationInfo, ReindexStatus } from '../../../../../../../common/types'; +import { ReindexAction, ReindexStatus } from '../../../../../../../common/types'; import { ReindexState } from '../polling_service'; import { ChecklistFlyoutStep } from './checklist_step'; @@ -37,7 +37,7 @@ interface ReindexFlyoutProps { startReindex: () => void; cancelReindex: () => void; docLinks: DocLinksStart; - reindexBlocker?: EnrichedDeprecationInfo['blockerForReindexing']; + reindexBlocker?: ReindexAction['blockerForReindexing']; } interface ReindexFlyoutState { diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts index 1c42c249e9d54..c4d9128baa56a 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts @@ -90,6 +90,38 @@ export class ApiService { return result; } + + public async upgradeMlSnapshot(body: { jobId: string; snapshotId: string }) { + const result = await this.sendRequest({ + path: `${API_BASE_PATH}/ml_snapshots`, + method: 'post', + body, + }); + + return result; + } + + public async deleteMlSnapshot({ jobId, snapshotId }: { jobId: string; snapshotId: string }) { + const result = await this.sendRequest({ + path: `${API_BASE_PATH}/ml_snapshots/${jobId}/${snapshotId}`, + method: 'delete', + }); + + return result; + } + + public async getMlSnapshotUpgradeStatus({ + jobId, + snapshotId, + }: { + jobId: string; + snapshotId: string; + }) { + return await this.sendRequest({ + path: `${API_BASE_PATH}/ml_snapshots/${jobId}/${snapshotId}`, + method: 'get', + }); + } } export const apiService = new ApiService(); diff --git a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts index 73e5d33e6c968..8cd9f8b6591e3 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts @@ -14,7 +14,6 @@ import { breadcrumbService } from './lib/breadcrumbs'; export async function mountManagementSection( coreSetup: CoreSetup, - isCloudEnabled: boolean, params: ManagementAppMountParams, kibanaVersionInfo: KibanaVersionContext, readonly: boolean @@ -31,7 +30,6 @@ export async function mountManagementSection( return renderApp({ element, - isCloudEnabled, http, i18n, docLinks, diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts index 4f5429201f304..4cffd40faf380 100644 --- a/x-pack/plugins/upgrade_assistant/public/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -9,19 +9,17 @@ import SemVer from 'semver/classes/semver'; import { i18n } from '@kbn/i18n'; import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/public'; -import { CloudSetup } from '../../cloud/public'; import { ManagementSetup } from '../../../../src/plugins/management/public'; import { Config } from '../common/config'; interface Dependencies { - cloud: CloudSetup; management: ManagementSetup; } export class UpgradeAssistantUIPlugin implements Plugin { constructor(private ctx: PluginInitializerContext) {} - setup(coreSetup: CoreSetup, { cloud, management }: Dependencies) { + setup(coreSetup: CoreSetup, { management }: Dependencies) { const { enabled, readonly } = this.ctx.config.get<Config>(); if (!enabled) { @@ -29,7 +27,6 @@ export class UpgradeAssistantUIPlugin implements Plugin { } const appRegistrar = management.sections.section.stack; - const isCloudEnabled = Boolean(cloud?.isCloudEnabled); const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version); const kibanaVersionInfo = { @@ -59,7 +56,6 @@ export class UpgradeAssistantUIPlugin implements Plugin { const { mountManagementSection } = await import('./application/mount_management_section'); const unmountAppCallback = await mountManagementSection( coreSetup, - isCloudEnabled, params, kibanaVersionInfo, readonly diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json index 10a5d39f5cece..2b8519d75cb2f 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json +++ b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json @@ -19,6 +19,12 @@ "message": "Datafeed [deprecation-datafeed] uses deprecated query options", "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes", "details": "[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]" + }, + { + "level": "critical", + "message": "model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded", + "url": "", + "details": "details" } ], "node_settings": [ @@ -46,6 +52,33 @@ "details": "[[type: tweet, field: liked]]" } ], + "old_index": [ + { + "level": "critical", + "message": "Index created before 7.0", + "url": + "https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html", + "details": "This index was created using version: 6.8.13" + } + ], + "closed_index": [ + { + "level": "critical", + "message": "Index created before 7.0", + "url": "https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html", + "details": "This index was created using version: 6.8.13" + } + ], + "deprecated_settings": [ + { + "level": "warning", + "message": "translog retention settings are ignored", + "url": + "https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html", + "details": + "translog retention settings [index.translog.retention.size] and [index.translog.retention.age] are ignored because translog is no longer used in peer recoveries with soft-deletes enabled (default in 7.0 or later)" + } + ], ".kibana": [ { "level": "warning", @@ -79,4 +112,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap index aefac2b4c63f6..a7890adf1f0eb 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap +++ b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap @@ -4,24 +4,39 @@ exports[`getUpgradeAssistantStatus returns the correct shape of data 1`] = ` Object { "cluster": Array [ Object { + "correctiveAction": undefined, "details": "templates using \`template\` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template", "level": "warning", "message": "Template patterns are no longer using \`template\` field, but \`index_patterns\` instead", "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", }, Object { + "correctiveAction": undefined, "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}", "level": "warning", "message": "one or more templates use deprecated mapping settings", "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", }, Object { + "correctiveAction": undefined, "details": "[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]", "level": "warning", "message": "Datafeed [deprecation-datafeed] uses deprecated query options", "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes", }, Object { + "correctiveAction": Object { + "jobId": "deprecation_check_job", + "snapshotId": "1", + "type": "mlSnapshot", + }, + "details": "details", + "level": "critical", + "message": "model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded", + "url": "", + }, + Object { + "correctiveAction": undefined, "details": "This node thing is wrong", "level": "critical", "message": "A node-level issue", @@ -30,63 +45,87 @@ Object { ], "indices": Array [ Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": Array [], + "correctiveAction": undefined, "details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]", "index": ".monitoring-es-6-2018.11.07", "level": "warning", "message": "Coercion of boolean fields", - "reindex": false, "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": Array [], + "correctiveAction": undefined, "details": "[[type: tweet, field: liked]]", "index": "twitter", "level": "warning", "message": "Coercion of boolean fields", - "reindex": false, "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": Array [], + "correctiveAction": Object { + "blockerForReindexing": undefined, + "type": "reindex", + }, + "details": "This index was created using version: 6.8.13", + "index": "old_index", + "level": "critical", + "message": "Index created before 7.0", + "url": "https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html", + }, + Object { + "correctiveAction": Object { + "blockerForReindexing": "index-closed", + "type": "reindex", + }, + "details": "This index was created using version: 6.8.13", + "index": "closed_index", + "level": "critical", + "message": "Index created before 7.0", + "url": "https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html", + }, + Object { + "correctiveAction": Object { + "deprecatedSettings": Array [ + "translog.retention.size", + "translog.retention.age", + ], + "type": "indexSetting", + }, + "details": "translog retention settings [index.translog.retention.size] and [index.translog.retention.age] are ignored because translog is no longer used in peer recoveries with soft-deletes enabled (default in 7.0 or later)", + "index": "deprecated_settings", + "level": "warning", + "message": "translog retention settings are ignored", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html", + }, + Object { + "correctiveAction": undefined, "details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]", "index": ".kibana", "level": "warning", "message": "Coercion of boolean fields", - "reindex": false, "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": Array [], + "correctiveAction": undefined, "details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]", "index": ".watcher-history-6-2018.11.07", "level": "warning", "message": "Coercion of boolean fields", - "reindex": false, "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": Array [], + "correctiveAction": undefined, "details": "[[type: doc, field: snapshot]]", "index": ".monitoring-kibana-6-2018.11.07", "level": "warning", "message": "Coercion of boolean fields", - "reindex": false, "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { - "blockerForReindexing": undefined, - "deprecatedIndexSettings": Array [], + "correctiveAction": undefined, "details": "[[type: tweet, field: liked]]", "index": "twitter2", "level": "warning", "message": "Coercion of boolean fields", - "reindex": false, "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, ], diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts index d78af9162e924..6477ce738c084 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts @@ -22,7 +22,13 @@ const asApiResponse = <T>(body: T): RequestEvent<T> => describe('getUpgradeAssistantStatus', () => { const resolvedIndices = { - indices: fakeIndexNames.map((f) => ({ name: f, attributes: ['open'] })), + indices: fakeIndexNames.map((indexName) => { + // mark one index as closed to test blockerForReindexing flag + if (indexName === 'closed_index') { + return { name: indexName, attributes: ['closed'] }; + } + return { name: indexName, attributes: ['open'] }; + }), }; // @ts-expect-error mock data is too loosely typed @@ -39,12 +45,12 @@ describe('getUpgradeAssistantStatus', () => { esClient.asCurrentUser.indices.resolveIndex.mockResolvedValue(asApiResponse(resolvedIndices)); it('calls /_migration/deprecations', async () => { - await getUpgradeAssistantStatus(esClient, false); + await getUpgradeAssistantStatus(esClient); expect(esClient.asCurrentUser.migration.deprecations).toHaveBeenCalled(); }); it('returns the correct shape of data', async () => { - const resp = await getUpgradeAssistantStatus(esClient, false); + const resp = await getUpgradeAssistantStatus(esClient); expect(resp).toMatchSnapshot(); }); @@ -59,7 +65,7 @@ describe('getUpgradeAssistantStatus', () => { }) ); - await expect(getUpgradeAssistantStatus(esClient, false)).resolves.toHaveProperty( + await expect(getUpgradeAssistantStatus(esClient)).resolves.toHaveProperty( 'readyForUpgrade', false ); @@ -76,32 +82,9 @@ describe('getUpgradeAssistantStatus', () => { }) ); - await expect(getUpgradeAssistantStatus(esClient, false)).resolves.toHaveProperty( + await expect(getUpgradeAssistantStatus(esClient)).resolves.toHaveProperty( 'readyForUpgrade', true ); }); - - it('filters out security realm deprecation on Cloud', async () => { - esClient.asCurrentUser.migration.deprecations.mockResolvedValue( - // @ts-expect-error not full interface - asApiResponse({ - cluster_settings: [ - { - level: 'critical', - message: 'Security realm settings structure changed', - url: 'https://...', - }, - ], - node_settings: [], - ml_settings: [], - index_settings: {}, - }) - ); - - const result = await getUpgradeAssistantStatus(esClient, true); - - expect(result).toHaveProperty('readyForUpgrade', true); - expect(result).toHaveProperty('cluster', []); - }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts index e775190d426df..85cde9069d60f 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts @@ -16,26 +16,12 @@ import { import { esIndicesStateCheck } from './es_indices_state_check'; export async function getUpgradeAssistantStatus( - dataClient: IScopedClusterClient, - isCloudEnabled: boolean + dataClient: IScopedClusterClient ): Promise<UpgradeAssistantStatus> { const { body: deprecations } = await dataClient.asCurrentUser.migration.deprecations(); - const cluster = getClusterDeprecations(deprecations, isCloudEnabled); - const indices = getCombinedIndexInfos(deprecations); - - const indexNames = indices.map(({ index }) => index!); - - // If we have found deprecation information for index/indices check whether the index is - // open or closed. - if (indexNames.length) { - const indexStates = await esIndicesStateCheck(dataClient.asCurrentUser, indexNames); - - indices.forEach((indexData) => { - indexData.blockerForReindexing = - indexStates[indexData.index!] === 'closed' ? 'index-closed' : undefined; - }); - } + const cluster = getClusterDeprecations(deprecations); + const indices = await getCombinedIndexInfos(deprecations, dataClient); const criticalWarnings = cluster.concat(indices).filter((d) => d.level === 'critical'); @@ -47,38 +33,91 @@ export async function getUpgradeAssistantStatus( } // Reformats the index deprecations to an array of deprecation warnings extended with an index field. -const getCombinedIndexInfos = (deprecations: DeprecationAPIResponse) => - Object.keys(deprecations.index_settings).reduce((indexDeprecations, indexName) => { - return indexDeprecations.concat( - deprecations.index_settings[indexName].map( - (d) => - ({ - ...d, - index: indexName, - reindex: /Index created before/.test(d.message), - deprecatedIndexSettings: getIndexSettingDeprecations(d.message), - } as EnrichedDeprecationInfo) - ) - ); - }, [] as EnrichedDeprecationInfo[]); - -const getClusterDeprecations = (deprecations: DeprecationAPIResponse, isCloudEnabled: boolean) => { - const combined = deprecations.cluster_settings +const getCombinedIndexInfos = async ( + deprecations: DeprecationAPIResponse, + dataClient: IScopedClusterClient +) => { + const indices = Object.keys(deprecations.index_settings).reduce( + (indexDeprecations, indexName) => { + return indexDeprecations.concat( + deprecations.index_settings[indexName].map( + (d) => + ({ + ...d, + index: indexName, + correctiveAction: getCorrectiveAction(d.message), + } as EnrichedDeprecationInfo) + ) + ); + }, + [] as EnrichedDeprecationInfo[] + ); + + const indexNames = indices.map(({ index }) => index!); + + // If we have found deprecation information for index/indices + // check whether the index is open or closed. + if (indexNames.length) { + const indexStates = await esIndicesStateCheck(dataClient.asCurrentUser, indexNames); + + indices.forEach((indexData) => { + if (indexData.correctiveAction?.type === 'reindex') { + indexData.correctiveAction.blockerForReindexing = + indexStates[indexData.index!] === 'closed' ? 'index-closed' : undefined; + } + }); + } + return indices as EnrichedDeprecationInfo[]; +}; + +const getClusterDeprecations = (deprecations: DeprecationAPIResponse) => { + const combinedDeprecations = deprecations.cluster_settings .concat(deprecations.ml_settings) .concat(deprecations.node_settings); - if (isCloudEnabled) { - // In Cloud, this is changed at upgrade time. Filter it out to improve upgrade UX. - return combined.filter((d) => d.message !== 'Security realm settings structure changed'); - } else { - return combined; - } + return combinedDeprecations.map((deprecation) => { + return { + ...deprecation, + correctiveAction: getCorrectiveAction(deprecation.message), + }; + }) as EnrichedDeprecationInfo[]; }; -const getIndexSettingDeprecations = (message: string) => { - const indexDeprecation = Object.values(indexSettingDeprecations).find( +const getCorrectiveAction = (message: string) => { + const indexSettingDeprecation = Object.values(indexSettingDeprecations).find( ({ deprecationMessage }) => deprecationMessage === message ); + const requiresReindexAction = /Index created before/.test(message); + const requiresIndexSettingsAction = Boolean(indexSettingDeprecation); + const requiresMlAction = /model snapshot/.test(message); + + if (requiresReindexAction) { + return { + type: 'reindex', + }; + } + + if (requiresIndexSettingsAction) { + return { + type: 'indexSetting', + deprecatedSettings: indexSettingDeprecation!.settings, + }; + } + + if (requiresMlAction) { + // This logic is brittle, as we are expecting the message to be in a particular format to extract the snapshot ID and job ID + // Implementing https://github.com/elastic/elasticsearch/issues/73089 in ES should address this concern + const regex = /(?<=\[).*?(?=\])/g; + const matches = message.match(regex); + + if (matches?.length === 2) { + return { + type: 'mlSnapshot', + snapshotId: matches[0], + jobId: matches[1], + }; + } + } - return indexDeprecation?.settings || []; + return undefined; }; diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index ae5975c2bc8a7..50b7330b4d466 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -17,7 +17,6 @@ import { SavedObjectsServiceStart, } from '../../../../src/core/server'; -import { CloudSetup } from '../../cloud/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; @@ -25,12 +24,13 @@ import { CredentialStore, credentialStoreFactory } from './lib/reindexing/creden import { ReindexWorker } from './lib/reindexing'; import { registerUpgradeAssistantUsageCollector } from './lib/telemetry'; import { versionService } from './lib/version'; -import { registerClusterCheckupRoutes } from './routes/cluster_checkup'; -import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging'; -import { registerReindexIndicesRoutes, createReindexWorker } from './routes/reindex_indices'; -import { registerTelemetryRoutes } from './routes/telemetry'; -import { registerUpdateSettingsRoute } from './routes/update_index_settings'; -import { telemetrySavedObjectType, reindexOperationSavedObjectType } from './saved_object_types'; +import { createReindexWorker } from './routes/reindex_indices'; +import { registerRoutes } from './routes/register_routes'; +import { + telemetrySavedObjectType, + reindexOperationSavedObjectType, + mlSavedObjectType, +} from './saved_object_types'; import { RouteDependencies } from './types'; @@ -38,7 +38,6 @@ interface PluginsSetup { usageCollection: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetup; - cloud?: CloudSetup; } export class UpgradeAssistantServerPlugin implements Plugin { @@ -68,12 +67,13 @@ export class UpgradeAssistantServerPlugin implements Plugin { setup( { http, getStartServices, capabilities, savedObjects }: CoreSetup, - { usageCollection, cloud, features, licensing }: PluginsSetup + { usageCollection, features, licensing }: PluginsSetup ) { this.licensing = licensing; savedObjects.registerType(reindexOperationSavedObjectType); savedObjects.registerType(telemetrySavedObjectType); + savedObjects.registerType(mlSavedObjectType); features.registerElasticsearchFeature({ id: 'upgrade_assistant', @@ -91,7 +91,6 @@ export class UpgradeAssistantServerPlugin implements Plugin { const router = http.createRouter(); const dependencies: RouteDependencies = { - cloud, router, credentialStore: this.credentialStore, log: this.logger, @@ -107,12 +106,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { // Initialize version service with current kibana version versionService.setup(this.kibanaVersion); - registerClusterCheckupRoutes(dependencies); - registerDeprecationLoggingRoutes(dependencies); - registerReindexIndicesRoutes(dependencies, this.getWorker.bind(this)); - // Bootstrap the needed routes and the collector for the telemetry - registerTelemetryRoutes(dependencies); - registerUpdateSettingsRoute(dependencies); + registerRoutes(dependencies, this.getWorker.bind(this)); if (usageCollection) { getStartServices().then(([{ savedObjects: savedObjectsService, elasticsearch }]) => { diff --git a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts index 3aabae87c06b1..09da52e4b6ffd 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts @@ -49,6 +49,7 @@ export const createMockRouter = () => { post: assign('post'), put: assign('put'), patch: assign('patch'), + delete: assign('delete'), }; }; diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts index a5da4741b10eb..934fdb1c4eb37 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts @@ -32,9 +32,6 @@ describe('cluster checkup API', () => { beforeEach(() => { mockRouter = createMockRouter(); routeDependencies = { - cloud: { - isCloudEnabled: true, - }, router: mockRouter, }; registerClusterCheckupRoutes(routeDependencies); @@ -44,24 +41,6 @@ describe('cluster checkup API', () => { jest.resetAllMocks(); }); - describe('with cloud enabled', () => { - it('is provided to getUpgradeAssistantStatus', async () => { - const spy = jest.spyOn(MigrationApis, 'getUpgradeAssistantStatus'); - - MigrationApis.getUpgradeAssistantStatus.mockResolvedValue({ - cluster: [], - indices: [], - nodes: [], - }); - - await routeDependencies.router.getHandler({ - method: 'get', - pathPattern: '/api/upgrade_assistant/status', - })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); - expect(spy.mock.calls[0][1]).toBe(true); - }); - }); - describe('GET /api/upgrade_assistant/reindex/{indexName}.json', () => { it('returns state', async () => { MigrationApis.getUpgradeAssistantStatus.mockResolvedValue({ diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts index fe5b9baef6c8d..31026be55fa30 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts @@ -12,9 +12,7 @@ import { RouteDependencies } from '../types'; import { reindexActionsFactory } from '../lib/reindexing/reindex_actions'; import { reindexServiceFactory } from '../lib/reindexing'; -export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: RouteDependencies) { - const isCloudEnabled = Boolean(cloud?.isCloudEnabled); - +export function registerClusterCheckupRoutes({ router, licensing, log }: RouteDependencies) { router.get( { path: `${API_BASE_PATH}/status`, @@ -32,7 +30,7 @@ export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: response ) => { try { - const status = await getUpgradeAssistantStatus(client, isCloudEnabled); + const status = await getUpgradeAssistantStatus(client); const asCurrentUser = client.asCurrentUser; const reindexActions = reindexActionsFactory(savedObjectsClient, asCurrentUser); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts new file mode 100644 index 0000000000000..741f704adac90 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts @@ -0,0 +1,365 @@ +/* + * 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 { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock'; +import { createRequestMock } from './__mocks__/request.mock'; +import { registerMlSnapshotRoutes } from './ml_snapshots'; + +jest.mock('../lib/es_version_precheck', () => ({ + versionCheckHandlerWrapper: <P, Q, B>(handler: RequestHandler<P, Q, B>) => handler, +})); + +const JOB_ID = 'job_id'; +const SNAPSHOT_ID = 'snapshot_id'; +const NODE_ID = 'node_id'; + +describe('ML snapshots APIs', () => { + let mockRouter: MockRouter; + let routeDependencies: any; + + beforeEach(() => { + mockRouter = createMockRouter(); + routeDependencies = { + router: mockRouter, + }; + registerMlSnapshotRoutes(routeDependencies); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('POST /api/upgrade_assistant/ml_snapshots', () => { + it('returns 200 status and in_progress status', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .upgradeJobSnapshot as jest.Mock).mockResolvedValue({ + body: { + node: NODE_ID, + completed: false, + }, + }); + + const resp = await routeDependencies.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/ml_snapshots', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + snapshotId: SNAPSHOT_ID, + jobId: JOB_ID, + }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + jobId: JOB_ID, + nodeId: NODE_ID, + snapshotId: SNAPSHOT_ID, + status: 'in_progress', + }); + }); + + it('returns 200 status and complete status', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .upgradeJobSnapshot as jest.Mock).mockResolvedValue({ + body: { + node: NODE_ID, + completed: true, + }, + }); + + const resp = await routeDependencies.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/ml_snapshots', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + snapshotId: SNAPSHOT_ID, + jobId: JOB_ID, + }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + jobId: JOB_ID, + nodeId: NODE_ID, + snapshotId: SNAPSHOT_ID, + status: 'complete', + }); + }); + + it('returns an error if it throws', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .upgradeJobSnapshot as jest.Mock).mockRejectedValue(new Error('scary error!')); + await expect( + routeDependencies.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/ml_snapshots', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + snapshotId: SNAPSHOT_ID, + jobId: JOB_ID, + }, + }), + kibanaResponseFactory + ) + ).rejects.toThrow('scary error!'); + }); + }); + + describe('DELETE /api/upgrade_assistant/ml_snapshots/:jobId/:snapshotId', () => { + it('returns 200 status', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .deleteModelSnapshot as jest.Mock).mockResolvedValue({ + body: { acknowledged: true }, + }); + + const resp = await routeDependencies.router.getHandler({ + method: 'delete', + pathPattern: '/api/upgrade_assistant/ml_snapshots/{jobId}/{snapshotId}', + })( + routeHandlerContextMock, + createRequestMock({ + params: { snapshotId: 'snapshot_id1', jobId: 'job_id1' }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + acknowledged: true, + }); + }); + + it('returns an error if it throws', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .deleteModelSnapshot as jest.Mock).mockRejectedValue(new Error('scary error!')); + await expect( + routeDependencies.router.getHandler({ + method: 'delete', + pathPattern: '/api/upgrade_assistant/ml_snapshots/{jobId}/{snapshotId}', + })( + routeHandlerContextMock, + createRequestMock({ + params: { snapshotId: 'snapshot_id1', jobId: 'job_id1' }, + }), + kibanaResponseFactory + ) + ).rejects.toThrow('scary error!'); + }); + }); + + describe('GET /api/upgrade_assistant/ml_snapshots/:jobId/:snapshotId', () => { + it('returns "idle" status if saved object does not exist', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .getModelSnapshots as jest.Mock).mockResolvedValue({ + body: { + count: 1, + model_snapshots: [ + { + job_id: JOB_ID, + min_version: '6.4.0', + timestamp: 1575402237000, + description: 'State persisted due to job close at 2019-12-03T19:43:57+0000', + snapshot_id: SNAPSHOT_ID, + snapshot_doc_count: 1, + model_size_stats: {}, + latest_record_time_stamp: 1576971072000, + latest_result_time_stamp: 1576965600000, + retain: false, + }, + ], + }, + }); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/ml_snapshots/{jobId}/{snapshotId}', + })( + routeHandlerContextMock, + createRequestMock({ + params: { + snapshotId: SNAPSHOT_ID, + jobId: JOB_ID, + }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + jobId: JOB_ID, + nodeId: undefined, + snapshotId: SNAPSHOT_ID, + status: 'idle', + }); + }); + + it('returns "in_progress" status if snapshot upgrade is in progress', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .getModelSnapshots as jest.Mock).mockResolvedValue({ + body: { + count: 1, + model_snapshots: [ + { + job_id: JOB_ID, + min_version: '6.4.0', + timestamp: 1575402237000, + description: 'State persisted due to job close at 2019-12-03T19:43:57+0000', + snapshot_id: SNAPSHOT_ID, + snapshot_doc_count: 1, + model_size_stats: {}, + latest_record_time_stamp: 1576971072000, + latest_result_time_stamp: 1576965600000, + retain: false, + }, + ], + }, + }); + + (routeHandlerContextMock.core.savedObjects.client.find as jest.Mock).mockResolvedValue({ + total: 1, + saved_objects: [ + { + attributes: { + nodeId: NODE_ID, + jobId: JOB_ID, + snapshotId: SNAPSHOT_ID, + }, + }, + ], + }); + + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.tasks + .list as jest.Mock).mockResolvedValue({ + body: { + nodes: { + [NODE_ID]: { + tasks: { + [`${NODE_ID}:12345`]: { + description: `job-snapshot-upgrade-${JOB_ID}-${SNAPSHOT_ID}`, + }, + }, + }, + }, + }, + }); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/ml_snapshots/{jobId}/{snapshotId}', + })( + routeHandlerContextMock, + createRequestMock({ + params: { + snapshotId: SNAPSHOT_ID, + jobId: JOB_ID, + }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + jobId: JOB_ID, + nodeId: NODE_ID, + snapshotId: SNAPSHOT_ID, + status: 'in_progress', + }); + }); + + it('returns "complete" status if snapshot upgrade has completed', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .getModelSnapshots as jest.Mock).mockResolvedValue({ + body: { + count: 1, + model_snapshots: [ + { + job_id: JOB_ID, + min_version: '6.4.0', + timestamp: 1575402237000, + description: 'State persisted due to job close at 2019-12-03T19:43:57+0000', + snapshot_id: SNAPSHOT_ID, + snapshot_doc_count: 1, + model_size_stats: {}, + latest_record_time_stamp: 1576971072000, + latest_result_time_stamp: 1576965600000, + retain: false, + }, + ], + }, + }); + + (routeHandlerContextMock.core.savedObjects.client.find as jest.Mock).mockResolvedValue({ + total: 1, + saved_objects: [ + { + attributes: { + nodeId: NODE_ID, + jobId: JOB_ID, + snapshotId: SNAPSHOT_ID, + }, + }, + ], + }); + + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.tasks + .list as jest.Mock).mockResolvedValue({ + body: { + nodes: { + [NODE_ID]: { + tasks: {}, + }, + }, + }, + }); + + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.migration + .deprecations as jest.Mock).mockResolvedValue({ + body: { + cluster_settings: [], + ml_settings: [], + node_settings: [], + index_settings: {}, + }, + }); + + (routeHandlerContextMock.core.savedObjects.client.delete as jest.Mock).mockResolvedValue({}); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/ml_snapshots/{jobId}/{snapshotId}', + })( + routeHandlerContextMock, + createRequestMock({ + params: { + snapshotId: SNAPSHOT_ID, + jobId: JOB_ID, + }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + jobId: JOB_ID, + nodeId: NODE_ID, + snapshotId: SNAPSHOT_ID, + status: 'complete', + }); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts new file mode 100644 index 0000000000000..80f5f2eb60e09 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts @@ -0,0 +1,348 @@ +/* + * 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 { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { schema } from '@kbn/config-schema'; +import { IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; +import { API_BASE_PATH } from '../../common/constants'; +import { MlOperation, ML_UPGRADE_OP_TYPE } from '../../common/types'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; +import { handleEsError } from '../shared_imports'; +import { RouteDependencies } from '../types'; + +const findMlOperation = async ( + savedObjectsClient: SavedObjectsClientContract, + snapshotId: string +) => { + return savedObjectsClient.find<MlOperation>({ + type: ML_UPGRADE_OP_TYPE, + search: `"${snapshotId}"`, + searchFields: ['snapshotId'], + }); +}; + +const createMlOperation = async ( + savedObjectsClient: SavedObjectsClientContract, + attributes: MlOperation +) => { + const foundSnapshots = await findMlOperation(savedObjectsClient, attributes.snapshotId); + + if (foundSnapshots?.total > 0) { + throw new Error(`A ML operation is already in progress for snapshot: ${attributes.snapshotId}`); + } + + return savedObjectsClient.create<MlOperation>(ML_UPGRADE_OP_TYPE, attributes); +}; + +const deleteMlOperation = (savedObjectsClient: SavedObjectsClientContract, id: string) => { + return savedObjectsClient.delete(ML_UPGRADE_OP_TYPE, id); +}; + +/* + * The tasks API can only tell us if the snapshot upgrade is in progress. + * We cannot rely on it to determine if a snapshot was upgraded successfully. + * If the task does not exist, it can mean one of two things: + * 1. The snapshot was upgraded successfully. + * 2. There was a failure upgrading the snapshot. + * In order to verify it was successful, we need to recheck the deprecation info API + * and verify the deprecation no longer exists. If it still exists, we assume there was a failure. + */ +const verifySnapshotUpgrade = async ( + esClient: IScopedClusterClient, + snapshot: { snapshotId: string; jobId: string } +): Promise<{ + isSuccessful: boolean; + error?: ResponseError; +}> => { + const { snapshotId, jobId } = snapshot; + + try { + const { body: deprecations } = await esClient.asCurrentUser.migration.deprecations(); + + const mlSnapshotDeprecations = deprecations.ml_settings.filter((deprecation) => { + return /model snapshot/.test(deprecation.message); + }); + + // If there are no ML deprecations, we assume the deprecation was resolved successfully + if (typeof mlSnapshotDeprecations === 'undefined' || mlSnapshotDeprecations.length === 0) { + return { + isSuccessful: true, + }; + } + + const isSuccessful = Boolean( + mlSnapshotDeprecations.find((snapshotDeprecation) => { + const regex = /(?<=\[).*?(?=\])/g; + const matches = snapshotDeprecation.message.match(regex); + + if (matches?.length === 2) { + // If there is no matching snapshot, we assume the deprecation was resolved successfully + return matches[0] === snapshotId && matches[1] === jobId ? false : true; + } + + return false; + }) + ); + + return { + isSuccessful, + }; + } catch (e) { + return { + isSuccessful: false, + error: e, + }; + } +}; + +export function registerMlSnapshotRoutes({ router }: RouteDependencies) { + // Upgrade ML model snapshot + router.post( + { + path: `${API_BASE_PATH}/ml_snapshots`, + validate: { + body: schema.object({ + snapshotId: schema.string(), + jobId: schema.string(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + savedObjects: { client: savedObjectsClient }, + elasticsearch: { client: esClient }, + }, + }, + request, + response + ) => { + try { + const { snapshotId, jobId } = request.body; + + const { body } = await esClient.asCurrentUser.ml.upgradeJobSnapshot({ + job_id: jobId, + snapshot_id: snapshotId, + }); + + const snapshotInfo: MlOperation = { + nodeId: body.node, + snapshotId, + jobId, + }; + + // Store snapshot in saved object if upgrade not complete + if (body.completed !== true) { + await createMlOperation(savedObjectsClient, snapshotInfo); + } + + return response.ok({ + body: { + ...snapshotInfo, + status: body.completed === true ? 'complete' : 'in_progress', + }, + }); + } catch (e) { + return handleEsError({ error: e, response }); + } + } + ) + ); + + // Get the status of the upgrade snapshot task + router.get( + { + path: `${API_BASE_PATH}/ml_snapshots/{jobId}/{snapshotId}`, + validate: { + params: schema.object({ + snapshotId: schema.string(), + jobId: schema.string(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + savedObjects: { client: savedObjectsClient }, + elasticsearch: { client: esClient }, + }, + }, + request, + response + ) => { + try { + const { snapshotId, jobId } = request.params; + + // Verify snapshot exists + await esClient.asCurrentUser.ml.getModelSnapshots({ + job_id: jobId, + snapshot_id: snapshotId, + }); + + const foundSnapshots = await findMlOperation(savedObjectsClient, snapshotId); + + // If snapshot is *not* found in SO, assume there has not been an upgrade operation started + if (typeof foundSnapshots === 'undefined' || foundSnapshots.total === 0) { + return response.ok({ + body: { + snapshotId, + jobId, + nodeId: undefined, + status: 'idle', + }, + }); + } + + const snapshotOp = foundSnapshots.saved_objects[0]; + const { nodeId } = snapshotOp.attributes; + + // Now that we have the node ID, check the upgrade snapshot task progress + const { body: taskResponse } = await esClient.asCurrentUser.tasks.list({ + nodes: [nodeId], + actions: 'xpack/ml/job/snapshot/upgrade', + detailed: true, // necessary in order to filter if there are more than 1 snapshot upgrades in progress + }); + + const nodeTaskInfo = taskResponse?.nodes && taskResponse!.nodes[nodeId]; + const snapshotInfo: MlOperation = { + ...snapshotOp.attributes, + }; + + if (nodeTaskInfo) { + // Find the correct snapshot task ID based on the task description + const snapshotTaskId = Object.keys(nodeTaskInfo.tasks).find((task) => { + // The description is in the format of "job-snapshot-upgrade-<job_id>-<snapshot_id>" + const taskDescription = nodeTaskInfo.tasks[task].description; + const taskSnapshotAndJobIds = taskDescription!.replace('job-snapshot-upgrade-', ''); + const taskSnapshotAndJobIdParts = taskSnapshotAndJobIds.split('-'); + const taskSnapshotId = + taskSnapshotAndJobIdParts[taskSnapshotAndJobIdParts.length - 1]; + const taskJobId = taskSnapshotAndJobIdParts.slice(0, 1).join('-'); + + return taskSnapshotId === snapshotId && taskJobId === jobId; + }); + + // If the snapshot task exists, assume the upgrade is in progress + if (snapshotTaskId && nodeTaskInfo.tasks[snapshotTaskId]) { + return response.ok({ + body: { + ...snapshotInfo, + status: 'in_progress', + }, + }); + } else { + // The task ID was not found; verify the deprecation was resolved + const { + isSuccessful: isSnapshotDeprecationResolved, + error: upgradeSnapshotError, + } = await verifySnapshotUpgrade(esClient, { + snapshotId, + jobId, + }); + + // Delete the SO; if it's complete, no need to store it anymore. If there's an error, this will give the user a chance to retry + await deleteMlOperation(savedObjectsClient, snapshotOp.id); + + if (isSnapshotDeprecationResolved) { + return response.ok({ + body: { + ...snapshotInfo, + status: 'complete', + }, + }); + } + + return response.customError({ + statusCode: upgradeSnapshotError ? upgradeSnapshotError.statusCode : 500, + body: { + message: + upgradeSnapshotError?.body?.error?.reason || + 'There was an error upgrading your snapshot. Check the Elasticsearch logs for more details.', + }, + }); + } + } else { + // No tasks found; verify the deprecation was resolved + const { + isSuccessful: isSnapshotDeprecationResolved, + error: upgradeSnapshotError, + } = await verifySnapshotUpgrade(esClient, { + snapshotId, + jobId, + }); + + // Delete the SO; if it's complete, no need to store it anymore. If there's an error, this will give the user a chance to retry + await deleteMlOperation(savedObjectsClient, snapshotOp.id); + + if (isSnapshotDeprecationResolved) { + return response.ok({ + body: { + ...snapshotInfo, + status: 'complete', + }, + }); + } + + return response.customError({ + statusCode: upgradeSnapshotError ? upgradeSnapshotError.statusCode : 500, + body: { + message: + upgradeSnapshotError?.body?.error?.reason || + 'There was an error upgrading your snapshot. Check the Elasticsearch logs for more details.', + }, + }); + } + } catch (e) { + return handleEsError({ error: e, response }); + } + } + ) + ); + + // Delete ML model snapshot + router.delete( + { + path: `${API_BASE_PATH}/ml_snapshots/{jobId}/{snapshotId}`, + validate: { + params: schema.object({ + snapshotId: schema.string(), + jobId: schema.string(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + elasticsearch: { client }, + }, + }, + request, + response + ) => { + try { + const { snapshotId, jobId } = request.params; + + const { + body: deleteSnapshotResponse, + } = await client.asCurrentUser.ml.deleteModelSnapshot({ + job_id: jobId, + snapshot_id: snapshotId, + }); + + return response.ok({ + body: deleteSnapshotResponse, + }); + } catch (e) { + return handleEsError({ error: e, response }); + } + } + ) + ); +} diff --git a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts new file mode 100644 index 0000000000000..50cb9257462b9 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts @@ -0,0 +1,25 @@ +/* + * 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 { RouteDependencies } from '../types'; + +import { registerClusterCheckupRoutes } from './cluster_checkup'; +import { registerDeprecationLoggingRoutes } from './deprecation_logging'; +import { registerReindexIndicesRoutes } from './reindex_indices'; +import { registerTelemetryRoutes } from './telemetry'; +import { registerUpdateSettingsRoute } from './update_index_settings'; +import { registerMlSnapshotRoutes } from './ml_snapshots'; +import { ReindexWorker } from '../lib/reindexing'; + +export function registerRoutes(dependencies: RouteDependencies, getWorker: () => ReindexWorker) { + registerClusterCheckupRoutes(dependencies); + registerDeprecationLoggingRoutes(dependencies); + registerReindexIndicesRoutes(dependencies, getWorker); + registerTelemetryRoutes(dependencies); + registerUpdateSettingsRoute(dependencies); + registerMlSnapshotRoutes(dependencies); +} diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts index 91779bd4224b8..e394cac5100f9 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts @@ -7,3 +7,4 @@ export { reindexOperationSavedObjectType } from './reindex_operation_saved_object_type'; export { telemetrySavedObjectType } from './telemetry_saved_object_type'; +export { mlSavedObjectType } from './ml_upgrade_operation_saved_object_type'; diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/ml_upgrade_operation_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/ml_upgrade_operation_saved_object_type.ts new file mode 100644 index 0000000000000..6dc70fab1203f --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/ml_upgrade_operation_saved_object_type.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsType } from 'src/core/server'; + +import { ML_UPGRADE_OP_TYPE } from '../../common/types'; + +export const mlSavedObjectType: SavedObjectsType = { + name: ML_UPGRADE_OP_TYPE, + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + nodeId: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + snapshotId: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + jobId: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + status: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, +}; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/index.ts b/x-pack/plugins/upgrade_assistant/server/shared_imports.ts similarity index 76% rename from x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/index.ts rename to x-pack/plugins/upgrade_assistant/server/shared_imports.ts index 94203c2b156af..7f55d189457c7 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/experimental_badge/index.ts +++ b/x-pack/plugins/upgrade_assistant/server/shared_imports.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { ExperimentalBadge } from './experimental_badge'; +export { handleEsError } from '../../../../src/plugins/es_ui_shared/server'; diff --git a/x-pack/plugins/upgrade_assistant/server/types.ts b/x-pack/plugins/upgrade_assistant/server/types.ts index 80c60e3f310bc..b25b73070e4cf 100644 --- a/x-pack/plugins/upgrade_assistant/server/types.ts +++ b/x-pack/plugins/upgrade_assistant/server/types.ts @@ -6,7 +6,6 @@ */ import { IRouter, Logger, SavedObjectsServiceStart } from 'src/core/server'; -import { CloudSetup } from '../../cloud/server'; import { CredentialStore } from './lib/reindexing/credential_store'; import { LicensingPluginSetup } from '../../licensing/server'; @@ -16,5 +15,4 @@ export interface RouteDependencies { log: Logger; getSavedObjectsService: () => SavedObjectsServiceStart; licensing: LicensingPluginSetup; - cloud?: CloudSetup; } diff --git a/x-pack/plugins/upgrade_assistant/tsconfig.json b/x-pack/plugins/upgrade_assistant/tsconfig.json index 6303b06c0d899..750bea75c6656 100644 --- a/x-pack/plugins/upgrade_assistant/tsconfig.json +++ b/x-pack/plugins/upgrade_assistant/tsconfig.json @@ -20,8 +20,8 @@ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/management/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, - { "path": "../cloud/tsconfig.json" }, { "path": "../features/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, + { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, ] } diff --git a/x-pack/plugins/uptime/kibana.json b/x-pack/plugins/uptime/kibana.json index 625406c0cb97b..5aa3c80610d03 100644 --- a/x-pack/plugins/uptime/kibana.json +++ b/x-pack/plugins/uptime/kibana.json @@ -15,7 +15,7 @@ "server": true, "ui": true, "version": "8.0.0", - "requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml"], + "requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml", "fleet"], "owner": { "name": "Uptime", "githubTeam": "uptime" diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 63d32948388d7..869ecda3d29cf 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -42,6 +42,7 @@ import { LazySyntheticsPolicyCreateExtension, LazySyntheticsPolicyEditExtension, } from '../components/fleet_package'; +import { LazySyntheticsCustomAssetsExtension } from '../components/fleet_package/lazy_synthetics_custom_assets_extension'; export interface ClientPluginsSetup { data: DataPublicPluginSetup; @@ -196,13 +197,19 @@ export class UptimePlugin registerExtension({ package: 'synthetics', view: 'package-policy-create', - component: LazySyntheticsPolicyCreateExtension, + Component: LazySyntheticsPolicyCreateExtension, }); registerExtension({ package: 'synthetics', view: 'package-policy-edit', - component: LazySyntheticsPolicyEditExtension, + Component: LazySyntheticsPolicyEditExtension, + }); + + registerExtension({ + package: 'synthetics', + view: 'package-detail-assets', + Component: LazySyntheticsCustomAssetsExtension, }); } } diff --git a/x-pack/plugins/uptime/public/components/common/higher_order/__snapshots__/responsive_wrapper.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/higher_order/__snapshots__/responsive_wrapper.test.tsx.snap deleted file mode 100644 index 65b6d7cc39e55..0000000000000 --- a/x-pack/plugins/uptime/public/components/common/higher_order/__snapshots__/responsive_wrapper.test.tsx.snap +++ /dev/null @@ -1,221 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResponsiveWrapper HOC is not responsive when prop is false 1`] = ` -<EuiPanel - paddingSize="m" -> - <Component - intl={ - Object { - "defaultFormats": Object {}, - "defaultLocale": "en", - "formatDate": [Function], - "formatHTMLMessage": [Function], - "formatMessage": [Function], - "formatNumber": [Function], - "formatPlural": [Function], - "formatRelative": [Function], - "formatTime": [Function], - "formats": Object { - "date": Object { - "full": Object { - "day": "numeric", - "month": "long", - "weekday": "long", - "year": "numeric", - }, - "long": Object { - "day": "numeric", - "month": "long", - "year": "numeric", - }, - "medium": Object { - "day": "numeric", - "month": "short", - "year": "numeric", - }, - "short": Object { - "day": "numeric", - "month": "numeric", - "year": "2-digit", - }, - }, - "number": Object { - "currency": Object { - "style": "currency", - }, - "percent": Object { - "style": "percent", - }, - }, - "relative": Object { - "days": Object { - "units": "day", - }, - "hours": Object { - "units": "hour", - }, - "minutes": Object { - "units": "minute", - }, - "months": Object { - "units": "month", - }, - "seconds": Object { - "units": "second", - }, - "years": Object { - "units": "year", - }, - }, - "time": Object { - "full": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "long": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "medium": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - }, - "short": Object { - "hour": "numeric", - "minute": "numeric", - }, - }, - }, - "formatters": Object { - "getDateTimeFormat": [Function], - "getMessageFormat": [Function], - "getNumberFormat": [Function], - "getPluralFormat": [Function], - "getRelativeFormat": [Function], - }, - "locale": "en", - "messages": Object {}, - "now": [Function], - "onError": [Function], - "textComponent": Symbol(react.fragment), - "timeZone": null, - } - } - /> -</EuiPanel> -`; - -exports[`ResponsiveWrapper HOC renders a responsive wrapper 1`] = ` -<styled.div> - <Component - intl={ - Object { - "defaultFormats": Object {}, - "defaultLocale": "en", - "formatDate": [Function], - "formatHTMLMessage": [Function], - "formatMessage": [Function], - "formatNumber": [Function], - "formatPlural": [Function], - "formatRelative": [Function], - "formatTime": [Function], - "formats": Object { - "date": Object { - "full": Object { - "day": "numeric", - "month": "long", - "weekday": "long", - "year": "numeric", - }, - "long": Object { - "day": "numeric", - "month": "long", - "year": "numeric", - }, - "medium": Object { - "day": "numeric", - "month": "short", - "year": "numeric", - }, - "short": Object { - "day": "numeric", - "month": "numeric", - "year": "2-digit", - }, - }, - "number": Object { - "currency": Object { - "style": "currency", - }, - "percent": Object { - "style": "percent", - }, - }, - "relative": Object { - "days": Object { - "units": "day", - }, - "hours": Object { - "units": "hour", - }, - "minutes": Object { - "units": "minute", - }, - "months": Object { - "units": "month", - }, - "seconds": Object { - "units": "second", - }, - "years": Object { - "units": "year", - }, - }, - "time": Object { - "full": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "long": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "medium": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - }, - "short": Object { - "hour": "numeric", - "minute": "numeric", - }, - }, - }, - "formatters": Object { - "getDateTimeFormat": [Function], - "getMessageFormat": [Function], - "getNumberFormat": [Function], - "getPluralFormat": [Function], - "getRelativeFormat": [Function], - }, - "locale": "en", - "messages": Object {}, - "now": [Function], - "onError": [Function], - "textComponent": Symbol(react.fragment), - "timeZone": null, - } - } - /> -</styled.div> -`; diff --git a/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.test.tsx index 5a3dca171b206..db254fcb56081 100644 --- a/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { render } from '../../../lib/helper/rtl_helpers'; import { withResponsiveWrapper } from './responsive_wrapper'; interface Prop { @@ -20,12 +20,12 @@ describe('ResponsiveWrapper HOC', () => { }); it('renders a responsive wrapper', () => { - const component = shallowWithIntl(<WrappedByHOC isResponsive={true} />); - expect(component).toMatchSnapshot(); + const { getByTestId } = render(<WrappedByHOC isResponsive={true} />); + expect(getByTestId('uptimeWithResponsiveWrapper--wrapper')).toBeInTheDocument(); }); it('is not responsive when prop is false', () => { - const component = shallowWithIntl(<WrappedByHOC isResponsive={false} />); - expect(component).toMatchSnapshot(); + const { getByTestId } = render(<WrappedByHOC isResponsive={false} />); + expect(getByTestId('uptimeWithResponsiveWrapper--panel')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx b/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx index 6802682db5f56..0e33cc3e38f03 100644 --- a/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx @@ -32,11 +32,11 @@ export const withResponsiveWrapper = <P extends {} & ResponsiveWrapperProps>( Component: FC<P> ): FC<ResponsiveWrapperProps & P> => ({ isResponsive, ...rest }: ResponsiveWrapperProps) => isResponsive ? ( - <ResponsiveWrapper> + <ResponsiveWrapper data-test-subj="uptimeWithResponsiveWrapper--wrapper"> <Component {...(rest as P)} /> </ResponsiveWrapper> ) : ( - <EuiPanel paddingSize="m"> + <EuiPanel paddingSize="m" hasBorder data-test-subj="uptimeWithResponsiveWrapper--panel"> <Component {...(rest as P)} /> </EuiPanel> ); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/lazy_synthetics_custom_assets_extension.tsx b/x-pack/plugins/uptime/public/components/fleet_package/lazy_synthetics_custom_assets_extension.tsx new file mode 100644 index 0000000000000..c2fe52d9004df --- /dev/null +++ b/x-pack/plugins/uptime/public/components/fleet_package/lazy_synthetics_custom_assets_extension.tsx @@ -0,0 +1,16 @@ +/* + * 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 { lazy } from 'react'; + +export const LazySyntheticsCustomAssetsExtension = lazy(async () => { + const { SyntheticsCustomAssetsExtension } = await import('./synthetics_custom_assets_extension'); + + return { + default: SyntheticsCustomAssetsExtension, + }; +}); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_custom_assets_extension.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_custom_assets_extension.tsx new file mode 100644 index 0000000000000..7eda6efa20530 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_custom_assets_extension.tsx @@ -0,0 +1,35 @@ +/* + * 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'; + +import { + PackageAssetsComponent, + CustomAssetsAccordionProps, + CustomAssetsAccordion, +} from '../../../../fleet/public'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { ClientPluginsStart } from '../../apps/plugin'; +import { PLUGIN } from '../../../common/constants/plugin'; + +export const SyntheticsCustomAssetsExtension: PackageAssetsComponent = () => { + const { http } = useKibana<ClientPluginsStart>().services; + const views: CustomAssetsAccordionProps['views'] = [ + { + name: i18n.translate('xpack.uptime.fleetIntegration.assets.name', { + defaultMessage: 'Monitors', + }), + url: http?.basePath.prepend(`/app/${PLUGIN.ID}`) ?? '', + description: i18n.translate('xpack.uptime.fleetIntegration.assets.description', { + defaultMessage: 'View monitors in Uptime', + }), + }, + ]; + + return <CustomAssetsAccordion views={views} initialIsOpen />; +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx index 86602a064b9d4..9ce5a509bdd52 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration.tsx @@ -34,7 +34,7 @@ export const MonitorDurationComponent = ({ hasMLJob, }: DurationChartProps) => { return ( - <EuiPanel paddingSize="m"> + <EuiPanel paddingSize="m" hasBorder> <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> <EuiFlexItem> <EuiTitle size="s"> diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx index 8cb1c49cbd974..2112af0653669 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx @@ -89,17 +89,9 @@ export const MonitorPageTitle: React.FC = () => { return ( <> - <EuiFlexGroup wrap={false} data-test-subj="monitorTitle"> - <EuiFlexItem grow={false}> - <EuiTitle> - <h1 className="eui-textNoWrap">{nameOrId}</h1> - </EuiTitle> - <EuiSpacer size="xs" /> - </EuiFlexItem> - <EuiFlexItem grow={false} style={{ justifyContent: 'center' }}> - <EnableMonitorAlert monitorId={monitorId} selectedMonitor={selectedMonitor!} /> - </EuiFlexItem> - </EuiFlexGroup> + <EuiTitle> + <h1 className="eui-textNoWrap">{nameOrId}</h1> + </EuiTitle> <EuiSpacer size="s" /> <EuiFlexGroup wrap={false} gutterSize="s" alignItems="center"> <EuiFlexItem grow={false}> @@ -126,6 +118,7 @@ export const MonitorPageTitle: React.FC = () => { </EuiFlexItem> )} </EuiFlexGroup> + <EnableMonitorAlert monitorId={monitorId} selectedMonitor={selectedMonitor!} /> </> ); }; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx index b9ad176b8ed76..06c7ab7bff843 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx @@ -251,7 +251,7 @@ export const PingList = () => { }; return ( - <EuiPanel> + <EuiPanel hasBorder> <PingListHeader /> <EuiSpacer size="s" /> <EuiBasicTable diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.test.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.test.ts new file mode 100644 index 0000000000000..82e7dfd2d0ced --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.test.ts @@ -0,0 +1,32 @@ +/* + * 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 { renderMonitorType } from './status_bar'; + +describe('StatusBar component', () => { + describe('renderMonitorType', () => { + it('handles http type', () => { + expect(renderMonitorType('http')).toBe('HTTP'); + }); + + it('handles tcp type', () => { + expect(renderMonitorType('tcp')).toBe('TCP'); + }); + + it('handles icmp type', () => { + expect(renderMonitorType('icmp')).toBe('ICMP'); + }); + + it('handles browser type', () => { + expect(renderMonitorType('browser')).toBe('Browser'); + }); + + it('returns empty string for `undefined`', () => { + expect(renderMonitorType(undefined)).toBe(''); + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.tsx index 18049a9de5c5c..e8374d3792bfe 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.tsx @@ -15,6 +15,7 @@ import { EuiDescriptionListDescription, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { MonitorSSLCertificate } from './ssl_certificate'; import * as labels from '../translations'; import { StatusByLocations } from './status_by_location'; @@ -40,6 +41,29 @@ export const MonListDescription = styled(EuiDescriptionListDescription)` } `; +export const renderMonitorType = (type: string | undefined) => { + switch (type) { + case 'http': + return i18n.translate('xpack.uptime.monitorDetails.statusBar.pingType.http', { + defaultMessage: 'HTTP', + }); + case 'tcp': + return i18n.translate('xpack.uptime.monitorDetails.statusBar.pingType.tcp', { + defaultMessage: 'TCP', + }); + case 'icmp': + return i18n.translate('xpack.uptime.monitorDetails.statusBar.pingType.icmp', { + defaultMessage: 'ICMP', + }); + case 'browser': + return i18n.translate('xpack.uptime.monitorDetails.statusBar.pingType.browser', { + defaultMessage: 'Browser', + }); + default: + return ''; + } +}; + export const MonitorStatusBar: React.FC = () => { const { monitorId, monitorStatus, monitorLocations = {} } = useStatusBar(); @@ -77,7 +101,7 @@ export const MonitorStatusBar: React.FC = () => { <> <MonListTitle aria-label={labels.typeAriaLabel}>{labels.typeLabel}</MonListTitle> <MonListDescription data-test-subj="monitor-page-type"> - {monitorStatus.monitor.type} + {renderMonitorType(monitorStatus?.monitor?.type)} </MonListDescription> </> )} diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/status_details.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_details.tsx index 517bfbe594f08..5b20e83f0ec85 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/status_details.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_details.tsx @@ -49,7 +49,7 @@ export const MonitorStatusDetailsComponent = ({ monitorLocations }: MonitorStatu }, []); return ( - <EuiPanel> + <EuiPanel hasBorder> <EuiFlexGroup gutterSize="l" wrap={true} responsive={true}> <EuiFlexItem grow={1}> <MonitorStatusBar /> diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx index df8f5dff59dc2..610107f406306 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback, useMemo } from 'react'; import { useSelector, useDispatch } from 'react-redux'; @@ -104,7 +104,7 @@ export const StepDetailContainer: React.FC<Props> = ({ checkGroup, stepIndex }) : [], }} > - <EuiPanel> + <> {(!journey || journey.loading) && ( <EuiFlexGroup justifyContent="center"> <EuiFlexItem grow={false}> @@ -124,7 +124,7 @@ export const StepDetailContainer: React.FC<Props> = ({ checkGroup, stepIndex }) {journey && activeStep && !journey.loading && ( <WaterfallChartContainer checkGroup={checkGroup} stepIndex={stepIndex} /> )} - </EuiPanel> + </> </PageTemplateComponent> ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__snapshots__/data_or_index_missing.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__snapshots__/data_or_index_missing.test.tsx.snap deleted file mode 100644 index 45e40f71c0fde..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/__snapshots__/data_or_index_missing.test.tsx.snap +++ /dev/null @@ -1,92 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DataOrIndexMissing component renders headingMessage 1`] = ` -<EuiFlexGroup - data-test-subj="data-missing" - justifyContent="center" -> - <EuiFlexItem - grow={false} - style={ - Object { - "flexBasis": 700, - } - } - > - <EuiSpacer - size="m" - /> - <EuiPanel> - <EuiEmptyPrompt - actions={ - <EuiFlexGroup> - <EuiFlexItem> - <EuiButton - color="primary" - fill={true} - href="/app/home#/tutorial/uptimeMonitors" - > - <FormattedMessage - defaultMessage="View setup instructions" - id="xpack.uptime.emptyState.viewSetupInstructions" - values={Object {}} - /> - </EuiButton> - </EuiFlexItem> - <EuiFlexItem> - <EuiButton - color="primary" - href="/app/uptime/settings" - > - <FormattedMessage - defaultMessage="Update index pattern settings" - id="xpack.uptime.emptyState.updateIndexPattern" - values={Object {}} - /> - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - } - body={ - <React.Fragment> - <p> - <FormattedMessage - defaultMessage="Set up Heartbeat to start monitoring your services." - id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage" - values={Object {}} - /> - </p> - <p> - <FormattedMessage - defaultMessage="If Heartbeat is already set up, confirm it's sending data to Elasticsearch, then update the index pattern settings to match the Heartbeat config." - id="xpack.uptime.emptyState.configureHeartbeatIndexSettings" - values={Object {}} - /> - </p> - </React.Fragment> - } - iconType="logoUptime" - title={ - <EuiTitle - size="l" - > - <h3> - <FormattedMessage - defaultMessage="Uptime index {indexName} not found" - id="xpack.uptime.emptyState.noIndexTitle" - values={ - Object { - "indexName": <em> - heartbeat-* - </em>, - } - } - /> - </h3> - </EuiTitle> - } - /> - </EuiPanel> - </EuiFlexItem> -</EuiFlexGroup> -`; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx index c6898971a693e..caff055ce987c 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx @@ -6,8 +6,9 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { screen } from '@testing-library/react'; import { FormattedMessage } from '@kbn/i18n/react'; +import { render } from '../../../lib/helper/rtl_helpers'; import { DataOrIndexMissing } from './data_or_index_missing'; describe('DataOrIndexMissing component', () => { @@ -19,7 +20,7 @@ describe('DataOrIndexMissing component', () => { values={{ indexName: <em>heartbeat-*</em> }} /> ); - const component = shallowWithIntl(<DataOrIndexMissing headingMessage={headingMessage} />); - expect(component).toMatchSnapshot(); + render(<DataOrIndexMissing headingMessage={headingMessage} />); + expect(screen.getByText(/heartbeat-*/)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx index 7f9839ff94dbe..44e55de990bbf 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx @@ -30,7 +30,7 @@ export const DataOrIndexMissing = ({ headingMessage, settings }: DataMissingProp <EuiFlexGroup justifyContent="center" data-test-subj="data-missing"> <EuiFlexItem grow={false} style={{ flexBasis: 700 }}> <EuiSpacer size="m" /> - <EuiPanel> + <EuiPanel hasBorder> <EuiEmptyPrompt iconType="logoUptime" title={ diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx index 41a5e1f6f7c00..3bf6c4f4bcb14 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx @@ -22,7 +22,7 @@ export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { return ( <EuiFlexGroup justifyContent="center"> <EuiFlexItem grow={false}> - <EuiPanel> + <EuiPanel hasBorder> <EuiEmptyPrompt iconType="securityApp" iconColor="subdued" diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__snapshots__/monitor_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/monitor_list/__snapshots__/monitor_list.test.tsx.snap index cfdf7afba4e85..a4fcb141d454b 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__snapshots__/monitor_list.test.tsx.snap @@ -845,7 +845,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` } <div - class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow" + class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder" > <div class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive c0" @@ -1299,28 +1299,37 @@ exports[`MonitorList component renders the monitor list 1`] = ` class="euiPopover__anchor" > <div - class="euiSwitch euiSwitch--compressed" + class="euiFormRow" + id="generated-id-row" > - <button - aria-checked="false" - aria-label="Enable rule" - class="euiSwitch__button" - data-test-subj="uptimeDisplayDefineConnector" - id="defineAlertSettingsSwitch" - role="switch" - type="button" + <div + class="euiFormRow__fieldWrapper" > - <span - class="euiSwitch__body" + <div + class="euiSwitch euiSwitch--compressed" > - <span - class="euiSwitch__thumb" - /> - <span - class="euiSwitch__track" - /> - </span> - </button> + <button + aria-checked="false" + aria-label="Enable status alerts" + class="euiSwitch__button" + data-test-subj="uptimeDisplayDefineConnector" + id="generated-id" + role="switch" + type="button" + > + <span + class="euiSwitch__body" + > + <span + class="euiSwitch__thumb" + /> + <span + class="euiSwitch__track" + /> + </span> + </button> + </div> + </div> </div> </div> </div> @@ -1552,28 +1561,37 @@ exports[`MonitorList component renders the monitor list 1`] = ` class="euiPopover__anchor" > <div - class="euiSwitch euiSwitch--compressed" + class="euiFormRow" + id="generated-id-row" > - <button - aria-checked="false" - aria-label="Enable rule" - class="euiSwitch__button" - data-test-subj="uptimeDisplayDefineConnector" - id="defineAlertSettingsSwitch" - role="switch" - type="button" + <div + class="euiFormRow__fieldWrapper" > - <span - class="euiSwitch__body" + <div + class="euiSwitch euiSwitch--compressed" > - <span - class="euiSwitch__thumb" - /> - <span - class="euiSwitch__track" - /> - </span> - </button> + <button + aria-checked="false" + aria-label="Enable status alerts" + class="euiSwitch__button" + data-test-subj="uptimeDisplayDefineConnector" + id="generated-id" + role="switch" + type="button" + > + <span + class="euiSwitch__body" + > + <span + class="euiSwitch__thumb" + /> + <span + class="euiSwitch__track" + /> + </span> + </button> + </div> + </div> </div> </div> </div> diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/define_connectors.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/define_connectors.test.tsx new file mode 100644 index 0000000000000..aa257177970a1 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/define_connectors.test.tsx @@ -0,0 +1,44 @@ +/* + * 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 { DefineAlertConnectors } from './define_connectors'; +import { screen } from '@testing-library/react'; +import { fireEvent } from '@testing-library/dom'; +import { ENABLE_STATUS_ALERT } from './translations'; +import { render } from '../../../../lib/helper/rtl_helpers'; + +describe('EnableAlertComponent', () => { + it('does not showHelpText or render popover when showHelpText and renderPopOver are false', () => { + render(<DefineAlertConnectors />); + expect(screen.getByTestId('uptimeDisplayDefineConnector')).toBeInTheDocument(); + expect(screen.queryByText(ENABLE_STATUS_ALERT)).not.toBeInTheDocument(); + expect(screen.queryByText(/Define a default connector/)).not.toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('uptimeDisplayDefineConnector')); + + expect(screen.queryByTestId('uptimeSettingsDefineConnector')).not.toBeInTheDocument(); + }); + + it('shows label when showLabel is true', () => { + render(<DefineAlertConnectors showLabel />); + expect(screen.getByText(ENABLE_STATUS_ALERT)).toBeInTheDocument(); + }); + + it('shows helpText when showHelpText is true', () => { + render(<DefineAlertConnectors showHelpText />); + expect(screen.getByText(/Define a default connector/)).toBeInTheDocument(); + }); + + it('renders popover on click when showPopover is true', () => { + render(<DefineAlertConnectors showPopover />); + + fireEvent.click(screen.getByTestId('uptimeDisplayDefineConnector')); + + expect(screen.getByTestId('uptimeSettingsDefineConnector')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/define_connectors.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/define_connectors.tsx index 956e18f3d7cc3..dcc70c16e920b 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/define_connectors.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/define_connectors.tsx @@ -6,41 +6,72 @@ */ import React, { useState } from 'react'; -import { EuiPopover, EuiSwitch, EuiText } from '@elastic/eui'; -import { useRouteMatch } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; +import { EuiSwitch, EuiPopover, EuiText, EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { ReactRouterEuiLink } from '../../../common/react_router_helpers'; -import { MONITOR_ROUTE, SETTINGS_ROUTE } from '../../../../../common/constants'; +import { SETTINGS_ROUTE } from '../../../../../common/constants'; import { ENABLE_STATUS_ALERT } from './translations'; -const SETTINGS_LINK_TEXT = i18n.translate('xpack.uptime.page_header.defineConnector', { - defaultMessage: 'Define a default connector', -}); +interface Props { + showPopover?: boolean; + showHelpText?: boolean; + showLabel?: boolean; +} -export const DefineAlertConnectors = () => { +export const DefineAlertConnectors = ({ + showPopover = false, + showHelpText = false, + showLabel = false, +}: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const onButtonClick = () => setIsPopoverOpen((val) => !val); const closePopover = () => setIsPopoverOpen(false); - const isMonitorPage = useRouteMatch(MONITOR_ROUTE); - return ( <EuiPopover button={ - <EuiSwitch - id={'defineAlertSettingsSwitch'} - label={ENABLE_STATUS_ALERT} - showLabel={!!isMonitorPage} - aria-label={ENABLE_STATUS_ALERT} - onChange={onButtonClick} - checked={false} - compressed={!isMonitorPage} - data-test-subj={'uptimeDisplayDefineConnector'} - /> + <> + <EuiFormRow + helpText={ + showHelpText ? ( + <FormattedMessage + id="xpack.uptime.monitorList.defineConnector.description" + defaultMessage="Define a default connector in {link} to enable monitor status alerts." + values={{ + link: ( + <ReactRouterEuiLink + to={SETTINGS_ROUTE + '?focusConnectorField=true'} + data-test-subj={'uptimeSettingsLink'} + target="_blank" + > + <FormattedMessage + id="xpack.uptime.page_header.defineConnector.settingsLink" + defaultMessage="Settings" + /> + </ReactRouterEuiLink> + ), + }} + /> + ) : undefined + } + > + <EuiSwitch + id={'defineAlertSettingsSwitch'} + label={ENABLE_STATUS_ALERT} + showLabel={showLabel} + aria-label={ENABLE_STATUS_ALERT} + // this switch is read only, no onChange applied + onChange={showPopover ? onButtonClick : () => {}} + checked={false} + compressed={true} + disabled={!showPopover} + data-test-subj={'uptimeDisplayDefineConnector'} + /> + </EuiFormRow> + </> } - isOpen={isPopoverOpen} + isOpen={showPopover ? isPopoverOpen : false} closePopover={closePopover} > <EuiText style={{ width: '350px' }} data-test-subj={'uptimeSettingsDefineConnector'}> @@ -48,10 +79,13 @@ export const DefineAlertConnectors = () => { to={SETTINGS_ROUTE + '?focusConnectorField=true'} data-test-subj={'uptimeSettingsLink'} > - {SETTINGS_LINK_TEXT} + <FormattedMessage + id="xpack.uptime.page_header.defineConnector.popover.defaultLink" + defaultMessage="Define a default connector" + /> </ReactRouterEuiLink>{' '} <FormattedMessage - id="xpack.uptime.monitorList.defineConnector.description" + id="xpack.uptime.monitorList.defineConnector.popover.description" defaultMessage="to receive status alerts." /> </EuiText> diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx index 427985839ba89..53ba1b8ed57eb 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx @@ -18,7 +18,7 @@ import { AlertsResult } from '../../../../state/actions/types'; describe('EnableAlertComponent', () => { it('it displays define connectors when there is none', () => { - const { getByTestId, getByLabelText, getByText } = render( + const { getByTestId, getByLabelText, getByText, getByRole } = render( <EnableMonitorAlert monitorId={'testMonitor'} selectedMonitor={makePing({ name: 'My website' })} @@ -29,11 +29,12 @@ describe('EnableAlertComponent', () => { fireEvent.click(getByTestId('uptimeDisplayDefineConnector')); - expect(getByTestId('uptimeSettingsLink')).toHaveAttribute( + expect(getByRole('link', { name: 'Define a default connector' })).toHaveAttribute( 'href', '/settings?focusConnectorField=true' ); - expect(getByText('to receive status alerts.')); + expect(getByText(/Define a default connector/)); + expect(getByText(/to receive status alerts./)); }); it('does not displays define connectors when there is connector', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx index f0f7c4d91c4f5..9db6c3b4b0acb 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx @@ -125,6 +125,10 @@ export const EnableMonitorAlert = ({ monitorId, selectedMonitor }: Props) => { </EuiToolTip> </div> ) : ( - <DefineAlertConnectors /> + <DefineAlertConnectors + showPopover={!isMonitorPage} + showHelpText={!!isMonitorPage} + showLabel={!!isMonitorPage} + /> ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/translations.ts b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/translations.ts index eadf8febebcf2..ce6708abf9ade 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/translations.ts @@ -8,11 +8,11 @@ import { i18n } from '@kbn/i18n'; export const ENABLE_STATUS_ALERT = i18n.translate('xpack.uptime.monitorList.enableDownAlert', { - defaultMessage: 'Enable rule', + defaultMessage: 'Enable status alerts', }); export const DISABLE_STATUS_ALERT = i18n.translate('xpack.uptime.monitorList.disableDownAlert', { - defaultMessage: 'Disable rule', + defaultMessage: 'Disable status alerts', }); export const EXPAND_TAGS_LABEL = i18n.translate('xpack.uptime.monitorList.tags.expand', { diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx index 9a0054f77252e..0b707588acfb5 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx @@ -173,7 +173,7 @@ export const MonitorListComponent: ({ ]; return ( - <EuiPanel> + <EuiPanel hasBorder> <MonitorListHeader /> <EuiSpacer size="m" /> <EuiBasicTable diff --git a/x-pack/plugins/uptime/public/components/overview/status_panel.tsx b/x-pack/plugins/uptime/public/components/overview/status_panel.tsx index 6faa56bb358fb..45e0ecaf9fc46 100644 --- a/x-pack/plugins/uptime/public/components/overview/status_panel.tsx +++ b/x-pack/plugins/uptime/public/components/overview/status_panel.tsx @@ -13,7 +13,7 @@ import { SnapshotComponent } from './snapshot'; const STATUS_CHART_HEIGHT = '160px'; export const StatusPanel = ({}) => ( - <EuiPanel> + <EuiPanel hasBorder> <EuiFlexGroup gutterSize="l"> <EuiFlexItem grow={2}> <SnapshotComponent height={STATUS_CHART_HEIGHT} /> diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx index 47b89e82dc5c7..0da6f034e53bb 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx @@ -5,13 +5,7 @@ * 2.0. */ -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiButtonIcon, - EuiPanel, - EuiTitle, -} from '@elastic/eui'; +import { EuiBasicTable, EuiBasicTableColumn, EuiButtonIcon, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { MouseEvent } from 'react'; import styled from 'styled-components'; @@ -147,7 +141,7 @@ export const StepsList = ({ data, error, loading }: Props) => { }; return ( - <EuiPanel> + <> <EuiTitle> <h2> {statusMessage( @@ -176,6 +170,6 @@ export const StepsList = ({ data, error, loading }: Props) => { tableLayout={'auto'} rowProps={getRowProps} /> - </EuiPanel> + </> ); }; diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 5f2699240425a..88bae5536c05f 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -13,7 +13,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiForm, - EuiPanel, EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -148,7 +147,7 @@ export const SettingsPage: React.FC = () => { ); return ( - <EuiPanel style={{ maxWidth: 1000, margin: 'auto' }}> + <> <EuiFlexGroup> <EuiFlexItem grow={false}>{cannotEditNotice}</EuiFlexItem> </EuiFlexGroup> @@ -213,6 +212,6 @@ export const SettingsPage: React.FC = () => { </div> </EuiFlexItem> </EuiFlexGroup> - </EuiPanel> + </> ); }; diff --git a/x-pack/test/api_integration/apis/management/index_management/indices.js b/x-pack/test/api_integration/apis/management/index_management/indices.js index d0e17476530b7..25b1ef97d3087 100644 --- a/x-pack/test/api_integration/apis/management/index_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_management/indices.js @@ -177,12 +177,19 @@ export default function ({ getService }) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/64473 - describe.skip('list', function () { + describe('list', function () { this.tags(['skipCloud']); it('should list all the indices with the expected properties and data enrichers', async function () { - const { body } = await list().expect(200); + // Create an index that we can assert against + await createIndex('test_index'); + + // Verify indices request + const { body: indices } = await list().expect(200); + + // Find the "test_index" created to verify expected keys + const indexCreated = indices.find((index) => index.name === 'test_index'); + const expectedKeys = [ 'health', 'hidden', @@ -203,7 +210,8 @@ export default function ({ getService }) { // We need to sort the keys before comparing then, because race conditions // can cause enrichers to register in non-deterministic order. const sortedExpectedKeys = expectedKeys.sort(); - const sortedReceivedKeys = Object.keys(body[0]).sort(); + const sortedReceivedKeys = Object.keys(indexCreated).sort(); + expect(sortedReceivedKeys).to.eql(sortedExpectedKeys); }); }); diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts index 80432f15f70a9..6b370117c447a 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts @@ -500,6 +500,26 @@ export default ({ getService }: FtrProviderContext): void => { expectedHttpCode: 400, }); }); + + it('400s if the title is too long', async () => { + const longTitle = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nulla enim, rutrum sit amet euismod venenatis, blandit et massa. Nulla id consectetur enim.'; + + const postedCase = await createCase(supertest, postCaseReq); + await updateCase({ + supertest, + params: { + cases: [ + { + id: postedCase.id, + version: postedCase.version, + title: longTitle, + }, + ], + }, + expectedHttpCode: 400, + }); + }); }); describe('alerts', () => { diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/post_case.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/post_case.ts index e8337fa9db502..2fe5a4c0165c0 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/post_case.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/cases/post_case.ts @@ -238,6 +238,13 @@ export default ({ getService }: FtrProviderContext): void => { .send({ ...req, status: CaseStatuses.open }) .expect(400); }); + + it('400s if the title is too long', async () => { + const longTitle = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nulla enim, rutrum sit amet euismod venenatis, blandit et massa. Nulla id consectetur enim.'; + + await createCase(supertest, getPostCaseRequest({ title: longTitle }), 400); + }); }); describe('rbac', () => { diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/migrations.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/migrations.ts index bf64500a88068..67eb23a43f397 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/migrations.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/migrations.ts @@ -11,12 +11,13 @@ import { CASE_CONFIGURE_URL, SECURITY_SOLUTION_OWNER, } from '../../../../../../plugins/cases/common/constants'; -import { getConfiguration } from '../../../../common/lib/utils'; +import { getConfiguration, getConnectorMappingsFromES } from '../../../../common/lib/utils'; // eslint-disable-next-line import/no-default-export -export default function createGetTests({ getService }: FtrProviderContext) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const es = getService('es'); describe('migrations', () => { describe('7.10.0', () => { @@ -64,6 +65,16 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(configuration[0].owner).to.be(SECURITY_SOLUTION_OWNER); }); + + it('adds the owner field to the connector mapping', async () => { + // We don't get the owner field back from the mappings when we retrieve the configuration so the only way to + // check that the migration worked is by checking the saved object stored in Elasticsearch directly + const mappings = await getConnectorMappingsFromES({ es }); + expect(mappings.body.hits.hits.length).to.be(1); + expect(mappings.body.hits.hits[0]._source?.['cases-connector-mappings'].owner).to.eql( + SECURITY_SOLUTION_OWNER + ); + }); }); }); } diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/connectors/migrations.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/connectors/migrations.ts deleted file mode 100644 index 863c565b4ab08..0000000000000 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/connectors/migrations.ts +++ /dev/null @@ -1,39 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { SECURITY_SOLUTION_OWNER } from '../../../../../../plugins/cases/common/constants'; -import { getConnectorMappingsFromES } from '../../../../common/lib/utils'; - -// eslint-disable-next-line import/no-default-export -export default function createGetTests({ getService }: FtrProviderContext) { - const es = getService('es'); - const esArchiver = getService('esArchiver'); - - describe('migrations', () => { - describe('7.13.2', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/cases/migrations/7.13.2'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/cases/migrations/7.13.2'); - }); - - it('adds the owner field', async () => { - // We don't get the owner field back from the mappings when we retrieve the configuration so the only way to - // check that the migration worked is by checking the saved object stored in Elasticsearch directly - const mappings = await getConnectorMappingsFromES({ es }); - expect(mappings.body.hits.hits.length).to.be(1); - expect(mappings.body.hits.hits[0]._source?.['cases-connector-mappings'].owner).to.eql( - SECURITY_SOLUTION_OWNER - ); - }); - }); - }); -} diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/migrations.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/migrations.ts index 810fecc127d08..122eeee411431 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/migrations.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/migrations.ts @@ -15,6 +15,5 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./comments/migrations')); loadTestFile(require.resolve('./configure/migrations')); loadTestFile(require.resolve('./user_actions/migrations')); - loadTestFile(require.resolve('./connectors/migrations')); }); }; diff --git a/x-pack/test/functional/apps/maps/blended_vector_layer.js b/x-pack/test/functional/apps/maps/blended_vector_layer.js index 3cc7d8e07d623..67e6d7f93a4cd 100644 --- a/x-pack/test/functional/apps/maps/blended_vector_layer.js +++ b/x-pack/test/functional/apps/maps/blended_vector_layer.js @@ -29,7 +29,7 @@ export default function ({ getPageObjects, getService }) { it('should request documents when zoomed to smaller regions showing less data', async () => { const { rawResponse: response } = await PageObjects.maps.getResponse(); // Allow a range of hits to account for variances in browser window size. - expect(response.hits.hits.length).to.be.within(35, 45); + expect(response.hits.hits.length).to.be.within(5, 12); }); it('should request clusters when zoomed to larger regions showing lots of data', async () => { diff --git a/x-pack/test/functional/apps/maps/documents_source/search_hits.js b/x-pack/test/functional/apps/maps/documents_source/search_hits.js index 4da36a44cff08..de2233fdf791d 100644 --- a/x-pack/test/functional/apps/maps/documents_source/search_hits.js +++ b/x-pack/test/functional/apps/maps/documents_source/search_hits.js @@ -53,7 +53,7 @@ export default function ({ getPageObjects, getService }) { describe('inspector', () => { it('should register elasticsearch request in inspector', async () => { const hits = await PageObjects.maps.getHits(); - expect(hits).to.equal('6'); + expect(hits).to.equal('5'); }); }); @@ -74,7 +74,7 @@ export default function ({ getPageObjects, getService }) { const requestStats = await inspector.getTableData(); const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits'); await inspector.close(); - expect(hits).to.equal('3'); + expect(hits).to.equal('2'); }); it('should re-fetch query when "refresh" is clicked', async () => { diff --git a/x-pack/test/functional/apps/maps/es_geo_grid_source.js b/x-pack/test/functional/apps/maps/es_geo_grid_source.js index 27949ca720e34..26eabafef7d1f 100644 --- a/x-pack/test/functional/apps/maps/es_geo_grid_source.js +++ b/x-pack/test/functional/apps/maps/es_geo_grid_source.js @@ -59,9 +59,9 @@ export default function ({ getPageObjects, getService }) { }); it('should not rerequest when zoom changes do not cause geotile_grid precision to change', async () => { - await PageObjects.maps.setView(DATA_CENTER_LAT, DATA_CENTER_LON, 1.2); + await PageObjects.maps.setView(DATA_CENTER_LAT, DATA_CENTER_LON, 1.4); const beforeSameZoom = await getRequestTimestamp(); - await PageObjects.maps.setView(DATA_CENTER_LAT, DATA_CENTER_LON, 1.8); + await PageObjects.maps.setView(DATA_CENTER_LAT, DATA_CENTER_LON, 1.6); const afterTimestamp = await getRequestTimestamp(); expect(afterTimestamp).to.equal(beforeSameZoom); }); diff --git a/x-pack/test/functional/apps/maps/layer_visibility.js b/x-pack/test/functional/apps/maps/layer_visibility.js index 52cfa44923f63..cf6051cde8be7 100644 --- a/x-pack/test/functional/apps/maps/layer_visibility.js +++ b/x-pack/test/functional/apps/maps/layer_visibility.js @@ -32,7 +32,7 @@ export default function ({ getPageObjects, getService }) { it('should fetch layer data when layer is made visible', async () => { await PageObjects.maps.toggleLayerVisibility('logstash'); const hits = await PageObjects.maps.getHits(); - expect(hits).to.equal('6'); + expect(hits).to.equal('5'); }); }); } diff --git a/x-pack/test/functional/apps/maps/saved_object_management.js b/x-pack/test/functional/apps/maps/saved_object_management.js index bdc9011ec2cba..3392e418bd987 100644 --- a/x-pack/test/functional/apps/maps/saved_object_management.js +++ b/x-pack/test/functional/apps/maps/saved_object_management.js @@ -82,7 +82,7 @@ export default function ({ getPageObjects, getService }) { const requestStats = await inspector.getTableData(); const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits'); await inspector.close(); - expect(hits).to.equal('2'); + expect(hits).to.equal('1'); }); it('should override query stored with map when query is provided in app state', async () => { @@ -130,7 +130,7 @@ export default function ({ getPageObjects, getService }) { const requestStats = await inspector.getTableData(); const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits'); await inspector.close(); - expect(hits).to.equal('2'); + expect(hits).to.equal('1'); }); }); }); diff --git a/x-pack/test/functional/apps/saved_objects_management/exports/_7.14_import_alerts_actions.ndjson b/x-pack/test/functional/apps/saved_objects_management/exports/_7.14_import_alerts_actions.ndjson new file mode 100644 index 0000000000000..f0215db3cda69 --- /dev/null +++ b/x-pack/test/functional/apps/saved_objects_management/exports/_7.14_import_alerts_actions.ndjson @@ -0,0 +1,24 @@ +{"attributes":{"actionTypeId":".server-log","config":{},"isMissingSecrets":false,"name":"Monitoring: Write to Kibana log"},"coreMigrationVersion":"7.14.0","id":"f1cf69c0-d9a7-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:34:50.470Z","version":"WzEzODksMV0="} +{"attributes":{"actionTypeId":".email","config":{"from":"user2@company.com","hasAuth":true,"host":"securehost","port":465,"secure":null,"service":null},"isMissingSecrets":true,"name":"email connector with auth"},"coreMigrationVersion":"7.14.0","id":"7eec9570-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:10:09.234Z","version":"WzI2LDFd"} +{"attributes":{"actionTypeId":".resilient","config":{"apiUrl":"https://resilienttest","orgId":"test"},"isMissingSecrets":true,"name":"ibm resilient connector"},"coreMigrationVersion":"7.14.0","id":"8e08afd0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:10:34.583Z","version":"WzI3LDFd"} +{"attributes":{"actionTypeId":".email","config":{"from":"user@company.com","hasAuth":false,"host":"host","port":22,"secure":null,"service":null},"isMissingSecrets":false,"name":"email connector no auth"},"coreMigrationVersion":"7.14.0","id":"711e30c0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:09:46.078Z","version":"WzI1LDFd"} +{"attributes":{"actionTypeId":".index","config":{"executionTimeField":null,"index":"test-index","refresh":false},"isMissingSecrets":false,"name":"index connector"},"coreMigrationVersion":"7.14.0","id":"95d329c0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:10:47.653Z","version":"WzI5LDFd"} +{"attributes":{"actionTypeId":".webhook","config":{"hasAuth":true,"headers":null,"method":"post","url":"https://webhook"},"isMissingSecrets":true,"name":"webhook with auth"},"coreMigrationVersion":"7.14.0","id":"07f32aa0-d9a5-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:13:59.125Z","version":"WzM5LDFd"} +{"attributes":{"actionTypeId":".servicenow-sir","config":{"apiUrl":"https://servicenowtestsecops"},"isMissingSecrets":true,"name":"servicenow secops connector"},"coreMigrationVersion":"7.14.0","id":"ca974fb0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:12:16.181Z","version":"WzM1LDFd"} +{"attributes":{"actionTypeId":".servicenow","config":{"apiUrl":"https://servicenowtest"},"isMissingSecrets":true,"name":"servicenow itsm connector"},"coreMigrationVersion":"7.14.0","id":"be5c5c40-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:11:55.662Z","version":"WzM0LDFd"} +{"attributes":{"actionTypeId":".webhook","config":{"hasAuth":false,"headers":null,"method":"post","url":"https://openwebhook"},"isMissingSecrets":false,"name":"webhook no auth"},"coreMigrationVersion":"7.14.0","id":"ff8c70b0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:13:45.031Z","version":"WzM3LDFd"} +{"attributes":{"actionTypeId":".pagerduty","config":{"apiUrl":""},"isMissingSecrets":true,"name":"pagerduty connector"},"coreMigrationVersion":"7.14.0","id":"b0bc3380-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:11:32.802Z","version":"WzMyLDFd"} +{"attributes":{"actionTypeId":".jira","config":{"apiUrl":"https://testjira","projectKey":"myproject"},"isMissingSecrets":true,"name":"jira connector"},"coreMigrationVersion":"7.14.0","id":"a081d7e0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:11:05.577Z","version":"WzMwLDFd"} +{"attributes":{"actionTypeId":".server-log","config":{},"isMissingSecrets":false,"name":"server log connector"},"coreMigrationVersion":"7.14.0","id":"b5442ca0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:11:40.404Z","version":"WzMzLDFd"} +{"attributes":{"actionTypeId":".teams","config":{},"isMissingSecrets":true,"name":"teams connector"},"coreMigrationVersion":"7.14.0","id":"a94be780-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:11:20.321Z","version":"WzMxLDFd"} +{"attributes":{"actionTypeId":".slack","config":{},"isMissingSecrets":true,"name":"slack connector"},"coreMigrationVersion":"7.14.0","id":"d6d1cdf0-d9a4-11eb-881a-218d2e96295d","migrationVersion":{"action":"7.14.0"},"references":[],"type":"action","updated_at":"2021-06-30T13:12:36.697Z","version":"WzM2LDFd"} +{"attributes":{"actions":[],"alertTypeId":".geo-containment","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:30:28.418Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"geo rule","notifyWhen":"onActionGroupChange","params":{"boundaryGeoField":"coordinates","boundaryIndexId":"c2a20a50-d9a6-11eb-881a-218d2e96295d","boundaryIndexTitle":"manhattan_boundaries","boundaryNameField":"<nothing selected>","boundaryType":"entireIndex","dateField":"@timestamp","entity":"azimuth","geoField":"location","index":"tracks*","indexId":"f653fcf0-d9a6-11eb-881a-218d2e96295d"},"schedule":{"interval":"5m"},"scheduledTaskId":null,"tags":["manhattan"],"throttle":null,"updatedAt":"2021-06-30T13:30:28.418Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"55626650-d9a7-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[],"type":"alert","updated_at":"2021-06-30T13:40:37.967Z","version":"WzE1MDksMV0="} +{"attributes":{"actions":[],"alertTypeId":"logs.alert.document.count","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:20:56.718Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"logs threshold rule","notifyWhen":"onActiveAlert","params":{"count":{"comparator":"more than","value":75},"criteria":[{"comparator":"equals","field":"host.keyword","value":"host1"}],"timeSize":5,"timeUnit":"m"},"schedule":{"interval":"1m"},"scheduledTaskId":null,"tags":["logs","test"],"throttle":null,"updatedAt":"2021-06-30T13:20:56.718Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"00b51cc0-d9a6-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[],"type":"alert","updated_at":"2021-06-30T13:42:01.071Z","version":"WzE1MjYsMV0="} +{"attributes":{"actions":[{"actionRef":"action_0","actionTypeId":".server-log","group":"query matched","params":{"level":"info","message":"Elasticsearch query alert '{{alertName}}' is active:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}"}}],"alertTypeId":".es-query","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:19:13.441Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"es query rule","notifyWhen":"onActionGroupChange","params":{"esQuery":"{\n \"query\":{\n \"match_all\" : {}\n }\n}","index":[".kibana"],"size":100,"threshold":[1000],"thresholdComparator":">","timeField":"updated_at","timeWindowSize":5,"timeWindowUnit":"m"},"schedule":{"interval":"1m"},"scheduledTaskId":null,"tags":["es","query"],"throttle":null,"updatedAt":"2021-06-30T13:19:13.441Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"c3172fc0-d9a5-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[{"id":"b5442ca0-d9a4-11eb-881a-218d2e96295d","name":"action_0","type":"action"}],"type":"alert","updated_at":"2021-06-30T13:41:19.057Z","version":"WzE1MjAsMV0="} +{"attributes":{"actions":[],"alertTypeId":"xpack.uptime.alerts.monitorStatus","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:22:48.241Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"uptime status","notifyWhen":"onActiveAlert","params":{"availability":{"range":30,"rangeUnit":"d","threshold":"99"},"numTimes":5,"search":"","shouldCheckAvailability":true,"shouldCheckStatus":true,"timerangeCount":15,"timerangeUnit":"m"},"schedule":{"interval":"1d"},"scheduledTaskId":null,"tags":[],"throttle":null,"updatedAt":"2021-06-30T13:22:48.241Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"432dddd0-d9a6-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[],"type":"alert","updated_at":"2021-06-30T13:22:51.893Z","version":"WzEwMywxXQ=="} +{"attributes":{"actions":[],"alertTypeId":"metrics.alert.threshold","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:22:25.161Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"metric threshold rule","notifyWhen":"onActionGroupChange","params":{"criteria":[{"aggType":"avg","comparator":">","metric":"_score","threshold":[0.5],"timeSize":1,"timeUnit":"m"}],"sourceId":"default"},"schedule":{"interval":"1h"},"scheduledTaskId":null,"tags":[],"throttle":null,"updatedAt":"2021-06-30T13:22:25.161Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"34dba320-d9a6-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[],"type":"alert","updated_at":"2021-06-30T13:22:27.874Z","version":"Wzk5LDFd"} +{"attributes":{"actions":[],"alertTypeId":"xpack.uptime.alerts.tlsCertificate","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:23:14.340Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"tls rule","notifyWhen":"onThrottleInterval","params":{},"schedule":{"interval":"1d"},"scheduledTaskId":null,"tags":["certificate"],"throttle":"1h","updatedAt":"2021-06-30T13:23:14.340Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"52990290-d9a6-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[],"type":"alert","updated_at":"2021-06-30T13:23:15.928Z","version":"WzEwNywxXQ=="} +{"attributes":{"actions":[{"actionRef":"action_0","actionTypeId":".index","group":"metrics.inventory_threshold.fired","params":{"documents":[{"alert_triggered":"{{rule.id}}"}]}}],"alertTypeId":"metrics.alert.inventory.threshold","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:21:53.897Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"inventory rule","notifyWhen":"onActionGroupChange","params":{"criteria":[{"comparator":">","customMetric":{"aggregation":"avg","field":"","id":"alert-custom-metric","type":"custom"},"metric":"cpu","threshold":[90],"timeSize":1,"timeUnit":"m"}],"nodeType":"host","sourceId":"default"},"schedule":{"interval":"10m"},"scheduledTaskId":null,"tags":["inventory"],"throttle":null,"updatedAt":"2021-06-30T13:21:53.897Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"22e0f9e0-d9a6-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[{"id":"95d329c0-d9a4-11eb-881a-218d2e96295d","name":"action_0","type":"action"}],"type":"alert","updated_at":"2021-06-30T13:42:01.078Z","version":"WzE1MjcsMV0="} +{"attributes":{"actions":[{"actionRef":"action_0","actionTypeId":".server-log","group":"anomaly_score_match","params":{"level":"info","message":"Elastic Stack Machine Learning Alert:\n- Job IDs: {{context.jobIds}}\n- Time: {{context.timestampIso8601}}\n- Anomaly score: {{context.score}}\n\n{{context.message}}\n\n{{#context.topInfluencers.length}}\n Top influencers:\n {{#context.topInfluencers}}\n {{influencer_field_name}} = {{influencer_field_value}} [{{score}}]\n {{/context.topInfluencers}}\n{{/context.topInfluencers.length}}\n\n{{#context.topRecords.length}}\n Top records:\n {{#context.topRecords}}\n {{function}}({{field_name}}) {{by_field_value}} {{over_field_value}} {{partition_field_value}} [{{score}}]\n {{/context.topRecords}}\n{{/context.topRecords.length}}\n\n{{! Replace kibanaBaseUrl if not configured in Kibana }}\n[Open in Anomaly Explorer]({{{kibanaBaseUrl}}}{{{context.anomalyExplorerUrl}}})\n"}}],"alertTypeId":"xpack.ml.anomaly_detection_alert","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:32:13.689Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"ecommerce ml","notifyWhen":"onActionGroupChange","params":{"includeInterim":false,"jobSelection":{"groupIds":[],"jobIds":["high_sum_total_sales"]},"lookbackInterval":null,"resultType":"bucket","severity":75,"topNBuckets":null},"schedule":{"interval":"1h"},"scheduledTaskId":null,"tags":[],"throttle":null,"updatedAt":"2021-06-30T13:32:13.689Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"93ea6530-d9a7-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[{"id":"b5442ca0-d9a4-11eb-881a-218d2e96295d","name":"action_0","type":"action"}],"type":"alert","updated_at":"2021-06-30T13:32:15.978Z","version":"WzI0NiwxXQ=="} +{"attributes":{"actions":[{"actionRef":"action_0","actionTypeId":".email","group":"threshold met","params":{"message":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}","subject":"alert fired!","to":["user@company.com"]}},{"actionRef":"action_1","actionTypeId":".email","group":"threshold met","params":{"message":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}","subject":"alert triggered!","to":["user2@company.com"]}},{"actionRef":"action_2","actionTypeId":".index","group":"threshold met","params":{"documents":[{"alert_triggered":"{{rule.id}}"}]}},{"actionRef":"action_3","actionTypeId":".teams","group":"threshold met","params":{"message":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}"}},{"actionRef":"action_4","actionTypeId":".pagerduty","group":"threshold met","params":{"dedupKey":"{{rule.id}}:{{alert.id}}","eventAction":"trigger","summary":"triggered"}},{"actionRef":"action_5","actionTypeId":".server-log","group":"threshold met","params":{"level":"info","message":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}"}},{"actionRef":"action_6","actionTypeId":".slack","group":"threshold met","params":{"message":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}"}},{"actionRef":"action_7","actionTypeId":".webhook","group":"threshold met","params":{"body":"{\n \"alert_triggered\": \"{{rule.id}}\"\n}"}},{"actionRef":"action_8","actionTypeId":".webhook","group":"threshold met","params":{"body":"{\n \"alert_triggered\": \"{{rule.id}}\"\n}"}}],"alertTypeId":".index-threshold","apiKey":null,"apiKeyOwner":null,"consumer":"alerts","createdAt":"2021-06-30T13:18:16.273Z","createdBy":"elastic","enabled":false,"executionStatus":{"error":null,"lastExecutionDate":"2021-06-30T13:42:03.746Z","status":"pending"},"meta":{"versionApiKeyLastmodified":"8.0.0"},"muteAll":false,"mutedInstanceIds":[],"name":"index threshold rule with actions","notifyWhen":"onActionGroupChange","params":{"aggType":"count","groupBy":"all","index":[".kibana"],"termSize":5,"threshold":[1000],"thresholdComparator":">","timeField":"updated_at","timeWindowSize":5,"timeWindowUnit":"m"},"schedule":{"interval":"1m"},"scheduledTaskId":null,"tags":[],"throttle":null,"updatedAt":"2021-06-30T13:41:45.350Z","updatedBy":"elastic"},"coreMigrationVersion":"7.14.0","id":"a0bfd5d0-d9a5-11eb-881a-218d2e96295d","migrationVersion":{"alert":"7.13.0"},"references":[{"id":"711e30c0-d9a4-11eb-881a-218d2e96295d","name":"action_0","type":"action"},{"id":"7eec9570-d9a4-11eb-881a-218d2e96295d","name":"action_1","type":"action"},{"id":"95d329c0-d9a4-11eb-881a-218d2e96295d","name":"action_2","type":"action"},{"id":"a94be780-d9a4-11eb-881a-218d2e96295d","name":"action_3","type":"action"},{"id":"b0bc3380-d9a4-11eb-881a-218d2e96295d","name":"action_4","type":"action"},{"id":"b5442ca0-d9a4-11eb-881a-218d2e96295d","name":"action_5","type":"action"},{"id":"d6d1cdf0-d9a4-11eb-881a-218d2e96295d","name":"action_6","type":"action"},{"id":"ff8c70b0-d9a4-11eb-881a-218d2e96295d","name":"action_7","type":"action"},{"id":"07f32aa0-d9a5-11eb-881a-218d2e96295d","name":"action_8","type":"action"}],"type":"alert","updated_at":"2021-06-30T13:41:45.359Z","version":"WzE1MjQsMV0="} +{"excludedObjects":[{"id":"f27594d0-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f2756dc0-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f2751fa0-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f275bbe0-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"bf522690-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f2771b70-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f276f460-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f274f890-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f27594d1-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f276cd50-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f274aa70-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f27546b0-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f27546b1-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f274d180-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"},{"id":"f2774280-d9a7-11eb-881a-218d2e96295d","reason":"excluded","type":"alert"}],"excludedObjectsCount":15,"exportedCount":23,"missingRefCount":0,"missingReferences":[]} diff --git a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts index 47fc2b756e8e8..427e42b7b7a65 100644 --- a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts +++ b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts @@ -21,7 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); describe('Export import saved objects between versions', function () { - beforeEach(async function () { + before(async function () { await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.load('x-pack/test/functional/es_archives/getting_started/shakespeare'); await kibanaServer.uiSettings.replace({}); @@ -50,5 +50,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // verifying the count of saved objects after importing .ndjson await expect(importedSavedObjects).to.be('Export 88 objects'); }); + + it('should be able to import alerts and actions saved objects from 7.14 into 8.0.0', async function () { + await retry.tryForTime(10000, async () => { + const existingSavedObjects = await testSubjects.getVisibleText('exportAllObjects'); + // Kibana always has 1 advanced setting as a saved object + await expect(existingSavedObjects).to.be('Export 88 objects'); + }); + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_7.14_import_alerts_actions.ndjson') + ); + await PageObjects.savedObjects.checkImportSucceeded(); + await PageObjects.savedObjects.clickImportDone(); + const importedSavedObjects = await testSubjects.getVisibleText('exportAllObjects'); + // verifying the count of saved objects after importing .ndjson + await expect(importedSavedObjects).to.be('Export 111 objects'); + }); }); } diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts index e83eabfb05f44..ff6103f16e494 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts @@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const security = getService('security'); + const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects([ 'common', 'error', @@ -24,10 +25,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('feature controls security', () => { before(async () => { - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/timelion/feature_controls'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' + ); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); }); + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' + ); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + }); + describe('global timelion all privileges', () => { before(async () => { await security.role.create('global_timelion_all_role', { diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts index 91c357f37085e..a1dea695fce86 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts @@ -13,6 +13,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const spacesService = getService('spaces'); const PageObjects = getPageObjects(['common', 'timelion', 'security', 'spaceSelector']); const appsMenu = getService('appsMenu'); + const kibanaServer = getService('kibanaServer'); describe('timelion', () => { before(async () => { @@ -23,29 +24,44 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { // we need to load the following in every situation as deleting // a space deletes all of the associated saved objects - await esArchiver.load('x-pack/test/functional/es_archives/timelion/feature_controls'); + // await esArchiver.load('x-pack/test/functional/es_archives/timelion/feature_controls'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' + ); + await spacesService.create({ id: 'custom_space', name: 'custom_space', disabledFeatures: [], }); + + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json', + { space: 'custom_space' } + ); }); after(async () => { await spacesService.delete('custom_space'); - await esArchiver.unload('x-pack/test/functional/es_archives/timelion/feature_controls'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' + ); }); it('shows timelion navlink', async () => { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); + const navLinks = (await appsMenu.readLinks()).map((link) => link.text); expect(navLinks).to.contain('Timelion'); }); it(`allows a timelion sheet to be created`, async () => { - await PageObjects.common.navigateToApp('timelion'); + await PageObjects.common.navigateToApp('timelion', { + basePath: '/s/custom_space', + }); + await PageObjects.timelion.saveTimelionSheet(); }); }); @@ -54,17 +70,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { // we need to load the following in every situation as deleting // a space deletes all of the associated saved objects - await esArchiver.load('x-pack/test/functional/es_archives/timelion/feature_controls'); + // await esArchiver.load('x-pack/test/functional/es_archives/timelion/feature_controls'); + + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' + ); + await spacesService.create({ id: 'custom_space', name: 'custom_space', disabledFeatures: ['timelion'], }); + + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json', + { space: 'custom_space' } + ); }); after(async () => { await spacesService.delete('custom_space'); - await esArchiver.unload('x-pack/test/functional/es_archives/timelion/feature_controls'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json' + ); }); it(`doesn't show timelion navlink`, async () => { diff --git a/x-pack/test/functional/apps/uptime/synthetics_integration.ts b/x-pack/test/functional/apps/uptime/synthetics_integration.ts index a4740de8e9a2b..c4996299f0d43 100644 --- a/x-pack/test/functional/apps/uptime/synthetics_integration.ts +++ b/x-pack/test/functional/apps/uptime/synthetics_integration.ts @@ -253,7 +253,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); }); - it.skip('allows configuring http advanced options', async () => { + it('allows configuring http advanced options', async () => { // This test ensures that updates made to the Synthetics Policy are carried all the way through // to the generated Agent Policy that is dispatch down to the Elastic Agent. const config = generateHTTPConfig('http://elastic.co'); diff --git a/x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json b/x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json new file mode 100644 index 0000000000000..323dbb67d54b8 --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/timelion/feature_controls.json @@ -0,0 +1,52 @@ +{ + "attributes": { + "buildNum": 9007199254740991, + "defaultIndex": "logstash-*" + }, + "coreMigrationVersion": "7.14.0", + "id": "7.0.0", + "migrationVersion": { + "config": "7.13.0" + }, + "references": [], + "type": "config", + "updated_at": "2019-01-22T19:32:02.235Z", + "version": "WzQsMl0=" +} + +{ + "attributes": { + "description": "", + "hits": 0, + "timelion_chart_height": 275, + "timelion_columns": 2, + "timelion_interval": "auto", + "timelion_rows": 2, + "timelion_sheet": [ + ".es(*)" + ], + "title": "i-exist", + "version": 1 + }, + "coreMigrationVersion": "7.14.0", + "id": "i-exist", + "references": [], + "type": "timelion-sheet", + "version": "WzYsMl0=" +} + +{ + "attributes": { + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kilobytes\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['bytes'].value / 1000\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"machine os raw\",\"type\":\"string\",\"count\":0,\"scripted\":true,\"script\":\"doc['machine.os.raw'].value\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "coreMigrationVersion": "7.14.0", + "id": "logstash-*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzUsMl0=" +} \ No newline at end of file diff --git a/x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json b/x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json new file mode 100644 index 0000000000000..f27149f9d7eb6 --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/timelion/timelion_custom_space.json @@ -0,0 +1,20 @@ +{ + "attributes": { + "description": "", + "hits": 0, + "timelion_chart_height": 275, + "timelion_columns": 2, + "timelion_interval": "auto", + "timelion_rows": 2, + "timelion_sheet": [ + ".es(*).label('custom space sheet')" + ], + "title": "i-exist", + "version": 1 + }, + "coreMigrationVersion": "7.14.0", + "id": "i-exist", + "references": [], + "type": "timelion-sheet", + "version": "WzcsMl0=" +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/synthetics_integration_page.ts b/x-pack/test/functional/page_objects/synthetics_integration_page.ts index 56a67d9e6fbd4..3321234a345e4 100644 --- a/x-pack/test/functional/page_objects/synthetics_integration_page.ts +++ b/x-pack/test/functional/page_objects/synthetics_integration_page.ts @@ -86,7 +86,7 @@ export function SyntheticsIntegrationPageProvider({ * @params {value} the value of the input */ async fillTextInputByTestSubj(testSubj: string, value: string) { - const field = await testSubjects.find(testSubj, 5000); + const field = await testSubjects.find(testSubj); await field.scrollIntoViewIfNecessary({ bottomOffset: fixedFooterHeight }); await field.click(); await field.clearValue(); @@ -118,7 +118,7 @@ export function SyntheticsIntegrationPageProvider({ */ async findHTTPAdvancedOptionsAccordion() { await this.ensureIsOnPackagePage(); - const accordion = await testSubjects.find('syntheticsHTTPAdvancedFieldsAccordion', 5000); + const accordion = await testSubjects.find('syntheticsHTTPAdvancedFieldsAccordion'); return accordion; }, diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts index 2626ef2421f0b..fd3a5abc0e4bf 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts @@ -30,7 +30,7 @@ interface MonitoringStats { non_recurring: number; owner_ids: number; estimated_schedule_density: number[]; - capacity_requirments: { + capacity_requirements: { per_minute: number; per_hour: number; per_day: number; @@ -218,9 +218,9 @@ export default function ({ getService }: FtrProviderContext) { expect(typeof workload.non_recurring).to.eql('number'); expect(typeof workload.owner_ids).to.eql('number'); - expect(typeof workload.capacity_requirments.per_minute).to.eql('number'); - expect(typeof workload.capacity_requirments.per_hour).to.eql('number'); - expect(typeof workload.capacity_requirments.per_day).to.eql('number'); + expect(typeof workload.capacity_requirements.per_minute).to.eql('number'); + expect(typeof workload.capacity_requirements.per_hour).to.eql('number'); + expect(typeof workload.capacity_requirements.per_day).to.eql('number'); expect(Array.isArray(workload.estimated_schedule_density)).to.eql(true); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts new file mode 100644 index 0000000000000..a0f4a3f91fe32 --- /dev/null +++ b/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts @@ -0,0 +1,115 @@ +/* + * 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 { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../services/fixtures'; +import { FtrProviderContext } from '../ftr_provider_context'; + +import { ILM_POLICY_NAME } from '../../../plugins/reporting/common/constants'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const supertestNoAuth = getService('supertestWithoutAuth'); + const reportingAPI = getService('reportingAPI'); + + describe('ILM policy migration APIs', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); + await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + }); + + afterEach(async () => { + await reportingAPI.deleteAllReports(); + await reportingAPI.migrateReportingIndices(); // ensure that the ILM policy exists + }); + + it('detects when no migration is needed', async () => { + expect(await reportingAPI.checkIlmMigrationStatus()).to.eql('ok'); + + // try creating a report + await supertestNoAuth + .post(`/api/reporting/generate/csv`) + .set('kbn-xsrf', 'xxx') + .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }); + + expect(await reportingAPI.checkIlmMigrationStatus()).to.eql('ok'); + }); + + it('detects when reporting indices should be migrated due to missing ILM policy', async () => { + await reportingAPI.makeAllReportingPoliciesUnmanaged(); + // TODO: Remove "any" when no longer through type issue "policy_id" missing + await es.ilm.deleteLifecycle({ policy: ILM_POLICY_NAME } as any); + + await supertestNoAuth + .post(`/api/reporting/generate/csv`) + .set('kbn-xsrf', 'xxx') + .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }); + + expect(await reportingAPI.checkIlmMigrationStatus()).to.eql('policy-not-found'); + // assert that migration fixes this + await reportingAPI.migrateReportingIndices(); + expect(await reportingAPI.checkIlmMigrationStatus()).to.eql('ok'); + }); + + it('detects when reporting indices should be migrated due to unmanaged indices', async () => { + await reportingAPI.makeAllReportingPoliciesUnmanaged(); + await supertestNoAuth + .post(`/api/reporting/generate/csv`) + .set('kbn-xsrf', 'xxx') + .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }); + + expect(await reportingAPI.checkIlmMigrationStatus()).to.eql('indices-not-managed-by-policy'); + // assert that migration fixes this + await reportingAPI.migrateReportingIndices(); + expect(await reportingAPI.checkIlmMigrationStatus()).to.eql('ok'); + }); + + it('does not override an existing ILM policy', async () => { + const customLifecycle = { + policy: { + phases: { + hot: { + min_age: '0ms', + actions: {}, + }, + delete: { + min_age: '0ms', + actions: { + delete: { + delete_searchable_snapshot: true, + }, + }, + }, + }, + }, + }; + + // customize the lifecycle policy + await es.ilm.putLifecycle({ + policy: ILM_POLICY_NAME, + body: customLifecycle, + }); + + await reportingAPI.migrateReportingIndices(); + + const { + body: { + [ILM_POLICY_NAME]: { policy }, + }, + } = await es.ilm.getLifecycle({ policy: ILM_POLICY_NAME }); + + expect(policy).to.eql(customLifecycle.policy); + }); + }); +} diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts index 15960e45d4a62..fed842427ab90 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts @@ -12,5 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Reporting API Integration Tests with Security disabled', function () { this.tags('ciGroup13'); loadTestFile(require.resolve('./job_apis')); + loadTestFile(require.resolve('./ilm_migration_apis')); }); } diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts index e45af4bd140b0..eb32de9d0dc9c 100644 --- a/x-pack/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -164,6 +164,36 @@ export function createScenarios({ getService }: Pick<FtrProviderContext, 'getSer }); }; + const checkIlmMigrationStatus = async () => { + log.debug('ReportingAPI.checkIlmMigrationStatus'); + const { body } = await supertestWithoutAuth + .get('/api/reporting/ilm_policy_status') + .set('kbn-xsrf', 'xxx') + .expect(200); + return body.status; + }; + + const migrateReportingIndices = async () => { + log.debug('ReportingAPI.migrateReportingIndices'); + await supertestWithoutAuth + .put('/api/reporting/deprecations/migrate_ilm_policy') + .set('kbn-xsrf', 'xxx') + .expect(200); + }; + + const makeAllReportingPoliciesUnmanaged = async () => { + log.debug('ReportingAPI.makeAllReportingPoliciesUnmanaged'); + const settings: any = { + 'index.lifecycle.name': null, + }; + await esSupertest + .put('/.reporting*/_settings') + .send({ + settings, + }) + .expect(200); + }; + return { initEcommerce, teardownEcommerce, @@ -182,5 +212,8 @@ export function createScenarios({ getService }: Pick<FtrProviderContext, 'getSer postJob, postJobJSON, deleteAllReports, + checkIlmMigrationStatus, + migrateReportingIndices, + makeAllReportingPoliciesUnmanaged, }; } diff --git a/x-pack/test/search_sessions_integration/config.ts b/x-pack/test/search_sessions_integration/config.ts index 1e2d648712098..9dc542038a48a 100644 --- a/x-pack/test/search_sessions_integration/config.ts +++ b/x-pack/test/search_sessions_integration/config.ts @@ -22,6 +22,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ resolve(__dirname, './tests/apps/dashboard/async_search'), + resolve(__dirname, './tests/apps/dashboard/session_sharing'), resolve(__dirname, './tests/apps/discover'), resolve(__dirname, './tests/apps/lens'), resolve(__dirname, './tests/apps/management/search_sessions'), diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/index.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/index.ts new file mode 100644 index 0000000000000..d06d5d5ebd6ab --- /dev/null +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/index.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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile, getService, getPageObjects }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common']); + + describe('Search session sharing', function () { + this.tags('ciGroup3'); + + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + await PageObjects.common.navigateToApp('dashboard'); + }); + + loadTestFile(require.resolve('./lens')); + }); +} diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts new file mode 100644 index 0000000000000..b6dfc29bb1c6b --- /dev/null +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts @@ -0,0 +1,71 @@ +/* + * 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'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const dashboardPanelActions = getService('dashboardPanelActions'); + const dashboardAddPanel = getService('dashboardAddPanel'); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['header', 'common', 'dashboard', 'timePicker', 'lens']); + + // Dashboard shares a search session with lens when navigating to and from by value lens to hit search cache + // https://github.com/elastic/kibana/issues/99310 + describe('Search session sharing with lens', () => { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.preserveCrossAppState(); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + }); + + // NOTE: This test doesn't check if the cache was actually hit, but just checks if the same search session id is used + // so it doesn't give the 100% confidence that cache-hit improvement works https://github.com/elastic/kibana/issues/99310 + // but if it fails, we for sure know it doesn't work + it("should share search session with by value lens and don't share with by reference", async () => { + // Add a by ref lens panel to a new dashboard + const lensTitle = 'Artistpreviouslyknownaslens'; + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.filterEmbeddableNames(lensTitle); + await find.clickByButtonText(lensTitle); + await dashboardAddPanel.closeAddPanel(); + await PageObjects.lens.goToTimeRange(); + await PageObjects.dashboard.waitForRenderComplete(); + + // Navigating to lens and back should create a new session + const byRefSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.lens.saveAndReturn(); + await PageObjects.dashboard.waitForRenderComplete(); + const newByRefSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); + + expect(byRefSessionId).not.to.eql(newByRefSessionId); + + // Convert to by-value + const byRefPanel = await testSubjects.find('embeddablePanelHeading-' + lensTitle); + await dashboardPanelActions.unlinkFromLibary(byRefPanel); + await PageObjects.dashboard.waitForRenderComplete(); + const byValueSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); + + // Navigating to lens and back should keep the session + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.lens.saveAndReturn(); + await PageObjects.dashboard.waitForRenderComplete(); + const newByValueSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); + expect(byValueSessionId).to.eql(newByValueSessionId); + }); + }); +}