From 114ab926511e10267fd93372259d07e0de3eea98 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Fri, 24 Jan 2025 05:38:32 -0600 Subject: [PATCH 01/13] [EUI visual refresh][Management] Background color fixes (#208135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes the background color in two places. It was too dark before, now its not. Ingest Pipelines -> Create/Edit pipeline Screenshot 2025-01-23 at 9 48 22 PM Index management -> Create/Edit index template AND Index management -> Create/Edit component template Screenshot 2025-01-23 at 9 47 35 PM Closes https://github.com/elastic/kibana/issues/208061 --- .../components/mappings_editor/_index.scss | 5 ----- .../fields/create_field/create_field.tsx | 22 +++++++++++++------ .../pipeline_editor/pipeline_editor.scss | 4 ---- .../pipeline_editor/pipeline_editor.tsx | 14 +++++++++--- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/_index.scss b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/_index.scss index 0ba1e74c2d5e8..bd3ddd7de5b67 100644 --- a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/_index.scss +++ b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/_index.scss @@ -13,11 +13,6 @@ } .mappingsEditor { - &__createFieldWrapper { - background-color: $euiColorLightestShade; - padding: $euiSize; - } - &__createFieldContent { position: relative; diff --git a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx index 8c3a809866bfd..3bdf9b1d883d1 100644 --- a/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx +++ b/x-pack/platform/plugins/shared/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx @@ -12,7 +12,9 @@ import { EuiFlexItem, EuiOutsideClickDetector, EuiSpacer, + useEuiTheme, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { TrainedModelStat } from '@kbn/ml-plugin/common/types/trained_models'; import { MlPluginStart } from '@kbn/ml-plugin/public'; @@ -279,6 +281,14 @@ export const CreateField = React.memo(function CreateFieldComponent({ ); + const { euiTheme } = useEuiTheme(); + + const paddingLeftCreateFieldWrapper = `${ + isMultiField + ? paddingLeft! - EUI_SIZE * 1.5 // As there are no "L" bullet list we need to substract some indent + : paddingLeft + }px`; + return ( <> @@ -294,13 +304,11 @@ export const CreateField = React.memo(function CreateFieldComponent({ Boolean(maxNestedDepth) && maxNestedDepth! > 0, 'mappingsEditor__createFieldWrapper--multiField': isMultiField, })} - style={{ - paddingLeft: `${ - isMultiField - ? paddingLeft! - EUI_SIZE * 1.5 // As there are no "L" bullet list we need to substract some indent - : paddingLeft - }px`, - }} + css={css` + padding: ${euiTheme.size.l}; + paddingleft: ${paddingLeftCreateFieldWrapper}; + background-color: ${euiTheme.colors.backgroundBaseSubdued}; + `} ref={createFieldFormRef} tabIndex={0} > diff --git a/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.scss b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.scss index 6a51f4f54f27c..469c777bd73b2 100644 --- a/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.scss +++ b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.scss @@ -2,10 +2,6 @@ margin-bottom: $euiSizeXL; } -.pipelineEditor__container { - background-color: $euiColorLightestShade; -} - .pipelineEditor__onFailureTitle { padding-left: $euiSizeS; } diff --git a/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.tsx b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.tsx index 0b817edbf5eab..7f15a39e25866 100644 --- a/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.tsx +++ b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/pipeline_editor.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiSpacer, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; import { usePipelineProcessorsContext } from './context'; import { @@ -28,6 +29,8 @@ export const PipelineEditor: React.FunctionComponent = ({ onLoadJson }) = state: { processors: allProcessors }, } = usePipelineProcessorsContext(); + const { euiTheme } = useEuiTheme(); + const { state: { processors, onFailure }, } = allProcessors; @@ -53,12 +56,17 @@ export const PipelineEditor: React.FunctionComponent = ({ onLoadJson }) = } return ( -
+
0} /> - + {content} From 2c62e26717c55b94751d7af7814cacc1aed29bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Bl=C3=A1zquez?= Date: Fri, 24 Jan 2025 12:39:01 +0100 Subject: [PATCH 02/13] Replace obsolete styles.css with CSS-in-JS in CSP Graph (#207745) ## Summary Deletes obsolete .css file and replaces it with a decorator that injects the same styles using Emotion's CSS-in-JS capabilities. ### Motivation Eliminate technical debt and get rid of any .css or .scss within our domains. ### How to test Run `yarn storybook cloud_security_posture_graph` and open http://localhost:9001/. Check everything is rendered the same as before. ### Checklist - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks No risks at all. --- .../decorators/global_styles_decorator.tsx | 29 +++++++++++++++++++ .../graph/.storybook/decorators/index.ts | 1 + .../graph/.storybook/preview.ts | 2 -- .../graph/.storybook/styles.css | 7 ----- .../components/controls/actions.stories.tsx | 2 ++ .../components/controls/controls.stories.tsx | 2 ++ .../components/edge/deafult_edge.stories.tsx | 2 ++ .../graph/graph_benchmark.stories.tsx | 2 ++ .../graph/graph_popover.stories.tsx | 2 ++ .../graph_investigation.stories.tsx | 2 ++ .../src/components/graph_layout.stories.tsx | 2 ++ .../src/components/node/label.stories.tsx | 2 ++ .../src/components/node/node.stories.tsx | 2 ++ .../node/node_expand_button.stories.tsx | 2 ++ 14 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/global_styles_decorator.tsx delete mode 100644 x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/styles.css diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/global_styles_decorator.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/global_styles_decorator.tsx new file mode 100644 index 0000000000000..5e26972cc1c0c --- /dev/null +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/global_styles_decorator.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, { ComponentType } from 'react'; +import { Global, css } from '@emotion/react'; + +const globalStyles = css` + html, + body, + #root { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + box-sizing: border-box; + } +`; + +export const GlobalStylesStorybookDecorator = (Story: ComponentType) => { + return ( + <> + + + + ); +}; diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/index.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/index.ts index 23cfd1b7b5c53..ec30eb74cc209 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/index.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/index.ts @@ -7,3 +7,4 @@ export { KibanaReactStorybookDecorator } from './kibana_react_decorator'; export { ReactQueryStorybookDecorator } from './react_query_decorator'; +export { GlobalStylesStorybookDecorator } from './global_styles_decorator'; diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/preview.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/preview.ts index fe9d78424f240..1b13e90bef744 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/preview.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/preview.ts @@ -5,6 +5,4 @@ * 2.0. */ -import './styles.css'; - export const parameters = {}; diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/styles.css b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/styles.css deleted file mode 100644 index 9144ae8d64dd1..0000000000000 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/styles.css +++ /dev/null @@ -1,7 +0,0 @@ -html, body, #root { - width: 100%; - height: 100%; - margin: 0; - padding: 0; - box-sizing: border-box; -} diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/actions.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/actions.stories.tsx index c6706e1d76436..c2201c6f3366f 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/actions.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/actions.stories.tsx @@ -10,6 +10,7 @@ import type { Meta, Story } from '@storybook/react'; import { ThemeProvider, css } from '@emotion/react'; import { action } from '@storybook/addon-actions'; import { Actions as ActionsComponent, type ActionsProps } from './actions'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; export default { title: 'Components/Graph Components/Additional Components', @@ -19,6 +20,7 @@ export default { control: 'object', }, }, + decorators: [GlobalStylesStorybookDecorator], } as Meta; const Template: Story = (props) => { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/controls.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/controls.stories.tsx index dbe33c2321fde..905dd0f0bd267 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/controls.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/controls.stories.tsx @@ -11,6 +11,7 @@ import { ThemeProvider, css } from '@emotion/react'; import { ReactFlowProvider } from '@xyflow/react'; import { action } from '@storybook/addon-actions'; import { Controls as ControlsComponent, type ControlsProps } from './controls'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; export default { title: 'Components/Graph Components/Additional Components', @@ -26,6 +27,7 @@ export default { control: { type: 'boolean' }, }, }, + decorators: [GlobalStylesStorybookDecorator], } as Meta; const Template: Story = (props) => { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/edge/deafult_edge.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/edge/deafult_edge.stories.tsx index 9949e0fdfea45..cb079840cfa0a 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/edge/deafult_edge.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/edge/deafult_edge.stories.tsx @@ -23,6 +23,7 @@ import { import { isEmpty, isEqual, pick, size, xorWith } from 'lodash'; import { Story } from '@storybook/react'; import { DefaultEdge } from '.'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; import { LabelNode } from '../node'; import type { EdgeViewModel } from '../types'; import { SvgDefsMarker } from './markers'; @@ -43,6 +44,7 @@ export default { control: { type: 'radio' }, }, }, + decorators: [GlobalStylesStorybookDecorator], }; const nodeTypes = { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_benchmark.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_benchmark.stories.tsx index fd06a37776ab8..958bfdc1f030d 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_benchmark.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_benchmark.stories.tsx @@ -11,6 +11,7 @@ import { Story } from '@storybook/react'; import { EuiListGroup, EuiHorizontalRule } from '@elastic/eui'; import type { NodeProps, NodeViewModel } from '..'; import { Graph } from '..'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; import { GraphPopover } from './graph_popover'; import { ExpandButtonClickCallback } from '../types'; import { useGraphPopover } from './use_graph_popover'; @@ -22,6 +23,7 @@ export default { title: 'Graph Benchmark', description: 'CDR - Graph visualization', argTypes: {}, + decorators: [GlobalStylesStorybookDecorator], }; const useExpandButtonPopover = () => { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_popover.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_popover.stories.tsx index dbdba9c236da1..7dea7e77838b6 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_popover.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph/graph_popover.stories.tsx @@ -11,6 +11,7 @@ import { Story } from '@storybook/react'; import { EuiListGroup, EuiHorizontalRule } from '@elastic/eui'; import type { EntityNodeViewModel, LabelNodeViewModel, NodeProps } from '..'; import { Graph } from '..'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; import { GraphPopover } from './graph_popover'; import { ExpandButtonClickCallback } from '../types'; import { useGraphPopover } from './use_graph_popover'; @@ -20,6 +21,7 @@ export default { title: 'Components/Graph Components/Graph Popovers', description: 'CDR - Graph visualization', argTypes: {}, + decorators: [GlobalStylesStorybookDecorator], }; const useExpandButtonPopover = () => { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_investigation/graph_investigation.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_investigation/graph_investigation.stories.tsx index 04f5d5c9882ef..2046f4fb4772d 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_investigation/graph_investigation.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_investigation/graph_investigation.stories.tsx @@ -12,6 +12,7 @@ import { GraphInvestigation, type GraphInvestigationProps } from './graph_invest import { KibanaReactStorybookDecorator, ReactQueryStorybookDecorator, + GlobalStylesStorybookDecorator, } from '../../../.storybook/decorators'; import { mockDataView } from '../mock/data_view.mock'; import { SHOW_SEARCH_BAR_BUTTON_TOUR_STORAGE_KEY } from '../../common/constants'; @@ -46,6 +47,7 @@ export default { decorators: [ ReactQueryStorybookDecorator, KibanaReactStorybookDecorator, + GlobalStylesStorybookDecorator, (StoryComponent, context) => { const { shouldShowSearchBarTour, isLoading } = context.args; localStorage.setItem(SHOW_SEARCH_BAR_BUTTON_TOUR_STORAGE_KEY, shouldShowSearchBarTour); diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_layout.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_layout.stories.tsx index 21a75d2500fec..2587f7aafe8f8 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_layout.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/graph_layout.stories.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { ThemeProvider, css } from '@emotion/react'; import { Story } from '@storybook/react'; import { Writable } from '@kbn/utility-types'; +import { GlobalStylesStorybookDecorator } from '../../.storybook/decorators'; import type { EdgeViewModel, LabelNodeViewModel, @@ -24,6 +25,7 @@ export default { argTypes: { interactive: { control: 'boolean', defaultValue: true }, }, + decorators: [GlobalStylesStorybookDecorator], }; interface GraphData { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/label.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/label.stories.tsx index b5b385398cff3..726da45b0be51 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/label.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/label.stories.tsx @@ -11,6 +11,7 @@ import { pick } from 'lodash'; import { ReactFlow, Controls, Background } from '@xyflow/react'; import { Story } from '@storybook/react'; import { NodeViewModel } from '../types'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; import { HexagonNode, PentagonNode, EllipseNode, RectangleNode, DiamondNode, LabelNode } from '.'; import '@xyflow/react/dist/style.css'; @@ -29,6 +30,7 @@ export default { }, expandButtonClick: { action: 'expandButtonClick' }, }, + decorators: [GlobalStylesStorybookDecorator], }; const nodeTypes = { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node.stories.tsx index 37f9f26ee975e..6bfac77ac4f22 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node.stories.tsx @@ -11,6 +11,7 @@ import { pick } from 'lodash'; import { ReactFlow, Controls, Background } from '@xyflow/react'; import { Story } from '@storybook/react'; import { NodeViewModel } from '../types'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; import { HexagonNode, PentagonNode, EllipseNode, RectangleNode, DiamondNode, LabelNode } from '.'; import '@xyflow/react/dist/style.css'; @@ -29,6 +30,7 @@ export default { }, expandButtonClick: { action: 'expandButtonClick' }, }, + decorators: [GlobalStylesStorybookDecorator], }; const nodeTypes = { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node_expand_button.stories.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node_expand_button.stories.tsx index 665dc42d3b66c..af62254d8e9da 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node_expand_button.stories.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/node/node_expand_button.stories.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { ThemeProvider } from '@emotion/react'; import { Story } from '@storybook/react'; +import { GlobalStylesStorybookDecorator } from '../../../.storybook/decorators'; import { NodeShapeContainer } from './styles'; import { NodeExpandButton, type NodeExpandButtonProps } from './node_expand_button'; @@ -24,6 +25,7 @@ export default { defaultValue: 'primary', }, }, + decorators: [GlobalStylesStorybookDecorator], }; const Template: Story = (args) => ( From 0df78e629b429f6007f559aca339b4323b71e4c0 Mon Sep 17 00:00:00 2001 From: Antonio Date: Fri, 24 Jan 2025 12:40:25 +0100 Subject: [PATCH 03/13] [ResponseOps][MW] Fix bug when creating repeating Maintenance Window (#207084) Closes #198774 ## Summary - There was a bug when submitting `rrule` with a `byweekday` I fixed that validation to use a more inclusive regex. `byweekday` can be the expected `MO`, `TU`, etc but also `-1FR` or `+3SA` where the number corresponds to the week in a month. - The model version for the maintenance window was incorrect so when saving the SO the validation was failing. I fixed that and now we are allowed to save `number[]` as expected. - I removed some duplicated code and we now use the `rrule` schema from the `common` folder --- .../routes/r_rule/request/schemas/v1.ts | 18 ++--- .../common/routes/r_rule/validation/index.ts | 2 + .../validate_recurrence_by_weekday/latest.ts} | 6 +- .../validate_recurrence_by_weekday/v1.test.ts | 29 +++++++++ .../validate_recurrence_by_weekday/v1.ts | 25 +++++++ .../create/create_maintenance_window.test.ts | 16 ++++- ...create_maintenance_window_params_schema.ts | 3 +- ...update_maintenance_window_params_schema.ts | 2 +- .../update/update_maintenance_window.test.ts | 5 +- .../application/r_rule/schemas/index.ts | 1 - .../r_rule/schemas/r_rule_request_schema.ts | 63 ------------------ .../server/application/r_rule/types/index.ts | 1 - .../r_rule/types/r_rule_request.ts | 10 --- .../application/r_rule/validation/index.ts | 10 --- .../r_rule/validation/validate_end_date.ts | 13 ---- .../validation/validate_recurrence_by.ts | 16 ----- .../schemas/bulk_edit_rules_option_schemas.ts | 2 +- .../schemas/raw_maintenance_window/v1.ts | 22 ++++--- .../create_maintenance_window.ts | 65 +++++++++++++++++++ 19 files changed, 158 insertions(+), 151 deletions(-) rename x-pack/platform/plugins/shared/alerting/{server/application/r_rule/validation/validate_start_date.ts => common/routes/r_rule/validation/validate_recurrence_by_weekday/latest.ts} (60%) create mode 100644 x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.test.ts create mode 100644 x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.ts delete mode 100644 x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/r_rule_request_schema.ts delete mode 100644 x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/r_rule_request.ts delete mode 100644 x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/index.ts delete mode 100644 x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_end_date.ts delete mode 100644 x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_recurrence_by.ts diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/request/schemas/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/request/schemas/v1.ts index 42d238b5a9d59..a03370a55e8d7 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/request/schemas/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/request/schemas/v1.ts @@ -10,6 +10,7 @@ import { validateStartDateV1, validateEndDateV1, createValidateRecurrenceByV1, + validateRecurrenceByWeekdayV1, } from '../../validation'; export const rRuleRequestSchema = schema.object({ @@ -40,20 +41,9 @@ export const rRuleRequestSchema = schema.object({ }) ), byweekday: schema.maybe( - schema.arrayOf( - schema.oneOf([ - schema.literal('MO'), - schema.literal('TU'), - schema.literal('WE'), - schema.literal('TH'), - schema.literal('FR'), - schema.literal('SA'), - schema.literal('SU'), - ]), - { - validate: createValidateRecurrenceByV1('byweekday'), - } - ) + schema.arrayOf(schema.string(), { + validate: validateRecurrenceByWeekdayV1, + }) ), bymonthday: schema.maybe( schema.arrayOf(schema.number({ min: 1, max: 31 }), { diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/index.ts b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/index.ts index fc98963e03189..b52c37cb76b47 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/index.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/index.ts @@ -8,7 +8,9 @@ export { validateStartDate } from './validate_start_date/latest'; export { validateEndDate } from './validate_end_date/latest'; export { createValidateRecurrenceBy } from './validate_recurrence_by/latest'; +export { validateRecurrenceByWeekday } from './validate_recurrence_by_weekday/latest'; export { validateStartDate as validateStartDateV1 } from './validate_start_date/v1'; export { validateEndDate as validateEndDateV1 } from './validate_end_date/v1'; export { createValidateRecurrenceBy as createValidateRecurrenceByV1 } from './validate_recurrence_by/v1'; +export { validateRecurrenceByWeekday as validateRecurrenceByWeekdayV1 } from './validate_recurrence_by_weekday/v1'; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_start_date.ts b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/latest.ts similarity index 60% rename from x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_start_date.ts rename to x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/latest.ts index 3cbc0da0af1b5..25300c97a6d2e 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_start_date.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/latest.ts @@ -5,8 +5,4 @@ * 2.0. */ -export const validateStartDate = (date: string) => { - const parsedValue = Date.parse(date); - if (isNaN(parsedValue)) return `Invalid date: ${date}`; - return; -}; +export * from './v1'; diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.test.ts b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.test.ts new file mode 100644 index 0000000000000..975b55fd07227 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.test.ts @@ -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 { validateRecurrenceByWeekday } from './v1'; + +describe('validateRecurrenceByWeekday', () => { + it('validates empty array', () => { + expect(validateRecurrenceByWeekday([])).toEqual('rRule byweekday cannot be empty'); + }); + + it('validates properly formed byweekday strings', () => { + const weekdays = ['+1MO', '+2TU', '+3WE', '+4TH', '-4FR', '-3SA', '-2SU', '-1MO']; + + expect(validateRecurrenceByWeekday(weekdays)).toBeUndefined(); + }); + + it('validates improperly formed byweekday strings', () => { + expect(validateRecurrenceByWeekday(['+1MO', 'FOO', '+3WE', 'BAR', '-4FR'])).toEqual( + 'invalid byweekday values in rRule byweekday: FOO,BAR' + ); + }); + + it('validates byweekday strings without recurrence', () => { + expect(validateRecurrenceByWeekday(['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'])).toBeUndefined(); + }); +}); diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.ts new file mode 100644 index 0000000000000..daf1f38c78198 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/common/routes/r_rule/validation/validate_recurrence_by_weekday/v1.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. + */ + +export const validateRecurrenceByWeekday = (array: string[]) => { + if (array.length === 0) { + return 'rRule byweekday cannot be empty'; + } + + const byWeekDayRegex = new RegExp('^(((\\+|-)[1-4])?(MO|TU|WE|TH|FR|SA|SU))$'); + const invalidDays: string[] = []; + + array.forEach((day) => { + if (!byWeekDayRegex.test(day)) { + invalidDays.push(day); + } + }); + + if (invalidDays.length > 0) { + return `invalid byweekday values in rRule byweekday: ${invalidDays.join(',')}`; + } +}; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts index 57f7153bd0231..c1091f58abe13 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts @@ -6,21 +6,24 @@ */ import moment from 'moment-timezone'; -import { createMaintenanceWindow } from './create_maintenance_window'; -import { CreateMaintenanceWindowParams } from './types'; + import { savedObjectsClientMock, loggingSystemMock, uiSettingsServiceMock, } from '@kbn/core/server/mocks'; import { SavedObject } from '@kbn/core/server'; +import { FilterStateStore } from '@kbn/es-query'; +import { Frequency } from '@kbn/rrule'; + import { MaintenanceWindowClientContext, MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, } from '../../../../../common'; import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/test_helpers'; import type { MaintenanceWindow } from '../../types'; -import { FilterStateStore } from '@kbn/es-query'; +import { createMaintenanceWindow } from './create_maintenance_window'; +import { CreateMaintenanceWindowParams } from './types'; const savedObjectsClient = savedObjectsClientMock.create(); const uiSettings = uiSettingsServiceMock.createClient(); @@ -57,6 +60,13 @@ describe('MaintenanceWindowClient - create', () => { const mockMaintenanceWindow = getMockMaintenanceWindow({ expirationDate: moment(new Date()).tz('UTC').add(1, 'year').toISOString(), + rRule: { + tzid: 'UTC', + dtstart: '2023-02-26T00:00:00.000Z', + freq: Frequency.WEEKLY, + byweekday: ['-4MO', 'TU'], + count: 2, + }, }); savedObjectsClient.create.mockResolvedValueOnce({ diff --git a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts index b55a4871d0b6f..8a00dbc704862 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts @@ -6,8 +6,9 @@ */ import { schema } from '@kbn/config-schema'; +import { rRuleRequestSchema } from '../../../../../../common/routes/r_rule'; import { maintenanceWindowCategoryIdsSchema } from '../../../schemas'; -import { rRuleRequestSchema } from '../../../../r_rule/schemas'; + import { alertsFilterQuerySchema } from '../../../../alerts_filter_query/schemas'; export const createMaintenanceWindowParamsSchema = schema.object({ diff --git a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts index 1aca8ab24fcda..679571c6f324b 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowCategoryIdsSchema } from '../../../schemas'; -import { rRuleRequestSchema } from '../../../../r_rule/schemas'; +import { rRuleRequestSchema } from '../../../../../../common/routes/r_rule'; import { alertsFilterQuerySchema } from '../../../../alerts_filter_query/schemas'; export const updateMaintenanceWindowParamsSchema = schema.object({ diff --git a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts index e377fb3209d63..586a5a0e882ed 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts @@ -38,6 +38,7 @@ const updatedAttributes = { dtstart: '2023-03-26T00:00:00.000Z', freq: Frequency.WEEKLY, count: 2, + byweekday: ['-1MO', 'WE'], }, }; @@ -110,8 +111,8 @@ describe('MaintenanceWindowClient - update', () => { { ...updatedAttributes, events: [ - { gte: '2023-03-26T00:00:00.000Z', lte: '2023-03-26T02:00:00.000Z' }, - { gte: '2023-04-01T23:00:00.000Z', lte: '2023-04-02T01:00:00.000Z' }, // Daylight savings + { gte: '2023-03-26T23:00:00.000Z', lte: '2023-03-27T01:00:00.000Z' }, + { gte: '2023-03-28T23:00:00.000Z', lte: '2023-03-29T01:00:00.000Z' }, // Daylight savings ], expirationDate: moment(new Date(secondTimestamp)).tz('UTC').add(1, 'year').toISOString(), createdAt: '2023-02-26T00:00:00.000Z', diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/index.ts b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/index.ts index 12e793318558d..9ff41d1fcad0d 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/index.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/index.ts @@ -6,4 +6,3 @@ */ export { rRuleSchema } from './r_rule_schema'; -export { rRuleRequestSchema } from './r_rule_request_schema'; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/r_rule_request_schema.ts b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/r_rule_request_schema.ts deleted file mode 100644 index 978d000b137ab..0000000000000 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/schemas/r_rule_request_schema.ts +++ /dev/null @@ -1,63 +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 { schema } from '@kbn/config-schema'; -import { validateStartDate, validateEndDate, createValidateRecurrenceBy } from '../validation'; - -export const rRuleRequestSchema = schema.object({ - dtstart: schema.string({ validate: validateStartDate }), - tzid: schema.string(), - freq: schema.maybe( - schema.oneOf([schema.literal(0), schema.literal(1), schema.literal(2), schema.literal(3)]) - ), - interval: schema.maybe( - schema.number({ - validate: (interval: number) => { - if (!Number.isInteger(interval)) { - return 'rRule interval must be an integer greater than 0'; - } - }, - min: 1, - }) - ), - until: schema.maybe(schema.string({ validate: validateEndDate })), - count: schema.maybe( - schema.number({ - validate: (count: number) => { - if (!Number.isInteger(count)) { - return 'rRule count must be an integer greater than 0'; - } - }, - min: 1, - }) - ), - byweekday: schema.maybe( - schema.arrayOf( - schema.oneOf([ - schema.literal('MO'), - schema.literal('TU'), - schema.literal('WE'), - schema.literal('TH'), - schema.literal('FR'), - schema.literal('SA'), - schema.literal('SU'), - ]), - { - validate: createValidateRecurrenceBy('byweekday'), - } - ) - ), - bymonthday: schema.maybe( - schema.arrayOf(schema.number({ min: 1, max: 31 }), { - validate: createValidateRecurrenceBy('bymonthday'), - }) - ), - bymonth: schema.maybe( - schema.arrayOf(schema.number({ min: 1, max: 12 }), { - validate: createValidateRecurrenceBy('bymonth'), - }) - ), -}); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/index.ts b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/index.ts index 080598aca88e3..6f5fe18d0539c 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/index.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/index.ts @@ -6,4 +6,3 @@ */ export type { RRule } from './r_rule'; -export type { RRuleRequest } from './r_rule_request'; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/r_rule_request.ts b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/r_rule_request.ts deleted file mode 100644 index 4f90eae946935..0000000000000 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/types/r_rule_request.ts +++ /dev/null @@ -1,10 +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 { TypeOf } from '@kbn/config-schema'; -import { rRuleRequestSchema } from '../schemas/r_rule_request_schema'; - -export type RRuleRequest = TypeOf; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/index.ts b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/index.ts deleted file mode 100644 index f48e8d5bf1c9a..0000000000000 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { validateStartDate } from './validate_start_date'; -export { validateEndDate } from './validate_end_date'; -export { validateRecurrenceBy, createValidateRecurrenceBy } from './validate_recurrence_by'; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_end_date.ts b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_end_date.ts deleted file mode 100644 index 895c1aeea4dfb..0000000000000 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_end_date.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const validateEndDate = (date: string) => { - const parsedValue = Date.parse(date); - if (isNaN(parsedValue)) return `Invalid date: ${date}`; - if (parsedValue <= Date.now()) return `Invalid snooze date as it is in the past: ${date}`; - return; -}; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_recurrence_by.ts b/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_recurrence_by.ts deleted file mode 100644 index 0ab609355c680..0000000000000 --- a/x-pack/platform/plugins/shared/alerting/server/application/r_rule/validation/validate_recurrence_by.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const validateRecurrenceBy = (name: string, array: T[]) => { - if (array.length === 0) { - return `rRule ${name} cannot be empty`; - } -}; - -export const createValidateRecurrenceBy = (name: string) => { - return (array: T[]) => validateRecurrenceBy(name, array); -}; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/schemas/bulk_edit_rules_option_schemas.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/schemas/bulk_edit_rules_option_schemas.ts index 1f3456d8ca02b..8fbf1380790a5 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/schemas/bulk_edit_rules_option_schemas.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/schemas/bulk_edit_rules_option_schemas.ts @@ -5,8 +5,8 @@ * 2.0. */ import { schema } from '@kbn/config-schema'; -import { rRuleRequestSchema } from '../../../../r_rule/schemas'; import { notifyWhenSchema, actionRequestSchema, systemActionRequestSchema } from '../../../schemas'; +import { rRuleRequestSchema } from '../../../../../../common/routes/r_rule'; import { validateDuration } from '../../../validation'; import { validateSnoozeSchedule } from '../validation'; diff --git a/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts index 66c5c432bb370..90cee9c9fec9a 100644 --- a/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_maintenance_window/v1.ts @@ -27,7 +27,7 @@ export const alertsFilterQuerySchema = schema.object({ dsl: schema.maybe(schema.string()), }); -const rRuleSchema = schema.object({ +export const rRuleSchema = schema.object({ dtstart: schema.string(), tzid: schema.string(), freq: schema.maybe( @@ -55,15 +55,17 @@ const rRuleSchema = schema.object({ schema.literal('SU'), ]) ), - byweekday: schema.maybe(schema.arrayOf(schema.oneOf([schema.string(), schema.number()]))), - bymonth: schema.maybe(schema.number()), - bysetpos: schema.maybe(schema.number()), - bymonthday: schema.maybe(schema.number()), - byyearday: schema.maybe(schema.number()), - byweekno: schema.maybe(schema.number()), - byhour: schema.maybe(schema.number()), - byminute: schema.maybe(schema.number()), - bysecond: schema.maybe(schema.number()), + byweekday: schema.maybe( + schema.nullable(schema.arrayOf(schema.oneOf([schema.string(), schema.number()]))) + ), + bymonth: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), + bysetpos: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), + bymonthday: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), + byyearday: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), + byweekno: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), + byhour: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), + byminute: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), + bysecond: schema.maybe(schema.nullable(schema.arrayOf(schema.number()))), }); const rawMaintenanceWindowEventsSchema = schema.object({ diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts index 1b6f32715c9ef..fc2fdf11d8ea9 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts @@ -110,6 +110,71 @@ export default function createMaintenanceWindowTests({ getService }: FtrProvider }); } + describe('rRuleSchema validation', () => { + it('should create maintenance window with byweekday', async () => { + const rrule = { + dtstart: new Date().toISOString(), + tzid: 'UTC', + byweekday: ['+1MO', 'TH'], + }; + + const response = await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + ...createParams, + r_rule: rrule, + }) + .expect(200); + + objectRemover.add('space1', response.body.id, 'rules/maintenance_window', 'alerting', true); + + expect(response.body.r_rule.byweekday).to.eql(rrule.byweekday); + }); + + it('should create maintenance window with bymonth', async () => { + const rrule = { + dtstart: new Date().toISOString(), + tzid: 'UTC', + bymonth: [9, 4], + }; + + const response = await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + ...createParams, + r_rule: rrule, + }) + .expect(200); + + objectRemover.add('space1', response.body.id, 'rules/maintenance_window', 'alerting', true); + + expect(response.body.r_rule.bymonth).to.eql(rrule.bymonth); + }); + + it('should create maintenance window with bymonthday', async () => { + const rrule = { + dtstart: new Date().toISOString(), + tzid: 'UTC', + bymonthday: [1, 30], + }; + + const response = await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + ...createParams, + r_rule: rrule, + }) + .expect(200); + + objectRemover.add('space1', response.body.id, 'rules/maintenance_window', 'alerting', true); + + expect(response.body.r_rule.bymonthday).to.eql(rrule.bymonthday); + }); + }); + it('should create maintenance window with category ids', async () => { const response = await supertest .post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`) From 8894c1f395ce5d30c750db9d5b6c9be4fecac6f7 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Fri, 24 Jan 2025 12:49:42 +0100 Subject: [PATCH 04/13] [Security Solution] Fix incorrect default value for History Window Size (#207827) **Resolves: https://github.com/elastic/kibana/issues/207348** ## Summary This PR resolves a bug in the editable History Window Size component, which incorrectly displays "0 seconds" as the default value instead of "7 days". The issue was caused by passing an array of units `(['m', 'h', 'd'])` using an incorrect prop name. Also, I fixed stuck last digit being stuck when trying to remove it **Before my changes: can't remove or reset the last digit** https://github.com/user-attachments/assets/3a7d28ea-ea71-4ee8-8805-902bae1dc3c1 **After my changes: value resets to minValue on last digit removal** https://github.com/user-attachments/assets/1cba36bb-9127-4197-871e-62b978b3612c Work started on: 22-Jan-2025 --- .../schedule_item_field.test.tsx | 24 +++++++++++++++++++ .../schedule_item_field.tsx | 14 +++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx index 211593652fc6d..90152016a030f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx @@ -37,6 +37,30 @@ describe('ScheduleItemField', () => { expect(mockField.setValue).toHaveBeenCalledWith('5000000s'); }); + it(`uses the "units" prop when it's passed`, async () => { + const mockField = useFormFieldMock({ value: '7d' }); + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="interval"]').last().prop('value')).toEqual(7); + expect(wrapper.find('[data-test-subj="timeType"]').last().prop('value')).toEqual('d'); + + wrapper + .find('[data-test-subj="interval"]') + .last() + .simulate('change', { target: { value: '8' } }); + + expect(mockField.setValue).toHaveBeenCalledWith('8d'); + }); + it.each([ [-10, -5], [-5, 0], diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.tsx index a33251ebfbc57..53d112389fdf8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.tsx @@ -29,7 +29,7 @@ interface ScheduleItemProps { isDisabled?: boolean; minValue?: number; maxValue?: number; - timeTypes?: string[]; + units?: string[]; fullWidth?: boolean; } @@ -47,10 +47,10 @@ export function ScheduleItemField({ idAria, minValue = Number.MIN_SAFE_INTEGER, maxValue = Number.MAX_SAFE_INTEGER, - timeTypes = DEFAULT_TIME_DURATION_UNITS, + units = DEFAULT_TIME_DURATION_UNITS, fullWidth = false, }: ScheduleItemProps): JSX.Element { - const [timeType, setTimeType] = useState(timeTypes[0]); + const [timeType, setTimeType] = useState(units[0]); const [timeVal, setTimeVal] = useState(0); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); const { value, setValue } = field; @@ -84,7 +84,7 @@ export function ScheduleItemField({ const onChangeTimeVal = useCallback>( (e) => { - const number = parseInt(e.target.value, 10); + const number = e.target.value === '' ? minValue : parseInt(e.target.value, 10); if (Number.isNaN(number)) { return; @@ -104,7 +104,7 @@ export function ScheduleItemField({ } const isNegative = value.startsWith('-'); - const durationRegexp = new RegExp(`^\\-?(\\d+)(${timeTypes.join('|')})$`); + const durationRegexp = new RegExp(`^\\-?(\\d+)(${units.join('|')})$`); const durationMatchArray = value.match(durationRegexp); if (!durationMatchArray) { @@ -116,7 +116,7 @@ export function ScheduleItemField({ setTimeVal(time); setTimeType(unit); - }, [timeType, timeTypes, timeVal, value]); + }, [timeType, units, timeVal, value]); const label = useMemo( () => ( @@ -148,7 +148,7 @@ export function ScheduleItemField({ timeTypes.includes(type.value))} + options={timeTypeOptions.filter((type) => units.includes(type.value))} value={timeType} onChange={onChangeTimeType} disabled={isDisabled} From 7c9298c7062307de5da4fae4dbd24cb61b871959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Fri, 24 Jan 2025 13:17:59 +0100 Subject: [PATCH 05/13] [SKA] Move orphan README (#208167) --- src/{plugins => platform/plugins/shared}/vis_types/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{plugins => platform/plugins/shared}/vis_types/README.md (100%) diff --git a/src/plugins/vis_types/README.md b/src/platform/plugins/shared/vis_types/README.md similarity index 100% rename from src/plugins/vis_types/README.md rename to src/platform/plugins/shared/vis_types/README.md From e394abf6e922622e13d73ab547cccec89373a0f1 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Fri, 24 Jan 2025 13:18:41 +0100 Subject: [PATCH 06/13] [EDR Workflows] Fix not aligned text for RunScript (#208101) --- .../security_solution/public/management/common/translations.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts index 50885a9db6b6a..b4ca5d6250323 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts @@ -266,8 +266,7 @@ export const CROWDSTRIKE_CONSOLE_COMMANDS = { defaultMessage: 'Run a script on the host', }), helpUsage: i18n.translate('xpack.securitySolution.crowdStrikeConsoleCommands.runscript.about', { - defaultMessage: ` -Command Examples for Running Scripts: + defaultMessage: `Command Examples for Running Scripts: 1. Executes a script saved in the CrowdStrike cloud with the specified command-line arguments. From 86666bf790c87ee8bde353d53dcf5413e80e50e5 Mon Sep 17 00:00:00 2001 From: Agustina Nahir Ruidiaz <61565784+agusruidiazgd@users.noreply.github.com> Date: Fri, 24 Jan 2025 13:47:41 +0100 Subject: [PATCH 07/13] [Security Solution] Connector selector onboarding (#203742) ## Summary Summarize your PR. If it involves visual changes include a screenshot or gif. https://github.com/user-attachments/assets/6d7527d1-dc8d-4f3a-9b03-cfd0022701d2 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + tsconfig.base.json | 2 + .../security/packages/connectors/index.ts | 9 + .../packages/connectors/jest.config.js | 12 ++ .../security/packages/connectors/kibana.jsonc | 9 + .../security/packages/connectors/package.json | 7 + .../src/connector_selector.styles.ts | 41 ++++ .../connectors/src/connector_selector.tsx | 145 +++++++++++++ .../packages/connectors/src/constants.ts | 8 + .../packages/connectors/src/translations.ts | 36 ++++ .../packages/connectors/tsconfig.json | 19 ++ .../components/hooks/use_stored_state.ts | 7 + .../cards/assistant/assistant_card.tsx | 113 +++++++++- .../cards/assistant/translations.ts | 2 +- .../common/connectors/connector_cards.tsx | 150 ++++--------- .../connectors/connector_selector_panel.tsx | 50 +++++ .../connector_selector_with_icon.tsx | 118 +++++++++++ .../common/connectors/connector_setup.tsx | 197 +++++++----------- .../connectors/create_connector_popover.tsx | 59 ------ .../connectors/hooks/use_load_action_types.ts | 6 +- .../cards/common/connectors/translations.ts | 17 +- .../cards/common/connectors/types.ts | 15 ++ .../ai_connector/ai_connector_card.tsx | 15 +- .../plugins/security_solution/tsconfig.json | 1 + yarn.lock | 4 + 26 files changed, 740 insertions(+), 304 deletions(-) create mode 100644 x-pack/solutions/security/packages/connectors/index.ts create mode 100644 x-pack/solutions/security/packages/connectors/jest.config.js create mode 100644 x-pack/solutions/security/packages/connectors/kibana.jsonc create mode 100644 x-pack/solutions/security/packages/connectors/package.json create mode 100644 x-pack/solutions/security/packages/connectors/src/connector_selector.styles.ts create mode 100644 x-pack/solutions/security/packages/connectors/src/connector_selector.tsx create mode 100644 x-pack/solutions/security/packages/connectors/src/constants.ts create mode 100644 x-pack/solutions/security/packages/connectors/src/translations.ts create mode 100644 x-pack/solutions/security/packages/connectors/tsconfig.json create mode 100644 x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_panel.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_with_icon.tsx delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/create_connector_popover.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fce259dc7edb6..08146425bd3a4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -965,6 +965,7 @@ x-pack/solutions/search/plugins/search_playground @elastic/search-kibana x-pack/solutions/search/plugins/search_solution/search_navigation @elastic/search-kibana x-pack/solutions/search/plugins/search_synonyms @elastic/search-kibana x-pack/solutions/search/plugins/serverless_search @elastic/search-kibana +x-pack/solutions/security/packages/connectors @elastic/security-threat-hunting-explore x-pack/solutions/security/packages/data_table @elastic/security-threat-hunting-investigations x-pack/solutions/security/packages/data-stream-adapter @elastic/security-threat-hunting x-pack/solutions/security/packages/distribution_bar @elastic/kibana-cloud-security-posture diff --git a/package.json b/package.json index 0c773fe1b71ba..d13941bda9a76 100644 --- a/package.json +++ b/package.json @@ -822,6 +822,7 @@ "@kbn/security-plugin-types-public": "link:x-pack/platform/packages/shared/security/plugin_types_public", "@kbn/security-plugin-types-server": "link:x-pack/platform/packages/shared/security/plugin_types_server", "@kbn/security-role-management-model": "link:x-pack/platform/packages/private/security/role_management_model", + "@kbn/security-solution-connectors": "link:x-pack/solutions/security/packages/connectors", "@kbn/security-solution-distribution-bar": "link:x-pack/solutions/security/packages/distribution_bar", "@kbn/security-solution-ess": "link:x-pack/solutions/security/plugins/security_solution_ess", "@kbn/security-solution-features": "link:x-pack/solutions/security/packages/features", diff --git a/tsconfig.base.json b/tsconfig.base.json index d01f6976835eb..54671599879c5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1632,6 +1632,8 @@ "@kbn/security-plugin-types-server/*": ["x-pack/platform/packages/shared/security/plugin_types_server/*"], "@kbn/security-role-management-model": ["x-pack/platform/packages/private/security/role_management_model"], "@kbn/security-role-management-model/*": ["x-pack/platform/packages/private/security/role_management_model/*"], + "@kbn/security-solution-connectors": ["x-pack/solutions/security/packages/connectors"], + "@kbn/security-solution-connectors/*": ["x-pack/solutions/security/packages/connectors/*"], "@kbn/security-solution-distribution-bar": ["x-pack/solutions/security/packages/distribution_bar"], "@kbn/security-solution-distribution-bar/*": ["x-pack/solutions/security/packages/distribution_bar/*"], "@kbn/security-solution-ess": ["x-pack/solutions/security/plugins/security_solution_ess"], diff --git a/x-pack/solutions/security/packages/connectors/index.ts b/x-pack/solutions/security/packages/connectors/index.ts new file mode 100644 index 0000000000000..01eae1b554656 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/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 { ConnectorSelector } from './src/connector_selector'; +export type { ConnectorSelectorProps } from './src/connector_selector'; diff --git a/x-pack/solutions/security/packages/connectors/jest.config.js b/x-pack/solutions/security/packages/connectors/jest.config.js new file mode 100644 index 0000000000000..a56ddf1903cc2 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + roots: ['/x-pack/solutions/security/packages/connectors'], + rootDir: '../../../../..', +}; diff --git a/x-pack/solutions/security/packages/connectors/kibana.jsonc b/x-pack/solutions/security/packages/connectors/kibana.jsonc new file mode 100644 index 0000000000000..b3f8ff1d76925 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/kibana.jsonc @@ -0,0 +1,9 @@ +{ + "type": "shared-browser", + "id": "@kbn/security-solution-connectors", + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/solutions/security/packages/connectors/package.json b/x-pack/solutions/security/packages/connectors/package.json new file mode 100644 index 0000000000000..7b906bac6b9a4 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/security-solution-connectors", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0", + "sideEffects": false +} \ No newline at end of file diff --git a/x-pack/solutions/security/packages/connectors/src/connector_selector.styles.ts b/x-pack/solutions/security/packages/connectors/src/connector_selector.styles.ts new file mode 100644 index 0000000000000..b2d90aee0b3a1 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/src/connector_selector.styles.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 { useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; + +export const useConnectorSelectorStyles = () => { + const { euiTheme } = useEuiTheme(); + + return { + placeholder: css` + color: ${euiTheme.colors.primary}; + margin-right: ${euiTheme.size.xs}; + `, + optionDisplay: css` + margin-right: 8px; + overflow: hidden; + text-overflow: ellipsis; + `, + offset: css` + width: 24px; + `, + inputContainer: css` + .euiSuperSelectControl { + border: none; + box-shadow: none; + background: none; + padding-left: 0; + } + + .euiFormControlLayoutIcons { + right: 14px; + top: 2px; + } + `, + }; +}; diff --git a/x-pack/solutions/security/packages/connectors/src/connector_selector.tsx b/x-pack/solutions/security/packages/connectors/src/connector_selector.tsx new file mode 100644 index 0000000000000..5824e1486c9e1 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/src/connector_selector.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiSuperSelect, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { some } from 'lodash'; +import * as i18n from './translations'; +import { useConnectorSelectorStyles } from './connector_selector.styles'; +import { ADD_NEW_CONNECTOR } from './constants'; + +export interface ConnectorDetails { + id: string; + name: string; + description: string; +} + +export interface ConnectorSelectorProps { + connectors: ConnectorDetails[]; + onChange: (connectorId: string) => void; + selectedId?: string; + onNewConnectorClicked?: () => void; + isDisabled?: boolean; +} + +export const ConnectorSelector = React.memo( + ({ connectors, onChange, selectedId, onNewConnectorClicked, isDisabled }) => { + const styles = useConnectorSelectorStyles(); + const { euiTheme } = useEuiTheme(); + + const addNewConnectorOption = useMemo(() => { + return { + value: ADD_NEW_CONNECTOR, + inputDisplay: i18n.ADD_NEW_CONNECTOR, + dropdownDisplay: ( + + + + {i18n.ADD_NEW_CONNECTOR} + + + + {/* Right offset to compensate for 'selected' icon of EuiSuperSelect since native footers aren't supported*/} +
+ + + ), + }; + }, [isDisabled, styles.offset]); + + const connectorExists = useMemo( + () => some(connectors, ['id', selectedId]), + [connectors, selectedId] + ); + + const mappedConnectorOptions = connectors.map((connector) => ({ + value: connector.id, + 'data-test-subj': connector.id, + inputDisplay: ( + + {connector.name} + + ), + dropdownDisplay: ( + + + + {connector.name} + +

{connector.description}

+
+
+
+
+ ), + })); + + const allConnectorOptions = useMemo( + () => + onNewConnectorClicked + ? [...mappedConnectorOptions, addNewConnectorOption] + : [...mappedConnectorOptions], + [onNewConnectorClicked, mappedConnectorOptions, addNewConnectorOption] + ); + + const onChangeConnector = useCallback( + (connectorId: string) => { + if (connectorId === ADD_NEW_CONNECTOR) { + onNewConnectorClicked?.(); + return; + } + onChange(connectorId); + }, + [onChange, onNewConnectorClicked] + ); + + return ( +
+ {!connectorExists && !connectors.length ? ( + onNewConnectorClicked?.()} + > + {i18n.ADD_CONNECTOR} + + ) : ( + + )} +
+ ); + } +); + +ConnectorSelector.displayName = 'ConnectorSelector'; diff --git a/x-pack/solutions/security/packages/connectors/src/constants.ts b/x-pack/solutions/security/packages/connectors/src/constants.ts new file mode 100644 index 0000000000000..f9a41a7e7d16d --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/src/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ADD_NEW_CONNECTOR = 'ADD_NEW_CONNECTOR'; diff --git a/x-pack/solutions/security/packages/connectors/src/translations.ts b/x-pack/solutions/security/packages/connectors/src/translations.ts new file mode 100644 index 0000000000000..9356a08c06d8a --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/src/translations.ts @@ -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 { i18n } from '@kbn/i18n'; + +export const CONNECTOR_SELECTOR_TITLE = i18n.translate( + 'securitySolutionPackages.connectors.connectorSelector.ariaLabel', + { + defaultMessage: 'Connector Selector', + } +); + +export const ADD_NEW_CONNECTOR = i18n.translate( + 'securitySolutionPackages.connectors.connectorSelector.newConnectorOptions', + { + defaultMessage: 'Add new Connector...', + } +); + +export const ADD_CONNECTOR = i18n.translate( + 'securitySolutionPackages.connectors.connectorSelector.addConnectorButtonLabel', + { + defaultMessage: 'Add connector', + } +); + +export const CONNECTOR_SELECTOR_PLACEHOLDER = i18n.translate( + 'securitySolutionPackages.connectors.connectorSelectorInline.connectorPlaceholder', + { + defaultMessage: 'Select a connector', + } +); diff --git a/x-pack/solutions/security/packages/connectors/tsconfig.json b/x-pack/solutions/security/packages/connectors/tsconfig.json new file mode 100644 index 0000000000000..f93f3b17903b0 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop", + "@testing-library/jest-dom", + "@testing-library/react", + ] + }, + "include": ["**/*.ts", "**/*.tsx"], + "kbn_references": [ + "@kbn/i18n", + ], + "exclude": ["target/**/*"] +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/hooks/use_stored_state.ts b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/hooks/use_stored_state.ts index 22867e1199705..98f865741d4a9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/hooks/use_stored_state.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/hooks/use_stored_state.ts @@ -19,6 +19,7 @@ const LocalStorageKey = { selectedIntegrationTabId: 'securitySolution.onboarding.selectedIntegrationTabId', selectedCardItemId: 'securitySolution.onboarding.selectedCardItem', integrationSearchTerm: 'securitySolution.onboarding.integrationSearchTerm', + assistantConnectorId: 'securitySolution.onboarding.assistantCard.connectorId', } as const; /** @@ -80,3 +81,9 @@ export const useStoredIntegrationSearchTerm = (spaceId: string) => `${LocalStorageKey.integrationSearchTerm}.${spaceId}`, null ); + +/** + * Stores the integration search term per space + */ +export const useStoredAssistantConnectorId = (spaceId: string) => + useDefinedLocalStorage(`${LocalStorageKey.assistantConnectorId}.${spaceId}`, null); diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx index c971bd2a6f0b7..10e3690a63a32 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx @@ -8,15 +8,25 @@ import React, { useCallback, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink } from '@elastic/eui'; import { css } from '@emotion/css'; +import { useAssistantContext, type Conversation } from '@kbn/elastic-assistant'; +import { useCurrentConversation } from '@kbn/elastic-assistant/impl/assistant/use_current_conversation'; +import { useDataStreamApis } from '@kbn/elastic-assistant/impl/assistant/use_data_stream_apis'; +import { getDefaultConnector } from '@kbn/elastic-assistant/impl/assistant/helpers'; +import { getGenAiConfig } from '@kbn/elastic-assistant/impl/connectorland/helpers'; +import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conversation'; +import { CenteredLoadingSpinner } from '../../../../../common/components/centered_loading_spinner'; import { OnboardingCardId } from '../../../../constants'; import type { OnboardingCardComponent } from '../../../../types'; import * as i18n from './translations'; +import { useStoredAssistantConnectorId } from '../../../hooks/use_stored_state'; +import { useOnboardingContext } from '../../../onboarding_context'; import { OnboardingCardContentPanel } from '../common/card_content_panel'; import { ConnectorCards } from '../common/connectors/connector_cards'; import { CardCallOut } from '../common/card_callout'; import { CardSubduedText } from '../common/card_subdued_text'; import type { AssistantCardMetadata } from './types'; import { MissingPrivilegesCallOut } from '../common/connectors/missing_privileges'; +import type { AIConnector } from '../common/connectors/types'; export const AssistantCard: OnboardingCardComponent = ({ isCardComplete, @@ -25,6 +35,9 @@ export const AssistantCard: OnboardingCardComponent = ({ checkComplete, isCardAvailable, }) => { + const { spaceId } = useOnboardingContext(); + const { connectors, canExecuteConnectors, canCreateConnectors } = checkCompleteMetadata ?? {}; + const isIntegrationsCardComplete = useMemo( () => isCardComplete(OnboardingCardId.integrations), [isCardComplete] @@ -39,9 +52,99 @@ export const AssistantCard: OnboardingCardComponent = ({ setExpandedCardId(OnboardingCardId.integrations, { scroll: true }); }, [setExpandedCardId]); - const connectors = checkCompleteMetadata?.connectors; - const canExecuteConnectors = checkCompleteMetadata?.canExecuteConnectors; - const canCreateConnectors = checkCompleteMetadata?.canCreateConnectors; + const [selectedConnectorId, setSelectedConnectorId] = useStoredAssistantConnectorId(spaceId); + + const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); + + const { setApiConfig } = useConversation(); + + const { + http, + assistantAvailability: { isAssistantEnabled }, + baseConversations, + getLastConversationId, + } = useAssistantContext(); + const { + allSystemPrompts, + conversations, + isFetchedCurrentUserConversations, + isFetchedPrompts, + refetchCurrentUserConversations, + } = useDataStreamApis({ http, baseConversations, isAssistantEnabled }); + + const { currentConversation, handleOnConversationSelected } = useCurrentConversation({ + allSystemPrompts, + conversations, + defaultConnector, + refetchCurrentUserConversations, + conversationId: getLastConversationId(), + mayUpdateConversations: + isFetchedCurrentUserConversations && + isFetchedPrompts && + Object.keys(conversations).length > 0, + }); + + const onConversationChange = useCallback( + (updatedConversation: Conversation) => { + handleOnConversationSelected({ + cId: updatedConversation.id, + cTitle: updatedConversation.title, + }); + }, + [handleOnConversationSelected] + ); + + const onConnectorSelected = useCallback( + async (connector: AIConnector) => { + const connectorId = connector.id; + + const config = getGenAiConfig(connector); + const apiProvider = config?.apiProvider; + const model = config?.defaultModel; + + if (currentConversation != null) { + const conversation = await setApiConfig({ + conversation: currentConversation, + apiConfig: { + ...currentConversation.apiConfig, + actionTypeId: connector.actionTypeId, + connectorId, + // With the inline component, prefer config args to handle 'new connector' case + provider: apiProvider, + model, + }, + }); + + if (conversation && onConversationChange != null) { + onConversationChange(conversation); + } + } + + if (selectedConnectorId != null) { + setSelectedConnectorId(connectorId); + } + }, + [ + currentConversation, + selectedConnectorId, + setApiConfig, + onConversationChange, + setSelectedConnectorId, + ] + ); + + if (!checkCompleteMetadata) { + return ( + + + + ); + } + + const onNewConnectorSaved = (connectorId: string) => { + checkComplete(); + setSelectedConnectorId(connectorId); + }; return ( @@ -77,7 +180,9 @@ export const AssistantCard: OnboardingCardComponent = ({ )} diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/translations.ts index 1c526d4974a9a..a0f1a933ea939 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/translations.ts @@ -18,7 +18,7 @@ export const ASSISTANT_CARD_DESCRIPTION = i18n.translate( 'xpack.securitySolution.onboarding.assistantCard.description', { defaultMessage: - 'Choose and configure any AI provider available to use with Elastic AI Assistant.', + 'The Elastic AI connector is currently configured, powered by OpenAI gpt 4.0 for optimal performance for migrating SIEM rules. However,any AI service provider can be configured. Read more about AI provider performance and other Elastic powered by AI features.', } ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_cards.tsx index b8b51198c75ff..7c7ba2b31557a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_cards.tsx @@ -6,42 +6,39 @@ */ import React, { useCallback } from 'react'; -import { type AIConnector } from '@kbn/elastic-assistant/impl/connectorland/connector_selector'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiLoadingSpinner, - EuiText, - EuiBadge, - EuiSpacer, - EuiCallOut, - useEuiTheme, -} from '@elastic/eui'; -import { css } from '@emotion/css'; -import { useKibana } from '../../../../../../common/lib/kibana'; -import { - CreateConnectorPopover, - type CreateConnectorPopoverProps, -} from './create_connector_popover'; -import { ConnectorSetup } from './connector_setup'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiCallOut } from '@elastic/eui'; +import { css } from '@emotion/react'; +import type { AIConnector } from './types'; import * as i18n from './translations'; import { MissingPrivilegesDescription } from './missing_privileges'; +import { ConnectorSetup } from './connector_setup'; +import { ConnectorSelectorPanel } from './connector_selector_panel'; -interface ConnectorCardsProps - extends CreateConnectorPopoverProps, - Omit { +interface ConnectorCardsProps { + onNewConnectorSaved: (connectorId: string) => void; + canCreateConnectors?: boolean; connectors?: AIConnector[]; // make connectors optional to handle loading state + selectedConnectorId?: string | null; + onConnectorSelected: (connector: AIConnector) => void; } export const ConnectorCards = React.memo( ({ connectors, - onConnectorSaved, + onNewConnectorSaved, canCreateConnectors, selectedConnectorId, - setSelectedConnectorId, + onConnectorSelected, }) => { + const onNewConnectorStoredSave = useCallback( + (newConnector: AIConnector) => { + onNewConnectorSaved(newConnector.id); + // default select the new connector created + onConnectorSelected(newConnector); + }, + [onConnectorSelected, onNewConnectorSaved] + ); + if (!connectors) { return ; } @@ -59,95 +56,26 @@ export const ConnectorCards = React.memo( return ( <> - {hasConnectors ? ( - <> - - - - - ) : ( - - )} + + {hasConnectors && ( + + + + )} + + + + ); } ); ConnectorCards.displayName = 'ConnectorCards'; - -interface ConnectorListProps { - connectors: AIConnector[]; - selectedConnectorId?: string | null; - setSelectedConnectorId?: (id: string) => void; -} - -const ConnectorList = React.memo( - ({ connectors, selectedConnectorId, setSelectedConnectorId }) => { - const { euiTheme } = useEuiTheme(); - const { actionTypeRegistry } = useKibana().services.triggersActionsUi; - const onConnectorClick = useCallback( - (id: string) => { - setSelectedConnectorId?.(id); - }, - [setSelectedConnectorId] - ); - - const selectedCss = `border: 2px solid ${euiTheme.colors.primary};`; - - return ( - - {connectors.map((connector) => ( - - onConnectorClick(connector.id) : undefined} - css={css` - ${selectedConnectorId === connector.id ? selectedCss : ''} - `} - color={selectedConnectorId === connector.id ? 'primary' : 'plain'} - > - - - {connector.name} - - - - {actionTypeRegistry.get(connector.actionTypeId).actionTypeTitle} - - - - - - ))} - - ); - } -); - -ConnectorList.displayName = 'ConnectorList'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_panel.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_panel.tsx new file mode 100644 index 0000000000000..149b65e62a458 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_panel.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { ConnectorSelectorWithIcon } from './connector_selector_with_icon'; +import * as i18n from './translations'; +import type { AIConnector } from './types'; + +interface ConnectorSelectorPanelProps { + connectors: AIConnector[]; + selectedConnectorId?: string | null; + onConnectorSelected: (connector: AIConnector) => void; +} + +export const ConnectorSelectorPanel = React.memo( + ({ connectors, selectedConnectorId, onConnectorSelected }) => { + return ( + + + + {i18n.SELECTED_PROVIDER} + + + + + + + ); + } +); + +ConnectorSelectorPanel.displayName = 'ConnectorSelectorPanel'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_with_icon.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_with_icon.tsx new file mode 100644 index 0000000000000..bbfbd56f2fac7 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_with_icon.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; +import React, { useMemo, useEffect, useCallback } from 'react'; +import { useAssistantContext } from '@kbn/elastic-assistant'; +import { ConnectorSelector } from '@kbn/security-solution-connectors'; +import { + getActionTypeTitle, + getGenAiConfig, +} from '@kbn/elastic-assistant/impl/connectorland/helpers'; +import { css } from '@emotion/react'; +import type { AIConnector } from './types'; +import { useFilteredActionTypes } from './hooks/use_load_action_types'; +import * as i18n from './translations'; + +interface Props { + isDisabled?: boolean; + selectedConnectorId?: string | null; + connectors: AIConnector[]; + onConnectorSelected: (connector: AIConnector) => void; +} + +/** + * A compact wrapper of the ConnectorSelector with a Selected Icon + */ +export const ConnectorSelectorWithIcon = React.memo( + ({ isDisabled = false, selectedConnectorId, connectors, onConnectorSelected }) => { + const { actionTypeRegistry, assistantAvailability } = useAssistantContext(); + + const actionTypes = useFilteredActionTypes(); + + const selectedConnector = useMemo( + () => connectors.find((connector) => connector.id === selectedConnectorId), + [connectors, selectedConnectorId] + ); + + useEffect(() => { + if (connectors.length === 1) { + onConnectorSelected(connectors[0]); + } + }, [connectors, onConnectorSelected]); + + const localIsDisabled = isDisabled || !assistantAvailability.hasConnectorsReadPrivilege; + + const connectorOptions = useMemo( + () => + (connectors ?? []).map((connector) => { + const connectorTypeTitle = + getGenAiConfig(connector)?.apiProvider ?? + getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId)); + const connectorDetails = connector.isPreconfigured + ? i18n.PRECONFIGURED_CONNECTOR + : connectorTypeTitle; + + return { + id: connector.id, + name: connector.name, + description: connectorDetails, + }; + }), + [actionTypeRegistry, connectors] + ); + + const onConnectorSelectionChange = useCallback( + (connectorId: string) => { + const connector = (connectors ?? []).find((c) => c.id === connectorId); + if (connector) { + onConnectorSelected(connector); + } + }, + [connectors, onConnectorSelected] + ); + + if (!actionTypes) { + return ; + } + + return ( + + {selectedConnector && ( + + + + )} + {selectedConnectorId && ( + + + + )} + + ); + } +); + +ConnectorSelectorWithIcon.displayName = 'ConnectorSelectorWithIcon'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_setup.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_setup.tsx index 96b4b9da4d6a5..16086bf5eb73d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_setup.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_setup.tsx @@ -6,142 +6,105 @@ */ import React, { useCallback, useState } from 'react'; +import { type ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/common/constants'; import { - useEuiTheme, EuiFlexGroup, EuiFlexItem, - EuiListGroup, EuiIcon, EuiPanel, EuiLoadingSpinner, - EuiText, - EuiLink, - EuiTextColor, + EuiButton, } from '@elastic/eui'; import { css } from '@emotion/css'; -import { - ConnectorAddModal, - type ActionConnector, -} from '@kbn/triggers-actions-ui-plugin/public/common/constants'; import type { ActionType } from '@kbn/actions-plugin/common'; +import { AddConnectorModal } from '@kbn/elastic-assistant/impl/connectorland/add_connector_modal'; +import * as i18n from './translations'; import { useKibana } from '../../../../../../common/lib/kibana'; import { useFilteredActionTypes } from './hooks/use_load_action_types'; -const usePanelCss = () => { - const { euiTheme } = useEuiTheme(); - return css` - .connectorSelectorPanel { - height: 160px; - &.euiPanel:hover { - background-color: ${euiTheme.colors.backgroundBaseSubdued}; - } - } - `; -}; - interface ConnectorSetupProps { onConnectorSaved?: (savedAction: ActionConnector) => void; onClose?: () => void; - compressed?: boolean; } -export const ConnectorSetup = React.memo( - ({ onConnectorSaved, onClose, compressed = false }) => { - const panelCss = usePanelCss(); - const { - http, - triggersActionsUi: { actionTypeRegistry }, - notifications: { toasts }, - } = useKibana().services; - const [selectedActionType, setSelectedActionType] = useState(null); - - const onModalClose = useCallback(() => { - setSelectedActionType(null); - onClose?.(); - }, [onClose]); +export const ConnectorSetup = React.memo(({ onConnectorSaved, onClose }) => { + const [isModalVisible, setIsModalVisible] = useState(false); + const [selectedActionType, setSelectedActionType] = useState(null); - const actionTypes = useFilteredActionTypes(http, toasts); + const { + triggersActionsUi: { actionTypeRegistry }, + } = useKibana().services; - if (!actionTypes) { - return ; - } + const onModalClose = useCallback(() => { + setSelectedActionType(null); + setIsModalVisible(false); + onClose?.(); + }, [onClose]); - return ( - <> - {compressed ? ( - ({ - key: actionType.id, - id: actionType.id, - label: actionType.name, - size: 's', - icon: ( - - ), - isDisabled: !actionType.enabled, - onClick: () => setSelectedActionType(actionType), - }))} - /> - ) : ( - - {actionTypes.map((actionType: ActionType) => ( - - setSelectedActionType(actionType)} - data-test-subj={`actionType-${actionType.id}`} - className={panelCss} - > - - - - - - - - {actionType.name} - - - - - - - ))} - - )} + const actionTypes = useFilteredActionTypes(); - {selectedActionType && ( - - )} - - ); + if (!actionTypes) { + return ; } -); + + return ( + <> + + + + + {actionTypes.map((actionType: ActionType) => ( + + + + + + + + ))} + + + + setIsModalVisible(true)} + isLoading={false} + > + {i18n.CREATE_NEW_CONNECTOR_BUTTON} + + + + + {isModalVisible && onConnectorSaved && ( + + )} + + ); +}); ConnectorSetup.displayName = 'ConnectorSetup'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/create_connector_popover.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/create_connector_popover.tsx deleted file mode 100644 index c6c378fc8e29f..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/create_connector_popover.tsx +++ /dev/null @@ -1,59 +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, { useCallback, useState } from 'react'; -import { css } from '@emotion/css'; -import { EuiPopover, EuiLink, EuiText } from '@elastic/eui'; -import { ConnectorSetup } from './connector_setup'; -import * as i18n from './translations'; -import { MissingPrivilegesTooltip } from './missing_privileges'; - -export interface CreateConnectorPopoverProps { - onConnectorSaved: () => void; - canCreateConnectors?: boolean; -} - -export const CreateConnectorPopover = React.memo( - ({ onConnectorSaved, canCreateConnectors }) => { - const [isOpen, setIsPopoverOpen] = useState(false); - const closePopover = useCallback(() => setIsPopoverOpen(false), []); - - const onButtonClick = useCallback( - () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen), - [] - ); - if (!canCreateConnectors) { - return ( - - - {i18n.ASSISTANT_CARD_CREATE_NEW_CONNECTOR_POPOVER} - - - ); - } - - return ( - - - {i18n.ASSISTANT_CARD_CREATE_NEW_CONNECTOR_POPOVER} - - - } - isOpen={isOpen} - closePopover={closePopover} - data-test-subj="createConnectorPopover" - > - - - ); - } -); -CreateConnectorPopover.displayName = 'CreateConnectorPopover'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/hooks/use_load_action_types.ts b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/hooks/use_load_action_types.ts index 48b8fdfc20d59..5c1c18df71a45 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/hooks/use_load_action_types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/hooks/use_load_action_types.ts @@ -7,11 +7,11 @@ import { useMemo } from 'react'; import { useLoadActionTypes as loadActionTypes } from '@kbn/elastic-assistant/impl/connectorland/use_load_action_types'; -import type { HttpSetup } from '@kbn/core-http-browser'; -import type { IToasts } from '@kbn/core-notifications-browser'; +import { useAssistantContext } from '@kbn/elastic-assistant'; import { AIActionTypeIds } from '../constants'; -export const useFilteredActionTypes = (http: HttpSetup, toasts: IToasts) => { +export const useFilteredActionTypes = () => { + const { http, toasts } = useAssistantContext(); const { data } = loadActionTypes({ http, toasts }); return useMemo(() => data?.filter(({ id }) => AIActionTypeIds.includes(id)), [data]); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/translations.ts index 983a6a67f5b32..a9ba5ac647ff8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/translations.ts @@ -7,10 +7,16 @@ import { i18n } from '@kbn/i18n'; -export const ASSISTANT_CARD_CREATE_NEW_CONNECTOR_POPOVER = i18n.translate( +export const CREATE_NEW_CONNECTOR_BUTTON = i18n.translate( 'xpack.securitySolution.onboarding.assistantCard.createNewConnectorPopover', { - defaultMessage: 'Create new connector', + defaultMessage: 'AI service provider', + } +); +export const SELECTED_PROVIDER = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.selectedProvider', + { + defaultMessage: 'Selected provider', } ); @@ -21,6 +27,13 @@ export const PRIVILEGES_MISSING_TITLE = i18n.translate( } ); +export const PRECONFIGURED_CONNECTOR = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.preconfiguredTitle', + { + defaultMessage: 'Preconfigured', + } +); + export const PRIVILEGES_REQUIRED_TITLE = i18n.translate( 'xpack.securitySolution.onboarding.assistantCard.requiredPrivileges', { diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/types.ts b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/types.ts new file mode 100644 index 0000000000000..0f15a8132210c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/types.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 type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; + +import type { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; + +export type AIConnector = ActionConnector & { + // related to OpenAI connectors, ex: Azure OpenAI, OpenAI + apiProvider?: OpenAiProviderType; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/siem_migrations/ai_connector/ai_connector_card.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/siem_migrations/ai_connector/ai_connector_card.tsx index 1786c9cbee85c..ccb98aef00c84 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/siem_migrations/ai_connector/ai_connector_card.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/siem_migrations/ai_connector/ai_connector_card.tsx @@ -17,6 +17,7 @@ import { ConnectorCards } from '../../common/connectors/connector_cards'; import { CardSubduedText } from '../../common/card_subdued_text'; import type { AIConnectorCardMetadata } from './types'; import { MissingPrivilegesCallOut } from '../../common/connectors/missing_privileges'; +import type { AIConnector } from '../../common/connectors/types'; export const AIConnectorCard: OnboardingCardComponent = ({ checkCompleteMetadata, @@ -24,13 +25,13 @@ export const AIConnectorCard: OnboardingCardComponent = setComplete, }) => { const { siemMigrations } = useKibana().services; - const [storedConnectorId, setStoredConnectorId] = useDefinedLocalStorage( + const [storedConnectorId, setStoredConnectorId] = useDefinedLocalStorage( siemMigrations.rules.connectorIdStorage.key, - null + '' ); - const setSelectedConnectorId = useCallback( - (connectorId: string) => { - setStoredConnectorId(connectorId); + const setSelectedConnector = useCallback( + (connector: AIConnector) => { + setStoredConnectorId(connector.id); setComplete(true); }, [setComplete, setStoredConnectorId] @@ -57,9 +58,9 @@ export const AIConnectorCard: OnboardingCardComponent = diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index c84de904c249f..5ab84a11506ff 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -230,6 +230,7 @@ "@kbn/react-hooks", "@kbn/index-adapter", "@kbn/core-http-server-utils", + "@kbn/security-solution-connectors", "@kbn/core-chrome-browser-mocks", "@kbn/ai-assistant-icon", "@kbn/llm-tasks-plugin", diff --git a/yarn.lock b/yarn.lock index 88bd797a2f337..39416aeeaf54c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7124,6 +7124,10 @@ version "0.0.0" uid "" +"@kbn/security-solution-connectors@link:x-pack/solutions/security/packages/connectors": + version "0.0.0" + uid "" + "@kbn/security-solution-distribution-bar@link:x-pack/solutions/security/packages/distribution_bar": version "0.0.0" uid "" From c22b9828a6551ea5205a025f1c5ace4046bdc66c Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Fri, 24 Jan 2025 13:50:19 +0100 Subject: [PATCH 08/13] [inference] add support for `auto` function calling mode (#208144) ## Summary Fix https://github.com/elastic/kibana/issues/208143 Add a new value for the `functionCalling` parameter, `auto`, which is the new default. When `functionCalling=auto`, the system will detect if the underlying model/provider supports native function calling, and otherwise automatically fallback to simulated function calling. --- .../inference-common/src/chat_complete/api.ts | 9 +-- .../inference-common/src/output/api.ts | 2 +- .../inference/inference_adapter.test.mocks.ts | 16 +++++ .../inference/inference_adapter.test.ts | 50 +++++++++++---- .../adapters/inference/inference_adapter.ts | 12 ++-- .../openai/openai_adapter.test.mocks.ts | 16 +++++ .../adapters/openai/openai_adapter.test.ts | 35 ++++++++-- .../adapters/openai/openai_adapter.ts | 12 ++-- .../chat_complete/adapters/openai/types.ts | 8 +++ .../utils/function_calling_support.test.ts | 64 +++++++++++++++++++ .../utils/function_calling_support.ts | 26 ++++++++ .../server/chat_complete/utils/index.ts | 1 + .../inference/server/routes/chat_complete.ts | 2 +- 13 files changed, 221 insertions(+), 32 deletions(-) create mode 100644 x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.test.mocks.ts create mode 100644 x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.mocks.ts create mode 100644 x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.test.ts create mode 100644 x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.ts diff --git a/x-pack/platform/packages/shared/ai-infra/inference-common/src/chat_complete/api.ts b/x-pack/platform/packages/shared/ai-infra/inference-common/src/chat_complete/api.ts index 15596c32066e1..155de9b286c9b 100644 --- a/x-pack/platform/packages/shared/ai-infra/inference-common/src/chat_complete/api.ts +++ b/x-pack/platform/packages/shared/ai-infra/inference-common/src/chat_complete/api.ts @@ -102,7 +102,7 @@ export type ChatCompleteOptions< */ modelName?: string; /** - * Function calling mode, defaults to "native". + * Function calling mode, defaults to "auto". */ functionCalling?: FunctionCallingMode; /** @@ -152,7 +152,8 @@ export interface ChatCompleteResponse { + const actual = jest.requireActual('../../utils/function_calling_support'); + return { + ...actual, + isNativeFunctionCallingSupported: isNativeFunctionCallingSupportedMock, + }; +}); diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.test.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.test.ts index 68733c8f4dae2..65558d68bc90c 100644 --- a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.test.ts +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.test.ts @@ -5,13 +5,14 @@ * 2.0. */ +import { isNativeFunctionCallingSupportedMock } from './inference_adapter.test.mocks'; import OpenAI from 'openai'; import { v4 } from 'uuid'; import { PassThrough } from 'stream'; import { lastValueFrom, Subject, toArray, filter } from 'rxjs'; -import type { Logger } from '@kbn/logging'; import { loggerMock } from '@kbn/logging-mocks'; import { + ToolChoiceType, ChatCompletionEventType, MessageRole, isChatCompletionChunkEvent, @@ -48,21 +49,23 @@ function createOpenAIChunk({ describe('inferenceAdapter', () => { const executorMock = { + getConnector: jest.fn(), invoke: jest.fn(), - } as InferenceExecutor & { invoke: jest.MockedFn }; + } as InferenceExecutor & { + invoke: jest.MockedFn; + getConnector: jest.MockedFn; + }; - const logger = { - debug: jest.fn(), - error: jest.fn(), - } as unknown as Logger; + const logger = loggerMock.create(); beforeEach(() => { executorMock.invoke.mockReset(); + isNativeFunctionCallingSupportedMock.mockReset().mockReturnValue(true); }); const defaultArgs = { executor: executorMock, - logger: loggerMock.create(), + logger, }; describe('when creating the request', () => { @@ -232,6 +235,25 @@ describe('inferenceAdapter', () => { ]); }); + it('propagates the temperature parameter', () => { + inferenceAdapter.chatComplete({ + logger, + executor: executorMock, + messages: [{ role: MessageRole.User, content: 'question' }], + temperature: 0.4, + }); + + expect(executorMock.invoke).toHaveBeenCalledTimes(1); + expect(executorMock.invoke).toHaveBeenCalledWith({ + subAction: 'unified_completion_stream', + subActionParams: expect.objectContaining({ + body: expect.objectContaining({ + temperature: 0.4, + }), + }), + }); + }); + it('propagates the abort signal when provided', () => { const abortController = new AbortController(); @@ -251,20 +273,26 @@ describe('inferenceAdapter', () => { }); }); - it('propagates the temperature parameter', () => { + it('uses the right value for functionCalling=auto', () => { + isNativeFunctionCallingSupportedMock.mockReturnValue(false); + inferenceAdapter.chatComplete({ logger, executor: executorMock, messages: [{ role: MessageRole.User, content: 'question' }], - temperature: 0.4, + tools: { + foo: { description: 'my tool' }, + }, + toolChoice: ToolChoiceType.auto, + functionCalling: 'auto', }); expect(executorMock.invoke).toHaveBeenCalledTimes(1); expect(executorMock.invoke).toHaveBeenCalledWith({ subAction: 'unified_completion_stream', subActionParams: expect.objectContaining({ - body: expect.objectContaining({ - temperature: 0.4, + body: expect.not.objectContaining({ + tools: expect.any(Array), }), }), }); diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.ts index 168b0f9cf2fb4..e220e3ebf5f8e 100644 --- a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.ts +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/inference/inference_adapter.ts @@ -15,6 +15,7 @@ import { parseInlineFunctionCalls, wrapWithSimulatedFunctionCalling, } from '../../simulated_function_calling'; +import { isNativeFunctionCallingSupported } from '../../utils/function_calling_support'; import { toolsToOpenAI, toolChoiceToOpenAI, @@ -30,16 +31,19 @@ export const inferenceAdapter: InferenceConnectorAdapter = { messages, toolChoice, tools, - functionCalling, + functionCalling = 'auto', temperature = 0, modelName, logger, abortSignal, }) => { - const simulatedFunctionCalling = functionCalling === 'simulated'; + const useSimulatedFunctionCalling = + functionCalling === 'auto' + ? !isNativeFunctionCallingSupported(executor.getConnector()) + : functionCalling === 'simulated'; let request: Omit & { model?: string }; - if (simulatedFunctionCalling) { + if (useSimulatedFunctionCalling) { const wrapped = wrapWithSimulatedFunctionCalling({ system, messages, @@ -87,7 +91,7 @@ export const inferenceAdapter: InferenceConnectorAdapter = { }), processOpenAIStream(), emitTokenCountEstimateIfMissing({ request }), - simulatedFunctionCalling ? parseInlineFunctionCalls({ logger }) : identity + useSimulatedFunctionCalling ? parseInlineFunctionCalls({ logger }) : identity ); }, }; diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.mocks.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.mocks.ts new file mode 100644 index 0000000000000..625a06ecf3515 --- /dev/null +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.mocks.ts @@ -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. + */ + +export const isNativeFunctionCallingSupportedMock = jest.fn(); + +jest.doMock('../../utils/function_calling_support', () => { + const actual = jest.requireActual('../../utils/function_calling_support'); + return { + ...actual, + isNativeFunctionCallingSupported: isNativeFunctionCallingSupportedMock, + }; +}); diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts index c1ef52a3bc241..c620f1d01bf7f 100644 --- a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts @@ -5,14 +5,15 @@ * 2.0. */ +import { isNativeFunctionCallingSupportedMock } from './openai_adapter.test.mocks'; import OpenAI from 'openai'; import { v4 } from 'uuid'; import { PassThrough } from 'stream'; import { pick } from 'lodash'; import { lastValueFrom, Subject, toArray, filter } from 'rxjs'; -import type { Logger } from '@kbn/logging'; import { loggerMock } from '@kbn/logging-mocks'; import { + ToolChoiceType, ChatCompletionEventType, isChatCompletionChunkEvent, MessageRole, @@ -48,21 +49,23 @@ function createOpenAIChunk({ describe('openAIAdapter', () => { const executorMock = { + getConnector: jest.fn(), invoke: jest.fn(), - } as InferenceExecutor & { invoke: jest.MockedFn }; + } as InferenceExecutor & { + invoke: jest.MockedFn; + getConnector: jest.MockedFn; + }; - const logger = { - debug: jest.fn(), - error: jest.fn(), - } as unknown as Logger; + const logger = loggerMock.create(); beforeEach(() => { executorMock.invoke.mockReset(); + isNativeFunctionCallingSupportedMock.mockReset().mockReturnValue(true); }); const defaultArgs = { executor: executorMock, - logger: loggerMock.create(), + logger, }; describe('when creating the request', () => { @@ -359,6 +362,24 @@ describe('openAIAdapter', () => { }); }); + it('uses the right value for functionCalling=auto', () => { + isNativeFunctionCallingSupportedMock.mockReturnValue(false); + + openAIAdapter.chatComplete({ + logger, + executor: executorMock, + messages: [{ role: MessageRole.User, content: 'question' }], + tools: { + foo: { description: 'my tool' }, + }, + toolChoice: ToolChoiceType.auto, + functionCalling: 'auto', + }); + + expect(executorMock.invoke).toHaveBeenCalledTimes(1); + expect(getRequest().body.tools).toBeUndefined(); + }); + it('propagates the temperature parameter', () => { openAIAdapter.chatComplete({ logger, diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.ts index a8abec5f43204..83b1a47131bbd 100644 --- a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.ts +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/openai_adapter.ts @@ -14,6 +14,7 @@ import { parseInlineFunctionCalls, wrapWithSimulatedFunctionCalling, } from '../../simulated_function_calling'; +import { isNativeFunctionCallingSupported } from '../../utils/function_calling_support'; import type { OpenAIRequest } from './types'; import { messagesToOpenAI, toolsToOpenAI, toolChoiceToOpenAI } from './to_openai'; import { processOpenAIStream } from './process_openai_stream'; @@ -27,15 +28,18 @@ export const openAIAdapter: InferenceConnectorAdapter = { toolChoice, tools, temperature = 0, - functionCalling, + functionCalling = 'auto', modelName, logger, abortSignal, }) => { - const simulatedFunctionCalling = functionCalling === 'simulated'; + const useSimulatedFunctionCalling = + functionCalling === 'auto' + ? !isNativeFunctionCallingSupported(executor.getConnector()) + : functionCalling === 'simulated'; let request: OpenAIRequest; - if (simulatedFunctionCalling) { + if (useSimulatedFunctionCalling) { const wrapped = wrapWithSimulatedFunctionCalling({ system, messages, @@ -86,7 +90,7 @@ export const openAIAdapter: InferenceConnectorAdapter = { }), processOpenAIStream(), emitTokenCountEstimateIfMissing({ request }), - simulatedFunctionCalling ? parseInlineFunctionCalls({ logger }) : identity + useSimulatedFunctionCalling ? parseInlineFunctionCalls({ logger }) : identity ); }, }; diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/types.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/types.ts index 15e5dbc3684b1..956c0366ee1fd 100644 --- a/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/types.ts +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/openai/types.ts @@ -8,3 +8,11 @@ import type OpenAI from 'openai'; export type OpenAIRequest = Omit & { model?: string }; + +// duplicated from x-pack/platform/plugins/shared/stack_connectors/common/openai/constants.ts +// because depending on stack_connectors from the inference plugin creates a cyclic dependency... +export enum OpenAiProviderType { + OpenAi = 'OpenAI', + AzureAi = 'Azure OpenAI', + Other = 'Other', +} diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.test.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.test.ts new file mode 100644 index 0000000000000..a3723309a242c --- /dev/null +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.test.ts @@ -0,0 +1,64 @@ +/* + * 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 { OpenAiProviderType } from '../adapters/openai/types'; +import { InferenceConnector, InferenceConnectorType } from '@kbn/inference-common'; +import { isNativeFunctionCallingSupported } from './function_calling_support'; + +const createConnector = ( + parts: Partial & Pick +): InferenceConnector => { + return { + connectorId: 'connector-id', + name: 'my connector', + config: {}, + ...parts, + }; +}; + +describe('isNativeFunctionCallingSupported', () => { + it('returns true for gemini connector', () => { + const connector = createConnector({ type: InferenceConnectorType.Gemini }); + expect(isNativeFunctionCallingSupported(connector)).toBe(true); + }); + + it('returns true for bedrock connector', () => { + const connector = createConnector({ type: InferenceConnectorType.Bedrock }); + expect(isNativeFunctionCallingSupported(connector)).toBe(true); + }); + + it('returns true for inference connector', () => { + const connector = createConnector({ type: InferenceConnectorType.Inference }); + expect(isNativeFunctionCallingSupported(connector)).toBe(true); + }); + + describe('openAI connector', () => { + it('returns true for "OpenAI" provider', () => { + const connector = createConnector({ + type: InferenceConnectorType.OpenAI, + config: { apiProvider: OpenAiProviderType.OpenAi }, + }); + expect(isNativeFunctionCallingSupported(connector)).toBe(true); + }); + + it('returns true for "Azure" provider', () => { + const connector = createConnector({ + type: InferenceConnectorType.OpenAI, + config: { apiProvider: OpenAiProviderType.AzureAi }, + }); + expect(isNativeFunctionCallingSupported(connector)).toBe(true); + }); + + it('returns false for "Other" provider', () => { + const connector = createConnector({ + type: InferenceConnectorType.OpenAI, + config: { apiProvider: OpenAiProviderType.Other }, + }); + expect(isNativeFunctionCallingSupported(connector)).toBe(false); + }); + }); +}); diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.ts new file mode 100644 index 0000000000000..7e70d417a2996 --- /dev/null +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/function_calling_support.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 { InferenceConnector, InferenceConnectorType } from '@kbn/inference-common'; +import { OpenAiProviderType } from '../adapters/openai/types'; + +export const isNativeFunctionCallingSupported = (connector: InferenceConnector): boolean => { + switch (connector.type) { + case InferenceConnectorType.OpenAI: + const apiProvider = + (connector.config.apiProvider as OpenAiProviderType) ?? OpenAiProviderType.Other; + return apiProvider !== OpenAiProviderType.Other; + case InferenceConnectorType.Inference: + // note: later we might need to check the provider type, for now let's assume support + // will be handled by ES and that all providers will support native FC. + return true; + case InferenceConnectorType.Bedrock: + return true; + case InferenceConnectorType.Gemini: + return true; + } +}; diff --git a/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/index.ts b/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/index.ts index 12256630bd741..70322d0af00bd 100644 --- a/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/index.ts +++ b/x-pack/platform/plugins/shared/inference/server/chat_complete/utils/index.ts @@ -15,3 +15,4 @@ export { chunksIntoMessage } from './chunks_into_message'; export { streamToResponse } from './stream_to_response'; export { handleCancellation } from './handle_cancellation'; export { mergeChunks } from './merge_chunks'; +export { isNativeFunctionCallingSupported } from './function_calling_support'; diff --git a/x-pack/platform/plugins/shared/inference/server/routes/chat_complete.ts b/x-pack/platform/plugins/shared/inference/server/routes/chat_complete.ts index 87ace0b2b7cc6..e4292c0af89da 100644 --- a/x-pack/platform/plugins/shared/inference/server/routes/chat_complete.ts +++ b/x-pack/platform/plugins/shared/inference/server/routes/chat_complete.ts @@ -85,7 +85,7 @@ const chatCompleteBodySchema: Type = schema.object({ ]) ), functionCalling: schema.maybe( - schema.oneOf([schema.literal('native'), schema.literal('simulated')]) + schema.oneOf([schema.literal('native'), schema.literal('simulated'), schema.literal('auto')]) ), temperature: schema.maybe(schema.number()), modelName: schema.maybe(schema.string()), From 3805b4677263df7f806967ace801326de65e0156 Mon Sep 17 00:00:00 2001 From: Agustina Nahir Ruidiaz <61565784+agusruidiazgd@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:02:07 +0100 Subject: [PATCH 09/13] [Security Solution] [Cases] EUI Refresh: Change cases in progress status color (#206240) ## Summary This PR resolves #205740 Change the `In Progress` badge to the hollow style to make it less prominent. Screenshot 2025-01-10 at 10 45 46 Screenshot 2025-01-10 at 10 46 07 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine --- .../shared/kbn-cases-components/src/status/config.ts | 4 ++-- .../public/components/status/status_popover_button.test.tsx | 4 ++-- .../detection_response/cases_by_status/cases_by_status.tsx | 6 +++--- .../detection_response/cases_table/status_badge.tsx | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/platform/packages/shared/kbn-cases-components/src/status/config.ts b/src/platform/packages/shared/kbn-cases-components/src/status/config.ts index 74197b25ae1f5..a23c4cbd5b241 100644 --- a/src/platform/packages/shared/kbn-cases-components/src/status/config.ts +++ b/src/platform/packages/shared/kbn-cases-components/src/status/config.ts @@ -12,12 +12,12 @@ import { CaseStatuses } from './types'; export const getStatusConfiguration = () => ({ [CaseStatuses.open]: { - color: 'primary', + color: 'success', label: i18n.OPEN, icon: 'folderOpen' as const, }, [CaseStatuses['in-progress']]: { - color: 'warning', + color: 'primary', label: i18n.IN_PROGRESS, icon: 'folderExclamation' as const, }, diff --git a/x-pack/platform/plugins/shared/cases/public/components/status/status_popover_button.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/status/status_popover_button.test.tsx index 04de1845f450d..d55c509338bd1 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/status/status_popover_button.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/status/status_popover_button.test.tsx @@ -69,7 +69,7 @@ describe('StatusPopoverButton', () => { .find(`[data-test-subj="case-status-badge-popover-button-open"]`) .first() .prop('color') - ).toBe('primary'); + ).toBe('success'); }); it('shows the correct color when status is in-progress', async () => { @@ -82,7 +82,7 @@ describe('StatusPopoverButton', () => { .find(`[data-test-subj="case-status-badge-popover-button-in-progress"]`) .first() .prop('color') - ).toBe('warning'); + ).toBe('primary'); }); it('shows the correct color when status is closed', async () => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx b/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx index a7fdca017b55d..1993247674c3f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx @@ -110,14 +110,14 @@ const CasesByStatusComponent: React.FC = () => { const barColors = useMemo( () => ({ empty: euiTheme.colors.vis.euiColorVis8, - open: euiTheme.colors.primary, - 'in-progress': euiTheme.colors.warning, + open: euiTheme.colors.success, + 'in-progress': euiTheme.colors.primary, closed: euiTheme.colors.borderBaseSubdued, }), [ euiTheme.colors.vis.euiColorVis8, euiTheme.colors.primary, - euiTheme.colors.warning, + euiTheme.colors.success, euiTheme.colors.borderBaseSubdued, ] ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_table/status_badge.tsx b/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_table/status_badge.tsx index ba3e51d17383c..236fdbcb46829 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_table/status_badge.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/overview/components/detection_response/cases_table/status_badge.tsx @@ -18,11 +18,11 @@ interface Props { const statuses = { [CaseStatuses.open]: { - color: 'primary', + color: 'success', label: i18n.STATUS_OPEN, }, [CaseStatuses['in-progress']]: { - color: 'warning', + color: 'primary', label: i18n.STATUS_IN_PROGRESS, }, [CaseStatuses.closed]: { From 1ca4d967d926a3e6295cb08dcd55dcf1adbd351c Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Fri, 24 Jan 2025 14:09:12 +0100 Subject: [PATCH 10/13] [SecuritySolution] Add enrichPolicyExecutionInterval to entity enablement and init APIs (#207374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Add `enrichPolicyExecutionInterval`param to entity enablement and init APIs ### How to test? * Start kibana * Call the entity store enablement API with a short value for `enrichPolicyExecutionInterval` param * Check in the logs if the enrichment process is running frequently * Clear the entity store * Call the entity store enablement API without `enrichPolicyExecutionInterval` param * Check in the logs if the enrichment process is running less frequently **Enable Entity store API call:** ``` POST kbn:/api/entity_store/enable { "enrichPolicyExecutionInterval": "10s" } ``` **Init Entity store API call:** ``` POST kbn:/api/entity_store/engines/user/init { "enrichPolicyExecutionInterval": "10s" } ``` **Enrich policy log message:** ``` │ info [o.e.x.e.EnrichPolicyRunner] [...] Policy [entity_store_field_retention_user_default_v1.0.0]: Running enrich policy ``` ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- oas_docs/output/kibana.serverless.yaml | 9 +++++++++ oas_docs/output/kibana.yaml | 9 +++++++++ .../entity_store/common.gen.ts | 6 ++++++ .../entity_store/common.schema.yaml | 5 +++++ .../entity_store/enable.gen.ts | 3 ++- .../entity_store/enable.schema.yaml | 2 ++ .../entity_store/engine/init.gen.ts | 3 ++- .../entity_store/engine/init.schema.yaml | 2 ++ ...nalytics_api_2023_10_31.bundled.schema.yaml | 11 +++++++++++ ...nalytics_api_2023_10_31.bundled.schema.yaml | 11 +++++++++++ .../entity_store/entity_store_data_client.ts | 18 ++++++++++++++++-- .../entity_store/task/constants.ts | 2 +- .../task/field_retention_enrichment_task.ts | 8 +++++--- 13 files changed, 81 insertions(+), 8 deletions(-) diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index cb0ad9d6d62ae..1abb5e6471079 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -9721,6 +9721,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Security_Entity_Analytics_API_Interval' entityTypes: items: $ref: '#/components/schemas/Security_Entity_Analytics_API_EntityType' @@ -9840,6 +9842,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Security_Entity_Analytics_API_Interval' fieldHistoryLength: default: 10 description: The number of historical values to keep for each field. @@ -50543,6 +50547,11 @@ components: required: - dsl - response + Security_Entity_Analytics_API_Interval: + description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. + example: 1h + pattern: ^[1-9]\d*[smh]$ + type: string Security_Entity_Analytics_API_RiskEngineScheduleNowErrorResponse: type: object properties: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 5350c07f1b676..b5fe849f226ef 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -11892,6 +11892,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Security_Entity_Analytics_API_Interval' entityTypes: items: $ref: '#/components/schemas/Security_Entity_Analytics_API_EntityType' @@ -12007,6 +12009,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Security_Entity_Analytics_API_Interval' fieldHistoryLength: default: 10 description: The number of historical values to keep for each field. @@ -57418,6 +57422,11 @@ components: required: - dsl - response + Security_Entity_Analytics_API_Interval: + description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. + example: 1h + pattern: ^[1-9]\d*[smh]$ + type: string Security_Entity_Analytics_API_RiskEngineScheduleNowErrorResponse: type: object properties: diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts index 8fd0b17161154..8fa1a680a2396 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts @@ -80,3 +80,9 @@ export const InspectQuery = z.object({ response: z.array(z.string()), dsl: z.array(z.string()), }); + +/** + * Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. + */ +export type Interval = z.infer; +export const Interval = z.string().regex(/^[1-9]\d*[smh]$/); diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml index 68b6e6612735c..f4c174e1213bb 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml @@ -113,3 +113,8 @@ components: required: - dsl - response + Interval: + type: string + description: Interval in which enrich policy runs. For example, `"1h"` means the rule runs every hour. + pattern: '^[1-9]\d*[smh]$' # any number except zero followed by one of the suffixes 's', 'm', 'h' + example: '1h' diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.gen.ts index 9d19d64f0f581..2ef7e2e76cd4d 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { IndexPattern, EntityType, EngineDescriptor } from './common.gen'; +import { IndexPattern, EntityType, Interval, EngineDescriptor } from './common.gen'; export type InitEntityStoreRequestBody = z.infer; export const InitEntityStoreRequestBody = z.object({ @@ -27,6 +27,7 @@ export const InitEntityStoreRequestBody = z.object({ indexPattern: IndexPattern.optional(), filter: z.string().optional(), entityTypes: z.array(EntityType).optional(), + enrichPolicyExecutionInterval: Interval.optional(), }); export type InitEntityStoreRequestBodyInput = z.input; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.schema.yaml index c9c752d1c4f88..7074f938fb839 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/enable.schema.yaml @@ -31,6 +31,8 @@ paths: type: array items: $ref: './common.schema.yaml#/components/schemas/EntityType' + enrichPolicyExecutionInterval: + $ref: './common.schema.yaml#/components/schemas/Interval' responses: '200': description: Successful response diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts index e3dcd55e0acc1..c5d0f438be63d 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { EntityType, IndexPattern, EngineDescriptor } from '../common.gen'; +import { EntityType, IndexPattern, Interval, EngineDescriptor } from '../common.gen'; export type InitEntityEngineRequestParams = z.infer; export const InitEntityEngineRequestParams = z.object({ @@ -35,6 +35,7 @@ export const InitEntityEngineRequestBody = z.object({ fieldHistoryLength: z.number().int().optional().default(10), indexPattern: IndexPattern.optional(), filter: z.string().optional(), + enrichPolicyExecutionInterval: Interval.optional(), }); export type InitEntityEngineRequestBodyInput = z.input; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml index 94a0d84e8cad0..155b8bb1e2185 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml @@ -33,6 +33,8 @@ paths: $ref: '../common.schema.yaml#/components/schemas/IndexPattern' filter: type: string + enrichPolicyExecutionInterval: + $ref: '../common.schema.yaml#/components/schemas/Interval' responses: '200': description: Successful response diff --git a/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 741495f2b98e8..543c1541dc792 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -307,6 +307,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Interval' entityTypes: items: $ref: '#/components/schemas/EntityType' @@ -422,6 +424,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Interval' fieldHistoryLength: default: 10 description: The number of historical values to keep for each field. @@ -1211,6 +1215,13 @@ components: required: - dsl - response + Interval: + description: >- + Interval in which enrich policy runs. For example, `"1h"` means the rule + runs every hour. + example: 1h + pattern: ^[1-9]\d*[smh]$ + type: string RiskEngineScheduleNowErrorResponse: type: object properties: diff --git a/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index ec6fe13a87f51..b103393a4f6ed 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -307,6 +307,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Interval' entityTypes: items: $ref: '#/components/schemas/EntityType' @@ -422,6 +424,8 @@ paths: schema: type: object properties: + enrichPolicyExecutionInterval: + $ref: '#/components/schemas/Interval' fieldHistoryLength: default: 10 description: The number of historical values to keep for each field. @@ -1211,6 +1215,13 @@ components: required: - dsl - response + Interval: + description: >- + Interval in which enrich policy runs. For example, `"1h"` means the rule + runs every hour. + example: 1h + pattern: ^[1-9]\d*[smh]$ + type: string RiskEngineScheduleNowErrorResponse: type: object properties: diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index f47adbdd3ad38..39fabfd19176d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -91,6 +91,7 @@ import { createKeywordBuilderPipeline, deleteKeywordBuilderPipeline, } from '../../asset_inventory/ingest_pipelines'; +import { DEFAULT_INTERVAL } from './task/constants'; // Workaround. TransformState type is wrong. The health type should be: TransformHealth from '@kbn/transform-plugin/common/types/transform_stats' export interface TransformHealth extends estypes.TransformGetTransformStatsTransformStatsHealth { @@ -208,6 +209,7 @@ export class EntityStoreDataClient { filter = '', fieldHistoryLength = 10, entityTypes, + enrichPolicyExecutionInterval, }: InitEntityStoreRequestBody, { pipelineDebugMode = false }: { pipelineDebugMode?: boolean } = {} ): Promise { @@ -229,7 +231,11 @@ export class EntityStoreDataClient { const promises = enginesTypes.map((entity) => run(() => - this.init(entity, { indexPattern, filter, fieldHistoryLength }, { pipelineDebugMode }) + this.init( + entity, + { indexPattern, filter, fieldHistoryLength, enrichPolicyExecutionInterval }, + { pipelineDebugMode } + ) ) ); @@ -287,7 +293,12 @@ export class EntityStoreDataClient { public async init( entityType: EntityType, - { indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityEngineRequestBody, + { + indexPattern = '', + filter = '', + fieldHistoryLength = 10, + enrichPolicyExecutionInterval = DEFAULT_INTERVAL, + }: InitEntityEngineRequestBody, { pipelineDebugMode = false }: { pipelineDebugMode?: boolean } = {} ): Promise { const { experimentalFeatures } = this.options; @@ -344,6 +355,7 @@ export class EntityStoreDataClient { this.asyncSetup( entityType, fieldHistoryLength, + enrichPolicyExecutionInterval, this.options.taskManager, indexPattern, filter, @@ -359,6 +371,7 @@ export class EntityStoreDataClient { private async asyncSetup( entityType: EntityType, fieldHistoryLength: number, + enrichPolicyExecutionInterval: string, taskManager: TaskManagerStartContract, indexPattern: string, filter: string, @@ -447,6 +460,7 @@ export class EntityStoreDataClient { namespace, logger, taskManager, + interval: enrichPolicyExecutionInterval, }); this.log(`debug`, entityType, `Started entity store field retention enrich task`); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/constants.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/constants.ts index 45d4bcb90fbb3..7eeb28e173048 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/constants.ts @@ -8,5 +8,5 @@ export const SCOPE = ['securitySolution']; export const TYPE = 'entity_store:field_retention:enrichment'; export const VERSION = '1.0.0'; -export const INTERVAL = '1h'; +export const DEFAULT_INTERVAL = '1h'; export const TIMEOUT = '10m'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts index c8725fe864213..3d12d16c3d7ae 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts @@ -24,7 +24,7 @@ import { stateSchemaByVersion, type LatestTaskStateSchema as EntityStoreFieldRetentionTaskState, } from './state'; -import { INTERVAL, SCOPE, TIMEOUT, TYPE, VERSION } from './constants'; +import { SCOPE, TIMEOUT, TYPE, VERSION } from './constants'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { executeFieldRetentionEnrichPolicy } from '../elasticsearch_assets'; @@ -120,10 +120,12 @@ export const startEntityStoreFieldRetentionEnrichTask = async ({ logger, namespace, taskManager, + interval, }: { logger: Logger; namespace: string; taskManager: TaskManagerStartContract; + interval: string; }) => { const taskId = getTaskId(namespace); const log = logFactory(logger, taskId); @@ -136,7 +138,7 @@ export const startEntityStoreFieldRetentionEnrichTask = async ({ taskType: getTaskName(), scope: SCOPE, schedule: { - interval: INTERVAL, + interval, }, state: { ...defaultState, namespace }, params: { version: VERSION }, @@ -234,7 +236,7 @@ export const runTask = async ({ telemetry.reportEvent(FIELD_RETENTION_ENRICH_POLICY_EXECUTION_EVENT.eventType, { duration: taskDurationInSeconds, - interval: INTERVAL, + interval: taskInstance.schedule?.interval, }); // Track entity store usage From dd6376d3be3d2916bae334cd50a56ca558d1ed4b Mon Sep 17 00:00:00 2001 From: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com> Date: Fri, 24 Jan 2025 13:31:56 +0000 Subject: [PATCH 11/13] [ResponseOps][Rules] Use rule form instead of rule flyout in observability solution (#206774) ## Summary Resolves https://github.com/elastic/kibana/issues/195574 This PR updates observability solution to use new rule form to `create` and `edit` rules same as `stack management > rules` page. It removes usage of rule flyout form o11y solution. Also updated functional tests. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### How to test - Create a rule in o11y, verify it works as expected - Edit rule in o11y via different options (from rule details page, rule list table, alert details page etc.) verify it works as expected - Verify the same in serverless o11y project ### Release Note Use rule form to create or edit rules in observability. --------- Co-authored-by: Maryam Saeidi --- .../rule_form/src/create_rule_form.tsx | 3 + .../response-ops/rule_form/src/rule_form.tsx | 19 +- .../utils/get_initial_multi_consumer.test.ts | 247 ++++++++++++++++++ .../src/utils/get_initial_multi_consumer.ts | 6 +- .../observability/common/locators/paths.ts | 3 + .../observability/public/pages/rules/rule.tsx | 102 ++++++++ .../public/pages/rules/rules.test.tsx | 49 +++- .../public/pages/rules/rules.tsx | 38 +-- .../observability/public/routes/routes.tsx | 9 + .../pages/alerts/custom_threshold.ts | 21 +- .../alerts/custom_threshold_preview_chart.ts | 2 +- .../apps/observability/pages/rules_page.ts | 31 ++- 12 files changed, 469 insertions(+), 61 deletions(-) create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.test.ts create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/rules/rule.tsx diff --git a/src/platform/packages/shared/response-ops/rule_form/src/create_rule_form.tsx b/src/platform/packages/shared/response-ops/rule_form/src/create_rule_form.tsx index 7b0a6f8fb3d6b..c590cbd105a42 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/create_rule_form.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/create_rule_form.tsx @@ -44,6 +44,7 @@ export interface CreateRuleFormProps { shouldUseRuleProducer?: boolean; canShowConsumerSelection?: boolean; showMustacheAutocompleteSwitch?: boolean; + isServerless?: boolean; onCancel?: () => void; onSubmit?: (ruleId: string) => void; } @@ -60,6 +61,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => { shouldUseRuleProducer = false, canShowConsumerSelection = true, showMustacheAutocompleteSwitch = false, + isServerless = false, onCancel, onSubmit, } = props; @@ -195,6 +197,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => { validConsumers, ruleType, ruleTypes, + isServerless, }), }} > diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx index 61ef0d775d505..de9282ef25747 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx @@ -10,6 +10,7 @@ import React, { useMemo } from 'react'; import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; +import { type RuleCreationValidConsumer } from '@kbn/rule-data-utils'; import { useParams } from 'react-router-dom'; import { CreateRuleForm } from './create_rule_form'; import { EditRuleForm } from './edit_rule_form'; @@ -27,10 +28,20 @@ export interface RuleFormProps { plugins: RuleFormPlugins; onCancel?: () => void; onSubmit?: (ruleId: string) => void; + validConsumers?: RuleCreationValidConsumer[]; + multiConsumerSelection?: RuleCreationValidConsumer | null; + isServerless?: boolean; } export const RuleForm = (props: RuleFormProps) => { - const { plugins: _plugins, onCancel, onSubmit } = props; + const { + plugins: _plugins, + onCancel, + onSubmit, + validConsumers, + multiConsumerSelection, + isServerless, + } = props; const { id, ruleTypeId } = useParams<{ id?: string; ruleTypeId?: string; @@ -80,6 +91,9 @@ export const RuleForm = (props: RuleFormProps) => { plugins={plugins} onCancel={onCancel} onSubmit={onSubmit} + validConsumers={validConsumers} + multiConsumerSelection={multiConsumerSelection} + isServerless={isServerless} /> ); } @@ -112,6 +126,9 @@ export const RuleForm = (props: RuleFormProps) => { actionTypeRegistry, id, ruleTypeId, + validConsumers, + multiConsumerSelection, + isServerless, onCancel, onSubmit, ]); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.test.ts b/src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.test.ts new file mode 100644 index 0000000000000..0a233a1352b73 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.test.ts @@ -0,0 +1,247 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { RuleTypeWithDescription } from '@kbn/alerts-ui-shared'; +import { getInitialMultiConsumer } from './get_initial_multi_consumer'; + +describe('getInitialMultiConsumer', () => { + const ruleType = { + id: '.es-query', + name: 'Test', + actionGroups: [ + { + id: 'testActionGroup', + name: 'Test Action Group', + }, + { + id: 'recovered', + name: 'Recovered', + }, + ], + defaultActionGroupId: 'testActionGroup', + minimumLicenseRequired: 'basic', + recoveryActionGroup: { + id: 'recovered', + name: 'Recovered', + }, + producer: 'logs', + authorizedConsumers: { + alerting: { read: true, all: true }, + test: { read: true, all: true }, + stackAlerts: { read: true, all: true }, + logs: { read: true, all: true }, + }, + actionVariables: { + params: [], + state: [], + }, + enabledInLicense: true, + category: 'test', + } as RuleTypeWithDescription; + + const ruleTypes = [ + { + id: '.es-query', + name: 'Test', + actionGroups: [ + { + id: 'testActionGroup', + name: 'Test Action Group', + }, + { + id: 'recovered', + name: 'Recovered', + }, + ], + defaultActionGroupId: 'testActionGroup', + minimumLicenseRequired: 'basic', + recoveryActionGroup: { + id: 'recovered', + }, + producer: 'logs', + authorizedConsumers: { + alerting: { read: true, all: true }, + test: { read: true, all: true }, + stackAlerts: { read: true, all: true }, + logs: { read: true, all: true }, + }, + actionVariables: { + params: [], + state: [], + }, + enabledInLicense: true, + }, + { + enabledInLicense: true, + recoveryActionGroup: { + id: 'recovered', + name: 'Recovered', + }, + actionGroups: [], + defaultActionGroupId: 'threshold met', + minimumLicenseRequired: 'basic', + authorizedConsumers: { + stackAlerts: { + read: true, + all: true, + }, + }, + actionVariables: { + params: [], + state: [], + }, + id: '.index-threshold', + name: 'Index threshold', + category: 'management', + producer: 'stackAlerts', + }, + ] as RuleTypeWithDescription[]; + + test('should return null when rule type id does not match', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: null, + validConsumers: ['logs', 'observability'], + ruleType: { + ...ruleType, + id: 'test', + }, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe(null); + }); + + test('should return null when no valid consumers', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: null, + validConsumers: [], + ruleType, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe(null); + }); + + test('should return same valid consumer when only one valid consumer', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: null, + validConsumers: ['alerts'], + ruleType, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe('alerts'); + }); + + test('should not return observability consumer for non serverless', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: null, + validConsumers: ['logs', 'infrastructure', 'observability'], + ruleType, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe('logs'); + }); + + test('should return observability consumer for serverless', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: null, + validConsumers: ['logs', 'infrastructure', 'observability'], + ruleType, + ruleTypes, + isServerless: true, + }); + + expect(res).toBe('observability'); + }); + + test('should return null when there is no authorized consumers', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: null, + validConsumers: ['alerts', 'infrastructure'], + ruleType: { + ...ruleType, + authorizedConsumers: {}, + }, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe(null); + }); + + test('should return null when multiConsumerSelection is null', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: null, + validConsumers: ['stackAlerts', 'logs'], + ruleType: { + ...ruleType, + authorizedConsumers: { + stackAlerts: { read: true, all: true }, + }, + }, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe(null); + }); + + test('should return valid multi consumer correctly', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: 'logs', + validConsumers: ['stackAlerts', 'logs'], + ruleType: { + ...ruleType, + authorizedConsumers: { + stackAlerts: { read: true, all: true }, + }, + }, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe('logs'); + }); + + test('should return stackAlerts correctly', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: 'alerts', + validConsumers: ['stackAlerts', 'logs'], + ruleType: { + ...ruleType, + authorizedConsumers: {}, + }, + ruleTypes, + isServerless: false, + }); + + expect(res).toBe('stackAlerts'); + }); + + test('should return null valid consumer correctly', () => { + const res = getInitialMultiConsumer({ + multiConsumerSelection: 'alerts', + validConsumers: ['infrastructure', 'logs'], + ruleType: { + ...ruleType, + authorizedConsumers: {}, + }, + ruleTypes: [], + isServerless: false, + }); + + expect(res).toBe(null); + }); +}); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.ts b/src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.ts index 088348c510100..66498ef535e9e 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.ts +++ b/src/platform/packages/shared/response-ops/rule_form/src/utils/get_initial_multi_consumer.ts @@ -35,11 +35,13 @@ export const getInitialMultiConsumer = ({ validConsumers, ruleType, ruleTypes, + isServerless, }: { multiConsumerSelection?: RuleCreationValidConsumer | null; validConsumers: RuleCreationValidConsumer[]; ruleType: RuleTypeWithDescription; ruleTypes: RuleTypeWithDescription[]; + isServerless?: boolean; }): RuleCreationValidConsumer | null => { // If rule type doesn't support multi-consumer or no valid consumers exists, // return nothing @@ -52,8 +54,8 @@ export const getInitialMultiConsumer = ({ return validConsumers[0]; } - // If o11y is in the valid consumers, just use that - if (validConsumers.includes(AlertConsumers.OBSERVABILITY)) { + // If o11y is in the valid consumers and it is serverless, just use that + if (isServerless && validConsumers.includes(AlertConsumers.OBSERVABILITY)) { return AlertConsumers.OBSERVABILITY; } diff --git a/x-pack/solutions/observability/plugins/observability/common/locators/paths.ts b/x-pack/solutions/observability/plugins/observability/common/locators/paths.ts index d6114efaf73ee..1c48cacfb6e97 100644 --- a/x-pack/solutions/observability/plugins/observability/common/locators/paths.ts +++ b/x-pack/solutions/observability/plugins/observability/common/locators/paths.ts @@ -15,6 +15,7 @@ export const EXPLORATORY_VIEW_PATH = '/exploratory-view' as const; // has been m export const RULES_PATH = '/alerts/rules' as const; export const RULES_LOGS_PATH = '/alerts/rules/logs' as const; export const RULE_DETAIL_PATH = '/alerts/rules/:ruleId' as const; +export const CREATE_RULE_PATH = '/alerts/rules/create/:ruleTypeId' as const; export const CASES_PATH = '/cases' as const; export const ANNOTATIONS_PATH = '/annotations' as const; export const SETTINGS_PATH = '/slos/settings' as const; @@ -37,6 +38,8 @@ export const paths = { rules: `${OBSERVABILITY_BASE_PATH}${RULES_PATH}`, ruleDetails: (ruleId: string) => `${OBSERVABILITY_BASE_PATH}${RULES_PATH}/${encodeURIComponent(ruleId)}`, + createRule: (ruleTypeId: string) => + `${OBSERVABILITY_BASE_PATH}${RULES_PATH}/create/${encodeURIComponent(ruleTypeId)}`, }, }; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/rules/rule.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rule.tsx new file mode 100644 index 0000000000000..d33c2a6cbea29 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rule.tsx @@ -0,0 +1,102 @@ +/* + * 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 React from 'react'; +import { RuleForm } from '@kbn/response-ops-rule-form'; +import { useLocation } from 'react-router-dom'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; +import { useKibana } from '../../utils/kibana_react'; +import { paths } from '../../../common/locators/paths'; +import { observabilityRuleCreationValidConsumers } from '../../../common/constants'; +import { usePluginContext } from '../../hooks/use_plugin_context'; + +export function RulePage() { + const { + http, + docLinks, + observabilityAIAssistant, + application, + notifications, + charts, + settings, + data, + dataViews, + unifiedSearch, + serverless, + actionTypeRegistry, + ruleTypeRegistry, + chrome, + ...startServices + } = useKibana().services; + const { ObservabilityPageTemplate } = usePluginContext(); + const location = useLocation<{ returnApp?: string; returnPath?: string }>(); + const { returnApp, returnPath } = location.state || {}; + + useBreadcrumbs( + [ + { + text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', { + defaultMessage: 'Alerts', + }), + href: http.basePath.prepend(paths.observability.alerts), + deepLinkId: 'observability-overview:alerts', + }, + { + href: http.basePath.prepend(paths.observability.rules), + text: i18n.translate('xpack.observability.breadcrumbs.rulesLinkText', { + defaultMessage: 'Rules', + }), + }, + { + text: i18n.translate('xpack.observability.breadcrumbs.createLinkText', { + defaultMessage: 'Create', + }), + }, + ], + { serverless } + ); + + return ( + + + { + if (returnApp && returnPath) { + application.navigateToApp(returnApp, { path: returnPath }); + } else { + return application.navigateToUrl(http.basePath.prepend(paths.observability.rules)); + } + }} + onSubmit={(ruleId) => { + return application.navigateToUrl( + http.basePath.prepend(paths.observability.ruleDetails(ruleId)) + ); + }} + /> + + ); +} diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx index d8dfec4098f66..843d1fb22a12e 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx @@ -5,14 +5,16 @@ * 2.0. */ +import React from 'react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useLocation } from 'react-router-dom'; import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { observabilityAIAssistantPluginMock } from '@kbn/observability-ai-assistant-plugin/public/mock'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import { render } from '@testing-library/react'; -import React from 'react'; -import { useLocation } from 'react-router-dom'; +import { RuleTypeModalProps } from '@kbn/response-ops-rule-form/src/rule_type_modal/components/rule_type_modal'; import * as pluginContext from '../../hooks/use_plugin_context'; import { ObservabilityPublicPluginsStart } from '../../plugin'; import { createObservabilityRuleTypeRegistryMock } from '../../rules/observability_rule_type_registry_mock'; @@ -21,6 +23,12 @@ import { RulesPage } from './rules'; const mockUseKibanaReturnValue = kibanaStartMock.startContract(); const mockObservabilityAIAssistant = observabilityAIAssistantPluginMock.createStartContract(); +const mockApplication = { + navigateToApp: jest.fn(), + navigateToUrl: jest.fn(), +}; + +const queryClient = new QueryClient(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -34,6 +42,11 @@ jest.mock('../../utils/kibana_react', () => ({ services: { ...mockUseKibanaReturnValue.services, observabilityAIAssistant: mockObservabilityAIAssistant, + application: { + ...mockUseKibanaReturnValue.services.application, + navigateToApp: mockApplication.navigateToApp, + navigateToUrl: mockApplication.navigateToUrl, + }, }, })), })); @@ -48,6 +61,15 @@ jest.mock('@kbn/triggers-actions-ui-plugin/public', () => ({ useLoadRuleTypesQuery: jest.fn(), })); +jest.mock('@kbn/response-ops-rule-form/src/rule_type_modal', () => ({ + RuleTypeModal: ({ onSelectRuleType }: RuleTypeModalProps) => ( +
+ RuleTypeModal + +
+ ), +})); + const useLocationMock = useLocation as jest.Mock; jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ @@ -130,13 +152,17 @@ describe('RulesPage with all capabilities', () => { useLoadRuleTypesQuery.mockReturnValue({ ruleTypesState: { + isLoading: false, + isInitialLoading: false, data: ruleTypeIndex, }, }); return render( - + + + ); } @@ -155,6 +181,21 @@ describe('RulesPage with all capabilities', () => { const wrapper = await setup(); expect(wrapper.getByTestId('createRuleButton')).not.toBeDisabled(); }); + + it('navigates to create rule form correctly', async () => { + const wrapper = await setup(); + expect(wrapper.getByTestId('createRuleButton')).toBeInTheDocument(); + + fireEvent.click(wrapper.getByTestId('createRuleButton')); + expect(await wrapper.findByTestId('ruleTypeModal')).toBeInTheDocument(); + + fireEvent.click(await wrapper.findByText('Rule type 1')); + await waitFor(() => { + expect(mockApplication.navigateToUrl).toHaveBeenCalledWith( + '/app/observability/alerts/rules/create/1' + ); + }); + }); }); describe('RulesPage with show only capability', () => { diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.tsx index 2e3c6d9d9f832..aeaf3f376cb26 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.tsx @@ -11,12 +11,10 @@ import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; -import { AlertConsumers } from '@kbn/rule-data-utils'; import { useLoadRuleTypesQuery } from '@kbn/triggers-actions-ui-plugin/public'; import React, { lazy, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { observabilityRuleCreationValidConsumers } from '../../../common/constants'; -import { RULES_LOGS_PATH, RULES_PATH } from '../../../common/locators/paths'; +import { RULES_LOGS_PATH, RULES_PATH, paths } from '../../../common/locators/paths'; import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useKibana } from '../../utils/kibana_react'; @@ -37,18 +35,13 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) { docLinks, notifications: { toasts }, observabilityAIAssistant, - triggersActionsUi: { - ruleTypeRegistry, - getAddRuleFlyout: AddRuleFlyout, - getRulesSettingsLink: RulesSettingsLink, - }, + application, + triggersActionsUi: { ruleTypeRegistry, getRulesSettingsLink: RulesSettingsLink }, serverless, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); const history = useHistory(); const [ruleTypeModalVisibility, setRuleTypeModalVisibility] = useState(false); - const [ruleTypeIdToCreate, setRuleTypeIdToCreate] = useState(undefined); - const [addRuleFlyoutVisibility, setAddRuleFlyoutVisibility] = useState(false); const [stateRefresh, setRefresh] = useState(new Date()); useBreadcrumbs( @@ -188,9 +181,10 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) { setRuleTypeModalVisibility(false)} onSelectRuleType={(ruleTypeId) => { - setRuleTypeIdToCreate(ruleTypeId); setRuleTypeModalVisibility(false); - setAddRuleFlyoutVisibility(true); + return application.navigateToUrl( + http.basePath.prepend(paths.observability.createRule(ruleTypeId)) + ); }} http={http} toasts={toasts} @@ -198,26 +192,6 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) { filteredRuleTypes={filteredRuleTypes} /> )} - - {addRuleFlyoutVisibility && ( - { - setAddRuleFlyoutVisibility(false); - }} - onSave={() => { - setRefresh(new Date()); - return Promise.resolve(); - }} - hideGrouping - useRuleProducer - /> - )} ); } diff --git a/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx b/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx index 0f9e5d337e1ec..b9ed00a6640be 100644 --- a/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx @@ -17,6 +17,7 @@ import { LandingPage } from '../pages/landing/landing'; import { OverviewPage } from '../pages/overview/overview'; import { RulesPage } from '../pages/rules/rules'; import { RuleDetailsPage } from '../pages/rule_details/rule_details'; +import { RulePage } from '../pages/rules/rule'; import { ALERTS_PATH, ALERT_DETAIL_PATH, @@ -34,6 +35,7 @@ import { OLD_SLOS_OUTDATED_DEFINITIONS_PATH, OLD_SLO_DETAIL_PATH, OLD_SLO_EDIT_PATH, + CREATE_RULE_PATH, } from '../../common/locators/paths'; import { HasDataContextProvider } from '../context/has_data_context/has_data_context'; @@ -133,6 +135,13 @@ export const routes = { params: {}, exact: true, }, + [CREATE_RULE_PATH]: { + handler: () => { + return ; + }, + params: {}, + exact: true, + }, [ALERT_DETAIL_PATH]: { handler: () => { return ; diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 13891653b605e..1f69ef8a6003f 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -9,7 +9,7 @@ import { Key } from 'selenium-webdriver'; import expect from 'expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; -export default ({ getService }: FtrProviderContext) => { +export default ({ getService, getPageObjects }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); @@ -17,9 +17,10 @@ export default ({ getService }: FtrProviderContext) => { const find = getService('find'); const logger = getService('log'); const retry = getService('retry'); + const toasts = getService('toasts'); + const PageObjects = getPageObjects(['header']); - // FLAKY: https://github.com/elastic/kibana/issues/196766 - describe.skip('Custom threshold rule', function () { + describe('Custom threshold rule', function () { this.tags('includeFirefox'); const observability = getService('observability'); @@ -58,13 +59,16 @@ export default ({ getService }: FtrProviderContext) => { it('shows the custom threshold rule in the observability section', async () => { await observability.alerts.rulesPage.clickCreateRuleButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); await observability.alerts.rulesPage.clickOnObservabilityCategory(); await observability.alerts.rulesPage.clickOnCustomThresholdRule(); }); it('can add name and tags', async () => { - await testSubjects.setValue('ruleNameInput', 'test custom threshold rule'); - await testSubjects.setValue('comboBoxSearchInput', 'tag1'); + await testSubjects.setValue('ruleDetailsNameInput', 'test custom threshold rule', { + clearWithKeyboard: true, + }); + await testSubjects.setValue('ruleDetailsTagsInput', 'tag1'); }); it('can add data view', async () => { @@ -204,9 +208,11 @@ export default ({ getService }: FtrProviderContext) => { }); it('can save the rule', async () => { - await testSubjects.click('saveRuleButton'); + await testSubjects.click('rulePageFooterSaveButton'); await testSubjects.click('confirmModalConfirmButton'); - await find.byCssSelector('button[title="test custom threshold rule"]'); + + const title = await toasts.getTitleAndDismiss(); + expect(title).toEqual(`Created rule "test custom threshold rule"`); }); it('saved the rule correctly', async () => { @@ -220,6 +226,7 @@ export default ({ getService }: FtrProviderContext) => { expect.objectContaining({ name: 'test custom threshold rule', tags: ['tag1'], + consumer: 'logs', params: expect.objectContaining({ alertOnGroupDisappear: false, alertOnNoData: false, diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold_preview_chart.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold_preview_chart.ts index 6bc0564c711c1..188e37d237d90 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold_preview_chart.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold_preview_chart.ts @@ -51,7 +51,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { }); it('does render the correct error message', async () => { - await testSubjects.setValue('ruleNameInput', 'test custom threshold rule'); + await testSubjects.setValue('ruleDetailsNameInput', 'test custom threshold rule'); await testSubjects.click('customEquation'); const customEquationField = await find.byCssSelector( diff --git a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts index f084c4ec2a0aa..9d4817fbff40d 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts @@ -64,11 +64,11 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const selectAndFillInEsQueryRule = async (ruleName: string) => { await testSubjects.click(`.es-query-SelectOption`); await retry.waitFor( - 'Create Rule flyout is visible', - async () => await testSubjects.exists('addRuleFlyoutTitle') + 'Create Rule form is visible', + async () => await testSubjects.exists('createRuleForm') ); - await testSubjects.setValue('ruleNameInput', ruleName); + await testSubjects.setValue('ruleDetailsNameInput', ruleName); await testSubjects.click('queryFormType_esQuery'); await testSubjects.click('selectIndexExpression'); const indexComboBox = await find.byCssSelector('#indexSelectSearchBox'); @@ -90,7 +90,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const observability = getService('observability'); - const navigateAndOpenCreateRuleFlyout = async () => { + const navigateAndOpenRuleTypeModal = async () => { await observability.alerts.common.navigateToRulesPage(); await retry.waitFor( 'Create Rule button is visible', @@ -128,11 +128,11 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { describe('Create rule button', () => { it('Show Rule Type Modal when Create Rule button is clicked', async () => { - await navigateAndOpenCreateRuleFlyout(); + await navigateAndOpenRuleTypeModal(); }); }); - describe('Create rules flyout', () => { + describe('Create rules form', () => { const ruleName = 'esQueryRule'; afterEach(async () => { @@ -151,13 +151,15 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { infrastructure: ['all'], }) ); - await navigateAndOpenCreateRuleFlyout(); + await navigateAndOpenRuleTypeModal(); await selectAndFillInEsQueryRule(ruleName); - await testSubjects.click('saveRuleButton'); + await testSubjects.click('rulePageFooterSaveButton'); await PageObjects.header.waitUntilLoadingHasFinished(); + await observability.alerts.common.navigateToRulesPage(); + const tableRows = await find.allByCssSelector('.euiTableRow'); const rows = await getRulesList(tableRows); expect(rows.length).to.be(1); @@ -174,13 +176,14 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { logs: ['all'], }) ); - await navigateAndOpenCreateRuleFlyout(); + await navigateAndOpenRuleTypeModal(); await selectAndFillInEsQueryRule(ruleName); - await testSubjects.click('saveRuleButton'); + await testSubjects.click('rulePageFooterSaveButton'); await PageObjects.header.waitUntilLoadingHasFinished(); + await observability.alerts.common.navigateToRulesPage(); const tableRows = await find.allByCssSelector('.euiTableRow'); const rows = await getRulesList(tableRows); expect(rows.length).to.be(1); @@ -196,17 +199,17 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { }) ); - await navigateAndOpenCreateRuleFlyout(); + await navigateAndOpenRuleTypeModal(); await selectAndFillInEsQueryRule(ruleName); await retry.waitFor('consumer select modal is visible', async () => { - return await testSubjects.exists('ruleFormConsumerSelect'); + return await testSubjects.exists('ruleConsumerSelection'); }); - const consumerSelect = await testSubjects.find('ruleFormConsumerSelect'); + const consumerSelect = await testSubjects.find('ruleConsumerSelection'); await consumerSelect.click(); const consumerOptionsList = await testSubjects.find( - 'comboBoxOptionsList ruleFormConsumerSelect-optionsList' + 'comboBoxOptionsList ruleConsumerSelectionInput-optionsList' ); const consumerOptions = await consumerOptionsList.findAllByClassName( 'euiComboBoxOption__content' From 18c73ea26a3a818717178cac886713d2e8abd4e4 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Fri, 24 Jan 2025 15:06:56 +0100 Subject: [PATCH 12/13] [Dashboard][Vega] Panel wrongly reporting unsaved changes (#207781) ## Summary Fixes #196954 Apparently the root issue here is the wrong dashboard SO, so here's the SO has been fixed and the bug is no longer present. --- .../services/sample_data/data_sets/logs/saved_objects.ts | 2 +- .../services/sample_data/data_sets/logs_tsdb/saved_objects.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 19d59c35523f4..5a7582de75074 100644 --- a/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -313,7 +313,7 @@ export const getSavedObjects = (): SavedObject[] => [ optionsJSON: '{"useMargins":true,"syncColors":false,"syncCursor":true,"syncTooltips":false,"hidePanelTitles":false}', panelsJSON: - '[{"version":"8.8.0","type":"map","gridData":{"x":0,"y":14,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"hiddenLayers":[],"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"openTOCDetails":[],"enhancements":{}},"panelRefName":"panel_4"},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"11"},"panelIndex":"11","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-28b89898-3feb-415a-8dd9-74d755ac7c2a"}],"state":{"visualization":{"layerId":"28b89898-3feb-415a-8dd9-74d755ac7c2a","layerType":"data","metricAccessor":"f92c482e-1eee-4c2a-9338-64fb3eec286a","palette":{"type":"palette","name":"status","params":{"steps":3,"rangeType":"number","rangeMin":0,"rangeMax":null,"progression":"fixed","stops":[{"color":"#23be8f","stop":0},{"color":"#fcd279","stop":608.66},{"color":"#f66d64","stop":1217.33}],"continuity":"above","maxSteps":5}}},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"28b89898-3feb-415a-8dd9-74d755ac7c2a":{"columns":{"f92c482e-1eee-4c2a-9338-64fb3eec286a":{"label":"Unique Visitors","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["f92c482e-1eee-4c2a-9338-64fb3eec286a"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":24,"y":14,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"15"},"panelIndex":"15","embeddableConfig":{"attributes":{"title":"[Logs] Response Codes Over Time + Annotations (converted)","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-b38fe501-4b47-4de8-a423-6656d1162174"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"xy-visualization-layer-f265e722-ae38-495c-903c-48aa7931fa82"}],"state":{"visualization":{"legend":{"isVisible":true,"showSingleSeries":true,"position":"bottom","shouldTruncate":true,"maxLines":1},"valueLabels":"hide","fittingFunction":"None","fillOpacity":0.5,"yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"yLeftScale":"linear","yRightScale":"linear","axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"seriesType":"area_percentage_stacked","layerType":"data","layerId":"b38fe501-4b47-4de8-a423-6656d1162174","accessors":["896c5eb2-81c5-44f1-a4a1-57344161ea62"],"yConfig":[{"forAccessor":"896c5eb2-81c5-44f1-a4a1-57344161ea62","color":"rgba(115,216,255,1)","axisMode":"left"}],"xAccessor":"8986e393-d24f-49b0-96ca-118fd66d75e5","splitAccessor":"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","palette":{"name":"default","type":"palette"}},{"layerId":"f265e722-ae38-495c-903c-48aa7931fa82","layerType":"annotations","ignoreGlobalFilters":true,"annotations":[{"type":"query","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","label":"Event","key":{"type":"point_in_time"},"color":"#D33115","timeField":"timestamp","icon":"asterisk","filter":{"type":"kibana_query","query":"tags:error AND tags:security","language":"lucene"},"extraFields":["geo.src"]}]}]},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"b38fe501-4b47-4de8-a423-6656d1162174":{"columns":{"8986e393-d24f-49b0-96ca-118fd66d75e5":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto","includeEmptyRows":true,"dropPartials":false}},"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b":{"label":"Filters","dataType":"string","operationType":"filters","scale":"ordinal","isBucketed":true,"params":{"filters":[{"input":{"query":"response.keyword >= 200 and response.keyword < 400","language":"kuery"},"label":"HTTP 2xx and 3xx"},{"input":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"label":"HTTP 4xx"},{"input":{"query":"response.keyword >= 500","language":"kuery"},"label":"HTTP 5xx"}]}},"896c5eb2-81c5-44f1-a4a1-57344161ea62":{"label":"Response Code Count","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["8986e393-d24f-49b0-96ca-118fd66d75e5","43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","896c5eb2-81c5-44f1-a4a1-57344161ea62"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{},"hidePanelTitles":false},"title":"[Logs] Response Codes Over Time + Annotations"},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}],"state":{"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","layerType":"data","metricAccessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":32,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":47,"w":24,"h":13,"i":"21bb0939-ee09-4021-8848-6552b3a6a788"},"panelIndex":"21bb0939-ee09-4021-8848-6552b3a6a788","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsDatatable","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c840e93e-2949-4723-ad35-6bdb2d724404"}],"state":{"visualization":{"columns":[{"columnId":"4e64d6d7-4f92-4d5e-abbb-13796604db30","isTransposed":false},{"columnId":"fb9a848d-76f3-4005-a067-4259a50b5621","isTransposed":false},{"columnId":"a2760bc2-9a6e-46a1-8595-86f61573c7cf","isTransposed":false},{"columnId":"2c8bd8d5-35ff-4386-8d27-3ba882b13e43","isTransposed":false,"colorMode":"text","palette":{"type":"palette","name":"status","params":{"steps":5,"stops":[{"color":"#23be8f","stop":20},{"color":"#9dedce","stop":40},{"color":"#fcd279","stop":60},{"color":"#ffc0b8","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}},{"columnId":"defa6f97-b874-4556-8438-056fb437787b","isTransposed":false,"colorMode":"text","palette":{"type":"palette","name":"status","params":{"steps":5,"stops":[{"color":"#23be8f","stop":20},{"color":"#9dedce","stop":40},{"color":"#fcd279","stop":60},{"color":"#ffc0b8","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}}],"layerId":"c840e93e-2949-4723-ad35-6bdb2d724404","layerType":"data"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"c840e93e-2949-4723-ad35-6bdb2d724404":{"columns":{"4e64d6d7-4f92-4d5e-abbb-13796604db30":{"label":"Type","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"extension.keyword","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"fb9a848d-76f3-4005-a067-4259a50b5621"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"},"include":[],"exclude":[],"includeIsRegex":false,"excludeIsRegex":false},"customLabel":true},"fb9a848d-76f3-4005-a067-4259a50b5621":{"label":"Bytes (Total)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"a2760bc2-9a6e-46a1-8595-86f61573c7cf":{"label":"Bytes (Last Hour)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","reducedTimeRange":"1h","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"2c8bd8d5-35ff-4386-8d27-3ba882b13e43":{"label":"Unique Visits (Total)","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true},"defa6f97-b874-4556-8438-056fb437787b":{"label":"Unique count of clientip","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"reducedTimeRange":"1h","params":{"emptyAsNull":true}}},"columnOrder":["4e64d6d7-4f92-4d5e-abbb-13796604db30","fb9a848d-76f3-4005-a067-4259a50b5621","a2760bc2-9a6e-46a1-8595-86f61573c7cf","2c8bd8d5-35ff-4386-8d27-3ba882b13e43","defa6f97-b874-4556-8438-056fb437787b"],"sampling":1,"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":47,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":60,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"formBased":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url.keyword","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"type":"palette","name":"negative","params":{"steps":5,"stops":[{"color":"#fcdedc","stop":20},{"color":"#fec3bd","stop":40},{"color":"#fea79e","stop":60},{"color":"#fb8b81","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"type":"palette","name":"negative","params":{"steps":5,"stops":[{"color":"#fcdedc","stop":20},{"color":"#fec3bd","stop":40},{"color":"#fea79e","stop":60},{"color":"#fb8b81","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","type":"index-pattern"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"}]', + '[{"version":"8.8.0","type":"map","gridData":{"x":0,"y":14,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"hiddenLayers":[],"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"openTOCDetails":[],"enhancements":{}},"panelRefName":"panel_4"},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"11"},"panelIndex":"11","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-28b89898-3feb-415a-8dd9-74d755ac7c2a"}],"state":{"visualization":{"layerId":"28b89898-3feb-415a-8dd9-74d755ac7c2a","layerType":"data","metricAccessor":"f92c482e-1eee-4c2a-9338-64fb3eec286a","palette":{"type":"palette","name":"status","params":{"steps":3,"rangeType":"number","rangeMin":0,"rangeMax":null,"progression":"fixed","stops":[{"color":"#23be8f","stop":0},{"color":"#fcd279","stop":608.66},{"color":"#f66d64","stop":1217.33}],"continuity":"above","maxSteps":5}}},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"28b89898-3feb-415a-8dd9-74d755ac7c2a":{"columns":{"f92c482e-1eee-4c2a-9338-64fb3eec286a":{"label":"Unique Visitors","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["f92c482e-1eee-4c2a-9338-64fb3eec286a"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":24,"y":14,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"15"},"panelIndex":"15","embeddableConfig":{"attributes":{"title":"[Logs] Response Codes Over Time + Annotations (converted)","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-b38fe501-4b47-4de8-a423-6656d1162174"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"xy-visualization-layer-f265e722-ae38-495c-903c-48aa7931fa82"}],"state":{"visualization":{"legend":{"isVisible":true,"showSingleSeries":true,"position":"bottom","shouldTruncate":true,"maxLines":1},"valueLabels":"hide","fittingFunction":"None","fillOpacity":0.5,"yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"yLeftScale":"linear","yRightScale":"linear","axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"seriesType":"area_percentage_stacked","layerType":"data","layerId":"b38fe501-4b47-4de8-a423-6656d1162174","accessors":["896c5eb2-81c5-44f1-a4a1-57344161ea62"],"yConfig":[{"forAccessor":"896c5eb2-81c5-44f1-a4a1-57344161ea62","color":"rgba(115,216,255,1)","axisMode":"left"}],"xAccessor":"8986e393-d24f-49b0-96ca-118fd66d75e5","splitAccessor":"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","palette":{"name":"default","type":"palette"}},{"layerId":"f265e722-ae38-495c-903c-48aa7931fa82","layerType":"annotations","ignoreGlobalFilters":true,"annotations":[{"type":"query","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","label":"Event","key":{"type":"point_in_time"},"color":"#D33115","timeField":"timestamp","icon":"asterisk","filter":{"type":"kibana_query","query":"tags:error AND tags:security","language":"lucene"},"extraFields":["geo.src"]}]}]},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"b38fe501-4b47-4de8-a423-6656d1162174":{"columns":{"8986e393-d24f-49b0-96ca-118fd66d75e5":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto","includeEmptyRows":true,"dropPartials":false}},"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b":{"label":"Filters","dataType":"string","operationType":"filters","scale":"ordinal","isBucketed":true,"params":{"filters":[{"input":{"query":"response.keyword >= 200 and response.keyword < 400","language":"kuery"},"label":"HTTP 2xx and 3xx"},{"input":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"label":"HTTP 4xx"},{"input":{"query":"response.keyword >= 500","language":"kuery"},"label":"HTTP 5xx"}]}},"896c5eb2-81c5-44f1-a4a1-57344161ea62":{"label":"Response Code Count","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["8986e393-d24f-49b0-96ca-118fd66d75e5","43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","896c5eb2-81c5-44f1-a4a1-57344161ea62"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{},"hidePanelTitles":false},"title":"[Logs] Response Codes Over Time + Annotations"},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}],"state":{"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","layerType":"data","metricAccessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":32,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":47,"w":24,"h":13,"i":"21bb0939-ee09-4021-8848-6552b3a6a788"},"panelIndex":"21bb0939-ee09-4021-8848-6552b3a6a788","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsDatatable","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c840e93e-2949-4723-ad35-6bdb2d724404"}],"state":{"visualization":{"columns":[{"columnId":"4e64d6d7-4f92-4d5e-abbb-13796604db30","isTransposed":false},{"columnId":"fb9a848d-76f3-4005-a067-4259a50b5621","isTransposed":false},{"columnId":"a2760bc2-9a6e-46a1-8595-86f61573c7cf","isTransposed":false},{"columnId":"2c8bd8d5-35ff-4386-8d27-3ba882b13e43","isTransposed":false,"colorMode":"text","palette":{"type":"palette","name":"status","params":{"steps":5,"stops":[{"color":"#23be8f","stop":20},{"color":"#9dedce","stop":40},{"color":"#fcd279","stop":60},{"color":"#ffc0b8","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}},{"columnId":"defa6f97-b874-4556-8438-056fb437787b","isTransposed":false,"colorMode":"text","palette":{"type":"palette","name":"status","params":{"steps":5,"stops":[{"color":"#23be8f","stop":20},{"color":"#9dedce","stop":40},{"color":"#fcd279","stop":60},{"color":"#ffc0b8","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}}],"layerId":"c840e93e-2949-4723-ad35-6bdb2d724404","layerType":"data"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"c840e93e-2949-4723-ad35-6bdb2d724404":{"columns":{"4e64d6d7-4f92-4d5e-abbb-13796604db30":{"label":"Type","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"extension.keyword","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"fb9a848d-76f3-4005-a067-4259a50b5621"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"},"include":[],"exclude":[],"includeIsRegex":false,"excludeIsRegex":false},"customLabel":true},"fb9a848d-76f3-4005-a067-4259a50b5621":{"label":"Bytes (Total)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"a2760bc2-9a6e-46a1-8595-86f61573c7cf":{"label":"Bytes (Last Hour)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","reducedTimeRange":"1h","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"2c8bd8d5-35ff-4386-8d27-3ba882b13e43":{"label":"Unique Visits (Total)","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true},"defa6f97-b874-4556-8438-056fb437787b":{"label":"Unique count of clientip","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"reducedTimeRange":"1h","params":{"emptyAsNull":true}}},"columnOrder":["4e64d6d7-4f92-4d5e-abbb-13796604db30","fb9a848d-76f3-4005-a067-4259a50b5621","a2760bc2-9a6e-46a1-8595-86f61573c7cf","2c8bd8d5-35ff-4386-8d27-3ba882b13e43","defa6f97-b874-4556-8438-056fb437787b"],"sampling":1,"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":47,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":60,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"formBased":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url.keyword","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"type":"palette","name":"negative","params":{"steps":5,"stops":[{"color":"#fcdedc","stop":20},{"color":"#fec3bd","stop":40},{"color":"#fea79e","stop":60},{"color":"#fb8b81","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"type":"palette","name":"negative","params":{"steps":5,"stops":[{"color":"#fcdedc","stop":20},{"color":"#fec3bd","stop":40},{"color":"#fea79e","stop":60},{"color":"#fb8b81","stop":80},{"color":"#f66d64","stop":100}],"rangeType":"percent","rangeMin":20,"rangeMax":null,"continuity":"above","reverse":false}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","type":"index-pattern"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"}]', timeFrom: 'now-7d/d', title: '[Logs] Web Traffic', timeTo: 'now', diff --git a/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts b/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts index a6d387d651832..c7586f15f0bd6 100644 --- a/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts +++ b/src/platform/plugins/shared/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts @@ -314,8 +314,7 @@ export const getSavedObjects = (): SavedObject[] => [ timeRestore: true, optionsJSON: '{"useMargins":true,"syncColors":false,"syncCursor":true,"syncTooltips":false,"hidePanelTitles":false}', - panelsJSON: - '[{"version":"8.8.0","type":"map","gridData":{"x":0,"y":14,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"hiddenLayers":[],"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"openTOCDetails":[],"enhancements":{}},"panelRefName":"panel_4"},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"11"},"panelIndex":"11","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-28b89898-3feb-415a-8dd9-74d755ac7c2a"}],"state":{"visualization":{"layerId":"28b89898-3feb-415a-8dd9-74d755ac7c2a","layerType":"data","metricAccessor":"f92c482e-1eee-4c2a-9338-64fb3eec286a","palette":{"name":"custom","type":"palette","params":{"steps":3,"name":"custom","reverse":false,"rangeType":"number","rangeMin":0,"rangeMax":null,"progression":"fixed","stops":[{"color":"#D23115","stop":500},{"color":"#FCC400","stop":1000},{"color":"#68BC00","stop":1658}],"colorStops":[{"color":"#D23115","stop":0},{"color":"#FCC400","stop":500},{"color":"#68BC00","stop":1000}],"continuity":"above","maxSteps":5}}},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"28b89898-3feb-415a-8dd9-74d755ac7c2a":{"columns":{"f92c482e-1eee-4c2a-9338-64fb3eec286a":{"label":"Unique Visitors","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["f92c482e-1eee-4c2a-9338-64fb3eec286a"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":24,"y":14,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"15"},"panelIndex":"15","embeddableConfig":{"attributes":{"title":"[Logs] Response Codes Over Time + Annotations (converted)","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-b38fe501-4b47-4de8-a423-6656d1162174"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"xy-visualization-layer-f265e722-ae38-495c-903c-48aa7931fa82"}],"state":{"visualization":{"legend":{"isVisible":true,"showSingleSeries":true,"position":"bottom","shouldTruncate":true,"maxLines":1},"valueLabels":"hide","fittingFunction":"None","fillOpacity":0.5,"yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"yLeftScale":"linear","yRightScale":"linear","axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"seriesType":"area_percentage_stacked","layerType":"data","layerId":"b38fe501-4b47-4de8-a423-6656d1162174","accessors":["896c5eb2-81c5-44f1-a4a1-57344161ea62"],"yConfig":[{"forAccessor":"896c5eb2-81c5-44f1-a4a1-57344161ea62","color":"rgba(115,216,255,1)","axisMode":"left"}],"xAccessor":"8986e393-d24f-49b0-96ca-118fd66d75e5","splitAccessor":"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","palette":{"name":"default","type":"palette"}},{"layerId":"f265e722-ae38-495c-903c-48aa7931fa82","layerType":"annotations","ignoreGlobalFilters":true,"annotations":[{"type":"query","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","label":"Event","key":{"type":"point_in_time"},"color":"#D33115","timeField":"timestamp","icon":"asterisk","filter":{"type":"kibana_query","query":"tags:error AND tags:security","language":"lucene"},"extraFields":["geo.src"]}]}]},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"b38fe501-4b47-4de8-a423-6656d1162174":{"columns":{"8986e393-d24f-49b0-96ca-118fd66d75e5":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto","includeEmptyRows":true,"dropPartials":false}},"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b":{"label":"Filters","dataType":"string","operationType":"filters","scale":"ordinal","isBucketed":true,"params":{"filters":[{"input":{"query":"response.keyword >= 200 and response.keyword < 400","language":"kuery"},"label":"HTTP 2xx and 3xx"},{"input":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"label":"HTTP 4xx"},{"input":{"query":"response.keyword >= 500","language":"kuery"},"label":"HTTP 5xx"}]}},"896c5eb2-81c5-44f1-a4a1-57344161ea62":{"label":"Response Code Count","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["8986e393-d24f-49b0-96ca-118fd66d75e5","43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","896c5eb2-81c5-44f1-a4a1-57344161ea62"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{},"hidePanelTitles":false},"title":"[Logs] Response Codes Over Time + Annotations"},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}],"state":{"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","layerType":"data","metricAccessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":32,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":47,"w":24,"h":13,"i":"21bb0939-ee09-4021-8848-6552b3a6a788"},"panelIndex":"21bb0939-ee09-4021-8848-6552b3a6a788","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsDatatable","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-c840e93e-2949-4723-ad35-6bdb2d724404"}],"state":{"visualization":{"columns":[{"columnId":"4e64d6d7-4f92-4d5e-abbb-13796604db30","isTransposed":false},{"columnId":"fb9a848d-76f3-4005-a067-4259a50b5621","isTransposed":false},{"columnId":"a2760bc2-9a6e-46a1-8595-86f61573c7cf","isTransposed":false},{"columnId":"2c8bd8d5-35ff-4386-8d27-3ba882b13e43","isTransposed":false,"colorMode":"text","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#d23115","stop":1000},{"color":"#fcc400","stop":1500},{"color":"#68bc00","stop":1501}],"rangeType":"number","rangeMin":0,"rangeMax":null,"continuity":"above","colorStops":[{"color":"#d23115","stop":0},{"color":"#fcc400","stop":1000},{"color":"#68bc00","stop":1500}],"name":"custom"}}},{"columnId":"defa6f97-b874-4556-8438-056fb437787b","isTransposed":false,"colorMode":"text","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#D23115","stop":10},{"color":"#FCC400","stop":25},{"color":"#68bc00","stop":26}],"rangeType":"number","rangeMin":0,"rangeMax":null,"continuity":"above","colorStops":[{"color":"#D23115","stop":0},{"color":"#FCC400","stop":10},{"color":"#68bc00","stop":25}],"name":"custom"}}}],"layerId":"c840e93e-2949-4723-ad35-6bdb2d724404","layerType":"data"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"c840e93e-2949-4723-ad35-6bdb2d724404":{"columns":{"4e64d6d7-4f92-4d5e-abbb-13796604db30":{"label":"Type","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"extension.keyword","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"fb9a848d-76f3-4005-a067-4259a50b5621"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"},"include":[],"exclude":[],"includeIsRegex":false,"excludeIsRegex":false},"customLabel":true},"fb9a848d-76f3-4005-a067-4259a50b5621":{"label":"Bytes (Total)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"a2760bc2-9a6e-46a1-8595-86f61573c7cf":{"label":"Bytes (Last Hour)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","reducedTimeRange":"1h","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"2c8bd8d5-35ff-4386-8d27-3ba882b13e43":{"label":"Unique Visits (Total)","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true},"defa6f97-b874-4556-8438-056fb437787b":{"label":"Unique count of clientip","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"reducedTimeRange":"1h","params":{"emptyAsNull":true}}},"columnOrder":["4e64d6d7-4f92-4d5e-abbb-13796604db30","fb9a848d-76f3-4005-a067-4259a50b5621","a2760bc2-9a6e-46a1-8595-86f61573c7cf","2c8bd8d5-35ff-4386-8d27-3ba882b13e43","defa6f97-b874-4556-8438-056fb437787b"],"sampling":1,"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":47,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":60,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"formBased":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","type":"index-pattern"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"}]', + panelsJSON: `[{"version":"8.8.0","type":"map","gridData":{"x":0,"y":14,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"hiddenLayers":[],"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"openTOCDetails":[],"enhancements":{}},"panelRefName":"panel_4"},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"11"},"panelIndex":"11","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-28b89898-3feb-415a-8dd9-74d755ac7c2a"}],"state":{"visualization":{"layerId":"28b89898-3feb-415a-8dd9-74d755ac7c2a","layerType":"data","metricAccessor":"f92c482e-1eee-4c2a-9338-64fb3eec286a","palette":{"name":"custom","type":"palette","params":{"steps":3,"name":"custom","reverse":false,"rangeType":"number","rangeMin":0,"rangeMax":null,"progression":"fixed","stops":[{"color":"#D23115","stop":500},{"color":"#FCC400","stop":1000},{"color":"#68BC00","stop":1658}],"colorStops":[{"color":"#D23115","stop":0},{"color":"#FCC400","stop":500},{"color":"#68BC00","stop":1000}],"continuity":"above","maxSteps":5}}},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"28b89898-3feb-415a-8dd9-74d755ac7c2a":{"columns":{"f92c482e-1eee-4c2a-9338-64fb3eec286a":{"label":"Unique Visitors","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["f92c482e-1eee-4c2a-9338-64fb3eec286a"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":24,"y":14,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"15"},"panelIndex":"15","embeddableConfig":{"attributes":{"title":"[Logs] Response Codes Over Time + Annotations (converted)","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-b38fe501-4b47-4de8-a423-6656d1162174"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"xy-visualization-layer-f265e722-ae38-495c-903c-48aa7931fa82"}],"state":{"visualization":{"legend":{"isVisible":true,"showSingleSeries":true,"position":"bottom","shouldTruncate":true,"maxLines":1},"valueLabels":"hide","fittingFunction":"None","fillOpacity":0.5,"yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"yLeftScale":"linear","yRightScale":"linear","axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"seriesType":"area_percentage_stacked","layerType":"data","layerId":"b38fe501-4b47-4de8-a423-6656d1162174","accessors":["896c5eb2-81c5-44f1-a4a1-57344161ea62"],"yConfig":[{"forAccessor":"896c5eb2-81c5-44f1-a4a1-57344161ea62","color":"rgba(115,216,255,1)","axisMode":"left"}],"xAccessor":"8986e393-d24f-49b0-96ca-118fd66d75e5","splitAccessor":"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","palette":{"name":"default","type":"palette"}},{"layerId":"f265e722-ae38-495c-903c-48aa7931fa82","layerType":"annotations","ignoreGlobalFilters":true,"annotations":[{"type":"query","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","label":"Event","key":{"type":"point_in_time"},"color":"#D33115","timeField":"timestamp","icon":"asterisk","filter":{"type":"kibana_query","query":"tags:error AND tags:security","language":"lucene"},"extraFields":["geo.src"]}]}]},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"b38fe501-4b47-4de8-a423-6656d1162174":{"columns":{"8986e393-d24f-49b0-96ca-118fd66d75e5":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto","includeEmptyRows":true,"dropPartials":false}},"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b":{"label":"Filters","dataType":"string","operationType":"filters","scale":"ordinal","isBucketed":true,"params":{"filters":[{"input":{"query":"response.keyword >= 200 and response.keyword < 400","language":"kuery"},"label":"HTTP 2xx and 3xx"},{"input":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"label":"HTTP 4xx"},{"input":{"query":"response.keyword >= 500","language":"kuery"},"label":"HTTP 5xx"}]}},"896c5eb2-81c5-44f1-a4a1-57344161ea62":{"label":"Response Code Count","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["8986e393-d24f-49b0-96ca-118fd66d75e5","43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","896c5eb2-81c5-44f1-a4a1-57344161ea62"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{},"hidePanelTitles":false},"title":"[Logs] Response Codes Over Time + Annotations"},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}],"state":{"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","layerType":"data","metricAccessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":32,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":47,"w":24,"h":13,"i":"21bb0939-ee09-4021-8848-6552b3a6a788"},"panelIndex":"21bb0939-ee09-4021-8848-6552b3a6a788","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsDatatable","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-c840e93e-2949-4723-ad35-6bdb2d724404"}],"state":{"visualization":{"columns":[{"columnId":"4e64d6d7-4f92-4d5e-abbb-13796604db30","isTransposed":false},{"columnId":"fb9a848d-76f3-4005-a067-4259a50b5621","isTransposed":false},{"columnId":"a2760bc2-9a6e-46a1-8595-86f61573c7cf","isTransposed":false},{"columnId":"2c8bd8d5-35ff-4386-8d27-3ba882b13e43","isTransposed":false,"colorMode":"text","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#d23115","stop":1000},{"color":"#fcc400","stop":1500},{"color":"#68bc00","stop":1501}],"rangeType":"number","rangeMin":0,"rangeMax":null,"continuity":"above","colorStops":[{"color":"#d23115","stop":0},{"color":"#fcc400","stop":1000},{"color":"#68bc00","stop":1500}],"name":"custom"}}},{"columnId":"defa6f97-b874-4556-8438-056fb437787b","isTransposed":false,"colorMode":"text","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#D23115","stop":10},{"color":"#FCC400","stop":25},{"color":"#68bc00","stop":26}],"rangeType":"number","rangeMin":0,"rangeMax":null,"continuity":"above","colorStops":[{"color":"#D23115","stop":0},{"color":"#FCC400","stop":10},{"color":"#68bc00","stop":25}],"name":"custom"}}}],"layerId":"c840e93e-2949-4723-ad35-6bdb2d724404","layerType":"data"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"c840e93e-2949-4723-ad35-6bdb2d724404":{"columns":{"4e64d6d7-4f92-4d5e-abbb-13796604db30":{"label":"Type","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"extension.keyword","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"fb9a848d-76f3-4005-a067-4259a50b5621"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"},"include":[],"exclude":[],"includeIsRegex":false,"excludeIsRegex":false},"customLabel":true},"fb9a848d-76f3-4005-a067-4259a50b5621":{"label":"Bytes (Total)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"a2760bc2-9a6e-46a1-8595-86f61573c7cf":{"label":"Bytes (Last Hour)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","reducedTimeRange":"1h","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"2c8bd8d5-35ff-4386-8d27-3ba882b13e43":{"label":"Unique Visits (Total)","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true},"defa6f97-b874-4556-8438-056fb437787b":{"label":"Unique count of clientip","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"reducedTimeRange":"1h","params":{"emptyAsNull":true}}},"columnOrder":["4e64d6d7-4f92-4d5e-abbb-13796604db30","fb9a848d-76f3-4005-a067-4259a50b5621","a2760bc2-9a6e-46a1-8595-86f61573c7cf","2c8bd8d5-35ff-4386-8d27-3ba882b13e43","defa6f97-b874-4556-8438-056fb437787b"],"sampling":1,"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":47,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":60,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"formBased":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","type":"index-pattern"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"}]`, timeFrom: 'now-7d/d', title: i18n.translate('home.sampleData.logsTsdbSpec.webTrafficTitle', { defaultMessage: '[Logs TSDB] Web Traffic', From f0eb5d695745f1f3a19ae6392618d1826ce29ce2 Mon Sep 17 00:00:00 2001 From: Bailey Cash Date: Fri, 24 Jan 2025 09:15:11 -0500 Subject: [PATCH 13/13] [Obs-UX-Management] SLO documentation in page header (#208075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Resolves #198176 This PR: - Adds link to SLO documentation to SLO page header - Creates a new path constant for SLO documentation to be shared between the welcome page and page header links Screenshot 2025-01-23 at 10 05 12 AM ### Testing (Manual) Both "Read the docs" and "SLO Documentation" links should both go to the same place, in a new tab: https://www.elastic.co/guide/en/observability/current/slo.html --- .../shared/kbn-doc-links/src/get_doc_links.ts | 3 +++ .../packages/shared/kbn-doc-links/src/types.ts | 1 + .../observability/public/utils/kibana_react.mock.ts | 8 ++++++++ .../public/components/header_menu/header_menu.tsx | 12 +++++++++++- .../public/pages/slo_details/slo_details.test.tsx | 8 ++++++++ .../slo/public/pages/slo_edit/slo_edit.test.tsx | 3 +++ .../plugins/slo/public/pages/slos/slos.test.tsx | 3 +++ .../public/pages/slos_welcome/slos_welcome.test.tsx | 8 ++++++++ .../slo/public/pages/slos_welcome/slos_welcome.tsx | 3 ++- 9 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts index e024e94a773e7..ad76bb9369cd9 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts @@ -575,6 +575,9 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D ? `${SERVERLESS_DOCS}observability-synthetics-get-started-project.html` : `${OBSERVABILITY_DOCS}synthetic-run-tests.html#synthetic-monitor-choose-project`, syntheticsMigrateFromIntegration: `${OBSERVABILITY_DOCS}synthetics-migrate-from-integration.html`, + slo: isServerless + ? `${SERVERLESS_DOCS}observability-slos.html` + : `${OBSERVABILITY_DOCS}slo.html`, sloBurnRateRule: isServerless ? `${SERVERLESS_DOCS}observability-create-slo-burn-rate-alert-rule.html` : `${OBSERVABILITY_DOCS}slo-burn-rate-alert.html`, diff --git a/src/platform/packages/shared/kbn-doc-links/src/types.ts b/src/platform/packages/shared/kbn-doc-links/src/types.ts index 7eb18d6582e91..a065b7a9f6db9 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/types.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/types.ts @@ -390,6 +390,7 @@ export interface DocLinks { syntheticsCommandReference: string; syntheticsProjectMonitors: string; syntheticsMigrateFromIntegration: string; + slo: string; sloBurnRateRule: string; aiAssistant: string; }>; diff --git a/x-pack/solutions/observability/plugins/observability/public/utils/kibana_react.mock.ts b/x-pack/solutions/observability/plugins/observability/public/utils/kibana_react.mock.ts index 1e854540afb5c..95609a6c53436 100644 --- a/x-pack/solutions/observability/plugins/observability/public/utils/kibana_react.mock.ts +++ b/x-pack/solutions/observability/plugins/observability/public/utils/kibana_react.mock.ts @@ -19,6 +19,14 @@ export const kibanaStartMock = { ...observabilityPublicPluginsStartMock.createStart(), storage: coreMock.createStorage(), cases: { ...casesPluginMock.createStartContract() }, + docLinks: { + links: { + query: {}, + observability: { + slo: 'dummy_link', + }, + }, + }, }, }; }, diff --git a/x-pack/solutions/observability/plugins/slo/public/components/header_menu/header_menu.tsx b/x-pack/solutions/observability/plugins/slo/public/components/header_menu/header_menu.tsx index bc01ed55acbdf..92be96a212e53 100644 --- a/x-pack/solutions/observability/plugins/slo/public/components/header_menu/header_menu.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/components/header_menu/header_menu.tsx @@ -14,7 +14,7 @@ import { usePluginContext } from '../../hooks/use_plugin_context'; import { SLOS_BASE_PATH, SLO_SETTINGS_PATH } from '../../../common/locators/paths'; export function HeaderMenu(): React.ReactElement | null { - const { http, theme } = useKibana().services; + const { http, theme, docLinks } = useKibana().services; const { appMountParameters, isServerless } = usePluginContext(); return ( @@ -25,6 +25,16 @@ export function HeaderMenu(): React.ReactElement | null { + + {i18n.translate('xpack.slo.headerMenu.documentation', { + defaultMessage: 'SLO documentation', + })} + { get: () => 'http://localhost:5601', }, }, + docLinks: { + links: { + query: {}, + observability: { + slo: 'dummy_link', + }, + }, + }, dataViews: { create: jest.fn().mockResolvedValue({ getIndexPattern: jest.fn().mockReturnValue('some-index'), diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/slo_edit.test.tsx index 8d52ed914302c..00c4cbb471ee8 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/slo_edit.test.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/slo_edit.test.tsx @@ -104,6 +104,9 @@ const mockKibana = (license: ILicense | null = licenseMock) => { docLinks: { links: { query: {}, + observability: { + slo: 'dummy_link', + }, }, }, http: { diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx index 3a9c21c2ffa0e..cdf795a8f2093 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx @@ -100,6 +100,9 @@ const mockKibana = () => { docLinks: { links: { query: {}, + observability: { + slo: 'dummy_link', + }, }, }, http: { diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.test.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.test.tsx index 369a1fbeed932..945c01a3496b6 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.test.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.test.tsx @@ -52,6 +52,14 @@ const mockKibana = () => { prepend: (url: string) => url, }, }, + docLinks: { + links: { + query: {}, + observability: { + slo: 'dummy_link', + }, + }, + }, observabilityAIAssistant: mockObservabilityAIAssistant, }, }); diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.tsx index ccb273aad913e..e99970dad2cc8 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos_welcome/slos_welcome.tsx @@ -32,6 +32,7 @@ export function SlosWelcomePage() { const { application: { navigateToUrl }, http: { basePath }, + docLinks, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); @@ -179,7 +180,7 @@ export function SlosWelcomePage() {   {i18n.translate('xpack.slo.sloList.welcomePrompt.learnMoreLink', {