From 52bce04ddaa99a6275a8bad95dcbb94f7aaf27d8 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Mon, 15 Aug 2022 13:44:56 +0300 Subject: [PATCH 01/11] Security nav beta badges (#138145) --- .../navigation/nav_item_beta_badge.tsx | 26 +++++++++++++++++++ .../solution_grouped_nav_panel.styles.tsx | 14 +++------- .../solution_grouped_nav_panel.tsx | 6 ++--- .../components/landing_links_icons.tsx | 18 ++++++++++--- .../components/landing_links_images.tsx | 18 ++++--------- 5 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx new file mode 100644 index 0000000000000..ddccbee1546d3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { css } from '@emotion/react'; +import { EuiBetaBadge, useEuiTheme } from '@elastic/eui'; +import { BETA } from '../../translations'; + +export const NavItemBetaBadge = () => { + const { euiTheme } = useEuiTheme(); + + return ( + + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx index 1342757042723..dc54976bb12e3 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiBetaBadge, EuiPanel } from '@elastic/eui'; +import { EuiPanel } from '@elastic/eui'; import styled from 'styled-components'; export const EuiPanelStyled = styled(EuiPanel)<{ $bottomOffset?: string }>` @@ -23,20 +23,14 @@ export const EuiPanelStyled = styled(EuiPanel)<{ $bottomOffset?: string }>` bottom: ${$bottomOffset}; box-shadow: // left - -${theme.eui.euiSizeS} 0 ${theme.eui.euiSizeS} -${theme.eui.euiSizeS} rgb(0 0 0 / 15%), + -${theme.eui.euiSizeS} 0 ${theme.eui.euiSizeS} -${theme.eui.euiSizeS} rgb(0 0 0 / 15%), // right - ${theme.eui.euiSizeS} 0 ${theme.eui.euiSizeS} -${theme.eui.euiSizeS} rgb(0 0 0 / 15%), + ${theme.eui.euiSizeS} 0 ${theme.eui.euiSizeS} -${theme.eui.euiSizeS} rgb(0 0 0 / 15%), // bottom inset to match timeline bar top shadow - inset 0 -${theme.eui.euiSizeXS} ${theme.eui.euiSizeXS} -${theme.eui.euiSizeXS} rgb(0 0 0 / 6%); + inset 0 -${theme.eui.euiSizeXS} ${theme.eui.euiSizeXS} -${theme.eui.euiSizeXS} rgb(0 0 0 / 6%); `} `; -// Remove explicit typing after eui update https://github.com/elastic/eui/pull/6086 -export const EuiBetaBadgeStyled: typeof EuiBetaBadge = styled(EuiBetaBadge)` - margin-left: ${({ theme }) => theme.eui.euiSizeS}; - color: ${(props) => props.theme.eui.euiTextColor}; -`; - export const FlexLink = styled.a` display: flex; align-items: center; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx index 4a789a8bb4343..4516f103f9167 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx @@ -23,10 +23,10 @@ import { useIsWithinBreakpoints, } from '@elastic/eui'; import classNames from 'classnames'; -import { EuiBetaBadgeStyled, EuiPanelStyled, FlexLink } from './solution_grouped_nav_panel.styles'; +import { EuiPanelStyled, FlexLink } from './solution_grouped_nav_panel.styles'; import type { DefaultSideNavItem } from './types'; import type { LinkCategories } from '../../../links/types'; -import { BETA } from '../../../translations'; +import { NavItemBetaBadge } from '../nav_item_beta_badge'; export interface SolutionNavPanelProps { onClose: () => void; @@ -170,7 +170,7 @@ const SolutionNavPanelItems: React.FC = ({ items, on }} > {label} - {isBeta && } + {isBeta && } {description} diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx index cfdc9e20d7c13..b82ecc9215a05 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx @@ -8,6 +8,7 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTitle } fr import React from 'react'; import styled from 'styled-components'; +import { NavItemBetaBadge } from '../../common/components/navigation/nav_item_beta_badge'; import { SecuritySolutionLinkAnchor, withSecuritySolutionLink, @@ -35,7 +36,7 @@ const StyledEuiTitle = styled(EuiTitle)` export const LandingLinksIcons: React.FC = ({ items }) => ( - {items.map(({ title, description, id, icon }) => ( + {items.map(({ title, description, id, icon, isBeta }) => ( = ({ items }) - -

{title}

-
+ + + +

{title}

+
+
+ {isBeta && ( + + + + )} +
diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx index d231d8c8d4113..b5479e0e6c5a9 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx @@ -5,7 +5,6 @@ * 2.0. */ import { - EuiBetaBadge, EuiCard, EuiFlexGroup, EuiFlexItem, @@ -17,8 +16,8 @@ import { import React from 'react'; import styled from 'styled-components'; import { withSecuritySolutionLink } from '../../common/components/links'; +import { NavItemBetaBadge } from '../../common/components/navigation/nav_item_beta_badge'; import type { NavLinkItem } from '../../common/components/navigation/types'; -import { BETA } from '../../common/translations'; interface LandingImagesProps { items: NavLinkItem[]; @@ -54,13 +53,6 @@ const TitleText = styled.h2` display: inline; `; -// Remove explicit typing after eui update https://github.com/elastic/eui/pull/6086 -const BetaBadge: typeof EuiBetaBadge = styled(EuiBetaBadge)` - vertical-align: text-top; - margin-left: ${({ theme }) => theme.eui.euiSizeS}; - color: ${(props) => props.theme.eui.euiTextColor}; -`; - const SecuritySolutionLink = withSecuritySolutionLink(Link); export const LandingLinksImages: React.FC = ({ items }) => ( @@ -86,7 +78,7 @@ export const LandingLinksImages: React.FC = ({ items }) => ( {title} - {isBeta && } + {isBeta && } @@ -142,10 +134,10 @@ export const LandingImageCards: React.FC = React.memo(({ ite } title={ -
+ {title} - {isBeta && } -
+ {isBeta && } +
} description={{description}} From 1f5b803d6fc6230ddc34c4bb32e7c0d85ae162b2 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Mon, 15 Aug 2022 07:39:20 -0400 Subject: [PATCH 02/11] Increase index poll timeout to 5 seconds (#138768) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/search_index/index_view_logic.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts index 319209c67fda7..3a9cf45ad9342 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts @@ -35,7 +35,7 @@ import { import { CrawlerLogic } from './crawler/crawler_logic'; import { IndexNameLogic } from './index_name_logic'; -const FETCH_INDEX_POLLING_DURATION = 1000; // 1 seconds +const FETCH_INDEX_POLLING_DURATION = 5000; // 1 seconds const FETCH_INDEX_POLLING_DURATION_ON_FAILURE = 30000; // 30 seconds type FetchIndexApiValues = Actions; From 99ea41c23762b1eba3090d9b4d330ec42d123d93 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Mon, 15 Aug 2022 15:03:55 +0300 Subject: [PATCH 03/11] [Canvas] PieVis integration to Canvas. (#124325) * Added the sidebar of the pieVis. * Added the element of the pieVis. * Removed pieVis from exposed elements. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../mosaic_vis_function.test.ts.snap | 2 +- .../treemap_vis_function.test.ts.snap | 2 +- .../common/expression_functions/i18n.ts | 3 +- .../elements/heatmap/index.ts | 3 +- .../canvas_plugin_src/elements/index.ts | 7 +- .../elements/pie_vis/index.ts | 21 ++ .../canvas_plugin_src/uis/arguments/index.ts | 2 + .../partition_labels/default_expression.ts | 25 ++ .../partition_labels/extended_template.tsx | 154 ++++++++ .../uis/arguments/partition_labels/index.ts | 21 ++ .../partition_labels/simple_template.tsx | 54 +++ .../uis/arguments/partition_labels/types.ts | 16 + .../uis/arguments/partition_labels/utils.ts | 25 ++ .../canvas_plugin_src/uis/models/index.js | 11 +- .../uis/models/partition_labels.ts | 77 ++++ .../canvas_plugin_src/uis/views/index.ts | 2 + .../canvas_plugin_src/uis/views/pie_vis.ts | 185 ++++++++++ .../canvas/i18n/elements/element_strings.ts | 8 + x-pack/plugins/canvas/i18n/ui.ts | 329 ++++++++++++++++++ .../arg_add_popover/arg_add_popover.scss | 5 + .../public/components/popover/popover.tsx | 2 + 21 files changed, 947 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/elements/pie_vis/index.ts create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/default_expression.ts create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/index.ts create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/types.ts create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/models/partition_labels.ts create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie_vis.ts diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap index 2a06459822a0e..859f644454169 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap @@ -177,6 +177,6 @@ Object { } `; -exports[`interpreter/functions#mosaicVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported"`; +exports[`interpreter/functions#mosaicVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported."`; exports[`interpreter/functions#mosaicVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap index 9f6210f42b48a..ef1c7be526670 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap @@ -177,6 +177,6 @@ Object { } `; -exports[`interpreter/functions#treemapVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported"`; +exports[`interpreter/functions#treemapVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported."`; exports[`interpreter/functions#treemapVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts index d7839d1f7d1e9..ec4357c269f37 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts @@ -123,9 +123,10 @@ export const strings = { export const errors = { moreThanNBucketsAreNotSupportedError: (maxLength: number) => i18n.translate('expressionPartitionVis.reusable.function.errors.moreThenNumberBuckets', { - defaultMessage: 'More than {maxLength} buckets are not supported', + defaultMessage: 'More than {maxLength} buckets are not supported.', values: { maxLength }, }), + splitRowAndSplitColumnAreSpecifiedError: () => i18n.translate('expressionPartitionVis.reusable.function.errors.splitRowAndColumnSpecified', { defaultMessage: diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/heatmap/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/heatmap/index.ts index ebb2b1c1edd1e..c087bd7e77d8f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/elements/heatmap/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/heatmap/index.ts @@ -12,7 +12,8 @@ export const heatmap: ElementFactory = () => ({ type: 'chart', help: 'Heatmap visualization', icon: 'heatmap', - expression: `filters + expression: `kibana +| selectFilter | demodata | head 10 | heatmap xAccessor={visdimension "age"} yAccessor={visdimension "project"} valueAccessor={visdimension "cost"} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts index 38a1e7567e73f..2cfbf5081491d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts @@ -38,6 +38,7 @@ import { heatmap } from './heatmap'; import { SetupInitializer } from '../plugin'; import { ElementFactory } from '../../types'; +import { pieVis } from './pie_vis'; const elementSpecs = [ areaChart, @@ -68,6 +69,8 @@ const elementSpecs = [ heatmap, ]; +const notExposedElementsSpecs = [metricVis, legacyMetricVis, pieVis]; + const initializeElementFactories = [metricElementInitializer]; export const initializeElements: SetupInitializer = (core, plugins) => { @@ -78,8 +81,8 @@ export const initializeElements: SetupInitializer = (core, plu return applyElementStrings(specs); }; -// For testing purpose. Will be removed after exposing `metricVis` element. +// For testing purpose. Will be removed after exposing `metricVis`, pieVis elements. export const initializeElementsSpec: SetupInitializer = (core, plugins) => { const specs = initializeElements(core, plugins); - return [...applyElementStrings([metricVis, legacyMetricVis]), ...specs]; + return [...applyElementStrings(notExposedElementsSpecs), ...specs]; }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/pie_vis/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/pie_vis/index.ts new file mode 100644 index 0000000000000..e7ece1155560a --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/pie_vis/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ElementFactory } from '../../../types'; + +export const pieVis: ElementFactory = () => ({ + name: 'pieVis', + displayName: '(New) Pie Vis', + type: 'chart', + help: 'Pie visualization', + icon: 'visPie', + expression: `kibana +| selectFilter +| demodata +| head 10 +| pieVis metric={visdimension "age"} buckets={visdimension "project"} buckets={visdimension "cost"} legendDisplay="default" +| render`, +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts index 897422edaeb76..a25d62fefa234 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts @@ -34,6 +34,7 @@ import { toggle } from './toggle'; import { visdimension } from './vis_dimension'; import { colorPicker } from './color_picker'; import { editor } from './editor'; +import { partitionLabels } from './partition_labels'; import { SetupInitializer } from '../../plugin'; @@ -55,6 +56,7 @@ export const args = [ visdimension, colorPicker, editor, + partitionLabels, ]; export const initializers = [dateFormatInitializer, numberFormatInitializer]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/default_expression.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/default_expression.ts new file mode 100644 index 0000000000000..6984f5ac5a431 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/default_expression.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExpressionAstExpression } from '@kbn/expressions-plugin/common'; + +export const defaultExpression = (): ExpressionAstExpression => ({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'partitionLabels', + arguments: { + show: [true], + position: ['default'], + values: [true], + percentDecimals: [2], + valuesFormat: ['percent'], + }, + }, + ], +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx new file mode 100644 index 0000000000000..b5aa5d6184c99 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/extended_template.tsx @@ -0,0 +1,154 @@ +/* + * 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, { ChangeEvent, MouseEvent, FunctionComponent, useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { + EuiFormRow, + EuiRange, + EuiSelect, + EuiSelectOption, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, + EuiText, +} from '@elastic/eui'; +import { ExpressionAstExpression } from '@kbn/expressions-plugin/common'; +import { set } from 'lodash'; +import { defaultExpression } from './default_expression'; +import { Fields } from './types'; +import { getFieldPath, getFieldValue } from './utils'; +import { ArgumentStrings } from '../../../../i18n'; + +const { PartitionLabels: strings } = ArgumentStrings; + +export interface Props { + onValueChange: (argValue: ExpressionAstExpression) => void; + argValue: null | ExpressionAstExpression; +} + +const SHOW_FIELD = 'show'; +const POSITION_FIELD = 'position'; +const VALUES_FIELD = 'values'; +const VALUES_FORMAT_FIELD = 'valuesFormat'; +const PERCENT_DECIMALS_FIELD = 'percentDecimals'; + +export const ExtendedTemplate: FunctionComponent = ({ onValueChange, argValue }) => { + const showLabels = getFieldValue(argValue, SHOW_FIELD); + const showValues = getFieldValue(argValue, VALUES_FIELD); + const valueFormat = getFieldValue(argValue, VALUES_FORMAT_FIELD); + const percentDecimals = getFieldValue(argValue, PERCENT_DECIMALS_FIELD); + + const positions: EuiSelectOption[] = [ + { text: strings.getPositionDefaultLabel(), value: 'default' }, + { text: strings.getPositionInsideLabel(), value: 'inside' }, + ]; + + const valuesFormats: EuiSelectOption[] = [ + { text: strings.getValuesFormatValueLabel(), value: 'value' }, + { text: strings.getValuesFormatPercentLabel(), value: 'percent' }, + ]; + + useEffect(() => { + if (!argValue) { + onValueChange(defaultExpression()); + } + }, [argValue, onValueChange]); + + const onChangeField = useCallback( + (field: Fields, value: unknown) => { + const path = getFieldPath(field); + const oldArgValue = argValue ?? defaultExpression(); + const newArgValue = set(oldArgValue, path, value); + + onValueChange(newArgValue); + }, + [argValue, onValueChange] + ); + + const onToggleFieldChange = useCallback( + (field: Fields) => (event: EuiSwitchEvent) => { + onChangeField(field, event.target.checked); + }, + [onChangeField] + ); + + const onCommonFieldChange = useCallback( + (field: Fields) => + ( + event: ChangeEvent | MouseEvent + ) => { + onChangeField(field, event.currentTarget.value); + }, + [onChangeField] + ); + + if (!showLabels) { + return ( + +

{strings.getSwitchedOffShowLabelsLabel()}

+
+ ); + } + + return ( + <> + + + + + + + + {showValues && ( + + + + )} + {showValues && valueFormat === 'percent' && ( + + { + if (isValid) { + onCommonFieldChange(PERCENT_DECIMALS_FIELD)(e); + } + }} + /> + + )} + + ); +}; + +ExtendedTemplate.propTypes = { + onValueChange: PropTypes.func.isRequired, + argValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]).isRequired, +}; + +ExtendedTemplate.displayName = 'PartitionLabelsExtendedArg'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/index.ts new file mode 100644 index 0000000000000..1a69ff5907fca --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; +import { ExtendedTemplate } from './extended_template'; +import { SimpleTemplate } from './simple_template'; +import { ArgumentStrings } from '../../../../i18n'; + +const { PartitionLabels: strings } = ArgumentStrings; + +export const partitionLabels = () => ({ + name: 'partitionLabels', + displayName: strings.getDisplayName(), + help: strings.getHelp(), + simpleTemplate: templateFromReactComponent(SimpleTemplate), + template: templateFromReactComponent(ExtendedTemplate), +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx new file mode 100644 index 0000000000000..53d9b3e2d4146 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/simple_template.tsx @@ -0,0 +1,54 @@ +/* + * 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, { FunctionComponent, useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { ExpressionAstExpression } from '@kbn/expressions-plugin/common'; +import { set } from 'lodash'; +import { defaultExpression } from './default_expression'; +import { getFieldPath, getFieldValue } from './utils'; + +export interface Props { + onValueChange: (argValue: ExpressionAstExpression) => void; + argValue: null | ExpressionAstExpression; +} + +const SHOW_FIELD = 'show'; + +export const SimpleTemplate: FunctionComponent = ({ onValueChange, argValue }) => { + const showValuePath = getFieldPath(SHOW_FIELD); + + useEffect(() => { + if (!argValue) { + onValueChange(defaultExpression()); + } + }, [argValue, onValueChange]); + + const onToggle = useCallback( + (event: EuiSwitchEvent) => { + const oldArgValue = argValue ?? defaultExpression(); + const newArgValue = set(oldArgValue, showValuePath, event.target.checked); + + onValueChange(newArgValue); + }, + [argValue, onValueChange, showValuePath] + ); + + const showLabels = getFieldValue(argValue, SHOW_FIELD, false); + + return ( + + ); +}; + +SimpleTemplate.propTypes = { + onValueChange: PropTypes.func.isRequired, + argValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]).isRequired, +}; + +SimpleTemplate.displayName = 'PartitionLabelsSimpleArg'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/types.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/types.ts new file mode 100644 index 0000000000000..5ca67c5f5a447 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/types.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 interface PartitionLabelsArguments { + show: boolean; + position: 'inside' | 'default'; + values: boolean; + valuesFormat: 'percent' | 'value'; + percentDecimals: number; +} + +export type Fields = keyof PartitionLabelsArguments; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts new file mode 100644 index 0000000000000..5aeffe9c6961f --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/partition_labels/utils.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExpressionAstExpression } from '@kbn/expressions-plugin/common'; +import { get } from 'lodash'; +import { PartitionLabelsArguments } from './types'; + +export const getFieldPath = (field: keyof PartitionLabelsArguments) => + `chain.0.arguments.${field}.0`; + +export const getFieldValue = ( + ast: null | ExpressionAstExpression, + field: keyof PartitionLabelsArguments, + defaultValue?: unknown +) => { + if (!ast) { + return null; + } + + return get(ast, getFieldPath(field), defaultValue); +}; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js index 82a0fbfbb3a45..492d110a52545 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js @@ -11,5 +11,14 @@ import { tagcloud } from './tagcloud'; import { metricVis } from './metric_vis'; import { heatmapLegend } from './heatmap_legend'; import { heatmapGrid } from './heatmap_grid'; +import { partitionLabels } from './partition_labels'; -export const modelSpecs = [pointseries, math, tagcloud, metricVis, heatmapLegend, heatmapGrid]; +export const modelSpecs = [ + pointseries, + math, + tagcloud, + metricVis, + heatmapLegend, + heatmapGrid, + partitionLabels, +]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/models/partition_labels.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/partition_labels.ts new file mode 100644 index 0000000000000..38148f26f408e --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/partition_labels.ts @@ -0,0 +1,77 @@ +/* + * 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 { get } from 'lodash'; +import { getState, getValue } from '../../../public/lib/resolved_arg'; +import { ModelStrings } from '../../../i18n'; +import { ResolvedColumns } from '../../../public/expression_types/arg'; + +const { PartitionLabels: strings } = ModelStrings; + +export const partitionLabels = () => ({ + name: 'partitionLabels', + displayName: strings.getDisplayName(), + args: [ + { + name: 'show', + displayName: strings.getShowDisplayName(), + help: strings.getShowHelp(), + argType: 'toggle', + default: true, + }, + { + name: 'position', + displayName: strings.getPositionDisplayName(), + help: strings.getPositionHelp(), + argType: 'select', + default: 'default', + options: { + choices: [ + { value: 'default', name: strings.getPositionDefaultOption() }, + { value: 'inside', name: strings.getPositionInsideOption() }, + ], + }, + }, + { + name: 'values', + displayName: strings.getValuesDisplayName(), + help: strings.getValuesHelp(), + argType: 'toggle', + default: true, + }, + { + name: 'percentDecimals', + displayName: strings.getPercentDecimalsDisplayName(), + help: strings.getPercentDecimalsHelp(), + argType: 'range', + default: 2, + options: { + min: 0, + max: 10, + }, + }, + { + name: 'valuesFormat', + displayName: strings.getValuesFormatDisplayName(), + help: strings.getValuesFormatHelp(), + argType: 'select', + default: 'percent', + options: { + choices: [ + { value: 'percent', name: strings.getValuesFormatPercentOption() }, + { value: 'value', name: strings.getValuesFormatValueOption() }, + ], + }, + }, + ], + resolve({ context }: any): ResolvedColumns { + if (getState(context) !== 'ready') { + return { columns: [] }; + } + return { columns: get(getValue(context), 'columns', []) }; + }, +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts index faa277224839e..7c750dc3ec0c1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts @@ -33,6 +33,7 @@ import { table } from './table'; // @ts-expect-error untyped local import { timefilterControl } from './timefilterControl'; import { heatmap } from './heatmap'; +import { pieVis } from './pie_vis'; import { SetupInitializer } from '../../plugin'; @@ -51,6 +52,7 @@ export const viewSpecs = [ table, timefilterControl, heatmap, + pieVis, ]; export const viewInitializers = [metricInitializer]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie_vis.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie_vis.ts new file mode 100644 index 0000000000000..c5b964473866b --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/pie_vis.ts @@ -0,0 +1,185 @@ +/* + * 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 { get } from 'lodash'; +import { ResolvedColumns } from '../../../public/expression_types/arg'; + +import { ViewStrings } from '../../../i18n'; +import { getState, getValue } from '../../../public/lib/resolved_arg'; + +const { PieVis: pieVisStrings, PartitionVis: strings } = ViewStrings; + +export const pieVis = () => ({ + name: 'pieVis', + displayName: pieVisStrings.getDisplayName(), + args: [ + { + name: 'metric', + displayName: strings.getMetricColumnDisplayName(), + help: strings.getMetricColumnHelp(), + argType: 'vis_dimension', + default: `{visdimension}`, + }, + { + name: 'buckets', + displayName: strings.getBucketColumnDisplayName(), + help: strings.getBucketColumnHelp(), + argType: 'vis_dimension', + default: `{visdimension}`, + multi: true, + }, + { + name: 'splitColumn', + displayName: strings.getSplitColumnDisplayName(), + help: strings.getSplitColumnHelp(), + argType: 'vis_dimension', + default: `{visdimension}`, + }, + { + name: 'splitRow', + displayName: strings.getSplitRowDisplayName(), + help: strings.getSplitRowHelp(), + argType: 'vis_dimension', + default: `{visdimension}`, + }, + { + name: 'isDonut', + displayName: strings.getIsDonutDisplayName(), + help: strings.getIsDonutHelp(), + argType: 'toggle', + default: false, + options: { + labelValue: strings.getIsDonutHelp(), + }, + }, + { + name: 'emptySizeRatio', + displayName: strings.getEmptySizeRatioDisplayName(), + help: strings.getEmptySizeRatioHelp(), + argType: 'range', + default: 0.4, + options: { + min: 0, + max: 1, + step: 0.01, + }, + }, + { + name: 'palette', + argType: 'palette', + help: strings.getPaletteHelp(), + }, + { + name: 'distinctColors', + displayName: strings.getDistictColorsDisplayName(), + help: strings.getDistictColorsHelp(), + argType: 'toggle', + default: false, + options: { + labelValue: strings.getDistictColorsToggleLabel(), + }, + }, + { + name: 'addTooltip', + displayName: strings.getAddTooltipDisplayName(), + help: strings.getAddTooltipHelp(), + argType: 'toggle', + default: true, + options: { + labelValue: strings.getAddTooltipToggleLabel(), + }, + }, + { + name: 'legendDisplay', + displayName: strings.getLegendDisplayName(), + help: strings.getLegendDisplayHelp(), + argType: 'select', + default: 'default', + options: { + choices: [ + { value: 'default', name: strings.getLegendDisplayDefaultOption() }, + { value: 'show', name: strings.getLegendDisplayShowOption() }, + { value: 'hide', name: strings.getLegendDisplayHideOption() }, + ], + }, + }, + { + name: 'legendPosition', + displayName: strings.getLegendPositionDisplayName(), + help: strings.getLegendPositionHelp(), + argType: 'select', + default: 'right', + options: { + choices: [ + { value: 'top', name: strings.getLegendPositionTopOption() }, + { value: 'right', name: strings.getLegendPositionRightOption() }, + { value: 'bottom', name: strings.getLegendPositionBottomOption() }, + { value: 'left', name: strings.getLegendPositionLeftOption() }, + ], + }, + }, + { + name: 'nestedLegend', + displayName: strings.getNestedLegendDisplayName(), + help: strings.getNestedLegendHelp(), + argType: 'toggle', + default: false, + options: { + labelValue: strings.getNestedLegendToggleLabel(), + }, + }, + { + name: 'truncateLegend', + displayName: strings.getTruncateLegendDisplayName(), + help: strings.getTruncateLegendHelp(), + argType: 'toggle', + default: true, + options: { + labelValue: strings.getTruncateLegendToggleLabel(), + }, + }, + { + name: 'maxLegendLines', + displayName: strings.getMaxLegendLinesDisplayName(), + help: strings.getMaxLegendLinesHelp(), + argType: 'number', + default: 1, + }, + { + name: 'respectSourceOrder', + displayName: strings.getRespectSourceOrderDisplayName(), + help: strings.getRespectSourceOrderHelp(), + argType: 'toggle', + default: true, + options: { + labelValue: strings.getRespectSourceOrderToggleLabel(), + }, + }, + { + name: 'startFromSecondLargestSlice', + displayName: strings.getStartFromSecondLargestSliceDisplayName(), + help: strings.getStartFromSecondLargestSliceHelp(), + argType: 'toggle', + default: true, + options: { + labelValue: strings.getStartFromSecondLargestSliceToggleLabel(), + }, + }, + { + name: 'labels', + displayName: strings.getLabelsDisplayName(), + help: strings.getLabelsHelp(), + argType: 'partitionLabels', + }, + ], + resolve({ context }: any): ResolvedColumns { + if (getState(context) !== 'ready') { + return { columns: [] }; + } + return { columns: get(getValue(context), 'columns', []) }; + }, +}); diff --git a/x-pack/plugins/canvas/i18n/elements/element_strings.ts b/x-pack/plugins/canvas/i18n/elements/element_strings.ts index 747e2ee8d0c18..06bded27e1f69 100644 --- a/x-pack/plugins/canvas/i18n/elements/element_strings.ts +++ b/x-pack/plugins/canvas/i18n/elements/element_strings.ts @@ -254,4 +254,12 @@ export const getElementStrings = (): ElementStringDict => ({ defaultMessage: 'Heatmap visualization', }), }, + pieVis: { + displayName: i18n.translate('xpack.canvas.elements.pieVisDisplayName', { + defaultMessage: '(New) Pie Vis', + }), + help: i18n.translate('xpack.canvas.elements.pieVisHelpText', { + defaultMessage: 'Pie visualization', + }), + }, }); diff --git a/x-pack/plugins/canvas/i18n/ui.ts b/x-pack/plugins/canvas/i18n/ui.ts index a449598038098..55d4f29f3182f 100644 --- a/x-pack/plugins/canvas/i18n/ui.ts +++ b/x-pack/plugins/canvas/i18n/ui.ts @@ -244,6 +244,56 @@ export const ArgumentStrings = { defaultMessage: 'Custom', }), }, + PartitionLabels: { + getDisplayName: () => + i18n.translate('xpack.canvas.uis.arguments.partitionLabelsTitle', { + defaultMessage: 'Partition labels', + }), + getHelp: () => + i18n.translate('xpack.canvas.uis.arguments.partitionLabelsLabel', { + defaultMessage: 'Labels configuration', + }), + getPositionDefaultLabel: () => + i18n.translate('xpack.canvas.uis.arguments.positionDefaultLabel', { + defaultMessage: 'Default', + }), + getPositionInsideLabel: () => + i18n.translate('xpack.canvas.uis.arguments.positionInsideLabel', { + defaultMessage: 'Inside', + }), + getPositionLabel: () => + i18n.translate('xpack.canvas.uis.arguments.positionLabel', { + defaultMessage: 'Position', + }), + getValuesLabel: () => + i18n.translate('xpack.canvas.uis.arguments.valuesLabel', { + defaultMessage: 'Values', + }), + getValuesToggle: () => + i18n.translate('xpack.canvas.uis.arguments.valuesToggle', { + defaultMessage: 'Show values', + }), + getValuesFormatLabel: () => + i18n.translate('xpack.canvas.uis.arguments.valuesFormatLabel', { + defaultMessage: 'Values format', + }), + getValuesFormatValueLabel: () => + i18n.translate('xpack.canvas.uis.arguments.valuesFormatValueLabel', { + defaultMessage: 'Value', + }), + getValuesFormatPercentLabel: () => + i18n.translate('xpack.canvas.uis.arguments.valuesFormatPercentLabel', { + defaultMessage: 'Percent', + }), + getPercentDecimalsLabel: () => + i18n.translate('xpack.canvas.uis.arguments.percentDecimals', { + defaultMessage: 'Percent decimals', + }), + getSwitchedOffShowLabelsLabel: () => + i18n.translate('xpack.canvas.uis.arguments.turnedOffShowLabelsLabel', { + defaultMessage: 'Switch on to view labels settings', + }), + }, Color: { getDisplayName: () => i18n.translate('xpack.canvas.uis.arguments.colorTitle', { @@ -727,6 +777,71 @@ export const ModelStrings = { defaultMessage: 'Specifies whether or not the X-axis title is visible', }), }, + PartitionLabels: { + getDisplayName: () => + i18n.translate('xpack.canvas.uis.models.parititionLabels.title', { + defaultMessage: 'Configure chart labels', + }), + getShowDisplayName: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.showTitle', { + defaultMessage: 'Show labels', + }), + getShowHelp: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.showDisplayName', { + defaultMessage: 'Show labels in a chart', + }), + getPositionDisplayName: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.positionDisplayName', { + defaultMessage: 'Defines the label position', + }), + getPositionHelp: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.positionTitle', { + defaultMessage: 'Defines the label position', + }), + getPositionDefaultOption: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.positionDefaultOption', { + defaultMessage: 'Default', + }), + getPositionInsideOption: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.positionInsideOption', { + defaultMessage: 'Inside', + }), + getValuesDisplayName: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.valuesDisplayName', { + defaultMessage: 'Show values in labels', + }), + getValuesHelp: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.valuesHelp', { + defaultMessage: 'Show values in labels', + }), + getPercentDecimalsDisplayName: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.percentDecimalsDisplayName', { + defaultMessage: 'Percent decimals in in labels', + }), + getPercentDecimalsHelp: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.percentDecimalsHelp', { + defaultMessage: 'Defines the number of decimals that will appear on the values as percent', + }), + getValuesFormatDisplayName: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.valuesFormatDisplayName', { + defaultMessage: 'Defines the format of the values', + }), + getValuesFormatHelp: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.valuesFormatHelp', { + defaultMessage: 'Defines the format of the values', + }), + getValuesFormatPercentOption: () => + i18n.translate( + 'xpack.canvas.uis.models.partitionLabels.args.valuesFormatPercentDisplayName', + { + defaultMessage: 'Percent', + } + ), + getValuesFormatValueOption: () => + i18n.translate('xpack.canvas.uis.models.partitionLabels.args.valuesFormatValueDisplayName', { + defaultMessage: 'Value', + }), + }, }; export const TransformStrings = { @@ -1572,4 +1687,218 @@ export const ViewStrings = { defaultMessage: 'If is set to true, the last range value will be right open', }), }, + PieVis: { + getDisplayName: () => + i18n.translate('xpack.canvas.uis.views.pieVisTitle', { + defaultMessage: '(New) Pie Visualization', + }), + }, + PartitionVis: { + getMetricColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.metricDisplayName', { + defaultMessage: 'Metric', + }), + getMetricColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.metricHelp', { + defaultMessage: 'Metric dimension configuration', + }), + getBucketColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.bucketDisplayName', { + defaultMessage: 'Bucket', + }), + getBucketColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.bucketHelp', { + defaultMessage: 'Bucket dimension configuration', + }), + getSplitRowDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.splitRowDisplayName', { + defaultMessage: 'Split row', + }), + getSplitRowHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.splitRowHelp', { + defaultMessage: 'Split row dimension configuration', + }), + getSplitColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.splitColumnDisplayName', { + defaultMessage: 'Split column', + }), + getSplitColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.splitColumnHelp', { + defaultMessage: 'Split column dimension configuration', + }), + getAddTooltipDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.addTooltipDisplayName', { + defaultMessage: 'Tooltip', + }), + getAddTooltipHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.addTooltipHelp', { + defaultMessage: 'Show tooltip on hover', + }), + getAddTooltipToggleLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.addTooltipToggleLabel', { + defaultMessage: 'Show tooltip', + }), + getLegendPositionDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendPositionTitle', { + defaultMessage: 'Placement', + }), + getLegendPositionHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendPositionLabel', { + defaultMessage: 'Set the legend position on the right, top, left, or bottom', + }), + getLegendPositionTopOption: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendPositionTopLabel', { + defaultMessage: 'Top', + }), + getLegendPositionBottomOption: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendPositionBottomLabel', { + defaultMessage: 'Bottom', + }), + getLegendPositionLeftOption: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendPositionLeftLabel', { + defaultMessage: 'Left', + }), + getLegendPositionRightOption: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendPositionRightLabel', { + defaultMessage: 'Right', + }), + getLegendDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendDisplayTitle', { + defaultMessage: 'Legend view', + }), + getLegendDisplayHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendDisplayLabel', { + defaultMessage: 'Show or hide the pie legend', + }), + getLegendDisplayDefaultOption: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendDisplayDefaultLabel', { + defaultMessage: 'Default', + }), + getLegendDisplayShowOption: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendDisplayShowLabel', { + defaultMessage: 'Show', + }), + getLegendDisplayHideOption: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.legendDisplayHideLabel', { + defaultMessage: 'Hide', + }), + getNestedLegendDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.nestedLegendDisplayName', { + defaultMessage: 'Detail legend', + }), + getNestedLegendHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.nestedLegendHelp', { + defaultMessage: 'Include details in the legend', + }), + getNestedLegendToggleLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.nestedLegendToggleLabel', { + defaultMessage: 'Show details', + }), + getTruncateLegendDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.truncateLegendDisplayName', { + defaultMessage: 'Legend text', + }), + getTruncateLegendHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.truncateLegendHelp', { + defaultMessage: 'Truncate the legend when it reaches the maximum width', + }), + getTruncateLegendToggleLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.truncateLegendToggleLabel', { + defaultMessage: 'Truncate when long', + }), + getMaxLegendLinesDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.maxLegendLinesDisplayName', { + defaultMessage: 'Max legend lines', + }), + getMaxLegendLinesHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.maxLegendLinesHelp', { + defaultMessage: 'Set the maximum number of lines for each legend item', + }), + getDistictColorsDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.distinctColorsDisplayName', { + defaultMessage: 'Slice colors', + }), + getDistictColorsHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.distinctColorsHelp', { + defaultMessage: 'Use different colors for slices with unequal values', + }), + getDistictColorsToggleLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.distictColorsToggleLabel', { + defaultMessage: 'Use distinct colors', + }), + getRespectSourceOrderDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.respectSourceOrderDisplayName', { + defaultMessage: 'Order of data', + }), + getRespectSourceOrderToggleLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.respectSourceOrderToggleLabel', { + defaultMessage: 'Use original order', + }), + getRespectSourceOrderHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.respectSourceOrderHelp', { + defaultMessage: 'Display the data in its original order instead of sorting it', + }), + getIsDonutDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.isDonutDisplayName', { + defaultMessage: 'Donut', + }), + getIsDonutHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.isDonutHelp', { + defaultMessage: 'Show donut chart', + }), + getEmptySizeRatioDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.emptySizeRatioDisplayName', { + defaultMessage: 'Donut hole size', + }), + getEmptySizeRatioHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.emptySizeRatioHelp', { + defaultMessage: 'Set the inner diameter of the donut hole', + }), + getPaletteHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.paletteHelp', { + defaultMessage: 'Specify the colors for the slices in the pie', + }), + getStartFromSecondLargestSliceDisplayName: () => + i18n.translate( + 'xpack.canvas.uis.views.partitionVis.args.startFromSecondLargestSliceDisplayName', + { + defaultMessage: 'Slice placement', + } + ), + getStartFromSecondLargestSliceHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.startFromSecondLargestSliceHelp', { + defaultMessage: 'Place the second largest slice in the first position of the pie', + }), + getStartFromSecondLargestSliceToggleLabel: () => + i18n.translate( + 'xpack.canvas.uis.views.partitionVis.args.startFromSecondLargestSliceToggleLabel', + { + defaultMessage: 'Start with second slice', + } + ), + getLabelsDisplayName: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.labelsDisplayName', { + defaultMessage: 'Label configuration', + }), + getLabelsHelp: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.args.labelsHelp', { + defaultMessage: 'Show label settings', + }), + getEnableLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.options.enableHelp', { + defaultMessage: 'Enable', + }), + getSaveLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.options.saveHelp', { + defaultMessage: 'Save', + }), + getTruncateLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.options.truncateHelp', { + defaultMessage: 'Truncate', + }), + getShowLabel: () => + i18n.translate('xpack.canvas.uis.views.partitionVis.options.showHelp', { + defaultMessage: 'Show', + }), + }, }; diff --git a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss index 15676d2b02490..b83dc3e60f581 100644 --- a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss +++ b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss @@ -3,5 +3,10 @@ } .canvasArg__addPopover { + @include euiScrollBar; + width: 250px; + max-height: 500px; + + overflow-y: auto; } diff --git a/x-pack/plugins/canvas/public/components/popover/popover.tsx b/x-pack/plugins/canvas/public/components/popover/popover.tsx index 0bd731476e724..2ef1d4efc10d3 100644 --- a/x-pack/plugins/canvas/public/components/popover/popover.tsx +++ b/x-pack/plugins/canvas/public/components/popover/popover.tsx @@ -95,6 +95,8 @@ export class Popover extends Component { isOpen={this.state.isPopoverOpen} closePopover={this.closePopover} container={appWrapper} + hasArrow={false} + offset={10} > {children({ closePopover: this.closePopover })} From 68130f7037c2aafcea89e14a14d747d9ff6278bc Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Mon, 15 Aug 2022 14:09:22 +0200 Subject: [PATCH 04/11] [Enterprise Search] Add replica settings to connectors indices (#138796) --- .../index_management/setup_indices.test.ts | 6 +++--- .../server/index_management/setup_indices.ts | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts index c713f204aeba4..63b777d0dff31 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts @@ -63,7 +63,7 @@ describe('Setup Indices', () => { }, properties: { completed_at: { type: 'date' }, - connector: connectorsMappings.properties, + connector: { properties: connectorsMappings.properties }, connector_id: { type: 'keyword', }, @@ -157,7 +157,7 @@ describe('Setup Indices', () => { expect(mockClient.asCurrentUser.indices.create).toHaveBeenCalledWith({ index: connectorsIndexName, mappings: connectorsMappings, - settings: { hidden: true }, + settings: { auto_expand_replicas: '0-3', hidden: true, number_of_replicas: 0 }, }); expect(mockClient.asCurrentUser.indices.updateAliases).toHaveBeenCalledWith({ actions: [ @@ -191,7 +191,7 @@ describe('Setup Indices', () => { expect(mockClient.asCurrentUser.indices.create).toHaveBeenCalledWith({ index: jobsIndexName, mappings: connectorsJobsMappings, - settings: { hidden: true }, + settings: { auto_expand_replicas: '0-3', hidden: true, number_of_replicas: 0 }, }); expect(mockClient.asCurrentUser.indices.updateAliases).toHaveBeenCalledWith({ actions: [ diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts index b0abe4d8ae8cf..08bcdbc38c3c5 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts @@ -53,6 +53,12 @@ const connectorMappingsProperties: Record = { sync_now: { type: 'boolean' }, }; +const defaultSettings: IndicesIndexSettings = { + auto_expand_replicas: '0-3', + hidden: true, + number_of_replicas: 0, +}; + const indices: IndexDefinition[] = [ { aliases: ['.elastic-connectors'], @@ -63,9 +69,7 @@ const indices: IndexDefinition[] = [ properties: connectorMappingsProperties, }, name: '.elastic-connectors-v1', - settings: { - hidden: true, - }, + settings: defaultSettings, }, { aliases: ['.elastic-connectors-sync-jobs'], @@ -75,7 +79,7 @@ const indices: IndexDefinition[] = [ }, properties: { completed_at: { type: 'date' }, - connector: connectorMappingsProperties, + connector: { properties: connectorMappingsProperties }, connector_id: { type: 'keyword', }, @@ -92,9 +96,7 @@ const indices: IndexDefinition[] = [ }, }, name: '.elastic-connectors-sync-jobs-v1', - settings: { - hidden: true, - }, + settings: defaultSettings, }, ]; From 6d1f3dff2c0398f15a1c50f4fff47ad4221a7d31 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Mon, 15 Aug 2022 15:08:28 +0200 Subject: [PATCH 05/11] [ResponseOps] Docs screenshots - initial set of automated docs screenshots (#138627) This PR adds initial support for automated ResponseOps docs screenshots and takes the first three screenshots. --- x-pack/test/functional/services/cases/api.ts | 22 ++++++- .../services/cases/single_case_view.ts | 53 +++++++++++++++ x-pack/test/screenshot_creation/apps/index.ts | 1 + .../ml_docs/anomaly_detection/custom_urls.ts | 6 +- .../anomaly_detection/geographic_data.ts | 22 ++++--- .../anomaly_detection/mapping_anomalies.ts | 11 ++-- .../anomaly_detection/population_analysis.ts | 10 +-- .../data_frame_analytics/classification.ts | 44 ++++++++----- .../data_frame_analytics/outlier_detection.ts | 16 ++--- .../data_frame_analytics/regression.ts | 32 ++++++---- .../apps/response_ops_docs/index.ts | 38 +++++++++++ .../observability_cases/index.ts | 14 ++++ .../observability_cases/list_view.ts | 64 +++++++++++++++++++ .../stack_cases/details_view.ts | 44 +++++++++++++ .../response_ops_docs/stack_cases/index.ts | 15 +++++ .../stack_cases/list_view.ts | 39 +++++++++++ x-pack/test/screenshot_creation/config.ts | 25 ++++++++ ...l_screenshots.ts => common_screenshots.ts} | 23 +++++-- .../screenshot_creation/services/index.ts | 4 +- 19 files changed, 419 insertions(+), 64 deletions(-) create mode 100644 x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts create mode 100644 x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/index.ts create mode 100644 x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/list_view.ts create mode 100644 x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/details_view.ts create mode 100644 x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/index.ts create mode 100644 x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/list_view.ts rename x-pack/test/screenshot_creation/services/{ml_screenshots.ts => common_screenshots.ts} (56%) diff --git a/x-pack/test/functional/services/cases/api.ts b/x-pack/test/functional/services/cases/api.ts index 3c487b25e842f..ad4678adcafc3 100644 --- a/x-pack/test/functional/services/cases/api.ts +++ b/x-pack/test/functional/services/cases/api.ts @@ -6,11 +6,12 @@ */ import pMap from 'p-map'; -import { CasePostRequest, CaseResponse } from '@kbn/cases-plugin/common/api'; +import { CasePostRequest, CaseResponse, CaseStatuses } from '@kbn/cases-plugin/common/api'; import { createCase as createCaseAPI, deleteAllCaseItems, createComment, + updateCase, } from '../../../cases_api_integration/common/lib/utils'; import { FtrProviderContext } from '../../ftr_provider_context'; import { generateRandomCaseWithoutConnector } from './helpers'; @@ -56,5 +57,24 @@ export function CasesAPIServiceProvider({ getService }: FtrProviderContext) { }): Promise { return createComment({ supertest: kbnSupertest, params, caseId }); }, + + async setStatus( + caseId: string, + caseVersion: string, + newStatus: 'open' | 'in-progress' | 'closed' + ) { + await updateCase({ + supertest: kbnSupertest, + params: { + cases: [ + { + id: caseId, + version: caseVersion, + status: CaseStatuses[newStatus], + }, + ], + }, + }); + }, }; } diff --git a/x-pack/test/functional/services/cases/single_case_view.ts b/x-pack/test/functional/services/cases/single_case_view.ts index 11595a2b80455..5c2afa179ab8b 100644 --- a/x-pack/test/functional/services/cases/single_case_view.ts +++ b/x-pack/test/functional/services/cases/single_case_view.ts @@ -15,6 +15,8 @@ export function CasesSingleViewServiceProvider({ getService, getPageObject }: Ft const testSubjects = getService('testSubjects'); const header = getPageObject('header'); const find = getService('find'); + const lensPage = getPageObject('lens'); + const retry = getService('retry'); return { async deleteCase() { @@ -33,5 +35,56 @@ export function CasesSingleViewServiceProvider({ getService, getPageObject }: Ft expect(userActionText).contain(contentToMatch); }, + + async getCommentCount(): Promise { + const commentsContainer = await testSubjects.find('user-actions'); + const comments = await commentsContainer.findAllByClassName('euiComment'); + return comments.length - 1; // don't count the element for adding a new comment + }, + + async submitComment() { + const commentCountBefore = await this.getCommentCount(); + await testSubjects.click('submit-comment'); + await retry.tryForTime(10 * 1000, async () => { + const commentCountAfter = await this.getCommentCount(); + expect(commentCountAfter).to.eql( + commentCountBefore + 1, + `Number of comments should increase by 1` + ); + }); + }, + + async addVisualization(visName: string) { + // open saved object finder + const addCommentElement = await testSubjects.find('add-comment'); + const addVisualizationButton = await addCommentElement.findByCssSelector( + '[data-test-subj="euiMarkdownEditorToolbarButton"][aria-label="Visualization"]' + ); + await addVisualizationButton.click(); + await testSubjects.existOrFail('savedObjectFinderItemList', { timeout: 10 * 1000 }); + + // select visualization + await testSubjects.setValue('savedObjectFinderSearchInput', visName, { + clearWithKeyboard: true, + }); + const sourceSubj = `savedObjectTitle${visName.replaceAll(' ', '-')}`; + await testSubjects.click(sourceSubj); + await header.waitUntilLoadingHasFinished(); + await lensPage.isLensPageOrFail(); + + // save and return to cases app, add comment + await lensPage.saveAndReturn(); + await testSubjects.existOrFail('cases-app', { timeout: 10 * 1000 }); + await this.submitComment(); + }, + + async openVisualizationButtonTooltip() { + const addCommentElement = await testSubjects.find('add-comment'); + const addVisualizationButton = await addCommentElement.findByCssSelector( + '[data-test-subj="euiMarkdownEditorToolbarButton"][aria-label="Visualization"]' + ); + await addVisualizationButton.moveMouseTo(); + await new Promise((resolve) => setTimeout(resolve, 500)); // give tooltip time to open + }, }; } diff --git a/x-pack/test/screenshot_creation/apps/index.ts b/x-pack/test/screenshot_creation/apps/index.ts index b02cf516a0088..2fde79a733ccd 100644 --- a/x-pack/test/screenshot_creation/apps/index.ts +++ b/x-pack/test/screenshot_creation/apps/index.ts @@ -10,5 +10,6 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('apps', function () { loadTestFile(require.resolve('./ml_docs')); + loadTestFile(require.resolve('./response_ops_docs')); }); } diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/custom_urls.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/custom_urls.ts index a329f4de12f62..285239abcac66 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/custom_urls.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/custom_urls.ts @@ -13,7 +13,7 @@ import { ECOMMERCE_INDEX_PATTERN } from '..'; export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); - const mlScreenshots = getService('mlScreenshots'); + const commonScreenshots = getService('commonScreenshots'); const screenshotDirectories = ['ml_docs', 'anomaly_detection']; @@ -73,7 +73,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.openEditCustomUrlsForJobTab(ecommerceJobConfig.job_id); const existingCustomUrlCount = await ml.jobTable.getExistingCustomUrlCount(); await ml.jobTable.fillInDashboardUrlForm(testDashboardCustomUrl); - await mlScreenshots.takeScreenshot('ml-customurl-edit', screenshotDirectories); + await commonScreenshots.takeScreenshot('ml-customurl-edit', screenshotDirectories); await ml.testExecution.logTestStep('add the custom url and save the job'); await ml.jobTable.saveCustomUrl(testDashboardCustomUrl.label, existingCustomUrlCount); @@ -94,7 +94,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.anomaliesTable.scrollTableIntoView(); await ml.anomaliesTable.ensureAnomalyActionsMenuOpen(0); - await mlScreenshots.takeScreenshot('ml-population-results', screenshotDirectories); + await commonScreenshots.takeScreenshot('ml-population-results', screenshotDirectories); }); }); } diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts index 88759715a36e8..c387599d50584 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts @@ -15,7 +15,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const maps = getPageObject('maps'); const ml = getService('ml'); - const mlScreenshots = getService('mlScreenshots'); + const commonScreenshots = getService('commonScreenshots'); const renderable = getService('renderable'); const screenshotDirectories = ['ml_docs', 'anomaly_detection']; @@ -119,7 +119,10 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await maps.setView(44.1, -68.9, 4.5); await maps.closeLegend(); - await mlScreenshots.takeScreenshot('weblogs-data-visualizer-geopoint', screenshotDirectories); + await commonScreenshots.takeScreenshot( + 'weblogs-data-visualizer-geopoint', + screenshotDirectories + ); }); it('ecommerce wizard screenshot', async () => { @@ -163,8 +166,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( 'ecommerce-advanced-wizard-geopoint', screenshotDirectories ); @@ -217,8 +220,11 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot('weblogs-advanced-wizard-geopoint', screenshotDirectories); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( + 'weblogs-advanced-wizard-geopoint', + screenshotDirectories + ); }); // the job stopped to produce an anomaly, needs investigation @@ -246,7 +252,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.anomaliesTable.ensureDetailsOpen(0); await ml.anomalyExplorer.scrollChartsContainerIntoView(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'ecommerce-anomaly-explorer-geopoint', screenshotDirectories ); @@ -284,7 +290,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.anomalyExplorer.scrollChartsContainerIntoView(); await maps.closeLegend(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'weblogs-anomaly-explorer-geopoint', screenshotDirectories ); diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts index 01d0ccfe488c5..4684b8ffc176c 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts @@ -13,7 +13,7 @@ import { LOGS_INDEX_PATTERN } from '..'; export default function ({ getPageObject, getService }: FtrProviderContext) { const header = getPageObject('header'); const ml = getService('ml'); - const mlScreenshots = getService('mlScreenshots'); + const commonScreenshots = getService('commonScreenshots'); const renderable = getService('renderable'); const screenshotDirectories = ['ml_docs', 'anomaly_detection']; @@ -77,7 +77,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.dataVisualizerTable.ensureDetailsOpen('geo.dest'); await renderable.waitForRender(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'weblogs-data-visualizer-choropleth', screenshotDirectories ); @@ -100,7 +100,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.jobWizardMultiMetric.scrollSplitFieldIntoView(); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'weblogs-multimetric-wizard-vector', screenshotDirectories ); @@ -119,7 +119,10 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.testExecution.logTestStep('scroll map into view and take screenshot'); await ml.anomalyExplorer.scrollMapContainerIntoView(); await renderable.waitForRender(); - await mlScreenshots.takeScreenshot('weblogs-anomaly-explorer-vectors', screenshotDirectories); + await commonScreenshots.takeScreenshot( + 'weblogs-anomaly-explorer-vectors', + screenshotDirectories + ); }); }); } diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/population_analysis.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/population_analysis.ts index a4e65d4f87da4..23fa9f047a5ce 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/population_analysis.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/population_analysis.ts @@ -13,7 +13,7 @@ import { LOGS_INDEX_PATTERN } from '..'; export default function ({ getService }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const ml = getService('ml'); - const mlScreenshots = getService('mlScreenshots'); + const commonScreenshots = getService('commonScreenshots'); const testSubjects = getService('testSubjects'); const screenshotDirectories = ['ml_docs', 'anomaly_detection']; @@ -71,8 +71,8 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('continue to the pick fields step and take screenshot'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot('ml-population-job', screenshotDirectories); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot('ml-population-job', screenshotDirectories); }); it('anomaly explorer screenshots', async () => { @@ -96,7 +96,7 @@ export default function ({ getService }: FtrProviderContext) { yOffset: Math.floor(cellSize / 2.0), }); - await mlScreenshots.takeScreenshot('ml-population-results', screenshotDirectories); + await commonScreenshots.takeScreenshot('ml-population-results', screenshotDirectories); await ml.testExecution.logTestStep( 'select swim lane tile, expand anomaly row and take screenshot' @@ -111,7 +111,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.anomaliesTable.ensureDetailsOpen(0); await ml.anomaliesTable.scrollRowIntoView(0); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'ml-population-anomaly', screenshotDirectories, 1500, diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/classification.ts b/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/classification.ts index 3912e3919e914..5fcbd6f2cb998 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/classification.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/classification.ts @@ -13,7 +13,7 @@ import { FLIGHTS_INDEX_PATTERN } from '..'; export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); - const mlScreenshots = getService('mlScreenshots'); + const commonScreenshots = getService('commonScreenshots'); const screenshotDirectories = ['ml_docs', 'data_frame_analytics']; @@ -70,14 +70,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.removeFocusFromElement(); + await commonScreenshots.removeFocusFromElement(); await ml.dataFrameAnalyticsCreation.scrollJobTypeSelectionIntoView(); - await mlScreenshots.takeScreenshot('flights-classification-job-1', screenshotDirectories); + await commonScreenshots.takeScreenshot('flights-classification-job-1', screenshotDirectories); await ml.testExecution.logTestStep('scroll to scatterplot matrix and take screenshot'); await ml.dataFrameAnalyticsCreation.assertScatterplotMatrixLoaded(); await ml.dataFrameAnalyticsCreation.scrollScatterplotMatrixIntoView(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'flights-classification-scatterplot', screenshotDirectories ); @@ -97,8 +97,11 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot('flights-classification-details', screenshotDirectories); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( + 'flights-classification-details', + screenshotDirectories + ); }); it('results view screenshots', async () => { @@ -124,14 +127,17 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsResults.expandFeatureImportanceSection(false); await ml.dataFrameAnalyticsResults.expandScatterplotMatrixSection(false); await ml.dataFrameAnalyticsResults.scrollAnalysisIntoView(); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot('flights-classification-results', screenshotDirectories); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( + 'flights-classification-results', + screenshotDirectories + ); await ml.testExecution.logTestStep('expand feature importance section and take screenshot'); await ml.dataFrameAnalyticsResults.expandFeatureImportanceSection(true); await ml.dataFrameAnalyticsResults.scrollFeatureImportanceIntoView(); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( 'flights-classification-total-importance', screenshotDirectories ); @@ -140,21 +146,27 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('expand evaluation section and take screenshot'); await ml.dataFrameAnalyticsResults.expandClassificationEvaluationSection(true); await ml.dataFrameAnalyticsResults.scrollClassificationEvaluationIntoView(); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( 'flights-classification-evaluation', screenshotDirectories ); - await mlScreenshots.takeScreenshot('confusion-matrix-binary', screenshotDirectories); - await mlScreenshots.takeScreenshot('confusion-matrix-binary-accuracy', screenshotDirectories); + await commonScreenshots.takeScreenshot('confusion-matrix-binary', screenshotDirectories); + await commonScreenshots.takeScreenshot( + 'confusion-matrix-binary-accuracy', + screenshotDirectories + ); await ml.dataFrameAnalyticsResults.scrollRocCurveChartIntoView(); - await mlScreenshots.takeScreenshot('flights-classification-roc-curve', screenshotDirectories); + await commonScreenshots.takeScreenshot( + 'flights-classification-roc-curve', + screenshotDirectories + ); await ml.dataFrameAnalyticsResults.expandClassificationEvaluationSection(false); await ml.testExecution.logTestStep('open decision path popover and take screenshot'); await ml.dataFrameAnalyticsResults.scrollResultsIntoView(); await ml.dataFrameAnalyticsResults.openFeatureImportancePopover(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'flights-classification-importance', screenshotDirectories ); diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/outlier_detection.ts b/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/outlier_detection.ts index d46900cd8bb3f..7c6e77f8e85c7 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/outlier_detection.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/outlier_detection.ts @@ -13,7 +13,7 @@ import { LOGS_INDEX_PATTERN } from '..'; export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); - const mlScreenshots = getService('mlScreenshots'); + const commonScreenshots = getService('commonScreenshots'); const transform = getService('transform'); const screenshotDirectories = ['ml_docs', 'data_frame_analytics']; @@ -79,7 +79,7 @@ export default function ({ getService }: FtrProviderContext) { await transform.wizard.assertDefineStepActive(); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.takeScreenshot('logs-transform-preview', screenshotDirectories); + await commonScreenshots.takeScreenshot('logs-transform-preview', screenshotDirectories); }); it('wizard screenshots', async () => { @@ -95,12 +95,12 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.takeScreenshot('weblog-outlier-job-1', screenshotDirectories); + await commonScreenshots.takeScreenshot('weblog-outlier-job-1', screenshotDirectories); await ml.testExecution.logTestStep('scroll to scatterplot matrix and take screenshot'); await ml.dataFrameAnalyticsCreation.assertScatterplotMatrixLoaded(); await ml.dataFrameAnalyticsCreation.scrollScatterplotMatrixIntoView(); - await mlScreenshots.takeScreenshot('weblog-outlier-scatterplot', screenshotDirectories); + await commonScreenshots.takeScreenshot('weblog-outlier-scatterplot', screenshotDirectories); }); it('results view screenshots', async () => { @@ -118,15 +118,15 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('fold scatterplot section and take screenshot'); await ml.dataFrameAnalyticsResults.expandScatterplotMatrixSection(false); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot('outliers', screenshotDirectories); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot('outliers', screenshotDirectories); await ml.testExecution.logTestStep('scroll to scatterplot matrix and take screenshot'); await ml.dataFrameAnalyticsResults.expandScatterplotMatrixSection(true); - await mlScreenshots.removeFocusFromElement(); + await commonScreenshots.removeFocusFromElement(); await ml.dataFrameAnalyticsResults.assertScatterplotMatrixLoaded(); await ml.dataFrameAnalyticsResults.scrollScatterplotMatrixIntoView(); - await mlScreenshots.takeScreenshot('outliers-scatterplot', screenshotDirectories); + await commonScreenshots.takeScreenshot('outliers-scatterplot', screenshotDirectories); }); }); } diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/regression.ts b/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/regression.ts index de3dee3a47905..f7844c3246e43 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/regression.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/data_frame_analytics/regression.ts @@ -13,7 +13,7 @@ import { FLIGHTS_INDEX_PATTERN } from '..'; export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); - const mlScreenshots = getService('mlScreenshots'); + const commonScreenshots = getService('commonScreenshots'); const screenshotDirectories = ['ml_docs', 'data_frame_analytics']; @@ -72,14 +72,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.removeFocusFromElement(); + await commonScreenshots.removeFocusFromElement(); await ml.dataFrameAnalyticsCreation.scrollJobTypeSelectionIntoView(); - await mlScreenshots.takeScreenshot('flights-regression-job-1', screenshotDirectories); + await commonScreenshots.takeScreenshot('flights-regression-job-1', screenshotDirectories); await ml.testExecution.logTestStep('scroll to scatterplot matrix and take screenshot'); await ml.dataFrameAnalyticsCreation.assertScatterplotMatrixLoaded(); await ml.dataFrameAnalyticsCreation.scrollScatterplotMatrixIntoView(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.takeScreenshot( 'flightdata-regression-scatterplot', screenshotDirectories ); @@ -99,8 +99,8 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.testExecution.logTestStep('take screenshot'); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot('flights-regression-details', screenshotDirectories); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot('flights-regression-details', screenshotDirectories); }); it('results view screenshots', async () => { @@ -124,8 +124,8 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsResults.expandScatterplotMatrixSection(false); await ml.dataFrameAnalyticsResults.scrollAnalysisIntoView(); await ml.dataFrameAnalyticsResults.enableResultsTablePreviewHistogramCharts(true); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( 'flights-regression-results', screenshotDirectories, 1500, @@ -135,8 +135,8 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('expand feature importance section and take screenshot'); await ml.dataFrameAnalyticsResults.expandFeatureImportanceSection(true); await ml.dataFrameAnalyticsResults.scrollFeatureImportanceIntoView(); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot( + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( 'flights-regression-total-importance', screenshotDirectories ); @@ -145,14 +145,20 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('expand evaluation section and take screenshot'); await ml.dataFrameAnalyticsResults.expandRegressionEvaluationSection(true); await ml.dataFrameAnalyticsResults.scrollRegressionEvaluationIntoView(); - await mlScreenshots.removeFocusFromElement(); - await mlScreenshots.takeScreenshot('flights-regression-evaluation', screenshotDirectories); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot( + 'flights-regression-evaluation', + screenshotDirectories + ); await ml.dataFrameAnalyticsResults.expandRegressionEvaluationSection(false); await ml.testExecution.logTestStep('open decision path popover and take screenshot'); await ml.dataFrameAnalyticsResults.scrollResultsIntoView(); await ml.dataFrameAnalyticsResults.openFeatureImportancePopover(); - await mlScreenshots.takeScreenshot('flights-regression-importance', screenshotDirectories); + await commonScreenshots.takeScreenshot( + 'flights-regression-importance', + screenshotDirectories + ); }); }); } diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts new file mode 100644 index 0000000000000..0e441857e9b2a --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export const ECOMMERCE_INDEX_PATTERN = 'kibana_sample_data_ecommerce'; +export const FLIGHTS_INDEX_PATTERN = 'kibana_sample_data_flights'; +export const LOGS_INDEX_PATTERN = 'kibana_sample_data_logs'; + +export default function ({ getPageObject, getService, loadTestFile }: FtrProviderContext) { + const browser = getService('browser'); + const ml = getService('ml'); + const securityPage = getPageObject('security'); + + describe('response ops docs', function () { + this.tags(['responseOps']); + + before(async () => { + await ml.testResources.installAllKibanaSampleData(); + await ml.testResources.setKibanaTimeZoneToUTC(); + await browser.setWindowSize(1920, 1080); + await securityPage.login(); + }); + + after(async () => { + await securityPage.forceLogout(); + await ml.testResources.removeAllKibanaSampleData(); + await ml.testResources.resetKibanaTimeZone(); + }); + + loadTestFile(require.resolve('./stack_cases')); + loadTestFile(require.resolve('./observability_cases')); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/index.ts new file mode 100644 index 0000000000000..0e0e81dca10c9 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('observability cases', function () { + loadTestFile(require.resolve('./list_view')); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/list_view.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/list_view.ts new file mode 100644 index 0000000000000..68393d6c9d6c1 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/observability_cases/list_view.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 { CommentType } from '@kbn/cases-plugin/common/api'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const cases = getService('cases'); + const commonScreenshots = getService('commonScreenshots'); + + const screenshotDirectories = ['response_ops_docs', 'observability_cases']; + + describe('list view', function () { + before(async () => { + const { id: caseIdMetrics } = await cases.api.createCase({ + title: 'Metrics inventory', + tags: ['IBM resilient'], + description: 'Test.', + owner: 'observability', + }); + await cases.api.createAttachment({ + caseId: caseIdMetrics, + params: { comment: 'test comment', type: CommentType.user, owner: 'observability' }, + }); + await cases.api.createAttachment({ + caseId: caseIdMetrics, + params: { comment: '2nd test comment', type: CommentType.user, owner: 'observability' }, + }); + + const { id: caseIdLogs, version: caseVersionLogs } = await cases.api.createCase({ + title: 'Logs threshold', + tags: ['jira'], + description: 'Test.', + owner: 'observability', + }); + await cases.api.setStatus(caseIdLogs, caseVersionLogs, 'closed'); + + const { id: caseIdMonitoring } = await cases.api.createCase({ + title: 'Monitor uptime', + tags: ['swimlane'], + description: 'Test.', + owner: 'observability', + }); + const { version: caseVersionMonitoring } = await cases.api.createAttachment({ + caseId: caseIdMonitoring, + params: { comment: 'test comment', type: CommentType.user, owner: 'observability' }, + }); + await cases.api.setStatus(caseIdMonitoring, caseVersionMonitoring, 'in-progress'); + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); + + it('cases list screenshot', async () => { + await cases.navigation.navigateToApp('observability/cases', 'cases-all-title'); + await commonScreenshots.takeScreenshot('cases', screenshotDirectories, 1400, 1024); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/details_view.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/details_view.ts new file mode 100644 index 0000000000000..3257814eae2bf --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/details_view.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const cases = getService('cases'); + const commonScreenshots = getService('commonScreenshots'); + + const screenshotDirectories = ['response_ops_docs', 'stack_cases']; + let CASE_ID: string; + + describe('deatils view', function () { + before(async () => { + const { id: caseId } = await cases.api.createCase({ + title: 'Web transactions', + tags: ['e-commerce'], + description: 'Investigate e-commerce sample data.', + }); + CASE_ID = caseId; + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); + + it('cases visualization screenshot', async () => { + await cases.navigation.navigateToApp(); + await cases.navigation.navigateToSingleCase('cases', CASE_ID); + await cases.singleCase.addVisualization('Transactions per day'); + await cases.singleCase.openVisualizationButtonTooltip(); + await commonScreenshots.takeScreenshot( + 'cases-visualization', + screenshotDirectories, + 1400, + 1024 + ); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/index.ts new file mode 100644 index 0000000000000..45138204e52f5 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('stack cases', function () { + loadTestFile(require.resolve('./list_view')); + loadTestFile(require.resolve('./details_view')); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/list_view.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/list_view.ts new file mode 100644 index 0000000000000..d79b790c612e6 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_cases/list_view.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CommentType } from '@kbn/cases-plugin/common/api'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const cases = getService('cases'); + const commonScreenshots = getService('commonScreenshots'); + + const screenshotDirectories = ['response_ops_docs', 'stack_cases']; + + describe('list view', function () { + before(async () => { + const { id: caseId } = await cases.api.createCase({ + title: 'Web transactions', + tags: ['e-commerce'], + description: 'Investigate e-commerce sample data.', + }); + await cases.api.createAttachment({ + caseId, + params: { comment: 'test comment', type: CommentType.user, owner: 'cases' }, + }); + }); + + after(async () => { + await cases.api.deleteAllCases(); + }); + + it('cases list screenshot', async () => { + await cases.navigation.navigateToApp(); + await commonScreenshots.takeScreenshot('cases', screenshotDirectories, 1400, 1024); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/config.ts b/x-pack/test/screenshot_creation/config.ts index 18dda361ac2cd..c83e90658ff72 100644 --- a/x-pack/test/screenshot_creation/config.ts +++ b/x-pack/test/screenshot_creation/config.ts @@ -5,6 +5,8 @@ * 2.0. */ +import Fs from 'fs'; +import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrConfigProviderContext } from '@kbn/test'; import { services } from './services'; @@ -13,14 +15,37 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../functional/config.base.js') ); + const servers = { + ...xpackFunctionalConfig.get('servers'), + elasticsearch: { + ...xpackFunctionalConfig.get('servers.elasticsearch'), + protocol: 'https', + certificateAuthorities: [Fs.readFileSync(CA_CERT_PATH)], + }, + }; + return { // default to the xpack functional config ...xpackFunctionalConfig.getAll(), + servers, services, testFiles: [require.resolve('./apps')], junit: { ...xpackFunctionalConfig.get('junit'), reportName: 'Chrome X-Pack UI Screenshot Creation', }, + esTestCluster: { + ...xpackFunctionalConfig.get('esTestCluster'), + ssl: true, + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + `--elasticsearch.hosts=https://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + `--xpack.alerting.rules.minimumScheduleInterval.value="2s"`, + ], + }, }; } diff --git a/x-pack/test/screenshot_creation/services/ml_screenshots.ts b/x-pack/test/screenshot_creation/services/common_screenshots.ts similarity index 56% rename from x-pack/test/screenshot_creation/services/ml_screenshots.ts rename to x-pack/test/screenshot_creation/services/common_screenshots.ts index b354b11712c5f..56db760859b3d 100644 --- a/x-pack/test/screenshot_creation/services/ml_screenshots.ts +++ b/x-pack/test/screenshot_creation/services/common_screenshots.ts @@ -7,10 +7,10 @@ import { FtrProviderContext } from '../ftr_provider_context'; -export function MachineLearningScreenshotsProvider({ getService }: FtrProviderContext) { +export function CommonScreenshotsProvider({ getService }: FtrProviderContext) { const browser = getService('browser'); - const ml = getService('ml'); const screenshot = getService('screenshots'); + const testSubjects = getService('testSubjects'); const DEFAULT_WIDTH = 1920; const DEFAULT_HEIGHT = 1080; @@ -18,14 +18,29 @@ export function MachineLearningScreenshotsProvider({ getService }: FtrProviderCo return { async takeScreenshot(name: string, subDirectories: string[], width?: number, height?: number) { await browser.setWindowSize(width ?? DEFAULT_WIDTH, height ?? DEFAULT_HEIGHT); + await new Promise((resolve) => setTimeout(resolve, 1000)); // give components time to resize await screenshot.take(`${name}_new`, undefined, subDirectories); await browser.setWindowSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); }, + async openKibanaNav() { + if (!(await testSubjects.exists('collapsibleNav'))) { + await testSubjects.click('toggleNavButton'); + } + await testSubjects.existOrFail('collapsibleNav'); + }, + + async closeKibanaNav() { + if (await testSubjects.exists('collapsibleNav')) { + await testSubjects.click('toggleNavButton'); + } + await testSubjects.missingOrFail('collapsibleNav'); + }, + async removeFocusFromElement() { // open and close the Kibana nav to un-focus the last used element - await ml.navigation.openKibanaNav(); - await ml.navigation.closeKibanaNav(); + await this.openKibanaNav(); + await this.closeKibanaNav(); }, }; } diff --git a/x-pack/test/screenshot_creation/services/index.ts b/x-pack/test/screenshot_creation/services/index.ts index dc5a107414415..b593b62553380 100644 --- a/x-pack/test/screenshot_creation/services/index.ts +++ b/x-pack/test/screenshot_creation/services/index.ts @@ -7,10 +7,10 @@ import { services as kibanaFunctionalServices } from '../../functional/services'; -import { MachineLearningScreenshotsProvider } from './ml_screenshots'; +import { CommonScreenshotsProvider } from './common_screenshots'; export const services = { ...kibanaFunctionalServices, - mlScreenshots: MachineLearningScreenshotsProvider, + commonScreenshots: CommonScreenshotsProvider, }; From edbf8f1bb4a5c9a1d37f987e3cd2bf8f816cfeab Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid Date: Mon, 15 Aug 2022 15:42:58 +0200 Subject: [PATCH 06/11] Fix: Encode URL reserved characters in Exploratory View URL (#138612) (#138365) --- .../exploratory_view_url.test.ts | 62 +++++++++++++++++++ .../configurations/exploratory_view_url.ts | 22 ++++++- .../exploratory_view/configurations/utils.ts | 6 +- 3 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.test.ts diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.test.ts new file mode 100644 index 0000000000000..9be8b1adeda86 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.test.ts @@ -0,0 +1,62 @@ +/* + * 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 { createExploratoryViewUrl } from './exploratory_view_url'; +import type { AllSeries } from '../../../..'; + +describe('createExploratoryViewUrl', () => { + const testAllSeries = [ + { + dataType: 'synthetics', + seriesType: 'area', + selectedMetricField: 'monitor.duration.us', + time: { + from: 'now-15m', + to: 'now', + }, + breakdown: 'monitor.type', + reportDefinitions: { + 'monitor.name': [], + 'url.full': ['ALL_VALUES'], + }, + name: 'All monitors response duration', + }, + ] as AllSeries; + + describe('handles URL reserved chars', () => { + const urlReservedRegex = /[;,\/?:@&=+$#]/; + + it('encodes &', () => { + const seriesWithAmpersand = [{ ...testAllSeries[0], name: 'Name with &' }]; + const url = createExploratoryViewUrl({ + reportType: 'kpi-over-time', + allSeries: seriesWithAmpersand, + }); + + expect(urlReservedRegex.test(grabRisonQueryFromUrl(url))).toEqual(false); + }); + + it('encodes other reserved chars', () => { + const seriesWithAmpersand = [ + { + ...testAllSeries[0], + name: 'Name with URL reserved chars ;,/?:@&=+$#', + }, + ]; + const url = createExploratoryViewUrl({ + reportType: 'kpi-over-time', + allSeries: seriesWithAmpersand, + }); + + expect(urlReservedRegex.test(grabRisonQueryFromUrl(url))).toEqual(false); + }); + }); +}); + +function grabRisonQueryFromUrl(url: string) { + return url.split('sr=')[1]; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.ts index 86731c0d8f877..40ca09e428d11 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.ts @@ -50,8 +50,26 @@ export function createExploratoryViewUrl( return ( baseHref + - `/app/${appId}/exploratory-view/#?reportType=${reportType}&sr=${rison.encode( - allShortSeries as unknown as RisonValue + `/app/${appId}/exploratory-view/#?reportType=${reportType}&sr=${encodeUriIfNeeded( + rison.encode(allShortSeries as unknown as RisonValue) )}` ); } + +/** + * Encodes the uri if it contains characters (`/?@&=+#`). + * It doesn't consider `,` and `:` as they are part of [Rison]{@link https://www.npmjs.com/package/rison-node} syntax. + * + * @param uri Non encoded URI + */ +export function encodeUriIfNeeded(uri: string) { + if (!uri) { + return uri; + } + + if (/[\/?@&=+#]/.test(uri)) { + return encodeURIComponent(uri); + } + + return uri; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts index 115bebf84bc52..2d1a8bba8e5ea 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts @@ -17,7 +17,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { PersistableFilter } from '@kbn/lens-plugin/common'; import type { ReportViewType, UrlFilter } from '../types'; import type { AllSeries, AllShortSeries } from '../hooks/use_series_storage'; -import { convertToShortUrl } from './exploratory_view_url'; +import { convertToShortUrl, encodeUriIfNeeded } from './exploratory_view_url'; export function createExploratoryViewRoutePath({ reportType, @@ -28,8 +28,8 @@ export function createExploratoryViewRoutePath({ }) { const allShortSeries: AllShortSeries = allSeries.map((series) => convertToShortUrl(series)); - return `/exploratory-view/#?reportType=${reportType}&sr=${rison.encode( - allShortSeries as unknown as RisonValue + return `/exploratory-view/#?reportType=${reportType}&sr=${encodeUriIfNeeded( + rison.encode(allShortSeries as unknown as RisonValue) )}`; } From fb409bdf34e6ce6a01f5d227eb8a6f72bee055bc Mon Sep 17 00:00:00 2001 From: Wafaa Nasr Date: Mon, 15 Aug 2022 15:45:14 +0200 Subject: [PATCH 07/11] Fix overwrite existing rules checkbox of Import rules remain selected till user disabled it (#138758) * clean user state when the user closes the modal + tests * add missing reacthooks deps Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/index.test.tsx.snap | 263 ++++++++++++++---- .../import_data_modal/index.test.tsx | 100 ++++++- .../components/import_data_modal/index.tsx | 12 +- 3 files changed, 307 insertions(+), 68 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap index 1e93394660808..1278b8b22eb63 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap @@ -1,65 +1,210 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ImportDataModal renders correctly against snapshot 1`] = ` - - - - - title - - - - +
+
-

- description -

- - - - - - - - - Cancel - - - submitBtnText - - - - +
+ +
+
+
+

+ title +

+
+
+
+
+
+

+ description +

+
+
+
+
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+ + +
+
+
+
+
+ , + "container":
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} `; diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx index 642f5b6dcabaa..9864941ebcbe7 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.test.tsx @@ -5,23 +5,37 @@ * 2.0. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; + import { ImportDataModalComponent } from '.'; + jest.mock('../../lib/kibana'); +jest.mock('../../hooks/use_app_toasts', () => ({ + useAppToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + }), +})); + +const closeModal = jest.fn(); +const importComplete = jest.fn(); +const importData = jest.fn().mockReturnValue({ success: true, errors: [] }); +const file = new File(['file'], 'image1.png', { type: 'image/png' }); + describe('ImportDataModal', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow( + const wrapper = render( { ); expect(wrapper).toMatchSnapshot(); }); + test('should import file, cleanup the states and close Modal', async () => { + const { queryByTestId } = render( + 'successMessage')} + title="title" + /> + ); + await waitFor(() => { + fireEvent.change(queryByTestId('rule-file-picker') as HTMLInputElement, { + target: { files: [file] }, + }); + }); + await waitFor(() => { + fireEvent.click(queryByTestId('import-data-modal-button') as HTMLButtonElement); + }); + expect(importData).toHaveBeenCalled(); + expect(closeModal).toHaveBeenCalled(); + expect(importComplete).toHaveBeenCalled(); + }); + test('should uncheck the selected checkboxes after importing new file', async () => { + const { queryByTestId } = render( + 'successMessage')} + title="title" + showExceptionsCheckBox={true} + /> + ); + const overwriteCheckbox: HTMLInputElement = queryByTestId( + 'import-data-modal-checkbox-label' + ) as HTMLInputElement; + const exceptionCheckbox: HTMLInputElement = queryByTestId( + 'import-data-modal-exceptions-checkbox-label' + ) as HTMLInputElement; + + await waitFor(() => fireEvent.click(overwriteCheckbox)); + await waitFor(() => fireEvent.click(exceptionCheckbox)); + + await waitFor(() => + fireEvent.change(queryByTestId('rule-file-picker') as HTMLInputElement, { + target: { files: [file] }, + }) + ); + expect(overwriteCheckbox.checked).toBeTruthy(); + expect(exceptionCheckbox.checked).toBeTruthy(); + + await waitFor(() => { + fireEvent.click(queryByTestId('import-data-modal-button') as HTMLButtonElement); + }); + expect(importData).toHaveBeenCalled(); + expect(closeModal).toHaveBeenCalled(); + + expect(overwriteCheckbox.checked).toBeFalsy(); + expect(exceptionCheckbox.checked).toBeFalsy(); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx index bccf6604dc5f6..f3223709a0ce4 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx @@ -74,7 +74,9 @@ export const ImportDataModalComponent = ({ setIsImporting(false); setSelectedFiles(null); closeModal(); - }, [setIsImporting, setSelectedFiles, closeModal]); + setOverwrite(false); + setOverwriteExceptions(false); + }, [setIsImporting, setSelectedFiles, closeModal, setOverwrite, setOverwriteExceptions]); const importDataCallback = useCallback(async () => { if (selectedFiles != null) { @@ -122,9 +124,8 @@ export const ImportDataModalComponent = ({ ]); const handleCloseModal = useCallback(() => { - setSelectedFiles(null); - closeModal(); - }, [closeModal]); + cleanupAndCloseModal(); + }, [cleanupAndCloseModal]); const handleCheckboxClick = useCallback(() => { setOverwrite((shouldOverwrite) => !shouldOverwrite); @@ -149,6 +150,7 @@ export const ImportDataModalComponent = ({ {showExceptionsCheckBox && ( Date: Mon, 15 Aug 2022 10:07:10 -0400 Subject: [PATCH 08/11] Removing type field (#138676) --- .../plugins/cases/public/client/attachment_framework/types.ts | 2 -- .../components/user_actions/comment/registered_attachments.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/x-pack/plugins/cases/public/client/attachment_framework/types.ts b/x-pack/plugins/cases/public/client/attachment_framework/types.ts index 1f974d09c887f..2f8d7eacb0553 100644 --- a/x-pack/plugins/cases/public/client/attachment_framework/types.ts +++ b/x-pack/plugins/cases/public/client/attachment_framework/types.ts @@ -12,10 +12,8 @@ import { CommentRequestPersistableStateType, } from '../../../common/api'; import { Case } from '../../containers/types'; -import { SupportedUserActionTypes } from '../../components/user_actions/types'; export interface AttachmentViewObject { - type?: SupportedUserActionTypes; timelineAvatar?: EuiCommentProps['timelineAvatar']; actions?: EuiCommentProps['actions']; event?: EuiCommentProps['event']; diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx index cd91d75b4062d..9b003c373873b 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx @@ -90,7 +90,6 @@ export const createRegisteredAttachmentUserActionBuilder = < fullName={comment.createdBy.fullName} /> ), - type: attachmentViewObject.type, className: `comment-${comment.type}-attachment-${attachmentTypeId}`, event: attachmentViewObject.event, 'data-test-subj': `comment-${comment.type}-${attachmentTypeId}`, From 67b6f85f6bf4fe9563429a76f491e54f799c3c80 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Mon, 15 Aug 2022 10:15:17 -0400 Subject: [PATCH 09/11] Fix(alert): reason for non percentage threshold metrics (#138138) * Fix typo * Fix metric threshold reason format * Add test for percentage metric * Fix formatter usage * Invert if condition Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../infra/common/formatters/high_precision.ts | 2 +- .../plugins/infra/common/formatters/index.ts | 4 +- .../inventory_models/aws_rds/layout.tsx | 2 +- .../infra/common/inventory_models/types.ts | 2 +- .../metric_threshold_executor.test.ts | 97 ++++++++++++++++++- .../metric_threshold_executor.ts | 30 +++--- 6 files changed, 117 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/infra/common/formatters/high_precision.ts b/x-pack/plugins/infra/common/formatters/high_precision.ts index 70e4d3a0d1796..d111d61346736 100644 --- a/x-pack/plugins/infra/common/formatters/high_precision.ts +++ b/x-pack/plugins/infra/common/formatters/high_precision.ts @@ -5,7 +5,7 @@ * 2.0. */ -export const formatHighPercision = (val: number) => { +export const formatHighPrecision = (val: number) => { return Number(val).toLocaleString('en', { maximumFractionDigits: 5, }); diff --git a/x-pack/plugins/infra/common/formatters/index.ts b/x-pack/plugins/infra/common/formatters/index.ts index 372df5b28ca1b..efa4f2faf769f 100644 --- a/x-pack/plugins/infra/common/formatters/index.ts +++ b/x-pack/plugins/infra/common/formatters/index.ts @@ -9,7 +9,7 @@ import { createBytesFormatter } from './bytes'; import { formatNumber } from './number'; import { formatPercent } from './percent'; import { InventoryFormatterType } from '../inventory_models/types'; -import { formatHighPercision } from './high_precision'; +import { formatHighPrecision } from './high_precision'; import { InfraWaffleMapDataFormat } from './types'; export const FORMATTERS = { @@ -22,7 +22,7 @@ export const FORMATTERS = { // bytes in bits formatted string out bits: createBytesFormatter(InfraWaffleMapDataFormat.bitsDecimal), percent: formatPercent, - highPercision: formatHighPercision, + highPrecision: formatHighPrecision, }; export const createFormatter = diff --git a/x-pack/plugins/infra/common/inventory_models/aws_rds/layout.tsx b/x-pack/plugins/infra/common/inventory_models/aws_rds/layout.tsx index 3c52b6ec623d8..025119cff2f85 100644 --- a/x-pack/plugins/infra/common/inventory_models/aws_rds/layout.tsx +++ b/x-pack/plugins/infra/common/inventory_models/aws_rds/layout.tsx @@ -143,7 +143,7 @@ export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPr ; export type InventoryItemType = rt.TypeOf; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 4f0060b3dddf2..08870ddfb6f94 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -29,6 +29,7 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS, NO_DATA_ACTIONS, + WARNING_ACTIONS, } from './metric_threshold_executor'; import { Evaluation } from './lib/evaluate_rule'; import type { LogMeta, Logger } from '@kbn/logging'; @@ -1504,6 +1505,91 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID)).toBeErrorAction(); }); }); + + describe('querying the entire infrastructure with warning threshold', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + + const execute = () => + executor({ + ...mockOptions, + services, + params: { + sourceId: 'default', + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [9999], + }, + ], + }, + }); + + const setResults = ({ + comparator = Comparator.GT, + threshold = [9999], + warningComparator = Comparator.GT, + warningThreshold = [2.49], + metric = 'test.metric.1', + currentValue = 7.59, + shouldWarn = false, + }) => + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator, + threshold, + warningComparator, + warningThreshold, + metric, + currentValue, + timestamp: new Date().toISOString(), + shouldFire: false, + shouldWarn, + isNoData: false, + }, + }, + ]); + + test('warns as expected with the > comparator', async () => { + setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); + await execute(); + expect(mostRecentAction(instanceID)).toBeWarnAction(); + + setResults({ warningThreshold: [2.49], currentValue: 1.23, shouldWarn: false }); + await execute(); + expect(mostRecentAction(instanceID)).toBe(undefined); + }); + + test('reports expected warning values to the action context', async () => { + setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true }); + await execute(); + + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toBe( + 'test.metric.1 is 2.5 in the last 1 min for all hosts. Alert when > 2.49.' + ); + }); + + test('reports expected warning values to the action context for percentage metric', async () => { + setResults({ + warningThreshold: [0.81], + currentValue: 0.82, + shouldWarn: true, + metric: 'system.cpu.user.pct', + }); + await execute(); + + const { action } = mostRecentAction(instanceID); + expect(action.group).toBe('*'); + expect(action.reason).toBe( + 'system.cpu.user.pct is 82% in the last 1 min for all hosts. Alert when > 81%.' + ); + }); + }); }); const createMockStaticConfiguration = (sources: any) => ({ @@ -1622,6 +1708,14 @@ expect.extend({ pass, }; }, + toBeWarnAction(action?: Action) { + const pass = action?.id === WARNING_ACTIONS.id && action?.action.alertState === 'WARNING'; + const message = () => `expected ${JSON.stringify(action)} to be an WARNING action`; + return { + message, + pass, + }; + }, toBeNoDataAction(action?: Action) { const pass = action?.id === NO_DATA_ACTIONS.id && action?.action.alertState === 'NO DATA'; const message = () => `expected ${action} to be a NO DATA action`; @@ -1645,9 +1739,8 @@ declare global { namespace jest { interface Matchers { toBeAlertAction(action?: Action): R; - + toBeWarnAction(action?: Action): R; toBeNoDataAction(action?: Action): R; - toBeErrorAction(action?: Action): R; } } diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 2a77f7ca79beb..14b5fe8e75614 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -322,28 +322,32 @@ const formatAlertResult = ( alertResult; const noDataValue = i18n.translate( 'xpack.infra.metrics.alerting.threshold.noDataFormattedValue', - { - defaultMessage: '[NO DATA]', - } + { defaultMessage: '[NO DATA]' } ); - if (!metric.endsWith('.pct')) + const thresholdToFormat = useWarningThreshold ? warningThreshold! : threshold; + const comparatorToUse = useWarningThreshold ? warningComparator! : comparator; + + if (metric.endsWith('.pct')) { + const formatter = createFormatter('percent'); return { ...alertResult, - currentValue: currentValue ?? noDataValue, + currentValue: + currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, + threshold: Array.isArray(thresholdToFormat) + ? thresholdToFormat.map((v: number) => formatter(v)) + : formatter(thresholdToFormat), + comparator: comparatorToUse, }; - const formatter = createFormatter('percent'); - const thresholdToFormat = useWarningThreshold ? warningThreshold! : threshold; - const comparatorToFormat = useWarningThreshold ? warningComparator! : comparator; + } + const formatter = createFormatter('highPrecision'); return { ...alertResult, currentValue: - currentValue !== null && typeof currentValue !== 'undefined' - ? formatter(currentValue) - : noDataValue, + currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, threshold: Array.isArray(thresholdToFormat) ? thresholdToFormat.map((v: number) => formatter(v)) - : thresholdToFormat, - comparator: comparatorToFormat, + : formatter(thresholdToFormat), + comparator: comparatorToUse, }; }; From 16a83ec7c39f26df767ac17a7c32dfff613b2c2b Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Mon, 15 Aug 2022 10:26:57 -0400 Subject: [PATCH 10/11] [CI] Remove unused agent configuration (#138771) --- .buildkite/agents.json | 79 ------------------------------------------ 1 file changed, 79 deletions(-) delete mode 100644 .buildkite/agents.json diff --git a/.buildkite/agents.json b/.buildkite/agents.json deleted file mode 100644 index 797b7e71f2be6..0000000000000 --- a/.buildkite/agents.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "gcp": { - "project": "elastic-kibana-ci", - "zones": ["us-central1-a", "us-central1-b", "us-central1-c", "us-central1-f"], - "serviceAccount": "elastic-buildkite-agent@elastic-kibana-ci.iam.gserviceaccount.com", - "imageFamily": "kb-ubuntu", - "subnetwork": "buildkite", - "disableExternalIp": true, - "diskType": "pd-ssd", - "diskSizeGb": 75, - "overprovision": 0, - "minimumAgents": 0, - "maximumAgents": 50, - "gracefulStopAfterMins": 360, - "hardStopAfterMins": 540, - "idleTimeoutMins": 10, - "exitAfterOneJob": false, - - "agents": [ - { - "queue": "default", - "name": "kb-default", - "minimumAgents": 1, - "maximumAgents": 100, - "idleTimeoutMins": 60, - "machineType": "e2-small" - }, - { - "queue": "c2-8", - "name": "kb-c2-8", - "machineType": "c2-standard-8", - "localSsds": 1 - }, - { - "queue": "c2-4", - "name": "kb-c2-4", - "machineType": "c2-standard-4", - "localSsds": 1 - }, - { - "queue": "jest", - "name": "kb-jest", - "machineType": "n2-standard-2", - "diskSizeGb": 128 - }, - { - "queue": "ci-group", - "name": "kb-cigroup", - "machineType": "n2-standard-8", - "diskSizeGb": 256 - }, - { - "queue": "ci-group-4", - "name": "kb-cigroup-4", - "machineType": "n2-standard-4", - "diskSizeGb": 128 - }, - { - "queue": "ci-group-4d", - "name": "kb-cigroup-4d", - "machineType": "n2d-standard-4", - "diskSizeGb": 128 - }, - { - "queue": "ci-group-6", - "name": "kb-cigroup-6", - "machineType": "n2-custom-6-16384", - "diskSizeGb": 128 - }, - { - "queue": "packer", - "name": "kb-packer", - "serviceAccount": "buildkite-packer-agent@elastic-kibana-ci.iam.gserviceaccount.com", - "maximumAgents": 10, - "machineType": "e2-small" - } - ] - } -} From fe4553ddee0ce5fa4234ce7aaf53531d28388004 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 15 Aug 2022 16:53:15 +0200 Subject: [PATCH 11/11] [Graph] Fix closing behaviour of the field picker popover (#138799) --- .../graph/public/components/field_manager/field_picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx index bc7718b142231..6a3126c8617cb 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx @@ -63,7 +63,7 @@ export function FieldPicker({ aria-disabled={!hasFields} onClick={() => { if (hasFields) { - setOpen(true); + setOpen(!open); } }} onClickAriaLabel={badgeDescription}