From e4ccf19f758ce03646ca2d0fb91c7de60d5fc52a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 6 Jan 2020 16:54:37 +0100 Subject: [PATCH 01/18] [Uptime] Fix/location map hide layer view control (#53568) * hide layer control and added loc tags * update test * remove unused comment * remove capitalization * style fix * update types Co-authored-by: Elastic Machine --- .../{ => __tests__}/__mocks__/mock.ts | 2 +- .../{ => __tests__}/map_config.test.ts | 6 +- .../location_map/embeddables/embedded_map.tsx | 14 ++-- .../location_map/embeddables/map_config.ts | 12 ++- .../functional/location_map/location_map.tsx | 21 ++++-- .../location_map/location_status_tags.tsx | 73 +++++++++++++++++++ 6 files changed, 110 insertions(+), 18 deletions(-) rename x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/{ => __tests__}/__mocks__/mock.ts (98%) rename x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/{ => __tests__}/map_config.test.ts (83%) create mode 100644 x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts similarity index 98% rename from x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts rename to x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts index 9b902651690bf..291ab555fbdc6 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import lowPolyLayerFeatures from '../low_poly_layer.json'; +import lowPolyLayerFeatures from '../../low_poly_layer.json'; export const mockDownPointsLayer = { id: 'down_points', diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts similarity index 83% rename from x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts rename to x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts index 1e8e5b6012a79..263c3fc787da9 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getLayerList } from './map_config'; +import { getLayerList } from '../map_config'; import { mockLayerList } from './__mocks__/mock'; -import { LocationPoint } from './embedded_map'; +import { LocationPoint } from '../embedded_map'; jest.mock('uuid', () => { return { @@ -33,7 +33,7 @@ describe('map_config', () => { describe('#getLayerList', () => { test('it returns the low poly layer', () => { - const layerList = getLayerList(upPoints, downPoints); + const layerList = getLayerList(upPoints, downPoints, { danger: '#BC261E', gray: '#000' }); expect(layerList).toStrictEqual(mockLayerList); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx index 93de1d478fb83..fe8a1a0bad7ec 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import uuid from 'uuid'; import styled from 'styled-components'; @@ -15,6 +15,7 @@ import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common/constants'; import { MapEmbeddable } from './types'; import { getLayerList } from './map_config'; +import { UptimeSettingsContext } from '../../../../contexts'; export interface EmbeddedMapProps { upPoints: LocationPoint[]; @@ -45,6 +46,7 @@ const EmbeddedPanel = styled.div` `; export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { + const { colors } = useContext(UptimeSettingsContext); const [embeddable, setEmbeddable] = useState(); const embeddableRoot: React.RefObject = React.createRef(); const factory = start.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE); @@ -58,16 +60,18 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { viewMode: 'view', isLayerTOCOpen: false, hideFilterActions: true, - mapCenter: { lon: 11, lat: 47, zoom: 0 }, + mapCenter: { lon: 11, lat: 20, zoom: 0 }, disableInteractive: true, disableTooltipControl: true, hideToolbarOverlay: true, + hideLayerControl: true, + hideViewControl: true, }; useEffect(() => { async function setupEmbeddable() { const mapState = { - layerList: getLayerList(upPoints, downPoints), + layerList: getLayerList(upPoints, downPoints, colors), title: i18n.MAP_TITLE, }; // @ts-ignore @@ -82,9 +86,9 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { useEffect(() => { if (embeddable) { - embeddable.setLayerList(getLayerList(upPoints, downPoints)); + embeddable.setLayerList(getLayerList(upPoints, downPoints, colors)); } - }, [upPoints, downPoints, embeddable]); + }, [upPoints, downPoints, embeddable, colors]); useEffect(() => { if (embeddableRoot.current && embeddable) { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts index 608df8b235f00..b423b8baf41bf 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts @@ -12,8 +12,12 @@ import { LocationPoint } from './embedded_map'; * destination, and line layer for each of the provided indexPatterns * */ -export const getLayerList = (upPoints: LocationPoint[], downPoints: LocationPoint[]) => { - return [getLowPolyLayer(), getDownPointsLayer(downPoints), getUpPointsLayer(upPoints)]; +export const getLayerList = ( + upPoints: LocationPoint[], + downPoints: LocationPoint[], + { gray, danger }: { gray: string; danger: string } +) => { + return [getLowPolyLayer(), getDownPointsLayer(downPoints, danger), getUpPointsLayer(upPoints)]; }; export const getLowPolyLayer = () => { @@ -62,7 +66,7 @@ export const getLowPolyLayer = () => { }; }; -export const getDownPointsLayer = (downPoints: LocationPoint[]) => { +export const getDownPointsLayer = (downPoints: LocationPoint[], dangerColor: string) => { const features = downPoints?.map(point => ({ type: 'feature', geometry: { @@ -87,7 +91,7 @@ export const getDownPointsLayer = (downPoints: LocationPoint[]) => { fillColor: { type: 'STATIC', options: { - color: '#BC261E', + color: dangerColor, }, }, lineColor: { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx index b271632cb631f..f70d145ec05c3 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx @@ -6,15 +6,19 @@ import React from 'react'; import styled from 'styled-components'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { LocationStatusTags } from './location_status_tags'; import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; +import { MonitorLocations } from '../../../../common/runtime_types'; const MapPanel = styled.div` - height: 400px; + height: 240px; width: 520px; + margin-right: 10px; `; interface LocationMapProps { - monitorLocations: any; + monitorLocations: MonitorLocations; } export const LocationMap = ({ monitorLocations }: LocationMapProps) => { @@ -31,8 +35,15 @@ export const LocationMap = ({ monitorLocations }: LocationMapProps) => { }); } return ( - - - + + + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx new file mode 100644 index 0000000000000..a10d8e02e6863 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import styled from 'styled-components'; +import { EuiBadge, EuiText } from '@elastic/eui'; +import { UptimeSettingsContext } from '../../../contexts'; +import { MonitorLocation } from '../../../../common/runtime_types'; + +const TextStyle = styled.div` + font-weight: 600; +`; + +const BadgeItem = styled.div` + margin-bottom: 5px; +`; + +const TagContainer = styled.div` + padding: 10px; + max-height: 200px; + overflow: hidden; +`; + +interface Props { + locations: MonitorLocation[]; +} + +export const LocationStatusTags = ({ locations }: Props) => { + const { + colors: { gray, danger }, + } = useContext(UptimeSettingsContext); + + const upLocs: string[] = []; + const downLocs: string[] = []; + + locations.forEach((item: any) => { + if (item.summary.down === 0) { + upLocs.push(item.geo.name); + } else { + downLocs.push(item.geo.name); + } + }); + + return ( + + + {downLocs.map((item, ind) => ( + + + + {item} + + + + ))} + + + {upLocs.map((item, ind) => ( + + + + {item} + + + + ))} + + + ); +}; From 1e5135ec538e2fa5fcd84ca463d9aa63320ada4b Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 6 Jan 2020 11:42:01 -0500 Subject: [PATCH 02/18] [Maps] Vector style UI redesign (#53946) * [Maps] style editor update * update label editor * update size editor * update orienation editor * i18n cleanup * deconstruct props * review feedback Co-authored-by: Elastic Machine --- .../components/color/dynamic_color_form.js | 62 ++++++++ .../color/dynamic_color_selection.js | 48 ------ .../components/color/static_color_form.js | 33 ++++ .../color/static_color_selection.js | 30 ---- .../color/vector_style_color_editor.js | 34 ++-- .../components/label/dynamic_label_form.js | 37 +++++ .../label/dynamic_label_selector.js | 24 --- .../components/label/static_label_form.js | 34 ++++ .../components/label/static_label_selector.js | 28 ---- .../label/vector_style_label_editor.js | 18 +-- .../orientation/dynamic_orientation_form.js | 40 +++++ .../dynamic_orientation_selection.js | 32 ---- .../orientation/orientation_editor.js | 22 ++- .../orientation/static_orientation_form.js | 33 ++++ .../static_orientation_selection.js | 34 ---- .../components/size/dynamic_size_form.js | 63 ++++++++ .../components/size/dynamic_size_selection.js | 48 ------ .../components/size/static_size_form.js | 37 +++++ .../components/size/static_size_selection.js | 38 ----- .../size/vector_style_size_editor.js | 22 ++- .../components/static_dynamic_style_row.js | 145 ------------------ .../vector/components/style_prop_editor.js | 104 +++++++++++++ .../vector/components/vector_style_editor.js | 127 +++++++++++---- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 25 files changed, 585 insertions(+), 512 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js new file mode 100644 index 0000000000000..5e0f7434b04d0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React, { Fragment } from 'react'; +import { FieldSelect } from '../field_select'; +import { ColorRampSelect } from './color_ramp_select'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +export function DynamicColorForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + const onColorChange = colorOptions => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + ...colorOptions, + }); + }; + + let colorRampSelect; + if (styleOptions.field && styleOptions.field.name) { + colorRampSelect = ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {colorRampSelect} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js deleted file mode 100644 index 84327635f2b65..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { dynamicColorShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; -import { ColorRampSelect } from './color_ramp_select'; -import { EuiSpacer } from '@elastic/eui'; - -export function DynamicColorSelection({ fields, onChange, styleOptions }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - const onColorChange = colorOptions => { - onChange({ ...styleOptions, ...colorOptions }); - }; - - return ( - - - - - - ); -} - -DynamicColorSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicColorShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js new file mode 100644 index 0000000000000..48befa1ca74c0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiColorPicker, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticColorForm({ + onStaticStyleChange, + staticDynamicSelect, + styleProperty, + swatches, +}) { + const onColorChange = color => { + onStaticStyleChange(styleProperty.getStyleName(), { color }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js deleted file mode 100644 index e42b582dc3929..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiColorPicker } from '@elastic/eui'; -import { staticColorShape } from '../style_option_shapes'; - -export function StaticColorSelection({ onChange, styleOptions, swatches }) { - const onColorChange = color => { - onChange({ color }); - }; - - return ( - - ); -} - -StaticColorSelection.propTypes = { - styleOptions: staticColorShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js index c7745fa69a82f..43e7050b3d1d2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js @@ -6,21 +6,29 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicColorSelection } from './dynamic_color_selection'; -import { StaticColorSelection } from './static_color_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicColorForm } from './dynamic_color_form'; +import { StaticColorForm } from './static_color_form'; +import { i18n } from '@kbn/i18n'; export function VectorStyleColorEditor(props) { + const colorForm = props.styleProperty.isDynamic() ? ( + + ) : ( + + ); + return ( - + + {colorForm} + ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js new file mode 100644 index 0000000000000..bad13b487cc29 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FieldSelect } from '../field_select'; + +export function DynamicLabelForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js deleted file mode 100644 index e393341b90696..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React from 'react'; -import { FieldSelect } from '../field_select'; - -export function DynamicLabelSelector({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - return ( - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js new file mode 100644 index 0000000000000..721487b5d8ff0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticLabelForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onValueChange = event => { + onStaticStyleChange(styleProperty.getStyleName(), { value: event.target.value }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js deleted file mode 100644 index ea296a3312799..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiFieldText } from '@elastic/eui'; - -export function StaticLabelSelector({ onChange, styleOptions }) { - const onValueChange = event => { - onChange({ value: event.target.value }); - }; - - return ( - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js index 6bca56425d38d..aaa21ea315f36 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js @@ -6,16 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicLabelSelector } from './dynamic_label_selector'; -import { StaticLabelSelector } from './static_label_selector'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicLabelForm } from './dynamic_label_form'; +import { StaticLabelForm } from './static_label_form'; export function VectorStyleLabelEditor(props) { - return ( - + const labelForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {labelForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js new file mode 100644 index 0000000000000..e0b7e7b2865a2 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React from 'react'; +import { FieldSelect } from '../field_select'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function DynamicOrientationForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field, + }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js deleted file mode 100644 index 8ad3916ac6509..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React from 'react'; -import PropTypes from 'prop-types'; -import { dynamicOrientationShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; - -export function DynamicOrientationSelection({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - return ( - - ); -} - -DynamicOrientationSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicOrientationShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js index e97252a5e79da..915fc92c9fb38 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js @@ -6,20 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicOrientationSelection } from './dynamic_orientation_selection'; -import { StaticOrientationSelection } from './static_orientation_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicOrientationForm } from './dynamic_orientation_form'; +import { StaticOrientationForm } from './static_orientation_form'; export function OrientationEditor(props) { - return ( - + const orientationForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {orientationForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js new file mode 100644 index 0000000000000..8c4418f95e1d2 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ValidatedRange } from '../../../../../components/validated_range'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticOrientationForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onOrientationChange = orientation => { + onStaticStyleChange(styleProperty.getStyleName(), { orientation }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js deleted file mode 100644 index b5529c6987459..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { staticOrientationShape } from '../style_option_shapes'; -import { ValidatedRange } from '../../../../../components/validated_range'; - -export function StaticOrientationSelection({ onChange, styleOptions }) { - const onOrientationChange = orientation => { - onChange({ orientation }); - }; - - return ( - - ); -} - -StaticOrientationSelection.propTypes = { - styleOptions: staticOrientationShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js new file mode 100644 index 0000000000000..8b069cd53b731 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React, { Fragment } from 'react'; +import { FieldSelect } from '../field_select'; +import { SizeRangeSelector } from './size_range_selector'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +export function DynamicSizeForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + const onSizeRangeChange = ({ minSize, maxSize }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + minSize, + maxSize, + }); + }; + + let sizeRange; + if (styleOptions.field && styleOptions.field.name) { + sizeRange = ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {sizeRange} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js deleted file mode 100644 index 76c5b97976bbc..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { dynamicSizeShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; -import { SizeRangeSelector } from './size_range_selector'; -import { EuiSpacer } from '@elastic/eui'; - -export function DynamicSizeSelection({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - const onSizeRangeChange = ({ minSize, maxSize }) => { - onChange({ ...styleOptions, minSize, maxSize }); - }; - - return ( - - - - - - ); -} - -DynamicSizeSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicSizeShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js new file mode 100644 index 0000000000000..d8fe1322db3e3 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ValidatedRange } from '../../../../../components/validated_range'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onSizeChange = size => { + onStaticStyleChange(styleProperty.getStyleName(), { size }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js deleted file mode 100644 index 38f8fe53d1748..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { staticSizeShape } from '../style_option_shapes'; -import { ValidatedRange } from '../../../../../components/validated_range'; -import { i18n } from '@kbn/i18n'; - -export function StaticSizeSelection({ onChange, styleOptions }) { - const onSizeChange = size => { - onChange({ size }); - }; - - return ( - - ); -} - -StaticSizeSelection.propTypes = { - styleOptions: staticSizeShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js index 6580bfc00e0ad..e344f72bd429a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js @@ -6,20 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicSizeSelection } from './dynamic_size_selection'; -import { StaticSizeSelection } from './static_size_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicSizeForm } from './dynamic_size_form'; +import { StaticSizeForm } from './static_size_form'; export function VectorStyleSizeEditor(props) { - return ( - + const sizeForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {sizeForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js deleted file mode 100644 index 311406731801a..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component, Fragment } from 'react'; -import { VectorStyle } from '../vector_style'; -import { i18n } from '@kbn/i18n'; -import { FieldMetaOptionsPopover } from './field_meta_options_popover'; -import { getVectorStyleLabel } from './get_vector_style_label'; - -import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiFormRow, EuiButtonToggle } from '@elastic/eui'; - -export class StaticDynamicStyleRow extends Component { - // Store previous options locally so when type is toggled, - // previous style options can be used. - prevStaticStyleOptions = this.props.defaultStaticStyleOptions; - prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; - - _canBeDynamic() { - return this.props.fields.length > 0; - } - - _isDynamic() { - return this.props.styleProperty.isDynamic(); - } - - _getStyleOptions() { - return this.props.styleProperty.getOptions(); - } - - _onFieldMetaOptionsChange = fieldMetaOptions => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.DYNAMIC, - options: { - ...this._getStyleOptions(), - fieldMetaOptions, - }, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onStaticStyleChange = options => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.STATIC, - options, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onDynamicStyleChange = options => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.DYNAMIC, - options, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onTypeToggle = () => { - if (this._isDynamic()) { - // preserve current dynmaic style - this.prevDynamicStyleOptions = this._getStyleOptions(); - // toggle to static style - this._onStaticStyleChange(this.prevStaticStyleOptions); - return; - } - - // preserve current static style - this.prevStaticStyleOptions = this._getStyleOptions(); - // toggle to dynamic style - this._onDynamicStyleChange(this.prevDynamicStyleOptions); - }; - - _renderStyleSelector() { - if (this._isDynamic()) { - const DynamicSelector = this.props.DynamicSelector; - return ( - - - - - ); - } - - const StaticSelector = this.props.StaticSelector; - return ( - - ); - } - - render() { - const isDynamic = this._isDynamic(); - const dynamicTooltipContent = isDynamic - ? i18n.translate('xpack.maps.styles.staticDynamic.staticDescription', { - defaultMessage: 'Use static styling properties to symbolize features.', - }) - : i18n.translate('xpack.maps.styles.staticDynamic.dynamicDescription', { - defaultMessage: 'Use property values to symbolize features.', - }); - - return ( - - - - {this._renderStyleSelector()} - - - {this._canBeDynamic() && ( - - - - - - - - )} - - ); - } -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js new file mode 100644 index 0000000000000..1ac8edfb2cc69 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { FieldMetaOptionsPopover } from './field_meta_options_popover'; +import { getVectorStyleLabel } from './get_vector_style_label'; +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { VectorStyle } from '../vector_style'; +import { i18n } from '@kbn/i18n'; + +export class StylePropEditor extends Component { + _prevStaticStyleOptions = this.props.defaultStaticStyleOptions; + _prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; + + _onTypeToggle = () => { + if (this.props.styleProperty.isDynamic()) { + // preserve current dynmaic style + this._prevDynamicStyleOptions = this.props.styleProperty.getOptions(); + // toggle to static style + this.props.onStaticStyleChange( + this.props.styleProperty.getStyleName(), + this._prevStaticStyleOptions + ); + } else { + // preserve current static style + this._prevStaticStyleOptions = this.props.styleProperty.getOptions(); + // toggle to dynamic style + this.props.onDynamicStyleChange( + this.props.styleProperty.getStyleName(), + this._prevDynamicStyleOptions + ); + } + }; + + _onFieldMetaOptionsChange = fieldMetaOptions => { + const options = { + ...this.props.styleProperty.getOptions(), + fieldMetaOptions, + }; + this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), options); + }; + + renderStaticDynamicSelect() { + const options = [ + { + value: VectorStyle.STYLE_TYPE.STATIC, + text: this.props.customStaticOptionLabel + ? this.props.customStaticOptionLabel + : i18n.translate('xpack.maps.styles.staticDynamicSelect.staticLabel', { + defaultMessage: 'Fixed', + }), + }, + { + value: VectorStyle.STYLE_TYPE.DYNAMIC, + text: i18n.translate('xpack.maps.styles.staticDynamicSelect.dynamicLabel', { + defaultMessage: 'By value', + }), + }, + ]; + + return ( + + ); + } + + render() { + const fieldMetaOptionsPopover = this.props.styleProperty.isDynamic() ? ( + + ) : null; + + return ( + + + {React.cloneElement(this.props.children, { + staticDynamicSelect: this.renderStaticDynamicSelect(), + })} + {fieldMetaOptionsPopover} + + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 44f630db9d890..8e80e036dbb8b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -12,8 +12,13 @@ import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; +import { VectorStyle } from '../vector_style'; import { OrientationEditor } from './orientation/orientation_editor'; -import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults'; +import { + getDefaultDynamicProperties, + getDefaultStaticProperties, + VECTOR_STYLES, +} from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils'; import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types'; import { SYMBOLIZE_AS_ICON } from '../vector_constants'; @@ -128,15 +133,36 @@ export class VectorStyleEditor extends Component { this.props.onIsTimeAwareChange(event.target.checked); }; + _onStaticStyleChange = (propertyName, options) => { + const styleDescriptor = { + type: VectorStyle.STYLE_TYPE.STATIC, + options, + }; + this.props.handlePropertyChange(propertyName, styleDescriptor); + }; + + _onDynamicStyleChange = (propertyName, options) => { + const styleDescriptor = { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options, + }; + this.props.handlePropertyChange(propertyName, styleDescriptor); + }; + _renderFillColor() { return ( ); } @@ -145,11 +171,16 @@ export class VectorStyleEditor extends Component { return ( ); } @@ -157,11 +188,16 @@ export class VectorStyleEditor extends Component { _renderLineWidth() { return ( ); } @@ -169,11 +205,16 @@ export class VectorStyleEditor extends Component { _renderSymbolSize() { return ( ); } @@ -182,30 +223,45 @@ export class VectorStyleEditor extends Component { return ( @@ -217,11 +273,16 @@ export class VectorStyleEditor extends Component { if (this.props.symbolDescriptor.options.symbolizeAs === SYMBOLIZE_AS_ICON) { iconOrientation = ( ); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 83ef497e50649..af43110a8ba5e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6588,8 +6588,6 @@ "xpack.maps.source.wmsTitle": "ウェブマップサービス", "xpack.maps.style.heatmap.displayNameLabel": "ヒートマップスタイル", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "解像度パラメーターが認識されません: {resolution}", - "xpack.maps.styles.staticDynamic.dynamicDescription": "プロパティ値で特徴をシンボル化します。", - "xpack.maps.styles.staticDynamic.staticDescription": "静的スタイルプロパティで特徴をシンボル化します。", "xpack.maps.styles.vector.borderColorLabel": "境界線の色", "xpack.maps.styles.vector.borderWidthLabel": "境界線の幅", "xpack.maps.styles.vector.fillColorLabel": "塗りつぶす色", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 87c11adcb5e77..0306edcabd67d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6590,8 +6590,6 @@ "xpack.maps.source.wmsTitle": "Web 地图服务", "xpack.maps.style.heatmap.displayNameLabel": "热图样式", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "无法识别分辨率参数:{resolution}", - "xpack.maps.styles.staticDynamic.dynamicDescription": "使用属性值代表功能。", - "xpack.maps.styles.staticDynamic.staticDescription": "使用静态样式属性代表功能。", "xpack.maps.styles.vector.borderColorLabel": "边框颜色", "xpack.maps.styles.vector.borderWidthLabel": "边框宽度", "xpack.maps.styles.vector.fillColorLabel": "填充颜色", From eb5460fe6674c1171f71714c9ade89fb6e649707 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Mon, 6 Jan 2020 10:31:29 -0700 Subject: [PATCH 03/18] [Metrics UI] Add AWS Cloudwatch dimensions to groups filter in Metrics Explorer (#53624) * [Metrics UI] Adding aws.cloudwatch.* fields to group by for aws.metrics in Metrics Explorer * Ensuring the correct module is used for the field. * Adding a unique around the fields to prevent potential extra work * Adding missing file * Fixing possible missing event.dataset * clean up for review --- .../plugins/infra/common/ecs_allowed_list.ts | 10 ++-- .../create_timerange_with_interval.ts | 46 +++++++++++-------- .../lib/get_dataset_for_field.ts | 46 +++++++++++++++++++ .../lib/populate_series_with_tsvb_data.ts | 18 ++++---- 4 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts diff --git a/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts b/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts index 1728cd1fa4b45..f1d0577b4cb19 100644 --- a/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts +++ b/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { first } from 'lodash'; +import { first, memoize } from 'lodash'; export const ECS_ALLOWED_LIST = [ 'host', @@ -46,8 +46,9 @@ export const DOCKER_ALLOWED_LIST = [ ]; export const AWS_S3_ALLOWED_LIST = ['aws.s3']; +export const AWS_METRICS_ALLOWED_LIST = ['aws.cloudwatch']; -export const getAllowedListForPrefix = (prefix: string) => { +export const getAllowedListForPrefix = memoize((prefix: string) => { const firstPart = first(prefix.split(/\./)); const defaultAllowedList = prefix ? [...ECS_ALLOWED_LIST, prefix] : ECS_ALLOWED_LIST; switch (firstPart) { @@ -61,7 +62,10 @@ export const getAllowedListForPrefix = (prefix: string) => { if (prefix === 'aws.s3_daily_storage') { return [...defaultAllowedList, ...AWS_S3_ALLOWED_LIST]; } + if (prefix === 'aws.metrics') { + return [...defaultAllowedList, ...AWS_METRICS_ALLOWED_LIST]; + } default: return defaultAllowedList; } -}; +}); diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts index 6c27e54a78bee..4bb1aefb5b2c7 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts @@ -12,6 +12,7 @@ import { getMetricsAggregations } from './query_helpers'; import { calculateMetricInterval } from '../../utils/calculate_metric_interval'; import { SnapshotModel, SnapshotModelMetricAggRT } from '../../../common/inventory_models/types'; import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; +import { getDatasetForField } from '../../routes/metrics_explorer/lib/get_dataset_for_field'; export const createTimeRangeWithInterval = async ( framework: KibanaFramework, @@ -19,7 +20,7 @@ export const createTimeRangeWithInterval = async ( options: InfraSnapshotRequestOptions ): Promise => { const aggregations = getMetricsAggregations(options); - const modules = aggregationsToModules(aggregations); + const modules = await aggregationsToModules(framework, requestContext, aggregations, options); const interval = (await calculateMetricInterval( framework, @@ -39,21 +40,30 @@ export const createTimeRangeWithInterval = async ( }; }; -const aggregationsToModules = (aggregations: SnapshotModel): string[] => { - return uniq( - Object.values(aggregations) - .reduce((modules, agg) => { - if (SnapshotModelMetricAggRT.is(agg)) { - return modules.concat(Object.values(agg).map(a => a?.field)); - } - return modules; - }, [] as Array) - .filter(v => v) - .map(field => - field! - .split(/\./) - .slice(0, 2) - .join('.') - ) - ) as string[]; +const aggregationsToModules = async ( + framework: KibanaFramework, + requestContext: RequestHandlerContext, + aggregations: SnapshotModel, + options: InfraSnapshotRequestOptions +): Promise => { + const uniqueFields = Object.values(aggregations) + .reduce>((fields, agg) => { + if (SnapshotModelMetricAggRT.is(agg)) { + return uniq(fields.concat(Object.values(agg).map(a => a?.field))); + } + return fields; + }, []) + .filter(v => v) as string[]; + const fields = await Promise.all( + uniqueFields.map( + async field => + await getDatasetForField( + framework, + requestContext, + field as string, + options.sourceConfiguration.metricAlias + ) + ) + ); + return fields.filter(f => f) as string[]; }; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts new file mode 100644 index 0000000000000..66f0ca8fc706a --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'kibana/server'; +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; + +interface EventDatasetHit { + _source: { + event?: { + dataset?: string; + }; + }; +} + +export const getDatasetForField = async ( + framework: KibanaFramework, + requestContext: RequestHandlerContext, + field: string, + indexPattern: string +) => { + const params = { + allowNoIndices: true, + ignoreUnavailable: true, + terminateAfter: 1, + index: indexPattern, + body: { + query: { exists: { field } }, + size: 1, + _source: ['event.dataset'], + }, + }; + + const response = await framework.callWithRequest( + requestContext, + 'search', + params + ); + if (response.hits.total.value === 0) { + return null; + } + + return response.hits.hits?.[0]._source.event?.dataset; +}; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts index 17fc46b41278a..8ab3fdccbe72b 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { union } from 'lodash'; +import { union, uniq } from 'lodash'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { @@ -16,6 +16,7 @@ import { import { createMetricModel } from './create_metrics_model'; import { JsonObject } from '../../../../common/typed_json'; import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; +import { getDatasetForField } from './get_dataset_for_field'; export const populateSeriesWithTSVBData = ( request: KibanaRequest, @@ -53,6 +54,12 @@ export const populateSeriesWithTSVBData = ( // Create the TSVB model based on the request options const model = createMetricModel(options); + const modules = await Promise.all( + uniq(options.metrics.filter(m => m.field)).map( + async m => + await getDatasetForField(framework, requestContext, m.field as string, options.indexPattern) + ) + ); const calculatedInterval = await calculateMetricInterval( framework, requestContext, @@ -61,14 +68,7 @@ export const populateSeriesWithTSVBData = ( timestampField: options.timerange.field, timerange: options.timerange, }, - options.metrics - .filter(metric => metric.field) - .map(metric => { - return metric - .field!.split(/\./) - .slice(0, 2) - .join('.'); - }) + modules.filter(m => m) as string[] ); if (calculatedInterval) { From 194c97f4f6ef728012ac85697aee5237472813e2 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 6 Jan 2020 14:12:07 -0500 Subject: [PATCH 04/18] Reorganizing spaces client-side plugin (#53644) * reorganizing spaces client-side plugin * additional testing and cleanup * address PR feedback * additional cleanup * rename scss file * one more index --- .../new_platform/new_platform.karma_mock.js | 2 + .../privilege_matrix.tsx | 2 +- .../privilege_space_table.tsx | 2 +- .../space_selector.tsx | 2 +- .../spaces_popover_list.tsx | 6 +- x-pack/legacy/plugins/spaces/index.ts | 4 +- .../advanced_settings_service.test.tsx | 40 +++++++ .../advanced_settings_service.tsx | 28 +++++ .../advanced_settings_subtitle.test.tsx | 0 .../advanced_settings_subtitle.tsx | 2 +- .../advanced_settings_subtitle/index.ts | 0 .../advanced_settings_title.test.tsx | 2 +- .../advanced_settings_title.tsx | 4 +- .../advanced_settings_title/index.ts | 0 .../advanced_settings/components/index.ts | 8 ++ .../spaces/public/advanced_settings/index.ts | 7 ++ .../spaces/public/{lib => }/constants.ts | 0 .../copy_saved_objects_to_space/_index.scss | 1 + .../components/_copy_to_space.scss} | 0 .../components/_index.scss | 1 + .../components}/copy_status_indicator.tsx | 5 +- .../copy_status_summary_indicator.tsx | 4 +- .../components}/copy_to_space_flyout.test.tsx | 11 +- .../components}/copy_to_space_flyout.tsx | 10 +- .../copy_to_space_flyout_footer.tsx | 4 +- .../components}/copy_to_space_form.tsx | 4 +- .../components}/index.ts | 0 .../components}/processing_copy_to_space.tsx | 10 +- .../components}/selectable_spaces_control.tsx | 4 +- .../components}/space_result.tsx | 10 +- .../components}/space_result_details.tsx | 8 +- .../copy_saved_objects_to_space_action.tsx | 4 +- ...opy_saved_objects_to_space_service.test.ts | 37 ++++++ .../copy_saved_objects_to_space_service.ts | 21 ++++ .../copy_saved_objects_to_space/index.ts | 2 +- .../summarize_copy_result.test.ts | 2 +- .../summarize_copy_result.ts | 4 +- .../copy_saved_objects_to_space/types.ts | 0 .../public/create_feature_catalogue_entry.ts | 2 +- .../legacy/plugins/spaces/public/index.scss | 5 +- x-pack/legacy/plugins/spaces/public/index.ts | 2 + x-pack/legacy/plugins/spaces/public/legacy.ts | 19 ++- .../spaces/public/management/_index.scss | 4 + .../confirm_delete_modal.test.tsx.snap | 0 .../_confirm_delete_modal.scss | 0 .../confirm_delete_modal.test.tsx | 4 +- .../confirm_delete_modal.tsx | 4 +- .../components/confirm_delete_modal/index.ts | 7 ++ .../management/components/index.ts | 1 + .../secure_space_message.test.tsx.snap | 0 .../components/secure_space_message/index.ts | 0 .../secure_space_message.test.tsx | 2 +- .../secure_space_message.tsx | 2 +- .../unauthorized_prompt.test.tsx.snap | 0 .../components/unauthorized_prompt/index.ts | 7 ++ .../unauthorized_prompt.test.tsx | 0 .../unauthorized_prompt.tsx | 0 .../delete_spaces_button.test.tsx.snap | 0 ...irm_alter_active_space_modal.test.tsx.snap | 0 .../confirm_alter_active_space_modal.test.tsx | 0 .../confirm_alter_active_space_modal.tsx | 0 .../confirm_alter_active_space_modal/index.ts | 0 .../customize_space_avatar.test.tsx.snap | 0 .../space_identifier.test.tsx.snap | 0 .../customize_space/customize_space.tsx | 6 +- .../customize_space_avatar.test.tsx | 0 .../customize_space_avatar.tsx | 10 +- .../edit_space/customize_space/index.ts | 0 .../customize_space/space_identifier.test.tsx | 0 .../customize_space/space_identifier.tsx | 2 +- .../edit_space/delete_spaces_button.test.tsx | 4 +- .../edit_space/delete_spaces_button.tsx | 5 +- .../enabled_features.test.tsx.snap | 0 .../edit_space/enabled_features/_index.scss | 0 .../enabled_features.test.tsx | 4 +- .../enabled_features/enabled_features.tsx | 4 +- .../enabled_features/feature_table.tsx | 8 +- .../edit_space/enabled_features/index.ts | 0 .../enabled_features/toggle_all_features.tsx | 0 .../management/edit_space/index.ts | 0 .../edit_space/manage_space_page.test.tsx | 20 +++- .../edit_space/manage_space_page.tsx | 22 ++-- .../edit_space/reserved_space_badge.test.tsx | 0 .../edit_space/reserved_space_badge.tsx | 4 +- .../__snapshots__/section_panel.test.tsx.snap | 0 .../section_panel/_section_panel.scss | 0 .../edit_space/section_panel/index.ts | 0 .../section_panel/section_panel.test.tsx | 0 .../section_panel/section_panel.tsx | 0 .../plugins/spaces/public/management/index.ts | 7 ++ .../legacy_page_routes.tsx} | 47 ++++++-- .../management/lib/feature_utils.test.ts | 2 +- .../management/lib/feature_utils.ts | 4 +- .../{views => }/management/lib/index.ts | 2 - .../lib/space_identifier_utils.test.ts | 0 .../management/lib/space_identifier_utils.ts | 0 .../management/lib/validate_space.test.ts | 0 .../management/lib/validate_space.ts | 4 +- .../management/management_service.test.ts | 113 ++++++++++++++++++ .../public/management/management_service.ts | 37 ++++++ .../spaces_grid_pages.test.tsx.snap | 0 .../management/spaces_grid/index.ts | 0 .../spaces_grid/spaces_grid_page.tsx | 14 +-- .../spaces_grid/spaces_grid_pages.test.tsx | 8 +- .../{views => }/management/template.html | 0 .../nav_control_popover.test.tsx.snap | 0 .../{views => }/nav_control/_index.scss | 1 + .../{views => }/nav_control/_nav_control.scss | 0 .../manage_spaces_button.test.tsx.snap | 0 .../spaces_description.test.tsx.snap | 0 .../nav_control/components/_index.scss | 0 .../components/_spaces_description.scss | 0 .../nav_control/components/_spaces_menu.scss | 0 .../components/manage_spaces_button.test.tsx | 0 .../components/manage_spaces_button.tsx | 2 +- .../components/spaces_description.test.tsx | 0 .../components/spaces_description.tsx | 4 +- .../nav_control/components/spaces_menu.tsx | 7 +- .../public/{views => }/nav_control/index.ts | 0 .../{views => }/nav_control/nav_control.tsx | 2 +- .../nav_control/nav_control_popover.test.tsx | 7 +- .../nav_control/nav_control_popover.tsx | 6 +- .../public/{views => }/nav_control/types.tsx | 0 .../legacy/plugins/spaces/public/plugin.tsx | 57 +++++++-- .../__snapshots__/space_avatar.test.tsx.snap | 0 .../{components => space_avatar}/index.ts | 2 +- .../space_attributes.test.ts | 0 .../{lib => space_avatar}/space_attributes.ts | 0 .../space_avatar.test.tsx | 0 .../space_avatar.tsx | 2 +- .../space_selector.test.tsx.snap | 0 .../spaces/public/space_selector/_index.scss | 2 + .../space_selector/_space_selector.scss | 0 .../components/_index.scss | 0 .../components/_space_card.scss | 0 .../components/_space_cards.scss | 0 .../components/index.ts | 0 .../components/space_card.test.tsx | 0 .../components/space_card.tsx | 8 +- .../components/space_cards.test.tsx | 0 .../components/space_cards.tsx | 0 .../{views => }/space_selector/index.tsx | 8 +- .../space_selector/space_selector.html | 0 .../space_selector/space_selector.test.tsx | 4 +- .../space_selector/space_selector.tsx | 8 +- .../public/{lib => spaces_manager}/index.ts | 1 - .../public/{lib => spaces_manager}/mocks.ts | 0 .../spaces_manager.mock.ts | 6 +- .../spaces_manager.test.ts | 0 .../{lib => spaces_manager}/spaces_manager.ts | 2 +- .../plugins/spaces/public/views/_index.scss | 4 - .../public/views/management/_index.scss | 3 - .../spaces/public/views/management/index.tsx | 77 ------------ .../views/management/lib/breadcrumbs.ts | 36 ------ .../public/views/space_selector/_index.scss | 1 - 155 files changed, 589 insertions(+), 298 deletions(-) create mode 100644 x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx create mode 100644 x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx rename x-pack/legacy/plugins/spaces/public/{views/management => advanced_settings}/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views/management => advanced_settings}/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{views/management => advanced_settings}/components/advanced_settings_subtitle/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views/management => advanced_settings}/components/advanced_settings_title/advanced_settings_title.test.tsx (94%) rename x-pack/legacy/plugins/spaces/public/{views/management => advanced_settings}/components/advanced_settings_title/advanced_settings_title.tsx (90%) rename x-pack/legacy/plugins/spaces/public/{views/management => advanced_settings}/components/advanced_settings_title/index.ts (100%) create mode 100644 x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts create mode 100644 x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts rename x-pack/legacy/plugins/spaces/public/{lib => }/constants.ts (100%) create mode 100644 x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space/_index.scss => copy_saved_objects_to_space/components/_copy_to_space.scss} (100%) create mode 100644 x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/copy_status_indicator.tsx (96%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/copy_status_summary_indicator.tsx (94%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/copy_to_space_flyout.test.tsx (97%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/copy_to_space_flyout.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/copy_to_space_flyout_footer.tsx (97%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/copy_to_space_form.tsx (94%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/processing_copy_to_space.tsx (90%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/selectable_spaces_control.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/space_result.tsx (86%) rename x-pack/legacy/plugins/spaces/public/{views/management/components/copy_saved_objects_to_space => copy_saved_objects_to_space/components}/space_result_details.tsx (93%) rename x-pack/legacy/plugins/spaces/public/{lib => }/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx (89%) create mode 100644 x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts create mode 100644 x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts rename x-pack/legacy/plugins/spaces/public/{lib => }/copy_saved_objects_to_space/index.ts (74%) rename x-pack/legacy/plugins/spaces/public/{lib => }/copy_saved_objects_to_space/summarize_copy_result.test.ts (98%) rename x-pack/legacy/plugins/spaces/public/{lib => }/copy_saved_objects_to_space/summarize_copy_result.ts (96%) rename x-pack/legacy/plugins/spaces/public/{lib => }/copy_saved_objects_to_space/types.ts (100%) create mode 100644 x-pack/legacy/plugins/spaces/public/management/_index.scss rename x-pack/legacy/plugins/spaces/public/{views/management/components => management/components/confirm_delete_modal}/__snapshots__/confirm_delete_modal.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views/management/components => management/components/confirm_delete_modal}/_confirm_delete_modal.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views/management/components => management/components/confirm_delete_modal}/confirm_delete_modal.test.tsx (94%) rename x-pack/legacy/plugins/spaces/public/{views/management/components => management/components/confirm_delete_modal}/confirm_delete_modal.tsx (98%) create mode 100644 x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts rename x-pack/legacy/plugins/spaces/public/{views => }/management/components/index.ts (85%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/components/secure_space_message/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/components/secure_space_message/secure_space_message.test.tsx (93%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/components/secure_space_message/secure_space_message.tsx (94%) rename x-pack/legacy/plugins/spaces/public/{views/management/components => management/components/unauthorized_prompt}/__snapshots__/unauthorized_prompt.test.tsx.snap (100%) create mode 100644 x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts rename x-pack/legacy/plugins/spaces/public/{views/management/components => management/components/unauthorized_prompt}/unauthorized_prompt.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views/management/components => management/components/unauthorized_prompt}/unauthorized_prompt.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/confirm_alter_active_space_modal/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/customize_space.tsx (97%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/customize_space_avatar.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/customize_space_avatar.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/space_identifier.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/customize_space/space_identifier.tsx (98%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/delete_spaces_button.test.tsx (88%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/delete_spaces_button.tsx (96%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/enabled_features/_index.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/enabled_features/enabled_features.test.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/enabled_features/enabled_features.tsx (97%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/enabled_features/feature_table.tsx (92%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/enabled_features/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/enabled_features/toggle_all_features.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/manage_space_page.test.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/manage_space_page.tsx (93%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/reserved_space_badge.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/reserved_space_badge.tsx (89%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/section_panel/_section_panel.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/section_panel/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/section_panel/section_panel.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/edit_space/section_panel/section_panel.tsx (100%) create mode 100644 x-pack/legacy/plugins/spaces/public/management/index.ts rename x-pack/legacy/plugins/spaces/public/{views/management/page_routes.tsx => management/legacy_page_routes.tsx} (75%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/lib/feature_utils.test.ts (94%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/lib/feature_utils.ts (74%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/lib/index.ts (80%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/lib/space_identifier_utils.test.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/lib/space_identifier_utils.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/lib/validate_space.test.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/lib/validate_space.ts (96%) create mode 100644 x-pack/legacy/plugins/spaces/public/management/management_service.test.ts create mode 100644 x-pack/legacy/plugins/spaces/public/management/management_service.ts rename x-pack/legacy/plugins/spaces/public/{views => }/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/spaces_grid/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/spaces_grid/spaces_grid_page.tsx (96%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/spaces_grid/spaces_grid_pages.test.tsx (90%) rename x-pack/legacy/plugins/spaces/public/{views => }/management/template.html (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/__snapshots__/nav_control_popover.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/_index.scss (55%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/_nav_control.scss (100%) rename x-pack/legacy/plugins/spaces/public/{ => nav_control}/components/__snapshots__/manage_spaces_button.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/components/__snapshots__/spaces_description.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/components/_index.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/components/_spaces_description.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/components/_spaces_menu.scss (100%) rename x-pack/legacy/plugins/spaces/public/{ => nav_control}/components/manage_spaces_button.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{ => nav_control}/components/manage_spaces_button.tsx (96%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/components/spaces_description.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/components/spaces_description.tsx (89%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/components/spaces_menu.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/nav_control.tsx (94%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/nav_control_popover.test.tsx (91%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/nav_control_popover.tsx (96%) rename x-pack/legacy/plugins/spaces/public/{views => }/nav_control/types.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{components => space_avatar}/__snapshots__/space_avatar.test.tsx.snap (100%) rename x-pack/legacy/plugins/spaces/public/{components => space_avatar}/index.ts (82%) rename x-pack/legacy/plugins/spaces/public/{lib => space_avatar}/space_attributes.test.ts (100%) rename x-pack/legacy/plugins/spaces/public/{lib => space_avatar}/space_attributes.ts (100%) rename x-pack/legacy/plugins/spaces/public/{components => space_avatar}/space_avatar.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{components => space_avatar}/space_avatar.tsx (98%) rename x-pack/legacy/plugins/spaces/public/{views => }/space_selector/__snapshots__/space_selector.test.tsx.snap (100%) create mode 100644 x-pack/legacy/plugins/spaces/public/space_selector/_index.scss rename x-pack/legacy/plugins/spaces/public/{views => }/space_selector/_space_selector.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/_index.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/_space_card.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/_space_cards.scss (100%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/index.ts (100%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/space_card.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/space_card.tsx (90%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/space_cards.test.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => space_selector}/components/space_cards.tsx (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/space_selector/index.tsx (82%) rename x-pack/legacy/plugins/spaces/public/{views => }/space_selector/space_selector.html (100%) rename x-pack/legacy/plugins/spaces/public/{views => }/space_selector/space_selector.test.tsx (92%) rename x-pack/legacy/plugins/spaces/public/{views => }/space_selector/space_selector.tsx (95%) rename x-pack/legacy/plugins/spaces/public/{lib => spaces_manager}/index.ts (76%) rename x-pack/legacy/plugins/spaces/public/{lib => spaces_manager}/mocks.ts (100%) rename x-pack/legacy/plugins/spaces/public/{lib => spaces_manager}/spaces_manager.mock.ts (87%) rename x-pack/legacy/plugins/spaces/public/{lib => spaces_manager}/spaces_manager.test.ts (100%) rename x-pack/legacy/plugins/spaces/public/{lib => spaces_manager}/spaces_manager.ts (97%) delete mode 100644 x-pack/legacy/plugins/spaces/public/views/_index.scss delete mode 100644 x-pack/legacy/plugins/spaces/public/views/management/_index.scss delete mode 100644 x-pack/legacy/plugins/spaces/public/views/management/index.tsx delete mode 100644 x-pack/legacy/plugins/spaces/public/views/management/lib/breadcrumbs.ts delete mode 100644 x-pack/legacy/plugins/spaces/public/views/space_selector/_index.scss diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 3d4292cef27f4..06424ea48a40f 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -148,6 +148,8 @@ export const npStart = { legacy: { getSection: () => ({ register: sinon.fake(), + deregister: sinon.fake(), + hasItem: sinon.fake(), }), }, }, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx index 92dace65d466c..962487312c83d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx @@ -23,7 +23,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { SpaceAvatar } from '../../../../../../../../../spaces/public/components'; +import { SpaceAvatar } from '../../../../../../../../../spaces/public/space_avatar'; import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { FeaturesPrivileges, Role } from '../../../../../../../../common/model'; import { CalculatedPrivilege } from '../../../../../../../lib/kibana_privilege_calculator'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx index e54b5ff9c45da..65a3df9fb47a1 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx @@ -14,7 +14,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component } from 'react'; -import { getSpaceColor } from '../../../../../../../../../spaces/public/lib/space_attributes'; +import { getSpaceColor } from '../../../../../../../../../spaces/public/space_avatar'; import { Space } from '../../../../../../../../../spaces/common/model/space'; import { FeaturesPrivileges, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx index e6e206e5fc7f4..0eb9cf0b0ee9d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx @@ -8,7 +8,7 @@ import { EuiComboBox, EuiComboBoxOptionProps, EuiHealth, EuiHighlight } from '@e import { InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { getSpaceColor } from '../../../../../../../../../spaces/public/lib/space_attributes'; +import { getSpaceColor } from '../../../../../../../../../spaces/public/space_avatar'; const spaceToOption = (space?: Space, currentSelection?: 'global' | 'spaces') => { if (!space) { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx index 1ab2a27220eee..a99e389044eaa 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx @@ -14,9 +14,9 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../../../../spaces/common/constants'; -import { Space } from '../../../../../../../spaces/common/model/space'; -import { SpaceAvatar } from '../../../../../../../spaces/public/components'; +import { SpaceAvatar } from '../../../../../../../spaces/public/space_avatar'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../../../../../../plugins/spaces/common/constants'; +import { Space } from '../../../../../../../../../plugins/spaces/common/model/space'; interface Props { spaces: Space[]; diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 0083847cfb441..934b44b4accaf 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -49,12 +49,12 @@ export const spaces = (kibana: Record) => uiExports: { styleSheetPaths: resolve(__dirname, 'public/index.scss'), - managementSections: ['plugins/spaces/views/management'], + managementSections: [], apps: [ { id: 'space_selector', title: 'Spaces', - main: 'plugins/spaces/views/space_selector', + main: 'plugins/spaces/space_selector', url: 'space_selector', hidden: true, }, diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx new file mode 100644 index 0000000000000..aa3c6acf26236 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AdvancedSettingsService } from './advanced_settings_service'; +jest.mock('ui/management', () => { + return { + PAGE_TITLE_COMPONENT: 'page_title_component', + PAGE_SUBTITLE_COMPONENT: 'page_subtitle_component', + }; +}); + +describe('Advanced Settings Service', () => { + describe('#setup', () => { + it('registers space-aware components to augment the advanced settings screen', () => { + const deps = { + getActiveSpace: jest.fn().mockResolvedValue({ id: 'foo', name: 'foo-space' }), + registerSettingsComponent: jest.fn(), + }; + + const advancedSettingsService = new AdvancedSettingsService(); + advancedSettingsService.setup(deps); + + expect(deps.registerSettingsComponent).toHaveBeenCalledTimes(2); + expect(deps.registerSettingsComponent).toHaveBeenCalledWith( + 'page_title_component', + expect.any(Function), + true + ); + + expect(deps.registerSettingsComponent).toHaveBeenCalledWith( + 'page_subtitle_component', + expect.any(Function), + true + ); + }); + }); +}); diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx new file mode 100644 index 0000000000000..9c6c2fcc2cdda --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { PAGE_TITLE_COMPONENT, PAGE_SUBTITLE_COMPONENT } from 'ui/management'; +import { Space } from '../../common/model/space'; +import { AdvancedSettingsTitle, AdvancedSettingsSubtitle } from './components'; + +interface SetupDeps { + getActiveSpace: () => Promise; + registerSettingsComponent: ( + id: string, + component: string | React.FC, + allowOverride: boolean + ) => void; +} + +export class AdvancedSettingsService { + public setup({ getActiveSpace, registerSettingsComponent }: SetupDeps) { + const PageTitle = () => ; + const SubTitle = () => ; + + registerSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle, true); + registerSettingsComponent(PAGE_SUBTITLE_COMPONENT, SubTitle, true); + } +} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx index 433f8a8ccf0a2..e35d67c7214cf 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx @@ -7,7 +7,7 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment, useState, useEffect } from 'react'; -import { Space } from '../../../../../common/model/space'; +import { Space } from '../../../../common/model/space'; interface Props { getActiveSpace: () => Promise; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/index.ts rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx index bf792ca2cdacf..b772ff433abec 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { AdvancedSettingsTitle } from './advanced_settings_title'; -import { SpaceAvatar } from '../../../../components'; +import { SpaceAvatar } from '../../../space_avatar'; import { act } from '@testing-library/react'; describe('AdvancedSettingsTitle', () => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx index af6fa42cce07b..b74524db81d81 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx @@ -7,8 +7,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState, useEffect } from 'react'; -import { Space } from '../../../../../common/model/space'; -import { SpaceAvatar } from '../../../../components'; +import { Space } from '../../../../../../../plugins/spaces/common/model/space'; +import { SpaceAvatar } from '../../../space_avatar'; interface Props { getActiveSpace: () => Promise; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/index.ts rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts new file mode 100644 index 0000000000000..6678be7fa34e4 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedSettingsSubtitle } from './advanced_settings_subtitle'; +export { AdvancedSettingsTitle } from './advanced_settings_title'; diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts new file mode 100644 index 0000000000000..546831a84fa82 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedSettingsService } from './advanced_settings_service'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/constants.ts b/x-pack/legacy/plugins/spaces/public/constants.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/constants.ts rename to x-pack/legacy/plugins/spaces/public/constants.ts diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss new file mode 100644 index 0000000000000..30ac0c9fe9b27 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss @@ -0,0 +1 @@ +@import './components/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/_index.scss b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/_index.scss rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss new file mode 100644 index 0000000000000..92b19a8c8c6a3 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss @@ -0,0 +1 @@ +@import './copy_to_space'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_indicator.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_indicator.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx index f9da25409d60e..ff9035ff02be5 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_indicator.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx @@ -7,10 +7,7 @@ import React from 'react'; import { EuiLoadingSpinner, EuiText, EuiIconTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - SummarizedCopyToSpaceResult, - SummarizedSavedObjectResult, -} from '../../../../lib/copy_saved_objects_to_space'; +import { SummarizedCopyToSpaceResult, SummarizedSavedObjectResult } from '..'; interface Props { summarizedCopyResult: SummarizedCopyToSpaceResult; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_summary_indicator.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_summary_indicator.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx index 0ad5f72ba3e45..9d73c216c73ce 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_summary_indicator.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { EuiLoadingSpinner, EuiIconTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Space } from '../../../../../common/model/space'; -import { SummarizedCopyToSpaceResult } from '../../../../lib/copy_saved_objects_to_space'; +import { Space } from '../../../common/model/space'; +import { SummarizedCopyToSpaceResult } from '..'; interface Props { space: Space; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx index b3fd345b1d2b4..28011911e212e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx @@ -6,20 +6,20 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; +import { mockManagementPlugin } from '../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; -import { Space } from '../../../../../common/model/space'; +import { Space } from '../../../common/model/space'; import { findTestSubject } from 'test_utils/find_test_subject'; import { SelectableSpacesControl } from './selectable_spaces_control'; import { act } from '@testing-library/react'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; -import { spacesManagerMock } from '../../../../lib/mocks'; -import { SpacesManager } from '../../../../lib'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -jest.mock('../../../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ +jest.mock('../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ setup: mockManagementPlugin.createSetupContract(), start: mockManagementPlugin.createStartContract(), })); @@ -404,6 +404,7 @@ describe('CopyToSpaceFlyout', () => { id: 'my-viz', error: { type: 'missing_references', + blocking: [], references: [{ type: 'index-pattern', id: 'missing-index-pattern' }], }, }, diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx index 5a43e5878ab83..f486f2f24f13d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx @@ -22,17 +22,17 @@ import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; import { ProcessedImportResponse, processImportResponse, -} from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { Space } from '../../../../../common/model/space'; -import { SpacesManager } from '../../../../lib'; +} from '../../../../../../../src/legacy/core_plugins/management/public'; +import { Space } from '../../../common/model/space'; +import { SpacesManager } from '../../spaces_manager'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; import { CopyToSpaceFlyoutFooter } from './copy_to_space_flyout_footer'; import { CopyToSpaceForm } from './copy_to_space_form'; -import { CopyOptions, ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { CopyOptions, ImportRetry } from '../types'; interface Props { onClose: () => void; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx index 5853bebe3c669..56f39ce3ed4fb 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx @@ -8,8 +8,8 @@ import React, { Fragment } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ImportRetry } from '../types'; interface Props { copyInProgress: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_form.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_form.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx index 2a7e17c253f0b..f680793e27fe0 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_form.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx @@ -14,8 +14,8 @@ import { EuiListGroupItem, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CopyOptions } from '../../../../lib/copy_saved_objects_to_space/types'; -import { Space } from '../../../../../common/model/space'; +import { CopyOptions } from '../types'; +import { Space } from '../../../common/model/space'; import { SelectableSpacesControl } from './selectable_spaces_control'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/index.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/index.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx index b04c9598559b3..285abb828a011 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx @@ -13,12 +13,12 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { summarizeCopyResult } from '../../../../lib/copy_saved_objects_to_space'; -import { Space } from '../../../../../common/model/space'; -import { CopyOptions, ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { Space } from '../../../common/model/space'; +import { CopyOptions, ImportRetry } from '../types'; import { SpaceResult } from './space_result'; +import { summarizeCopyResult } from '..'; interface Props { savedObject: SavedObjectsManagementRecord; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/selectable_spaces_control.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/selectable_spaces_control.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx index 42d5707531380..9cf81b1cc4486 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/selectable_spaces_control.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx @@ -6,8 +6,8 @@ import React, { Fragment, useState } from 'react'; import { EuiSelectable, EuiLoadingSpinner } from '@elastic/eui'; -import { SpaceAvatar } from '../../../../components'; -import { Space } from '../../../../../common/model/space'; +import { SpaceAvatar } from '../../space_avatar'; +import { Space } from '../../../common/model/space'; interface Props { spaces: Space[]; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx similarity index 86% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx index f71be12276be5..22f0767ba196e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx @@ -6,13 +6,13 @@ import React from 'react'; import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { SummarizedCopyToSpaceResult } from '../../../../lib/copy_saved_objects_to_space'; -import { SpaceAvatar } from '../../../../components'; -import { Space } from '../../../../../common/model/space'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { SummarizedCopyToSpaceResult } from '../index'; +import { SpaceAvatar } from '../../space_avatar'; +import { Space } from '../../../common/model/space'; import { CopyStatusSummaryIndicator } from './copy_status_summary_indicator'; import { SpaceCopyResultDetails } from './space_result_details'; -import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { ImportRetry } from '../types'; interface Props { savedObject: SavedObjectsManagementRecord; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx similarity index 93% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx index 66ec38331c89a..d3ab406b87c3e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx @@ -5,13 +5,13 @@ */ import React from 'react'; -import { SummarizedCopyToSpaceResult } from 'plugins/spaces/lib/copy_saved_objects_to_space'; import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { Space } from '../../../../../common/model/space'; +import { SummarizedCopyToSpaceResult } from '../index'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { Space } from '../../../common/model/space'; import { CopyStatusIndicator } from './copy_status_indicator'; -import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { ImportRetry } from '../types'; interface Props { savedObject: SavedObjectsManagementRecord; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx similarity index 89% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx index 3b0fffa38e785..c016494a4cdf9 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx @@ -9,8 +9,8 @@ import { toastNotifications } from 'ui/notify'; import { SavedObjectsManagementAction, SavedObjectsManagementRecord, -} from '../../../../../../../src/legacy/core_plugins/management/public'; -import { CopySavedObjectsToSpaceFlyout } from '../../views/management/components/copy_saved_objects_to_space'; +} from '../../../../../../src/legacy/core_plugins/management/public'; +import { CopySavedObjectsToSpaceFlyout } from './components'; import { SpacesManager } from '../spaces_manager'; export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagementAction { diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts new file mode 100644 index 0000000000000..63a59344dfe5d --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; +import { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; +import { spacesManagerMock } from '../spaces_manager/mocks'; +import { CopySavedObjectsToSpaceService } from '.'; + +describe('CopySavedObjectsToSpaceService', () => { + describe('#setup', () => { + it('registers the CopyToSpaceSavedObjectsManagementAction', () => { + const deps = { + spacesManager: spacesManagerMock.create(), + // we don't have a proper NP mock for this yet + managementSetup: ({ + savedObjects: { + registry: { + has: jest.fn().mockReturnValue(false), + register: jest.fn(), + }, + }, + } as unknown) as ManagementSetup, + }; + + const service = new CopySavedObjectsToSpaceService(); + service.setup(deps); + + expect(deps.managementSetup.savedObjects.registry.register).toHaveBeenCalledTimes(1); + expect(deps.managementSetup.savedObjects.registry.register).toHaveBeenCalledWith( + expect.any(CopyToSpaceSavedObjectsManagementAction) + ); + }); + }); +}); diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts new file mode 100644 index 0000000000000..37354f985a2fc --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; +import { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; +import { SpacesManager } from '../spaces_manager'; + +interface SetupDeps { + spacesManager: SpacesManager; + managementSetup: ManagementSetup; +} + +export class CopySavedObjectsToSpaceService { + public setup({ spacesManager, managementSetup }: SetupDeps) { + const action = new CopyToSpaceSavedObjectsManagementAction(spacesManager); + managementSetup.savedObjects.registry.register(action); + } +} diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/index.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts similarity index 74% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/index.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts index be23d90cc242a..06969a52a3d8d 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/index.ts +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts @@ -5,4 +5,4 @@ */ export * from './summarize_copy_result'; -export { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; +export { CopySavedObjectsToSpaceService } from './copy_saved_objects_to_space_service'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts similarity index 98% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts index 0352902072790..0244a35711e6f 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts similarity index 96% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts index 8807489157d71..7bc47d35efc6c 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; -import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; export interface SummarizedSavedObjectResult { type: string; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/types.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/types.ts diff --git a/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts b/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts index 1f41bb89d7707..464066d2221de 100644 --- a/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts +++ b/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts @@ -9,7 +9,7 @@ import { FeatureCatalogueEntry, FeatureCatalogueCategory, } from '../../../../../src/plugins/home/public'; -import { getSpacesFeatureDescription } from './lib/constants'; +import { getSpacesFeatureDescription } from './constants'; export const createSpacesFeatureCatalogueEntry = (): FeatureCatalogueEntry => { return { diff --git a/x-pack/legacy/plugins/spaces/public/index.scss b/x-pack/legacy/plugins/spaces/public/index.scss index 7a40872b760cb..26269f1d31aa3 100644 --- a/x-pack/legacy/plugins/spaces/public/index.scss +++ b/x-pack/legacy/plugins/spaces/public/index.scss @@ -10,4 +10,7 @@ // spcChart__legend--small // spcChart__legend-isLoading -@import './views/index'; +@import './management/index'; +@import './nav_control/index'; +@import './space_selector/index'; +@import './copy_saved_objects_to_space/index'; diff --git a/x-pack/legacy/plugins/spaces/public/index.ts b/x-pack/legacy/plugins/spaces/public/index.ts index 9233aae9fb12f..53cb906a619d3 100644 --- a/x-pack/legacy/plugins/spaces/public/index.ts +++ b/x-pack/legacy/plugins/spaces/public/index.ts @@ -5,6 +5,8 @@ */ import { SpacesPlugin } from './plugin'; +export { SpaceAvatar } from './space_avatar'; + export const plugin = () => { return new SpacesPlugin(); }; diff --git a/x-pack/legacy/plugins/spaces/public/legacy.ts b/x-pack/legacy/plugins/spaces/public/legacy.ts index 99419206093e9..1dffbd2661714 100644 --- a/x-pack/legacy/plugins/spaces/public/legacy.ts +++ b/x-pack/legacy/plugins/spaces/public/legacy.ts @@ -4,15 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +import { registerSettingsComponent } from 'ui/management'; import { npSetup, npStart } from 'ui/new_platform'; +import { setup as managementSetup } from '../../../../../src/legacy/core_plugins/management/public/legacy'; import { plugin } from '.'; -import { SpacesPlugin, PluginsSetup } from './plugin'; +import { SpacesPlugin, PluginsSetup, PluginsStart } from './plugin'; +import './management/legacy_page_routes'; const spacesPlugin: SpacesPlugin = plugin(); -const plugins: PluginsSetup = { +const pluginsSetup: PluginsSetup = { home: npSetup.plugins.home, + management: managementSetup, + __managementLegacyCompat: { + registerSettingsComponent, + }, }; -export const setup = spacesPlugin.setup(npSetup.core, plugins); -export const start = spacesPlugin.start(npStart.core); +const pluginsStart: PluginsStart = { + management: npStart.plugins.management, +}; + +export const setup = spacesPlugin.setup(npSetup.core, pluginsSetup); +export const start = spacesPlugin.start(npStart.core, pluginsStart); diff --git a/x-pack/legacy/plugins/spaces/public/management/_index.scss b/x-pack/legacy/plugins/spaces/public/management/_index.scss new file mode 100644 index 0000000000000..72deb1f1cde8d --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/_index.scss @@ -0,0 +1,4 @@ +@import './components/confirm_delete_modal/confirm_delete_modal'; +@import './edit_space/enabled_features/index'; +@import './edit_space/section_panel/section_panel'; + diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx index f0ab2c99ac2e2..331435b54edb7 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { ConfirmDeleteModal } from './confirm_delete_modal'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { spacesManagerMock } from '../../../spaces_manager/mocks'; +import { SpacesManager } from '../../../spaces_manager'; describe('ConfirmDeleteModal', () => { it('renders as expected', () => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx index 0c76cb4a828fe..6eed58a784212 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx @@ -25,8 +25,8 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { ChangeEvent, Component } from 'react'; -import { Space } from '../../../../common/model/space'; -import { SpacesManager } from '../../../lib'; +import { SpacesManager } from '../../../spaces_manager'; +import { Space } from '../../../../../../plugins/spaces/common/model/space'; interface Props { space: Space; diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts new file mode 100644 index 0000000000000..651455d00e9f2 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConfirmDeleteModal } from './confirm_delete_modal'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/index.ts similarity index 85% rename from x-pack/legacy/plugins/spaces/public/views/management/components/index.ts rename to x-pack/legacy/plugins/spaces/public/management/components/index.ts index 91f4964e1da06..7f9f80f470d12 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/index.ts +++ b/x-pack/legacy/plugins/spaces/public/management/components/index.ts @@ -6,3 +6,4 @@ export { ConfirmDeleteModal } from './confirm_delete_modal'; export { UnauthorizedPrompt } from './unauthorized_prompt'; +export { SecureSpaceMessage } from './secure_space_message'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/index.ts rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.test.tsx b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx similarity index 93% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx index 1cc6f6c1f72be..b43010fe5f326 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx @@ -8,7 +8,7 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { SecureSpaceMessage } from './secure_space_message'; let mockShowLinks: boolean = true; -jest.mock('../../../../../../xpack_main/public/services/xpack_info', () => { +jest.mock('../../../../../xpack_main/public/services/xpack_info', () => { return { xpackInfo: { get: jest.fn().mockImplementation((key: string) => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.tsx b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx index 6bbc423968b4b..746b7e2ac4c98 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; // @ts-ignore -import { xpackInfo } from '../../../../../../xpack_main/public/services/xpack_info'; +import { xpackInfo } from '../../../../../xpack_main/public/services/xpack_info'; export const SecureSpaceMessage = ({}) => { const showSecurityLinks = xpackInfo.get('features.security.showLinks'); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/unauthorized_prompt.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/unauthorized_prompt.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts new file mode 100644 index 0000000000000..5a8120a77804b --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { UnauthorizedPrompt } from './unauthorized_prompt'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.test.tsx b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.tsx b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx index 8a7e384d44b35..b0d74afaa90aa 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx @@ -18,9 +18,9 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { ChangeEvent, Component, Fragment } from 'react'; -import { isReservedSpace } from '../../../../../common'; -import { Space } from '../../../../../common/model/space'; -import { SpaceAvatar } from '../../../../components'; +import { isReservedSpace } from '../../../../common'; +import { Space } from '../../../../common/model/space'; +import { SpaceAvatar } from '../../../space_avatar'; import { SpaceValidator, toSpaceIdentifier } from '../../lib'; import { SectionPanel } from '../section_panel'; import { CustomizeSpaceAvatar } from './customize_space_avatar'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx index 12fa0193b59a4..c3207c82bf95e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx @@ -17,12 +17,10 @@ import { isValidHex, } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; - -import { getSpaceColor, getSpaceInitials } from '../../../../lib/space_attributes'; -import { encode, imageTypes } from '../../../../../common/lib/dataurl'; - -import { MAX_SPACE_INITIALS } from '../../../../../common/constants'; -import { Space } from '../../../../../common/model/space'; +import { imageTypes, encode } from '../../../../common/lib/dataurl'; +import { getSpaceColor, getSpaceInitials } from '../../../space_avatar'; +import { Space } from '../../../../../../../plugins/spaces/common/model/space'; +import { MAX_SPACE_INITIALS } from '../../../../../../../plugins/spaces/common'; interface Props { space: Partial; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx index a717570b19c5d..1d6664273d21e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx @@ -7,7 +7,7 @@ import { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { ChangeEvent, Component, Fragment } from 'react'; -import { Space } from '../../../../../common/model/space'; +import { Space } from '../../../../common/model/space'; import { SpaceValidator, toSpaceIdentifier } from '../../lib'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx similarity index 88% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx index e7c7dfc5eb1b0..364145d6495b8 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DeleteSpacesButton } from './delete_spaces_button'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; const space = { id: 'my-space', diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx index 216dd7c41f124..56a858eb4ccf6 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx @@ -7,10 +7,9 @@ import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -// @ts-ignore import { toastNotifications } from 'ui/notify'; -import { Space } from '../../../../common/model/space'; -import { SpacesManager } from '../../../lib/spaces_manager'; +import { Space } from '../../../common/model/space'; +import { SpacesManager } from '../../spaces_manager'; import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/_index.scss b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/_index.scss rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx index f8bd4b889394a..f770857d9313d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx @@ -7,8 +7,8 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { Space } from '../../../../../common/model/space'; +import { Feature } from '../../../../../../../plugins/features/public'; +import { Space } from '../../../../common/model/space'; import { SectionPanel } from '../section_panel'; import { EnabledFeatures } from './enabled_features'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx index 628be759b7c5c..70312296f757b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment, ReactNode } from 'react'; import { Capabilities } from 'src/core/public'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { Space } from '../../../../../common/model/space'; +import { Feature } from '../../../../../../../plugins/features/public'; +import { Space } from '../../../../common/model/space'; import { getEnabledFeatures } from '../../lib/feature_utils'; import { SectionPanel } from '../section_panel'; import { FeatureTable } from './feature_table'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx similarity index 92% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx index b3654b4d35bd3..2866d0bfa8cf3 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx @@ -3,13 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { EuiCheckbox, EuiIcon, EuiInMemoryTable, EuiSwitch, EuiText, IconType } from '@elastic/eui'; + +import { EuiIcon, EuiInMemoryTable, EuiSwitch, EuiText, IconType } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { ChangeEvent, Component } from 'react'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { Space } from '../../../../../common/model/space'; +import { Feature } from '../../../../../../../plugins/features/public'; +import { Space } from '../../../../common/model/space'; import { ToggleAllFeatures } from './toggle_all_features'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/toggle_all_features.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/toggle_all_features.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index c69a885ae0587..d24e932bce112 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -6,7 +6,7 @@ jest.mock('ui/kfetch', () => ({ kfetch: () => Promise.resolve([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]), })); -import '../../../__mocks__/xpack_info'; +import '../../__mocks__/xpack_info'; import { EuiButton, EuiLink, EuiSwitch } from '@elastic/eui'; import { ReactWrapper } from 'enzyme'; import React from 'react'; @@ -14,8 +14,8 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { ManageSpacePage } from './manage_space_page'; import { SectionPanel } from './section_panel'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; const space = { id: 'my-space', @@ -65,21 +65,28 @@ describe('ManageSpacePage', () => { }); it('allows a space to be updated', async () => { - const spacesManager = spacesManagerMock.create(); - spacesManager.getSpace = jest.fn().mockResolvedValue({ + const spaceToUpdate = { id: 'existing-space', name: 'Existing Space', description: 'hey an existing space', color: '#aabbcc', initials: 'AB', disabledFeatures: [], + }; + + const spacesManager = spacesManagerMock.create(); + spacesManager.getSpace = jest.fn().mockResolvedValue({ + ...spaceToUpdate, }); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + const onLoadSpace = jest.fn(); + const wrapper = mountWithIntl( { await waitForDataLoad(wrapper); expect(spacesManager.getSpace).toHaveBeenCalledWith('existing-space'); + expect(onLoadSpace).toHaveBeenCalledWith({ + ...spaceToUpdate, + }); await Promise.resolve(); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.tsx similarity index 93% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.tsx index a5d60d1a731ba..6bbb32ccd654f 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -17,17 +17,15 @@ import { import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import { Breadcrumb } from 'ui/chrome'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import { Capabilities } from 'src/core/public'; -import { Feature } from '../../../../../../../plugins/features/public'; -import { isReservedSpace } from '../../../../common'; -import { Space } from '../../../../common/model/space'; -import { SpacesManager } from '../../../lib'; -import { SecureSpaceMessage } from '../components/secure_space_message'; -import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; -import { getEditBreadcrumbs, toSpaceIdentifier } from '../lib'; +import { Feature } from '../../../../../../plugins/features/public'; +import { isReservedSpace } from '../../../common'; +import { Space } from '../../../common/model/space'; +import { SpacesManager } from '../../spaces_manager'; +import { SecureSpaceMessage, UnauthorizedPrompt } from '../components'; +import { toSpaceIdentifier } from '../lib'; import { SpaceValidator } from '../lib/validate_space'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { CustomizeSpace } from './customize_space'; @@ -39,7 +37,7 @@ interface Props { spacesManager: SpacesManager; spaceId?: string; intl: InjectedIntl; - setBreadcrumbs?: (breadcrumbs: Breadcrumb[]) => void; + onLoadSpace?: (space: Space) => void; capabilities: Capabilities; } @@ -76,7 +74,7 @@ class ManageSpacePageUI extends Component { return; } - const { spaceId, spacesManager, intl, setBreadcrumbs } = this.props; + const { spaceId, spacesManager, intl, onLoadSpace } = this.props; const getFeatures = kfetch({ method: 'get', pathname: '/api/features' }); @@ -84,8 +82,8 @@ class ManageSpacePageUI extends Component { try { const [space, features] = await Promise.all([spacesManager.getSpace(spaceId), getFeatures]); if (space) { - if (setBreadcrumbs) { - setBreadcrumbs(getEditBreadcrumbs(space)); + if (onLoadSpace) { + onLoadSpace(space); } this.setState({ diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx similarity index 89% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx index e4b2dda3a668b..38bf351902096 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { isReservedSpace } from '../../../../common'; -import { Space } from '../../../../common/model/space'; +import { isReservedSpace } from '../../../common'; +import { Space } from '../../../common/model/space'; interface Props { space?: Space; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/_section_panel.scss b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/_section_panel.scss rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/index.ts b/x-pack/legacy/plugins/spaces/public/management/index.ts new file mode 100644 index 0000000000000..ad3cc6b245619 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ManagementService } from './management_service'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx b/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx similarity index 75% rename from x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx rename to x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx index d8fd0298df2fc..8cf4a129e5b8f 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx @@ -4,30 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ // @ts-ignore -import template from 'plugins/spaces/views/management/template.html'; +import template from 'plugins/spaces/management/template.html'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nContext } from 'ui/i18n'; // @ts-ignore import routes from 'ui/routes'; +import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; import { npStart } from 'ui/new_platform'; import { ManageSpacePage } from './edit_space'; -import { getCreateBreadcrumbs, getEditBreadcrumbs, getListBreadcrumbs } from './lib'; import { SpacesGridPage } from './spaces_grid'; -import { start as spacesNPStart } from '../../legacy'; +import { start as spacesNPStart } from '../legacy'; +import { Space } from '../../common/model/space'; const reactRootNodeId = 'manageSpacesReactRoot'; +function getListBreadcrumbs() { + return [ + MANAGEMENT_BREADCRUMB, + { + text: 'Spaces', + href: '#/management/spaces/list', + }, + ]; +} + +function getCreateBreadcrumbs() { + return [ + ...getListBreadcrumbs(), + { + text: 'Create', + }, + ]; +} + +function getEditBreadcrumbs(space?: Space) { + return [ + ...getListBreadcrumbs(), + { + text: space ? space.name : '...', + }, + ]; +} + routes.when('/management/spaces/list', { template, k7Breadcrumbs: getListBreadcrumbs, requireUICapability: 'management.kibana.spaces', controller($scope: any) { - $scope.$$postDigest(async () => { + $scope.$$postDigest(() => { const domNode = document.getElementById(reactRootNodeId); - const { spacesManager } = await spacesNPStart; + const { spacesManager } = spacesNPStart; render( @@ -54,10 +83,10 @@ routes.when('/management/spaces/create', { k7Breadcrumbs: getCreateBreadcrumbs, requireUICapability: 'management.kibana.spaces', controller($scope: any) { - $scope.$$postDigest(async () => { + $scope.$$postDigest(() => { const domNode = document.getElementById(reactRootNodeId); - const { spacesManager } = await spacesNPStart; + const { spacesManager } = spacesNPStart; render( @@ -100,7 +129,9 @@ routes.when('/management/spaces/edit/:spaceId', { { + npStart.core.chrome.setBreadcrumbs(getEditBreadcrumbs(space)); + }} capabilities={npStart.core.application.capabilities} /> , diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts index 8621ec5614368..ce874956d0ef2 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts +++ b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../../../plugins/features/public'; import { getEnabledFeatures } from './feature_utils'; +import { Feature } from '../../../../../../plugins/features/public'; const buildFeatures = () => [ diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts similarity index 74% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts index ef46a53967744..ff1688637ef73 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts +++ b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../../../plugins/features/public'; +import { Feature } from '../../../../../../plugins/features/common'; -import { Space } from '../../../../common/model/space'; +import { Space } from '../../../../../../plugins/spaces/common/model/space'; export function getEnabledFeatures(features: Feature[], space: Partial) { return features.filter(feature => !(space.disabledFeatures || []).includes(feature.id)); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/index.ts b/x-pack/legacy/plugins/spaces/public/management/lib/index.ts similarity index 80% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/index.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/index.ts index f65757f5dba26..4a158168febd8 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/index.ts +++ b/x-pack/legacy/plugins/spaces/public/management/lib/index.ts @@ -7,5 +7,3 @@ export { toSpaceIdentifier, isValidSpaceIdentifier } from './space_identifier_utils'; export { SpaceValidator } from './validate_space'; - -export { getCreateBreadcrumbs, getEditBreadcrumbs, getListBreadcrumbs } from './breadcrumbs'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.test.ts b/x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.test.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.ts b/x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.test.ts b/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.test.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/validate_space.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.ts b/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts index e7b9116131431..43d42dacdd36d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.ts +++ b/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { isReservedSpace } from '../../../../common/is_reserved_space'; -import { Space } from '../../../../common/model/space'; +import { isReservedSpace } from '../../../common/is_reserved_space'; +import { Space } from '../../../common/model/space'; import { isValidSpaceIdentifier } from './space_identifier_utils'; interface SpaceValidatorOptions { diff --git a/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts b/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts new file mode 100644 index 0000000000000..fa8ae64168673 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ManagementService } from '.'; + +describe('ManagementService', () => { + describe('#start', () => { + it('registers the spaces management page under the kibana section', () => { + const mockKibanaSection = { + hasItem: jest.fn().mockReturnValue(false), + register: jest.fn(), + }; + + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(mockKibanaSection), + }, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + expect(deps.managementStart.legacy.getSection).toHaveBeenCalledTimes(1); + expect(deps.managementStart.legacy.getSection).toHaveBeenCalledWith('kibana'); + + expect(mockKibanaSection.register).toHaveBeenCalledTimes(1); + expect(mockKibanaSection.register).toHaveBeenCalledWith('spaces', { + name: 'spacesManagementLink', + order: 10, + display: 'Spaces', + url: `#/management/spaces/list`, + }); + }); + + it('will not register the spaces management page twice', () => { + const mockKibanaSection = { + hasItem: jest.fn().mockReturnValue(true), + register: jest.fn(), + }; + + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(mockKibanaSection), + }, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + expect(mockKibanaSection.register).toHaveBeenCalledTimes(0); + }); + + it('will not register the spaces management page if the kibana section is missing', () => { + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(undefined), + }, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + expect(deps.managementStart.legacy.getSection).toHaveBeenCalledTimes(1); + }); + }); + + describe('#stop', () => { + it('deregisters the spaces management page', () => { + const mockKibanaSection = { + hasItem: jest + .fn() + .mockReturnValueOnce(false) + .mockReturnValueOnce(true), + register: jest.fn(), + deregister: jest.fn(), + }; + + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(mockKibanaSection), + }, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + service.stop(); + + expect(mockKibanaSection.register).toHaveBeenCalledTimes(1); + expect(mockKibanaSection.deregister).toHaveBeenCalledTimes(1); + expect(mockKibanaSection.deregister).toHaveBeenCalledWith('spaces'); + }); + }); +}); diff --git a/x-pack/legacy/plugins/spaces/public/management/management_service.ts b/x-pack/legacy/plugins/spaces/public/management/management_service.ts new file mode 100644 index 0000000000000..ada38f5cf3387 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/management_service.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { ManagementStart } from 'src/plugins/management/public'; + +interface StartDeps { + managementStart: ManagementStart; +} + +const MANAGE_SPACES_KEY = 'spaces'; + +export class ManagementService { + private kibanaSection!: any; + + public start({ managementStart }: StartDeps) { + this.kibanaSection = managementStart.legacy.getSection('kibana'); + if (this.kibanaSection && !this.kibanaSection.hasItem(MANAGE_SPACES_KEY)) { + this.kibanaSection.register(MANAGE_SPACES_KEY, { + name: 'spacesManagementLink', + order: 10, + display: i18n.translate('xpack.spaces.displayName', { + defaultMessage: 'Spaces', + }), + url: `#/management/spaces/list`, + }); + } + } + + public stop() { + if (this.kibanaSection && this.kibanaSection.hasItem(MANAGE_SPACES_KEY)) { + this.kibanaSection.deregister(MANAGE_SPACES_KEY); + } + } +} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/index.ts b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/index.ts rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx index 9fa03b1a9b74a..6ca1877642bdc 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx @@ -22,13 +22,13 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import { Capabilities } from 'src/core/public'; -import { Feature } from '../../../../../../../plugins/features/public'; -import { isReservedSpace } from '../../../../common'; -import { DEFAULT_SPACE_ID } from '../../../../common/constants'; -import { Space } from '../../../../common/model/space'; -import { SpaceAvatar } from '../../../components'; -import { getSpacesFeatureDescription } from '../../../lib/constants'; -import { SpacesManager } from '../../../lib/spaces_manager'; +import { Feature } from '../../../../../../plugins/features/public'; +import { isReservedSpace } from '../../../common'; +import { DEFAULT_SPACE_ID } from '../../../common/constants'; +import { Space } from '../../../common/model/space'; +import { SpaceAvatar } from '../../space_avatar'; +import { getSpacesFeatureDescription } from '../../constants'; +import { SpacesManager } from '../..//spaces_manager'; import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; import { SecureSpaceMessage } from '../components/secure_space_message'; import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx index 4add607707b24..7856d2e7bee01 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx @@ -6,12 +6,12 @@ jest.mock('ui/kfetch', () => ({ kfetch: () => Promise.resolve([]), })); -import '../../../__mocks__/xpack_info'; +import '../../__mocks__/xpack_info'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { SpaceAvatar } from '../../../components'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { SpaceAvatar } from '../../space_avatar'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; import { SpacesGridPage } from './spaces_grid_page'; const spaces = [ diff --git a/x-pack/legacy/plugins/spaces/public/views/management/template.html b/x-pack/legacy/plugins/spaces/public/management/template.html similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/template.html rename to x-pack/legacy/plugins/spaces/public/management/template.html diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/_index.scss b/x-pack/legacy/plugins/spaces/public/nav_control/_index.scss similarity index 55% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/_index.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/_index.scss index 192091fb04e3c..d0471da325cec 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/_index.scss +++ b/x-pack/legacy/plugins/spaces/public/nav_control/_index.scss @@ -1 +1,2 @@ @import './components/index'; +@import './nav_control'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/_nav_control.scss b/x-pack/legacy/plugins/spaces/public/nav_control/_nav_control.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/_nav_control.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/_nav_control.scss diff --git a/x-pack/legacy/plugins/spaces/public/components/__snapshots__/manage_spaces_button.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/__snapshots__/manage_spaces_button.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/_index.scss b/x-pack/legacy/plugins/spaces/public/nav_control/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/_index.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/components/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_description.scss b/x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_description.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_description.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_description.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_menu.scss b/x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_menu.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_menu.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_menu.scss diff --git a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx index 91a0803c20bc9..857d0c1f828a6 100644 --- a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx @@ -8,7 +8,7 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, CSSProperties } from 'react'; import { Capabilities } from 'src/core/public'; -import { getManageSpacesUrl } from '../lib/constants'; +import { getManageSpacesUrl } from '../../constants'; interface Props { isDisabled?: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.test.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.test.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.tsx similarity index 89% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.tsx index 043fc656a571e..abf3c636b839e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.tsx @@ -7,8 +7,8 @@ import { EuiContextMenuPanel, EuiText } from '@elastic/eui'; import React, { FC } from 'react'; import { Capabilities } from 'src/core/public'; -import { ManageSpacesButton } from '../../../components'; -import { getSpacesFeatureDescription } from '../../../lib/constants'; +import { ManageSpacesButton } from './manage_spaces_button'; +import { getSpacesFeatureDescription } from '../../constants'; interface Props { onManageSpacesClick: () => void; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx index 9a26f6802abdf..96ce18896b426 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx @@ -14,9 +14,10 @@ import { import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component, ReactElement } from 'react'; import { Capabilities } from 'src/core/public'; -import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../common/constants'; -import { Space } from '../../../../common/model/space'; -import { ManageSpacesButton, SpaceAvatar } from '../../../components'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common/constants'; +import { Space } from '../../../common/model/space'; +import { ManageSpacesButton } from './manage_spaces_button'; +import { SpaceAvatar } from '../../space_avatar'; interface Props { spaces: Space[]; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts b/x-pack/legacy/plugins/spaces/public/nav_control/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts rename to x-pack/legacy/plugins/spaces/public/nav_control/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx index 0df077e0d2da0..9ec070eff3fed 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; import React from 'react'; import ReactDOM from 'react-dom'; import { CoreStart } from 'src/core/public'; +import { SpacesManager } from '../spaces_manager'; import { NavControlPopover } from './nav_control_popover'; export function initSpacesNavControl(spacesManager: SpacesManager, core: CoreStart) { diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx similarity index 91% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx index a04f28242f984..5ce141abb713e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx @@ -7,9 +7,9 @@ import * as Rx from 'rxjs'; import { shallow } from 'enzyme'; import React from 'react'; -import { SpaceAvatar } from '../../components'; -import { spacesManagerMock } from '../../lib/mocks'; -import { SpacesManager } from '../../lib'; +import { SpaceAvatar } from '../space_avatar'; +import { spacesManagerMock } from '../spaces_manager/mocks'; +import { SpacesManager } from '../spaces_manager'; import { NavControlPopover } from './nav_control_popover'; import { EuiHeaderSectionItemButton } from '@elastic/eui'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; @@ -42,6 +42,7 @@ describe('NavControlPopover', () => { disabledFeatures: [], }, ]); + // @ts-ignore readonly check spacesManager.onActiveSpaceChange$ = Rx.of({ id: 'foo-space', name: 'foo', diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx index b37458aace2a2..f291027e15232 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -13,9 +13,9 @@ import { import React, { Component } from 'react'; import { Capabilities } from 'src/core/public'; import { Subscription } from 'rxjs'; -import { Space } from '../../../common/model/space'; -import { SpaceAvatar } from '../../components'; -import { SpacesManager } from '../../lib/spaces_manager'; +import { Space } from '../../common/model/space'; +import { SpaceAvatar } from '../space_avatar'; +import { SpacesManager } from '../spaces_manager'; import { SpacesDescription } from './components/spaces_description'; import { SpacesMenu } from './components/spaces_menu'; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/types.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/types.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/types.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/types.tsx diff --git a/x-pack/legacy/plugins/spaces/public/plugin.tsx b/x-pack/legacy/plugins/spaces/public/plugin.tsx index 4e070c3cee3df..1ddb69a5b595c 100644 --- a/x-pack/legacy/plugins/spaces/public/plugin.tsx +++ b/x-pack/legacy/plugins/spaces/public/plugin.tsx @@ -6,9 +6,14 @@ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; -import { SpacesManager } from './lib'; -import { initSpacesNavControl } from './views/nav_control'; +import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; +import { ManagementStart } from 'src/plugins/management/public'; +import { SpacesManager } from './spaces_manager'; +import { initSpacesNavControl } from './nav_control'; import { createSpacesFeatureCatalogueEntry } from './create_feature_catalogue_entry'; +import { CopySavedObjectsToSpaceService } from './copy_saved_objects_to_space'; +import { AdvancedSettingsService } from './advanced_settings'; +import { ManagementService } from './management'; export interface SpacesPluginStart { spacesManager: SpacesManager | null; @@ -16,25 +21,61 @@ export interface SpacesPluginStart { export interface PluginsSetup { home?: HomePublicPluginSetup; + management: ManagementSetup; + __managementLegacyCompat: { + registerSettingsComponent: ( + id: string, + component: string | React.FC, + allowOverride: boolean + ) => void; + }; +} + +export interface PluginsStart { + management: ManagementStart; } export class SpacesPlugin implements Plugin { - private spacesManager: SpacesManager | null = null; + private spacesManager!: SpacesManager; - public async start(core: CoreStart) { - const serverBasePath = core.injectedMetadata.getInjectedVar('serverBasePath') as string; + private managementService?: ManagementService; + public setup(core: CoreSetup, plugins: PluginsSetup) { + const serverBasePath = core.injectedMetadata.getInjectedVar('serverBasePath') as string; this.spacesManager = new SpacesManager(serverBasePath, core.http); + + const copySavedObjectsToSpaceService = new CopySavedObjectsToSpaceService(); + copySavedObjectsToSpaceService.setup({ + spacesManager: this.spacesManager, + managementSetup: plugins.management, + }); + + const advancedSettingsService = new AdvancedSettingsService(); + advancedSettingsService.setup({ + getActiveSpace: () => this.spacesManager.getActiveSpace(), + registerSettingsComponent: plugins.__managementLegacyCompat.registerSettingsComponent, + }); + + if (plugins.home) { + plugins.home.featureCatalogue.register(createSpacesFeatureCatalogueEntry()); + } + } + + public start(core: CoreStart, plugins: PluginsStart) { initSpacesNavControl(this.spacesManager, core); + this.managementService = new ManagementService(); + this.managementService.start({ managementStart: plugins.management }); + return { spacesManager: this.spacesManager, }; } - public async setup(core: CoreSetup, plugins: PluginsSetup) { - if (plugins.home) { - plugins.home.featureCatalogue.register(createSpacesFeatureCatalogueEntry()); + public stop() { + if (this.managementService) { + this.managementService.stop(); + this.managementService = undefined; } } } diff --git a/x-pack/legacy/plugins/spaces/public/components/__snapshots__/space_avatar.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/__snapshots__/space_avatar.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/components/index.ts b/x-pack/legacy/plugins/spaces/public/space_avatar/index.ts similarity index 82% rename from x-pack/legacy/plugins/spaces/public/components/index.ts rename to x-pack/legacy/plugins/spaces/public/space_avatar/index.ts index 2e73f0c704f8c..1525f2c8c6186 100644 --- a/x-pack/legacy/plugins/spaces/public/components/index.ts +++ b/x-pack/legacy/plugins/spaces/public/space_avatar/index.ts @@ -5,4 +5,4 @@ */ export { SpaceAvatar } from './space_avatar'; -export { ManageSpacesButton } from './manage_spaces_button'; +export * from './space_attributes'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/space_attributes.test.ts b/x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/space_attributes.test.ts rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/lib/space_attributes.ts b/x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/space_attributes.ts rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.ts diff --git a/x-pack/legacy/plugins/spaces/public/components/space_avatar.test.tsx b/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/space_avatar.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx b/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx index 0d9751ca43db9..c89f492a8fc99 100644 --- a/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx @@ -8,7 +8,7 @@ import { EuiAvatar, isValidHex } from '@elastic/eui'; import React, { FC } from 'react'; import { MAX_SPACE_INITIALS } from '../../common'; import { Space } from '../../common/model/space'; -import { getSpaceColor, getSpaceInitials, getSpaceImageUrl } from '../lib/space_attributes'; +import { getSpaceColor, getSpaceInitials, getSpaceImageUrl } from './space_attributes'; interface Props { space: Partial; diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/_index.scss b/x-pack/legacy/plugins/spaces/public/space_selector/_index.scss new file mode 100644 index 0000000000000..0621aa2a3efd7 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/space_selector/_index.scss @@ -0,0 +1,2 @@ +@import './space_selector'; +@import './components/index'; diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss b/x-pack/legacy/plugins/spaces/public/space_selector/_space_selector.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/_space_selector.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/_index.scss b/x-pack/legacy/plugins/spaces/public/space_selector/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/_index.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/components/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/_space_card.scss b/x-pack/legacy/plugins/spaces/public/space_selector/components/_space_card.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/_space_card.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/components/_space_card.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/_space_cards.scss b/x-pack/legacy/plugins/spaces/public/space_selector/components/_space_cards.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/_space_cards.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/components/_space_cards.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/index.ts b/x-pack/legacy/plugins/spaces/public/space_selector/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/index.ts rename to x-pack/legacy/plugins/spaces/public/space_selector/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_card.test.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/space_card.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_card.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/components/space_card.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx index 2386f6a6fe9d0..f898ba87c60bd 100644 --- a/x-pack/legacy/plugins/spaces/public/views/components/space_card.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx @@ -4,14 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - // FIXME: need updated typedefs - // @ts-ignore - EuiCard, -} from '@elastic/eui'; +import { EuiCard } from '@elastic/eui'; import React from 'react'; import { Space } from '../../../common/model/space'; -import { SpaceAvatar } from '../../components'; +import { SpaceAvatar } from '../../space_avatar'; interface Props { space: Space; diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_cards.test.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/space_cards.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_cards.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/space_cards.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx similarity index 82% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/index.tsx index c520c2683c965..c1c1b6dc3a2f3 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx @@ -5,7 +5,7 @@ */ // @ts-ignore -import template from 'plugins/spaces/views/space_selector/space_selector.html'; +import template from 'plugins/spaces/space_selector/space_selector.html'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; // @ts-ignore @@ -15,14 +15,14 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { SpaceSelector } from './space_selector'; -import { start as spacesNPStart } from '../../legacy'; +import { start as spacesNPStart } from '../legacy'; const module = uiModules.get('spaces_selector', []); module.controller('spacesSelectorController', ($scope: any) => { - $scope.$$postDigest(async () => { + $scope.$$postDigest(() => { const domNode = document.getElementById('spaceSelectorRoot'); - const { spacesManager } = await spacesNPStart; + const { spacesManager } = spacesNPStart; render( diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.html b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.html similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.html rename to x-pack/legacy/plugins/spaces/public/space_selector/space_selector.html diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.test.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx similarity index 92% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx index 829312061ca98..b4d0f96307500 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx @@ -6,8 +6,8 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Space } from '../../../common/model/space'; -import { spacesManagerMock } from '../../lib/mocks'; +import { Space } from '../../common/model/space'; +import { spacesManagerMock } from '../spaces_manager/mocks'; import { SpaceSelector } from './space_selector'; function getSpacesManager(spaces: Space[] = []) { diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx index d665752b3c8a6..206d38454fa8c 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx @@ -19,11 +19,11 @@ import { EuiLoadingSpinner, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { SpacesManager } from 'plugins/spaces/lib'; import React, { Component, Fragment } from 'react'; -import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common/constants'; -import { Space } from '../../../common/model/space'; -import { SpaceCards } from '../components/space_cards'; +import { SpacesManager } from '../spaces_manager'; +import { Space } from '../../common/model/space'; +import { SpaceCards } from './components'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../common/constants'; interface Props { spacesManager: SpacesManager; diff --git a/x-pack/legacy/plugins/spaces/public/lib/index.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts similarity index 76% rename from x-pack/legacy/plugins/spaces/public/lib/index.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts index 56ac7b8ff37f4..538dd77e053f5 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/index.ts +++ b/x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts @@ -5,4 +5,3 @@ */ export { SpacesManager } from './spaces_manager'; -export { getSpaceInitials, getSpaceColor, getSpaceImageUrl } from './space_attributes'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/mocks.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/mocks.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/mocks.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/mocks.ts diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts similarity index 87% rename from x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts index 69c6f7a452fdd..56879af33916f 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts +++ b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts @@ -6,9 +6,10 @@ import { of, Observable } from 'rxjs'; import { Space } from '../../common/model/space'; +import { SpacesManager } from './spaces_manager'; function createSpacesManagerMock() { - return { + return ({ onActiveSpaceChange$: (of(undefined) as unknown) as Observable, getSpaces: jest.fn().mockResolvedValue([]), getSpace: jest.fn().mockResolvedValue(undefined), @@ -19,7 +20,8 @@ function createSpacesManagerMock() { copySavedObjects: jest.fn().mockResolvedValue(undefined), resolveCopySavedObjectsErrors: jest.fn().mockResolvedValue(undefined), redirectToSpaceSelector: jest.fn().mockResolvedValue(undefined), - }; + changeSelectedSpace: jest.fn(), + } as unknown) as jest.Mocked; } export const spacesManagerMock = { diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.test.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/spaces_manager.test.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts similarity index 97% rename from x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts index ccc1b00dabb29..e9c738cf40c69 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts @@ -9,9 +9,9 @@ import { HttpSetup } from 'src/core/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; -import { CopySavedObjectsToSpaceResponse } from './copy_saved_objects_to_space/types'; import { ENTER_SPACE_PATH } from '../../common/constants'; import { addSpaceIdToPath } from '../../../../../plugins/spaces/common'; +import { CopySavedObjectsToSpaceResponse } from '../copy_saved_objects_to_space/types'; export class SpacesManager { private activeSpace$: BehaviorSubject = new BehaviorSubject(null); diff --git a/x-pack/legacy/plugins/spaces/public/views/_index.scss b/x-pack/legacy/plugins/spaces/public/views/_index.scss deleted file mode 100644 index 0cc8ccb10246b..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './components/index'; -@import './management/index'; -@import './nav_control/index'; -@import './space_selector/index' diff --git a/x-pack/legacy/plugins/spaces/public/views/management/_index.scss b/x-pack/legacy/plugins/spaces/public/views/management/_index.scss deleted file mode 100644 index e7cbdfe2de7e8..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import './components/confirm_delete_modal'; -@import './edit_space/enabled_features/index'; -@import './components/copy_saved_objects_to_space/index'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx deleted file mode 100644 index bf33273c614d6..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { i18n } from '@kbn/i18n'; -import 'plugins/spaces/views/management/page_routes'; -import React from 'react'; -import { - management, - PAGE_SUBTITLE_COMPONENT, - PAGE_TITLE_COMPONENT, - registerSettingsComponent, -} from 'ui/management'; -// @ts-ignore -import routes from 'ui/routes'; -import { setup as managementSetup } from '../../../../../../../src/legacy/core_plugins/management/public/legacy'; -import { AdvancedSettingsSubtitle } from './components/advanced_settings_subtitle'; -import { AdvancedSettingsTitle } from './components/advanced_settings_title'; -import { start as spacesNPStart } from '../../legacy'; -import { CopyToSpaceSavedObjectsManagementAction } from '../../lib/copy_saved_objects_to_space'; - -const MANAGE_SPACES_KEY = 'spaces'; - -routes.defaults(/\/management/, { - resolve: { - spacesManagementSection() { - function getKibanaSection() { - return management.getSection('kibana'); - } - - function deregisterSpaces() { - getKibanaSection().deregister(MANAGE_SPACES_KEY); - } - - function ensureSpagesRegistered() { - const kibanaSection = getKibanaSection(); - - if (!kibanaSection.hasItem(MANAGE_SPACES_KEY)) { - kibanaSection.register(MANAGE_SPACES_KEY, { - name: 'spacesManagementLink', - order: 10, - display: i18n.translate('xpack.spaces.displayName', { - defaultMessage: 'Spaces', - }), - url: `#/management/spaces/list`, - }); - } - - // Customize Saved Objects Management - spacesNPStart.then(({ spacesManager }) => { - const action = new CopyToSpaceSavedObjectsManagementAction(spacesManager!); - // This route resolve function executes any time the management screen is loaded, and we want to ensure - // that this action is only registered once. - if (!managementSetup.savedObjects.registry.has(action.id)) { - managementSetup.savedObjects.registry.register(action); - } - }); - - const getActiveSpace = async () => { - const { spacesManager } = await spacesNPStart; - return spacesManager!.getActiveSpace(); - }; - - const PageTitle = () => ; - registerSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle, true); - - const SubTitle = () => ; - registerSettingsComponent(PAGE_SUBTITLE_COMPONENT, SubTitle, true); - } - - deregisterSpaces(); - - ensureSpagesRegistered(); - }, - }, -}); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/breadcrumbs.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/breadcrumbs.ts deleted file mode 100644 index a4e8ba508b617..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/breadcrumbs.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; -import { Space } from '../../../../common/model/space'; - -export function getListBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: 'Spaces', - href: '#/management/spaces/list', - }, - ]; -} - -export function getCreateBreadcrumbs() { - return [ - ...getListBreadcrumbs(), - { - text: 'Create', - }, - ]; -} - -export function getEditBreadcrumbs(space?: Space) { - return [ - ...getListBreadcrumbs(), - { - text: space ? space.name : '...', - }, - ]; -} diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/_index.scss b/x-pack/legacy/plugins/spaces/public/views/space_selector/_index.scss deleted file mode 100644 index f23ac662dce1d..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './space_selector'; From 8751e7cf89256ce00c80ad58947a6de6d969bb18 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Mon, 6 Jan 2020 19:29:26 +0000 Subject: [PATCH 05/18] [Visualization] Add default filename when exporting if no filename provided (#54003) --- .../public/views/data/components/download_options.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/inspector/public/views/data/components/download_options.tsx b/src/plugins/inspector/public/views/data/components/download_options.tsx index 6d21dcdafa84d..e7bfbed23c074 100644 --- a/src/plugins/inspector/public/views/data/components/download_options.tsx +++ b/src/plugins/inspector/public/views/data/components/download_options.tsx @@ -20,6 +20,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { DataViewColumn, DataViewRow } from '../types'; @@ -66,8 +67,14 @@ class DataDownloadOptions extends Component { + let filename = this.props.title; + if (!filename || filename.length === 0) { + filename = i18n.translate('inspector.data.downloadOptionsUnsavedFilename', { + defaultMessage: 'unsaved', + }); + } exportAsCsv({ - filename: `${this.props.title}.csv`, + filename: `${filename}.csv`, columns: this.props.columns, rows: this.props.rows, csvSeparator: this.props.csvSeparator, From ae8925c1239fe1538c50fc6f6abfbf58a7ffa7e2 Mon Sep 17 00:00:00 2001 From: Chris Davies Date: Mon, 6 Jan 2020 14:54:49 -0500 Subject: [PATCH 06/18] Select Records when count operation is chosen (#53911) --- .../dimension_panel/dimension_panel.test.tsx | 11 ++++------- .../dimension_panel/popover_editor.tsx | 7 +------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index 82e172b6bd7e2..626ef99ac13aa 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -610,7 +610,7 @@ describe('IndexPatternDimensionPanel', () => { }); }); - it('should indicate document compatibility with selected field operation', () => { + it('should select the Records field when count is selected', () => { const initialState: IndexPatternPrivateState = { ...state, layers: { @@ -639,12 +639,9 @@ describe('IndexPatternDimensionPanel', () => { .find('button[data-test-subj="lns-indexPatternDimensionIncompatible-count"]') .simulate('click'); - const options = wrapper.find(EuiComboBox).prop('options'); - - expect(options![0]['data-test-subj']).not.toContain('Incompatible'); - options![1].options!.map(option => - expect(option['data-test-subj']).toContain('Incompatible') - ); + const newColumnState = setState.mock.calls[0][0].layers.first.columns.col2; + expect(newColumnState.operationType).toEqual('count'); + expect(newColumnState.sourceField).toEqual('Records'); }); it('should indicate document and field compatibility with selected document operation', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx index c44d63b01c1b3..98773c04db4a6 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx @@ -127,7 +127,7 @@ export function PopoverEditor(props: PopoverEditorProps) { compatibleWithCurrentField ? '' : 'Incompatible' }-${operationType}`, onClick() { - if (!selectedColumn) { + if (!selectedColumn || !compatibleWithCurrentField) { const possibleFields = fieldByOperation[operationType] || []; if (possibleFields.length === 1) { @@ -152,11 +152,6 @@ export function PopoverEditor(props: PopoverEditorProps) { trackUiEvent(`indexpattern_dimension_operation_${operationType}`); return; } - if (!compatibleWithCurrentField) { - setInvalidOperationType(operationType); - trackUiEvent(`indexpattern_dimension_operation_${operationType}`); - return; - } if (incompatibleSelectedOperationType) { setInvalidOperationType(null); } From dbe0bfdf7936b25061b6e8b8c52a122c2d73eeb7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 6 Jan 2020 14:55:12 -0500 Subject: [PATCH 07/18] [Maps] Allow editing EMS basemap selection (#53631) * [Maps] Allow editing EMS basemap selection * resync source data when map changes * remove old sources and layers * update ownsMbLayerId and ownsMbSourceId so they return true for previous sources when source changes * review feedback Co-authored-by: Elastic Machine --- .../sources/ems_tms_source/ems_tms_source.js | 17 ++-- ...ource_editor.js => tile_service_select.js} | 80 ++++++++++--------- .../ems_tms_source/update_source_editor.js | 38 +++++++++ .../maps/public/layers/vector_tile_layer.js | 76 ++++++++++++++---- 4 files changed, 153 insertions(+), 58 deletions(-) rename x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/{create_source_editor.js => tile_service_select.js} (52%) create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js index 8850c4c07ab73..76ecc18f2f7d7 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js @@ -11,7 +11,8 @@ import { AbstractTMSSource } from '../tms_source'; import { VectorTileLayer } from '../../vector_tile_layer'; import { getEMSClient } from '../../../meta'; -import { EMSTMSCreateSourceEditor } from './create_source_editor'; +import { TileServiceSelect } from './tile_service_select'; +import { UpdateSourceEditor } from './update_source_editor'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { EMS_TMS } from '../../../../common/constants'; @@ -41,7 +42,7 @@ export class EMSTMSSource extends AbstractTMSSource { onPreviewSource(source); }; - return ; + return ; } constructor(descriptor, inspectorAdapters) { @@ -55,6 +56,10 @@ export class EMSTMSSource extends AbstractTMSSource { ); } + renderSourceSettingsEditor({ onChange }) { + return ; + } + async getImmutableProperties() { const displayName = await this.getDisplayName(); const autoSelectMsg = i18n.translate('xpack.maps.source.emsTile.isAutoSelectLabel', { @@ -78,7 +83,7 @@ export class EMSTMSSource extends AbstractTMSSource { async _getEMSTMSService() { const emsClient = getEMSClient(); const emsTMSServices = await emsClient.getTMSServices(); - const emsTileLayerId = this._getEmsTileLayerId(); + const emsTileLayerId = this.getTileLayerId(); const tmsService = emsTMSServices.find(tmsService => tmsService.getId() === emsTileLayerId); if (!tmsService) { throw new Error( @@ -110,7 +115,7 @@ export class EMSTMSSource extends AbstractTMSSource { const emsTMSService = await this._getEMSTMSService(); return emsTMSService.getDisplayName(); } catch (error) { - return this._getEmsTileLayerId(); + return this.getTileLayerId(); } } @@ -129,7 +134,7 @@ export class EMSTMSSource extends AbstractTMSSource { } getSpriteNamespacePrefix() { - return 'ems/' + this._getEmsTileLayerId(); + return 'ems/' + this.getTileLayerId(); } async getVectorStyleSheetAndSpriteMeta(isRetina) { @@ -142,7 +147,7 @@ export class EMSTMSSource extends AbstractTMSSource { }; } - _getEmsTileLayerId() { + getTileLayerId() { if (!this._descriptor.isAutoSelect) { return this._descriptor.id; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js similarity index 52% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/create_source_editor.js rename to x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js index 65986d5bc93df..337fc7aa46693 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js @@ -13,74 +13,80 @@ import { i18n } from '@kbn/i18n'; export const AUTO_SELECT = 'auto_select'; -export class EMSTMSCreateSourceEditor extends React.Component { +export class TileServiceSelect extends React.Component { state = { - emsTmsOptionsRaw: null, + emsTmsOptions: [], + hasLoaded: false, }; + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadTmsOptions(); + } + _loadTmsOptions = async () => { const emsClient = getEMSClient(); const emsTMSServices = await emsClient.getTMSServices(); - const options = emsTMSServices.map(tmsService => { + + if (!this._isMounted) { + return; + } + + const emsTmsOptions = emsTMSServices.map(tmsService => { return { - id: tmsService.getId(), - name: tmsService.getDisplayName(), + value: tmsService.getId(), + text: tmsService.getDisplayName() ? tmsService.getDisplayName() : tmsService.getId(), }; }); - options.unshift({ - id: AUTO_SELECT, - name: i18n.translate('xpack.maps.source.emsTile.autoLabel', { + emsTmsOptions.unshift({ + value: AUTO_SELECT, + text: i18n.translate('xpack.maps.source.emsTile.autoLabel', { defaultMessage: 'Autoselect based on Kibana theme', }), }); - if (this._isMounted) { - this.setState({ - emsTmsOptionsRaw: options, - }); - } + this.setState({ emsTmsOptions, hasLoaded: true }); }; - _onEmsTileServiceChange = e => { + _onChange = e => { const value = e.target.value; const isAutoSelect = value === AUTO_SELECT; - this.props.onSourceConfigChange({ + this.props.onTileSelect({ id: isAutoSelect ? null : value, isAutoSelect, }); }; - componentWillUnmount() { - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - this._loadTmsOptions(); - } - render() { - if (!this.state.emsTmsOptionsRaw) { - // TODO display loading message - return null; - } + const helpText = + this.state.hasLoaded && this.state.emsTmsOptions.length === 0 + ? getEmsUnavailableMessage() + : null; - const emsTileOptions = this.state.emsTmsOptionsRaw.map(service => ({ - value: service.id, - text: service.name || service.id, - })); + let selectedId; + if (this.props.config) { + selectedId = this.props.config.isAutoSelect ? AUTO_SELECT : this.props.config.id; + } return ( ); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js new file mode 100644 index 0000000000000..4d567b8dbb32a --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment } from 'react'; +import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { TileServiceSelect } from './tile_service_select'; + +export function UpdateSourceEditor({ onChange, config }) { + const _onTileSelect = ({ id, isAutoSelect }) => { + onChange({ propName: 'id', value: id }); + onChange({ propName: 'isAutoSelect', value: isAutoSelect }); + }; + + return ( + + + +
+ +
+
+ + + + +
+ + +
+ ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js index c1b590f56ae52..b09ccdc3af8ba 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js @@ -31,32 +31,58 @@ export class VectorTileLayer extends TileLayer { return tileLayerDescriptor; } + _canSkipSync({ prevDataRequest, nextMeta }) { + if (!prevDataRequest) { + return false; + } + const prevMeta = prevDataRequest.getMeta(); + if (!prevMeta) { + return false; + } + + return prevMeta.tileLayerId === nextMeta.tileLayerId; + } + async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { return; } - const sourceDataRequest = this.getSourceDataRequest(); - if (sourceDataRequest) { - //data is immmutable + + const nextMeta = { tileLayerId: this._source.getTileLayerId() }; + const canSkipSync = this._canSkipSync({ + prevDataRequest: this.getSourceDataRequest(), + nextMeta, + }); + if (canSkipSync) { return; } + const requestToken = Symbol(`layer-source-refresh:${this.getId()} - source`); - startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, dataFilters); try { + startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, dataFilters); const styleAndSprites = await this._source.getVectorStyleSheetAndSpriteMeta(isRetina()); const spriteSheetImageData = await loadSpriteSheetImageData(styleAndSprites.spriteMeta.png); const data = { ...styleAndSprites, spriteSheetImageData, }; - stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, data, {}); + stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, data, nextMeta); } catch (error) { onLoadError(SOURCE_DATA_ID_ORIGIN, requestToken, error.message); } } _generateMbId(name) { - return this.getId() + '_' + name; + return `${this.getId()}_${name}`; + } + + _generateMbSourceIdPrefix() { + const DELIMITTER = '___'; + return `${this.getId()}${DELIMITTER}${this._source.getTileLayerId()}${DELIMITTER}`; + } + + _generateMbSourceId(name) { + return `${this._generateMbSourceIdPrefix()}${name}`; } _getVectorStyle() { @@ -103,19 +129,15 @@ export class VectorTileLayer extends TileLayer { return []; } const sourceIds = Object.keys(vectorStyle.sources); - return sourceIds.map(sourceId => this._generateMbId(sourceId)); + return sourceIds.map(sourceId => this._generateMbSourceId(sourceId)); } ownsMbLayerId(mbLayerId) { - //todo optimize: do not create temp array - const mbLayerIds = this.getMbLayerIds(); - return mbLayerIds.indexOf(mbLayerId) >= 0; + return mbLayerId.startsWith(this.getId()); } ownsMbSourceId(mbSourceId) { - //todo optimize: do not create temp array - const mbSourceIds = this.getMbSourceIds(); - return mbSourceIds.indexOf(mbSourceId) >= 0; + return mbSourceId.startsWith(this.getId()); } _makeNamespacedImageId(imageId) { @@ -123,19 +145,43 @@ export class VectorTileLayer extends TileLayer { return prefix + imageId; } + _requiresPrevSourceCleanup(mbMap) { + const sourceIdPrefix = this._generateMbSourceIdPrefix(); + const mbStyle = mbMap.getStyle(); + return Object.keys(mbStyle.sources).some(mbSourceId => { + const doesMbSourceBelongToLayer = this.ownsMbSourceId(mbSourceId); + const doesMbSourceBelongToSource = mbSourceId.startsWith(sourceIdPrefix); + return doesMbSourceBelongToLayer && !doesMbSourceBelongToSource; + }); + } + syncLayerWithMB(mbMap) { const vectorStyle = this._getVectorStyle(); if (!vectorStyle) { return; } + if (this._requiresPrevSourceCleanup(mbMap)) { + const mbStyle = mbMap.getStyle(); + mbStyle.layers.forEach(mbLayer => { + if (this.ownsMbLayerId(mbLayer.id)) { + mbMap.removeLayer(mbLayer.id); + } + }); + Object.keys(mbStyle.sources).some(mbSourceId => { + if (this.ownsMbSourceId(mbSourceId)) { + mbMap.removeSource(mbSourceId); + } + }); + } + let initialBootstrapCompleted = false; const sourceIds = Object.keys(vectorStyle.sources); sourceIds.forEach(sourceId => { if (initialBootstrapCompleted) { return; } - const mbSourceId = this._generateMbId(sourceId); + const mbSourceId = this._generateMbSourceId(sourceId); const mbSource = mbMap.getSource(mbSourceId); if (mbSource) { //if a single source is present, the layer already has bootstrapped with the mbMap @@ -174,7 +220,7 @@ export class VectorTileLayer extends TileLayer { } const newLayerObject = { ...layer, - source: this._generateMbId(layer.source), + source: this._generateMbSourceId(layer.source), id: mbLayerId, }; From f08fc201c8bc70b6e10b45fbc4c7aa3f3f1141b6 Mon Sep 17 00:00:00 2001 From: Chris Davies Date: Mon, 6 Jan 2020 14:55:51 -0500 Subject: [PATCH 08/18] Add support for scripted fields and (#53948) default index pattern Co-authored-by: Elastic Machine --- .../indexpattern_plugin/indexpattern.tsx | 8 ++++-- .../public/indexpattern_plugin/loader.test.ts | 27 ++++++++++++++++--- .../lens/public/indexpattern_plugin/loader.ts | 6 +++-- .../lens/public/indexpattern_plugin/types.ts | 1 + 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index 15f19bb9d97e6..b58a2d8ca52c7 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -115,8 +115,12 @@ export function getIndexPatternDatasource({ const indexPatternDatasource: Datasource = { id: 'indexpattern', - initialize(state?: IndexPatternPersistedState) { - return loadInitialState({ state, savedObjectsClient }); + async initialize(state?: IndexPatternPersistedState) { + return loadInitialState({ + state, + savedObjectsClient, + defaultIndexPatternId: core.uiSettings.get('defaultIndex'), + }); }, getPersistableState({ currentIndexPatternId, layers }: IndexPatternPrivateState) { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts index 2fb678aed5a54..e180ab690d418 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts @@ -114,8 +114,9 @@ const sampleIndexPatterns = { { name: 'source', type: 'string', - aggregatable: true, - searchable: true, + aggregatable: false, + searchable: false, + scripted: true, aggregationRestrictions: { terms: { agg: 'terms', @@ -196,7 +197,7 @@ describe('loader', () => { expect(cache).toMatchObject(sampleIndexPatterns); }); - it('should not allow full text fields', async () => { + it('should allow scripted, but not full text fields', async () => { const cache = await loadIndexPatterns({ cache: {}, patterns: ['a', 'b'], @@ -286,6 +287,26 @@ describe('loader', () => { }); }); + it('should use the default index pattern id, if provided', async () => { + const state = await loadInitialState({ + defaultIndexPatternId: 'b', + savedObjectsClient: mockClient(), + }); + + expect(state).toMatchObject({ + currentIndexPatternId: 'b', + indexPatternRefs: [ + { id: 'a', title: sampleIndexPatterns.a.title }, + { id: 'b', title: sampleIndexPatterns.b.title }, + ], + indexPatterns: { + b: sampleIndexPatterns.b, + }, + layers: {}, + showEmptyFields: false, + }); + }); + it('should initialize from saved state', async () => { const savedState: IndexPatternPersistedState = { currentIndexPatternId: 'b', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts index 661c627f3454f..7f46f50786cf4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts @@ -84,9 +84,11 @@ export async function loadIndexPatterns({ export async function loadInitialState({ state, savedObjectsClient, + defaultIndexPatternId, }: { state?: IndexPatternPersistedState; savedObjectsClient: SavedObjectsClient; + defaultIndexPatternId?: string; }): Promise { const indexPatternRefs = await loadIndexPatternRefs(savedObjectsClient); const requiredPatterns = _.unique( @@ -94,7 +96,7 @@ export async function loadInitialState({ ? Object.values(state.layers) .map(l => l.indexPatternId) .concat(state.currentIndexPatternId) - : [indexPatternRefs[0].id] + : [defaultIndexPatternId || indexPatternRefs[0].id] ); const currentIndexPatternId = requiredPatterns[0]; @@ -280,7 +282,7 @@ function fromSavedObject( type, title: attributes.title, fields: (JSON.parse(attributes.fields) as IndexPatternField[]) - .filter(({ aggregatable }) => !!aggregatable) + .filter(({ aggregatable, scripted }) => !!aggregatable || !!scripted) .concat(documentField), typeMeta: attributes.typeMeta ? (JSON.parse(attributes.typeMeta) as SavedRestrictionsInfo) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts index 9ed5083633314..50478515d19ce 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts @@ -39,6 +39,7 @@ export interface IndexPatternField { type: string; esTypes?: string[]; aggregatable: boolean; + scripted?: boolean; searchable: boolean; aggregationRestrictions?: AggregationRestrictions; } From 368a894bd2c1600755ea4f85464d8ddb0b704d5c Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Mon, 6 Jan 2020 13:05:07 -0700 Subject: [PATCH 09/18] [Maps] Fix regression preventing maps telemetry from populating & remove task manager logic (#52834) * Remove task logic. Remove server refs and revise for np. Migrate a few files to ts * Remove unused reference * Update mappings * Test usage collector register * Update api integration tests to include maps now that telemetry is 'normal' (not using task mgr state) * Update integration test to use stack stats * Update integration test to look for 'maps-telemetry' instead of 'maps' * Update jest test to reflect calls to register * Follow the same pattern as other int tests and test reliable nested attribute * Back out np-related changes for separate PR * timeCaptured hasn't changed but for some reason stopped working. Getting iso string fixes issue * Back out file shuffling for separate PR * Remove mappings updates (handled in separate PR) * Review feedback. Move telemetry type constant to constants file * Consolidate imports * Linting fix Co-authored-by: Elastic Machine --- .../legacy/plugins/maps/common/constants.js | 1 + .../server/maps_telemetry/maps_telemetry.js | 20 ++- .../maps_telemetry/maps_usage_collector.js | 84 ++---------- .../maps_usage_collector.test.js | 66 +++------ .../server/maps_telemetry/telemetry_task.js | 125 ------------------ .../maps_telemetry/telemetry_task.test.js | 68 ---------- .../apis/telemetry/telemetry_local.js | 4 + 7 files changed, 46 insertions(+), 322 deletions(-) delete mode 100644 x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js delete mode 100644 x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.test.js diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js index b97845a458d51..6e7776d43f4d4 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.js @@ -23,6 +23,7 @@ export const EMS_TILES_VECTOR_TILE_PATH = 'ems/tiles/vector/tile'; export const MAP_SAVED_OBJECT_TYPE = 'map'; export const APP_ID = 'maps'; export const APP_ICON = 'gisApp'; +export const TELEMETRY_TYPE = 'maps-telemetry'; export const MAP_APP_PATH = `app/${APP_ID}`; export const GIS_API_PATH = `api/${APP_ID}`; diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js index 6d078ae35ef85..848c964f4b6d4 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js @@ -5,10 +5,16 @@ */ import _ from 'lodash'; -import { EMS_FILE, ES_GEO_FIELD_TYPE, MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; +import { + EMS_FILE, + ES_GEO_FIELD_TYPE, + MAP_SAVED_OBJECT_TYPE, + TELEMETRY_TYPE, +} from '../../common/constants'; -function getSavedObjectsClient(server, callCluster) { +function getSavedObjectsClient(server) { const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; + const callCluster = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser; const internalRepository = getSavedObjectsRepository(callCluster); return new SavedObjectsClient(internalRepository); } @@ -79,7 +85,7 @@ export function buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, // Total count of maps mapsTotalCount: mapsCount, // Time of capture - timeCaptured: new Date(), + timeCaptured: new Date().toISOString(), attributesPerMap: { // Count of data sources per map dataSourcesCount: { @@ -115,16 +121,16 @@ async function getIndexPatternSavedObjects(savedObjectsClient) { return _.get(indexPatternSavedObjects, 'saved_objects', []); } -export async function getMapsTelemetry(server, callCluster) { - const savedObjectsClient = getSavedObjectsClient(server, callCluster); +export async function getMapsTelemetry(server) { + const savedObjectsClient = getSavedObjectsClient(server); const mapSavedObjects = await getMapSavedObjects(savedObjectsClient); const indexPatternSavedObjects = await getIndexPatternSavedObjects(savedObjectsClient); const settings = { showMapVisualizationTypes: server.config().get('xpack.maps.showMapVisualizationTypes'), }; const mapsTelemetry = buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, settings }); - return await savedObjectsClient.create('maps-telemetry', mapsTelemetry, { - id: 'maps-telemetry', + return await savedObjectsClient.create(TELEMETRY_TYPE, mapsTelemetry, { + id: TELEMETRY_TYPE, overwrite: true, }); } diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js index d1011736e77f8..9c575e66f7556 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js @@ -4,85 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; -import { TASK_ID, scheduleTask, registerMapsTelemetryTask } from './telemetry_task'; +import { getMapsTelemetry } from './maps_telemetry'; +import { TELEMETRY_TYPE } from '../../common/constants'; export function initTelemetryCollection(usageCollection, server) { - registerMapsTelemetryTask(server); - scheduleTask(server); - registerMapsUsageCollector(usageCollection, server); -} - -async function isTaskManagerReady(server) { - const result = await fetch(server); - return result !== null; -} - -async function fetch(server) { - let docs; - const taskManager = server.plugins.task_manager; - - if (!taskManager) { - return null; - } - - try { - ({ docs } = await taskManager.fetch({ - query: { - bool: { - filter: { - term: { - _id: `task:${TASK_ID}`, - }, - }, - }, - }, - })); - } catch (err) { - const errMessage = err && err.message ? err.message : err.toString(); - /* - * The usage service WILL to try to fetch from this collector before the task manager has been initialized, because the task manager - * has to wait for all plugins to initialize first. - * It's fine to ignore it as next time around it will be initialized (or it will throw a different type of error) - */ - if (errMessage.indexOf('NotInitialized') >= 0) { - return null; - } else { - throw err; - } + if (!usageCollection) { + return; } - return docs; -} - -export function buildCollectorObj(server) { - let isCollectorReady = false; - async function determineIfTaskManagerIsReady() { - let isReady = false; - try { - isReady = await isTaskManagerReady(server); - } catch (err) {} // eslint-disable-line - - if (isReady) { - isCollectorReady = true; - } else { - setTimeout(determineIfTaskManagerIsReady, 500); - } - } - determineIfTaskManagerIsReady(); - - return { - type: 'maps', - isReady: () => isCollectorReady, - fetch: async () => { - const docs = await fetch(server); - return _.get(docs, '[0].state.stats'); - }, - }; -} + const mapsUsageCollector = usageCollection.makeUsageCollector({ + type: TELEMETRY_TYPE, + isReady: () => true, + fetch: async () => await getMapsTelemetry(server), + }); -export function registerMapsUsageCollector(usageCollection, server) { - const collectorObj = buildCollectorObj(server); - const mapsUsageCollector = usageCollection.makeUsageCollector(collectorObj); usageCollection.registerCollector(mapsUsageCollector); } diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js index 727d60b5088aa..c5a3fca89b560 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js @@ -4,60 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import sinon from 'sinon'; -import { getMockCallWithInternal, getMockKbnServer, getMockTaskFetch } from '../test_utils'; -import { buildCollectorObj } from './maps_usage_collector'; +import { initTelemetryCollection } from './maps_usage_collector'; describe('buildCollectorObj#fetch', () => { - let mockKbnServer; + let makeUsageCollectorStub; + let registerStub; + let usageCollection; beforeEach(() => { - mockKbnServer = getMockKbnServer(); + makeUsageCollectorStub = jest.fn(); + registerStub = jest.fn(); + usageCollection = { + makeUsageCollector: makeUsageCollectorStub, + registerCollector: registerStub, + }; }); - test('can return empty stats', async () => { - const { type, fetch } = buildCollectorObj(mockKbnServer); - expect(type).toBe('maps'); - const fetchResult = await fetch(); - expect(fetchResult).toEqual({}); - }); - - test('provides known stats', async () => { - const mockTaskFetch = getMockTaskFetch([ - { - state: { - runs: 2, - stats: { wombat_sightings: { total: 712, max: 84, min: 7, avg: 63 } }, - }, - }, - ]); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { type, fetch } = buildCollectorObj(mockKbnServer); - expect(type).toBe('maps'); - const fetchResult = await fetch(); - expect(fetchResult).toEqual({ wombat_sightings: { total: 712, max: 84, min: 7, avg: 63 } }); - }); - - describe('Error handling', () => { - test('Silently handles Task Manager NotInitialized', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects( - new Error('NotInitialized taskManager is still waiting for plugins to load') - ); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { fetch } = buildCollectorObj(mockKbnServer); - await expect(fetch()).resolves.toBe(undefined); - }); - // In real life, the CollectorSet calls fetch and handles errors - test('defers the errors', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects(new Error('Sad violin')); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); + test('makes and registers maps usage collector', async () => { + const serverPlaceholder = {}; + initTelemetryCollection(usageCollection, serverPlaceholder); - const { fetch } = buildCollectorObj(mockKbnServer); - await expect(fetch()).rejects.toMatchObject(new Error('Sad violin')); + expect(registerStub).toHaveBeenCalledTimes(1); + expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1); + expect(makeUsageCollectorStub).toHaveBeenCalledWith({ + type: expect.any(String), + isReady: expect.any(Function), + fetch: expect.any(Function), }); }); }); diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js deleted file mode 100644 index db5df358abc39..0000000000000 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getMapsTelemetry } from './maps_telemetry'; - -const TELEMETRY_TASK_TYPE = 'maps_telemetry'; - -export const TASK_ID = `Maps-${TELEMETRY_TASK_TYPE}`; - -export function scheduleTask(server) { - const taskManager = server.plugins.task_manager; - - if (!taskManager) { - server.log(['debug', 'telemetry'], `Task manager is not available`); - return; - } - - const { kbnServer } = server.plugins.xpack_main.status.plugin; - - kbnServer.afterPluginsInit(() => { - // The code block below can't await directly within "afterPluginsInit" - // callback due to circular dependency. The server isn't "ready" until - // this code block finishes. Migrations wait for server to be ready before - // executing. Saved objects repository waits for migrations to finish before - // finishing the request. To avoid this, we'll await within a separate - // function block. - (async () => { - try { - await taskManager.ensureScheduled({ - id: TASK_ID, - taskType: TELEMETRY_TASK_TYPE, - state: { stats: {}, runs: 0 }, - }); - } catch (e) { - server.log(['warning', 'maps'], `Error scheduling telemetry task, received ${e.message}`); - } - })(); - }); -} - -export function registerMapsTelemetryTask(server) { - const taskManager = server.plugins.task_manager; - - if (!taskManager) { - server.log(['debug', 'telemetry'], `Task manager is not available`); - return; - } - - taskManager.registerTaskDefinitions({ - [TELEMETRY_TASK_TYPE]: { - title: 'Maps telemetry fetch task', - type: TELEMETRY_TASK_TYPE, - timeout: '1m', - createTaskRunner: telemetryTaskRunner(server), - }, - }); -} - -export function telemetryTaskRunner(server) { - return ({ taskInstance }) => { - const { state } = taskInstance; - const prevState = state; - - const callCluster = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser; - - let mapsTelemetryTask; - - return { - async run({ taskCanceled = false } = {}) { - try { - mapsTelemetryTask = makeCancelable(getMapsTelemetry(server, callCluster), taskCanceled); - } catch (err) { - server.log(['warning'], `Error loading maps telemetry: ${err}`); - } finally { - return mapsTelemetryTask.promise - .then((mapsTelemetry = {}) => { - return { - state: { - runs: state.runs || 0 + 1, - stats: mapsTelemetry.attributes || prevState.stats || {}, - }, - runAt: getNextMidnight(), - }; - }) - .catch(errMsg => - server.log(['warning'], `Error executing maps telemetry task: ${errMsg}`) - ); - } - }, - async cancel() { - if (mapsTelemetryTask) { - mapsTelemetryTask.cancel(); - } else { - server.log(['warning'], `Can not cancel "mapsTelemetryTask", it has not been defined`); - } - }, - }; - }; -} - -function makeCancelable(promise, isCanceled) { - const logMsg = 'Maps telemetry task has been cancelled'; - const wrappedPromise = new Promise((resolve, reject) => { - promise - .then(val => (isCanceled ? reject(logMsg) : resolve(val))) - .catch(err => (isCanceled ? reject(logMsg) : reject(err.message))); - }); - - return { - promise: wrappedPromise, - cancel() { - isCanceled = true; - }, - }; -} - -function getNextMidnight() { - const nextMidnight = new Date(); - nextMidnight.setHours(0, 0, 0, 0); - nextMidnight.setDate(nextMidnight.getDate() + 1); - return nextMidnight; -} diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.test.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.test.js deleted file mode 100644 index ad23ed1634204..0000000000000 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.test.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getMockKbnServer, getMockTaskInstance } from '../test_utils'; -import { telemetryTaskRunner } from './telemetry_task'; -import * as mapsTelemetry from './maps_telemetry'; -jest.mock('./maps_telemetry'); - -const expectedAttributes = { - expect: 'values', - toBe: 'populated', -}; - -const generateTelemetry = ({ includeAttributes = true } = {}) => { - mapsTelemetry.getMapsTelemetry = async () => ({ // eslint-disable-line - attributes: includeAttributes ? expectedAttributes : {}, - }); -}; - -describe('telemetryTaskRunner', () => { - let mockTaskInstance; - let mockKbnServer; - let taskRunner; - - beforeEach(() => { - mockTaskInstance = getMockTaskInstance(); - mockKbnServer = getMockKbnServer(); - taskRunner = telemetryTaskRunner(mockKbnServer)({ taskInstance: mockTaskInstance }); - }); - - test('returns empty stats as default', async () => { - generateTelemetry({ includeAttributes: false }); - - const runResult = await taskRunner.run(); - - expect(runResult).toMatchObject({ - state: { - runs: 1, - stats: {}, - }, - }); - }); - - // Return stats when run normally - test('returns stats normally', async () => { - generateTelemetry(); - - const runResult = await taskRunner.run(); - - expect(runResult).toMatchObject({ - state: { - runs: 1, - stats: expectedAttributes, - }, - }); - }); - - test('cancels when cancel flag set to "true", returns undefined', async () => { - generateTelemetry(); - - const runResult = await taskRunner.run({ taskCanceled: true }); - - expect(runResult).toBe(undefined); - }); -}); diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js index 0bf24be0fa0b0..ef7124e2864f8 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js @@ -79,6 +79,10 @@ export default function({ getService }) { expect(stats.stack_stats.kibana.plugins.apm.services_per_agent).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.infraops.last_24_hours).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.kql.defaultQueryLanguage).to.be.a('string'); + expect(stats.stack_stats.kibana.plugins['maps-telemetry'].attributes.timeCaptured).to.be.a( + 'string' + ); + expect(stats.stack_stats.kibana.plugins.reporting.enabled).to.be(true); expect(stats.stack_stats.kibana.plugins.rollups.index_patterns).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.spaces.available).to.be(true); From c9c80845d0299a7a69ad9bcda8a91168e1423196 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 6 Jan 2020 13:29:13 -0700 Subject: [PATCH 10/18] Check license to typescript (#52955) Co-authored-by: Elastic Machine --- x-pack/legacy/plugins/reporting/index.ts | 2 +- .../{check_license.js => check_license.ts} | 36 ++++++++++++------- .../plugins/reporting/server/lib/index.ts | 1 - 3 files changed, 24 insertions(+), 15 deletions(-) rename x-pack/legacy/plugins/reporting/server/lib/{check_license.js => check_license.ts} (69%) diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index c0c9e458132f0..faa27bfb2d6ea 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -94,7 +94,7 @@ export const reporting = (kibana: any) => { const { xpack_main: xpackMainPlugin } = server.plugins; mirrorPluginStatus(xpackMainPlugin, this); const checkLicense = checkLicenseFactory(exportTypesRegistry); - xpackMainPlugin.status.once('green', () => { + (xpackMainPlugin as any).status.once('green', () => { // Register a function that is called whenever the xpack info changes, // to re-compute the license check results for this plugin xpackMainPlugin.info.feature(this.id).registerLicenseCheckResultsGenerator(checkLicense); diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.js b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts similarity index 69% rename from x-pack/legacy/plugins/reporting/server/lib/check_license.js rename to x-pack/legacy/plugins/reporting/server/lib/check_license.ts index 5a784f9913352..02e1196f1d00d 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/check_license.js +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts @@ -4,19 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ +import { XPackInfo } from '../../../xpack_main/server/lib/xpack_info'; +import { XPackInfoLicense } from '../../../xpack_main/server/lib/xpack_info_license'; +import { ExportTypesRegistry, ExportTypeDefinition } from '../../types'; + +interface LicenseCheckResult { + showLinks: boolean; + enableLinks: boolean; + message?: string; +} + const messages = { getUnavailable: () => { return 'You cannot use Reporting because license information is not available at this time.'; }, - getExpired: license => { + getExpired: (license: XPackInfoLicense) => { return `You cannot use Reporting because your ${license.getType()} license has expired.`; }, }; -const makeManagementFeature = exportTypes => { +const makeManagementFeature = ( + exportTypes: Array> +) => { return { id: 'management', - checkLicense: license => { + checkLicense: (license: XPackInfoLicense | null) => { if (!license) { return { showLinks: true, @@ -46,10 +58,12 @@ const makeManagementFeature = exportTypes => { }; }; -const makeExportTypeFeature = exportType => { +const makeExportTypeFeature = ( + exportType: ExportTypeDefinition +) => { return { id: exportType.id, - checkLicense: license => { + checkLicense: (license: XPackInfoLicense | null) => { if (!license) { return { showLinks: true, @@ -84,13 +98,9 @@ const makeExportTypeFeature = exportType => { }; }; -export function checkLicenseFactory(exportTypesRegistry) { - return function checkLicense(xpackLicenseInfo) { - const license = - xpackLicenseInfo === null || !xpackLicenseInfo.isAvailable() - ? null - : xpackLicenseInfo.license; - +export function checkLicenseFactory(exportTypesRegistry: ExportTypesRegistry) { + return function checkLicense(xpackInfo: XPackInfo) { + const license = xpackInfo === null || !xpackInfo.isAvailable() ? null : xpackInfo.license; const exportTypes = Array.from(exportTypesRegistry.getAll()); const reportingFeatures = [ ...exportTypes.map(makeExportTypeFeature), @@ -100,6 +110,6 @@ export function checkLicenseFactory(exportTypesRegistry) { return reportingFeatures.reduce((result, feature) => { result[feature.id] = feature.checkLicense(license); return result; - }, {}); + }, {} as Record); }; } diff --git a/x-pack/legacy/plugins/reporting/server/lib/index.ts b/x-pack/legacy/plugins/reporting/server/lib/index.ts index 50d1a276b6b5d..0a2db749cb954 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/index.ts @@ -5,7 +5,6 @@ */ export { getExportTypesRegistry } from './export_types_registry'; -// @ts-ignore untyped module export { checkLicenseFactory } from './check_license'; export { LevelLogger } from './level_logger'; export { cryptoFactory } from './crypto'; From 8d37637c07b2b9498b74780f2d20c62991fa85d8 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Mon, 6 Jan 2020 12:44:42 -0800 Subject: [PATCH 11/18] [DOCS][Reporting] Updates introduction and troubleshooting sections" (#54036) * [Reporting/Docs] Various Documentation Additions - Add ECONNREFUSED to troubleshooting page: - System Requirements for Reporting - Layout and Sizing * [DOCS][Reporting] Updates index and troubleshooting pages Co-authored-by: Tim Sullivan --- .../reporting/images/shareable-container.png | Bin 0 -> 112809 bytes docs/user/reporting/index.asciidoc | 44 ++++++++++++---- .../reporting-troubleshooting.asciidoc | 49 +++++++++++++++--- 3 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 docs/user/reporting/images/shareable-container.png diff --git a/docs/user/reporting/images/shareable-container.png b/docs/user/reporting/images/shareable-container.png new file mode 100644 index 0000000000000000000000000000000000000000..db5a41dcff471ceded5a5fca5415a80dbc3fd08e GIT binary patch literal 112809 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 z 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⋙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

v= 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 literal 0 HcmV?d00001 diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 06af9e6038445..fde88130a26b4 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -6,11 +6,11 @@ -- -You can generate a report that contains a {kib} dashboard, visualization, -saved search, or Canvas workpad. Depending on the object type, you can export the data as +You can generate a report that contains a {kib} dashboard, visualization, +saved search, or Canvas workpad. Depending on the object type, you can export the data as a PDF, PNG, or CSV document, which you can keep for yourself, or share with others. -Reporting is available from the *Share* menu +Reporting is available from the *Share* menu in *Discover*, *Visualize*, *Dashboard*, and *Canvas*. [role="screenshot"] @@ -40,9 +40,9 @@ for an example. [[manually-generate-reports]] == Generate a report manually -. Open the dashboard, visualization, Canvas workpad, or saved search that you want to include in the report. +. Open the dashboard, visualization, Canvas workpad, or saved search that you want to include in the report. -. In the {kib} toolbar, click *Share*. If you are working in Canvas, +. In the {kib} toolbar, click *Share*. If you are working in Canvas, click the share icon image:user/reporting/images/canvas-share-button.png["Canvas Share button"]. . Select the option appropriate for your object. You can export: @@ -55,14 +55,36 @@ click the share icon image:user/reporting/images/canvas-share-button.png["Canvas + A notification appears when the report is complete. +[float] +[[reporting-layout-sizing]] +== Layout and sizing +The layout and size of the PDF or PNG image depends on the {kib} app +with which the Reporting plugin is integrated. For Canvas, the +worksheet dimensions determine the size for Reporting. In other apps, +the dimensions are taken on the fly by looking at +the size of the visualization elements or panels on the page. + +The size dimensions are part of the reporting job parameters. Therefore, to +make the report output larger or smaller, you can change the size of the browser. +This resizes the shareable container before generating the +report, so the desired dimensions are passed in the job parameters. + +In the following {kib} dashboard, the shareable container is highlighted. +The shareable container is captured when you click the +*Generate* or *Copy POST URL* button. It might take some trial and error +before you're satisfied with the layout and dimensions in the resulting +PNG or PDF image. + +[role="screenshot"] +image::user/reporting/images/shareable-container.png["Shareable Container"] + + + [float] [[optimize-pdf]] == Optimize PDF for print—dashboard only -By default, {kib} creates a PDF -using the existing layout and size of the dashboard. To create a -printer-friendly PDF with multiple A4 portrait pages and two visualizations -per page, turn on *Optimize for printing*. +To create a printer-friendly PDF with multiple A4 portrait pages and two visualizations per page, turn on *Optimize for printing*. [role="screenshot"] image::user/reporting/images/preserve-layout-switch.png["Share"] @@ -72,8 +94,8 @@ image::user/reporting/images/preserve-layout-switch.png["Share"] [[manage-report-history]] == View and manage report history -For a list of your reports, go to *Management > Reporting*. -From this view, you can monitor the generation of a report and +For a list of your reports, go to *Management > Reporting*. +From this view, you can monitor the generation of a report and download reports that you previously generated. [float] diff --git a/docs/user/reporting/reporting-troubleshooting.asciidoc b/docs/user/reporting/reporting-troubleshooting.asciidoc index ca7fa6abcc9d9..dc4ffdfebdae9 100644 --- a/docs/user/reporting/reporting-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-troubleshooting.asciidoc @@ -7,12 +7,20 @@ Having trouble? Here are solutions to common problems you might encounter while using Reporting. +* <> +* <> +* <> +* <> +* <> +* <> +* <> + [float] [[reporting-troubleshooting-system-dependencies]] === System dependencies Reporting launches a "headless" web browser called Chromium on the Kibana server. It is a custom build made by Elastic of an open source project, and it is intended to have minimal dependencies on OS libraries. However, the Kibana server OS might still require additional -dependencies for Chromium. +dependencies to run the Chromium executable. Make sure Kibana server OS has the appropriate packages installed for the distribution. @@ -33,19 +41,30 @@ If you are using Ubuntu/Debian systems, install the following packages: * `fonts-liberation` * `libfontconfig1` +If the system is missing dependencies, then Reporting will fail in a non-deterministic way. {kib} runs a self-test at server startup, and +if it encounters errors, logs them in the Console. Unfortunately, the error message does not include +information about why Chromium failed to run. The most common error message is `Error: connect ECONNREFUSED`, which indicates +that {kib} could not connect to the Chromium process. + +To troubleshoot the problem, start the {kib} server with environment variables that tell Chromium to print verbose logs. See the +<> for more information. + [float] -=== Text is rendered incorrectly in generated reports +[[reporting-troubleshooting-text-incorrect]] +=== Text rendered incorrectly in generated reports If a report label is rendered as an empty rectangle, no system fonts are available. Install at least one font package on the system. If the report is missing certain Chinese, Japanese or Korean characters, ensure that a system font with those characters is installed. [float] +[[reporting-troubleshooting-missing-data]] === Missing data in PDF report of data table visualization There is currently a known limitation with the Data Table visualization that only the first page of data rows, which are the only data visible on the screen, are shown in PDF reports. [float] +[[reporting-troubleshooting-file-permissions]] === File permissions Ensure that the `headless_shell` binary located in your Kibana data directory is owned by the user who is running Kibana, that the user has the execute permission, and if applicable, that the filesystem is mounted with the `exec` option. @@ -63,25 +82,25 @@ Whenever possible, a Reporting error message tries to be as self-explanatory as along with the solution. [float] -==== "Max attempts reached" +==== Max attempts reached There are two primary causes of this error: -. You're creating a PDF of a visualization or dashboard that spans a large amount of data and Kibana is hitting the `xpack.reporting.queue.timeout` +* You're creating a PDF of a visualization or dashboard that spans a large amount of data and Kibana is hitting the `xpack.reporting.queue.timeout` -. Kibana is hosted behind a reverse-proxy, and the <> are not configured correctly +* Kibana is hosted behind a reverse-proxy, and the <> are not configured correctly Create a Markdown visualization and then create a PDF report. If this succeeds, increase the `xpack.reporting.queue.timeout` setting. If the PDF report fails with "Max attempts reached," check your <>. [float] [[reporting-troubleshooting-nss-dependency]] -==== "You must install nss for Reporting to work" +==== You must install nss for Reporting to work Reporting using the Chromium browser relies on the Network Security Service libraries (NSS). Install the appropriate nss package for your distribution. [float] [[reporting-troubleshooting-sandbox-dependency]] -==== "Unable to use Chromium sandbox" +==== Unable to use Chromium sandbox Chromium uses sandboxing techniques that are built on top of operating system primitives. The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. @@ -90,6 +109,7 @@ Elastic recommends that you research the feasibility of enabling unprivileged us is if you are running Kibana in Docker because the container runs in a user namespace with the built-in seccomp/bpf filters. [float] +[[reporting-troubleshooting-verbose-logs]] === Verbose logs {kib} server logs have a lot of useful information for troubleshooting and understanding how things work. If you're having any issues at all, the full logs from Reporting will be the first place to look. In `kibana.yml`: @@ -101,10 +121,12 @@ logging.verbose: true For more information about logging, see <>. +[float] +[[reporting-troubleshooting-puppeteer-debug-logs]] === Puppeteer debug logs The Chromium browser that {kib} launches on the server is driven by a NodeJS library for Chromium called Puppeteer. The Puppeteer library has its own command-line method to generate its own debug logs, which can sometimes be helpful, particularly to figure out if a problem is -caused by Kibana or Chromium. See more at https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/README.md#debugging-tips +caused by Kibana or Chromium. See more at https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/README.md#debugging-tips[debugging tips]. Using Puppeteer's debug method when launching Kibana would look like: ``` @@ -114,3 +136,14 @@ The internal DevTools protocol traffic will be logged via the `debug` module und The Puppeteer logs are very verbose and could possibly contain sensitive information. Handle the generated output with care. + +[float] +[[reporting-troubleshooting-system-requirements]] +=== System requirements +In Elastic Cloud, the {kib} instances that most configurations provide by default is for 1GB of RAM for the instance. That is enough for +{kib} Reporting when the visualization or dashboard is relatively simple, such as a single pie chart or a dashboard with +a few visualizations. However, certain visualization types incur more load than others. For example, a TSVB panel has a lot of network +requests to render. + +If the {kib} instance doesn't have enough memory to run the report, the report fails with an error such as `Error: Page crashed!` +In this case, try increasing the memory for the {kib} instance to 2GB. From fa8da7c34911475aa9f5d4abe25876b736e2ca71 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 6 Jan 2020 14:34:27 -0700 Subject: [PATCH 12/18] [Reporting/PDF] Refactor screenshot pipeline for multi-url by default (#48588) * Multi-url pass to screenshotsObservable * Restore "first" operator * max attempt = 1 for testing * cleanup debug * restore more concatMap Co-authored-by: Elastic Machine --- .../execute_job/decrypt_job_headers.test.ts | 2 +- .../common/execute_job/decrypt_job_headers.ts | 12 +- .../get_conditional_headers.test.ts | 22 +-- .../execute_job/get_conditional_headers.ts | 6 +- .../execute_job/get_custom_logo.test.ts | 2 +- .../common/execute_job/get_custom_logo.ts | 12 +- .../common/execute_job/get_full_urls.test.ts | 115 +++++------- .../common/execute_job/get_full_urls.ts | 13 +- .../omit_blacklisted_headers.test.ts | 9 +- .../execute_job/omit_blacklisted_headers.ts | 5 +- .../common/lib/screenshots/index.ts | 169 ++++++------------ .../common/lib/screenshots/scan_page.ts | 30 ++++ .../common/lib/screenshots/types.ts | 4 +- .../png/server/execute_job/index.test.js | 2 +- .../png/server/execute_job/index.ts | 20 +-- .../png/server/lib/generate_png.ts | 12 +- .../printable_pdf/server/execute_job/index.ts | 48 +++-- .../printable_pdf/server/lib/generate_pdf.ts | 23 ++- .../browsers/chromium/driver_factory/index.ts | 91 +++++----- .../reporting/server/browsers/index.ts | 1 + .../server/lib/validate/validate_browser.ts | 16 +- x-pack/test/reporting/configs/chromium_api.js | 1 + 22 files changed, 269 insertions(+), 346 deletions(-) create mode 100644 x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/scan_page.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts index 9ba7cfbcac1d8..1b7ba3c90bab1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts @@ -43,7 +43,7 @@ describe('headers', () => { }; const encryptedHeaders = await encryptHeaders(headers); - const { decryptedHeaders } = await decryptJobHeaders({ + const decryptedHeaders = await decryptJobHeaders({ job: { title: 'cool-job-bro', type: 'csv', diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts index 486181117bbfb..436b2c2dab1ad 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts @@ -17,22 +17,18 @@ export const decryptJobHeaders = async < JobParamsType, JobDocPayloadType extends HasEncryptedHeaders >({ - job, server, + job, logger, }: { - job: JobDocPayloadType; server: ServerFacade; - logger: Logger; -}): Promise<{ job: JobDocPayloadType; - server: ServerFacade; - decryptedHeaders: Record; -}> => { + logger: Logger; +}): Promise> => { const crypto: CryptoFactory = cryptoFactory(server); try { const decryptedHeaders: Record = await crypto.decrypt(job.headers); - return { job, decryptedHeaders, server }; + return decryptedHeaders; } catch (err) { logger.error(err); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts index 66990c1f37df4..070bdb4314af9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts @@ -27,7 +27,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -44,7 +44,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -65,7 +65,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -82,7 +82,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -97,7 +97,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -120,7 +120,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -137,7 +137,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -153,7 +153,7 @@ test('uses basePath from job when creating saved object service', async () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -180,7 +180,7 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -203,7 +203,7 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav describe('config formatting', () => { test(`lowercases server.host`, async () => { mockServer = createMockServer({ settings: { 'server.host': 'COOL-HOSTNAME' } }); - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: {}, server: mockServer, @@ -215,7 +215,7 @@ describe('config formatting', () => { mockServer = createMockServer({ settings: { 'xpack.reporting.kibanaServer.hostname': 'GREAT-HOSTNAME' }, }); - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: { title: 'cool-job-bro', type: 'csv', diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts index 14c092ccfb4a6..975060a8052f0 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts @@ -6,13 +6,13 @@ import { ConditionalHeaders, ServerFacade } from '../../../types'; export const getConditionalHeaders = ({ + server, job, filteredHeaders, - server, }: { + server: ServerFacade; job: JobDocPayloadType; filteredHeaders: Record; - server: ServerFacade; }) => { const config = server.config(); const [hostname, port, basePath, protocol] = [ @@ -32,5 +32,5 @@ export const getConditionalHeaders = ({ }, }; - return { job, conditionalHeaders, server }; + return conditionalHeaders; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts index 4fca05337ea0c..ff2c44026315d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts @@ -19,7 +19,7 @@ test(`gets logo from uiSettings`, async () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayloadPDF, filteredHeaders: permittedHeaders, server: mockServer, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts index 9b64e896dad18..0059276f6df71 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts @@ -9,13 +9,13 @@ import { ConditionalHeaders, ServerFacade } from '../../../types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; // Logo is PDF only export const getCustomLogo = async ({ + server, job, conditionalHeaders, - server, }: { + server: ServerFacade; job: JobDocPayloadPDF; conditionalHeaders: ConditionalHeaders; - server: ServerFacade; }) => { const serverBasePath: string = server.config().get('server.basePath'); @@ -38,12 +38,8 @@ export const getCustomLogo = async ({ }; const savedObjects = server.savedObjects; - const savedObjectsClient = savedObjects.getScopedSavedObjectsClient(fakeRequest); - const uiSettings = server.uiSettingsServiceFactory({ savedObjectsClient }); - - const logo = await uiSettings.get(UI_SETTINGS_CUSTOM_PDF_LOGO); - - return { job, conditionalHeaders, logo, server }; + const logo: string = await uiSettings.get(UI_SETTINGS_CUSTOM_PDF_LOGO); + return { conditionalHeaders, logo }; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts index 60735b4abd446..e25b94e98d020 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts @@ -22,29 +22,26 @@ beforeEach(() => { }); test(`fails if no URL is passed`, async () => { - await expect( + const fn = () => getFullUrls({ job: {}, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`"` ); }); test(`fails if URLs are file-protocols for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - await expect( + const fn = () => getFullUrls({ - job: { - relativeUrl, - forceNow, - }, + job: { relativeUrl, forceNow }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); }); @@ -52,36 +49,26 @@ test(`fails if URLs are absolute for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; - await expect( + const fn = () => getFullUrls({ - job: { - relativeUrl, - forceNow, - }, + job: { relativeUrl, forceNow }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); }); test(`fails if URLs are file-protocols for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - await expect( + const fn = () => getFullUrls({ - job: { - objects: [ - { - relativeUrl, - }, - ], - forceNow, - }, + job: { objects: [{ relativeUrl }], forceNow }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); }); @@ -89,7 +76,7 @@ test(`fails if URLs are absolute for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; - await expect( + const fn = () => getFullUrls({ job: { objects: [ @@ -100,59 +87,48 @@ test(`fails if URLs are absolute for PDF`, async () => { forceNow, }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); }); test(`fails if any URLs are absolute or file's for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const objects = [ - { - relativeUrl: '/app/kibana#/something_aaa', - }, + { relativeUrl: '/app/kibana#/something_aaa' }, { relativeUrl: 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something', }, - { - relativeUrl: 'file://etc/passwd/#/something', - }, + { relativeUrl: 'file://etc/passwd/#/something' }, ]; - await expect( + + const fn = () => getFullUrls({ - job: { - objects, - forceNow, - }, + job: { objects, forceNow }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${objects[1].relativeUrl} ${objects[2].relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something file://etc/passwd/#/something"` ); }); test(`fails if URL does not route to a visualization`, async () => { - await expect( + const fn = () => getFullUrls({ - job: { - relativeUrl: '/app/phoney', - }, + job: { relativeUrl: '/app/phoney' }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: No valid hash in the URL! A hash is expected for the application to route to the intended visualization.]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"No valid hash in the URL! A hash is expected for the application to route to the intended visualization."` ); }); test(`adds forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const { urls } = await getFullUrls({ - job: { - relativeUrl: '/app/kibana#/something', - forceNow, - }, + const urls = await getFullUrls({ + job: { relativeUrl: '/app/kibana#/something', forceNow }, server: mockServer, } as FullUrlsOpts); @@ -164,11 +140,8 @@ test(`adds forceNow to hash's query, if it exists`, async () => { test(`appends forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const { urls } = await getFullUrls({ - job: { - relativeUrl: '/app/kibana#/something?_g=something', - forceNow, - }, + const urls = await getFullUrls({ + job: { relativeUrl: '/app/kibana#/something?_g=something', forceNow }, server: mockServer, } as FullUrlsOpts); @@ -178,10 +151,8 @@ test(`appends forceNow to hash's query, if it exists`, async () => { }); test(`doesn't append forceNow query to url, if it doesn't exists`, async () => { - const { urls } = await getFullUrls({ - job: { - relativeUrl: '/app/kibana#/something', - }, + const urls = await getFullUrls({ + job: { relativeUrl: '/app/kibana#/something' }, server: mockServer, } as FullUrlsOpts); @@ -190,7 +161,7 @@ test(`doesn't append forceNow query to url, if it doesn't exists`, async () => { test(`adds forceNow to each of multiple urls`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const { urls } = await getFullUrls({ + const urls = await getFullUrls({ job: { objects: [ { relativeUrl: '/app/kibana#/something_aaa' }, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts index 59c748aba9662..90049e8e3aea4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts @@ -12,7 +12,7 @@ import { } from 'url'; import { getAbsoluteUrlFactory } from '../../../common/get_absolute_url'; import { validateUrls } from '../../../common/validate_urls'; -import { ServerFacade, ConditionalHeaders } from '../../../types'; +import { ServerFacade } from '../../../types'; import { JobDocPayloadPNG } from '../../png/types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; @@ -23,15 +23,12 @@ function isPdfJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloa return (job as JobDocPayloadPDF).objects !== undefined; } -export async function getFullUrls({ - job, +export function getFullUrls({ server, - ...mergeValues // pass-throughs + job, }: { - job: JobDocPayloadPDF | JobDocPayloadPNG; server: ServerFacade; - conditionalHeaders: ConditionalHeaders; - logo?: string; + job: JobDocPayloadPDF | JobDocPayloadPNG; }) { const config = server.config(); @@ -96,5 +93,5 @@ export async function getFullUrls({ }); }); - return { job, server, urls, ...mergeValues }; + return urls; } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts index 4d4b0c8ade3f6..f446369fec78c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createMockServer } from '../../../test_helpers/create_mock_server'; import { omitBlacklistedHeaders } from './index'; -let mockServer: any; -beforeEach(() => { - mockServer = createMockServer(''); -}); - test(`omits blacklisted headers`, async () => { const permittedHeaders = { foo: 'bar', @@ -27,7 +21,7 @@ test(`omits blacklisted headers`, async () => { 'transfer-encoding': '', }; - const { filteredHeaders } = await omitBlacklistedHeaders({ + const filteredHeaders = await omitBlacklistedHeaders({ job: { title: 'cool-job-bro', type: 'csv', @@ -41,7 +35,6 @@ test(`omits blacklisted headers`, async () => { ...permittedHeaders, ...blacklistedHeaders, }, - server: mockServer, }); expect(filteredHeaders).toEqual(permittedHeaders); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts index 1bd52a0f1b2d2..cbebd6bc21b0e 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts @@ -5,20 +5,17 @@ */ import { omit } from 'lodash'; import { KBN_SCREENSHOT_HEADER_BLACKLIST } from '../../../common/constants'; -import { ServerFacade } from '../../../types'; export const omitBlacklistedHeaders = ({ job, decryptedHeaders, - server, }: { job: JobDocPayloadType; decryptedHeaders: Record; - server: ServerFacade; }) => { const filteredHeaders: Record = omit( decryptedHeaders, KBN_SCREENSHOT_HEADER_BLACKLIST ); - return { job, filteredHeaders, server }; + return filteredHeaders; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts index 152ef32e331b9..b83021d5e38dd 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts @@ -5,21 +5,9 @@ */ import * as Rx from 'rxjs'; -import { first, mergeMap } from 'rxjs/operators'; -import { - ServerFacade, - CaptureConfig, - HeadlessChromiumDriverFactory, - HeadlessChromiumDriver as HeadlessBrowser, -} from '../../../../types'; -import { - ElementsPositionAndAttribute, - ScreenshotResults, - ScreenshotObservableOpts, - TimeRange, -} from './types'; - -import { checkForToastMessage } from './check_for_toast'; +import { first, mergeMap, toArray } from 'rxjs/operators'; +import { ServerFacade, CaptureConfig, HeadlessChromiumDriverFactory } from '../../../../types'; +import { ScreenshotResults, ScreenshotObservableOpts } from './types'; import { injectCustomCss } from './inject_css'; import { openUrl } from './open_url'; import { waitForRenderComplete } from './wait_for_render'; @@ -28,6 +16,7 @@ import { waitForElementsToBeInDOM } from './wait_for_dom_elements'; import { getTimeRange } from './get_time_range'; import { getElementPositionAndAttributes } from './get_element_position_data'; import { getScreenshots } from './get_screenshots'; +import { scanPage } from './scan_page'; import { skipTelemetry } from './skip_telemetry'; export function screenshotsObservableFactory( @@ -39,108 +28,68 @@ export function screenshotsObservableFactory( return function screenshotsObservable({ logger, - url, + urls, conditionalHeaders, layout, browserTimezone, - }: ScreenshotObservableOpts): Rx.Observable { - const create$ = browserDriverFactory.create({ - viewport: layout.getBrowserViewport(), - browserTimezone, - }); + }: ScreenshotObservableOpts): Rx.Observable { + const create$ = browserDriverFactory.createPage( + { viewport: layout.getBrowserViewport(), browserTimezone }, + logger + ); - // @ts-ignore this needs to be refactored to use less random type declaration and instead rely on structures that work with inference TODO - return create$.pipe( - mergeMap(({ driver$, exit$ }) => { - const screenshot$ = driver$.pipe( - mergeMap( - (browser: HeadlessBrowser) => openUrl(browser, url, conditionalHeaders, logger), - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => skipTelemetry(browser, logger), - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => { - logger.debug( - 'waiting for elements or items count attribute; or not found to interrupt' - ); + return Rx.from(urls).pipe( + mergeMap(url => { + return create$.pipe( + mergeMap(({ driver, exit$ }) => { + const screenshot$ = Rx.of(driver).pipe( + mergeMap(() => openUrl(driver, url, conditionalHeaders, logger)), + mergeMap(() => skipTelemetry(driver, logger)), + mergeMap(() => scanPage(driver, layout, logger)), + mergeMap(() => getNumberOfItems(driver, layout, logger)), + mergeMap(async itemsCount => { + const viewport = layout.getViewport(itemsCount); + await Promise.all([ + driver.setViewport(viewport, logger), + waitForElementsToBeInDOM(driver, itemsCount, layout, logger), + ]); + }), + mergeMap(async () => { + // Waiting till _after_ elements have rendered before injecting our CSS + // allows for them to be displayed properly in many cases + await injectCustomCss(driver, layout, logger); - // the dashboard is using the `itemsCountAttribute` attribute to let us - // know how many items to expect since gridster incrementally adds panels - // we have to use this hint to wait for all of them - const renderSuccess = browser.waitForSelector( - `${layout.selectors.renderComplete},[${layout.selectors.itemsCountAttribute}]`, - {}, - logger - ); - const renderError = checkForToastMessage(browser, layout, logger); - return Rx.race(Rx.from(renderSuccess), Rx.from(renderError)); - }, - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => getNumberOfItems(browser, layout, logger), - (browser, itemsCount: number) => ({ browser, itemsCount }) - ), - mergeMap( - async ({ browser, itemsCount }) => { - logger.debug('setting viewport'); - const viewport = layout.getViewport(itemsCount); - return await browser.setViewport(viewport, logger); - }, - ({ browser, itemsCount }) => ({ browser, itemsCount }) - ), - mergeMap( - ({ browser, itemsCount }) => - waitForElementsToBeInDOM(browser, itemsCount, layout, logger), - ({ browser }) => browser - ), - mergeMap( - browser => { - // Waiting till _after_ elements have rendered before injecting our CSS - // allows for them to be displayed properly in many cases - return injectCustomCss(browser, layout, logger); - }, - browser => browser - ), - mergeMap( - async browser => { - if (layout.positionElements) { - // position panel elements for print layout - return await layout.positionElements(browser, logger); - } - }, - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => { - return waitForRenderComplete(captureConfig, browser, layout, logger); - }, - browser => browser - ), - mergeMap( - browser => getTimeRange(browser, layout, logger), - (browser, timeRange: TimeRange | null) => ({ browser, timeRange }) - ), - mergeMap( - ({ browser }) => getElementPositionAndAttributes(browser, layout), - ({ browser, timeRange }, elementsPositionAndAttributes: ElementsPositionAndAttribute[]) => { - return { browser, timeRange, elementsPositionAndAttributes }; - } // prettier-ignore - ), - mergeMap( - ({ browser, elementsPositionAndAttributes }) => { - return getScreenshots({ browser, elementsPositionAndAttributes, logger }); - }, - ({ timeRange }, screenshots) => ({ timeRange, screenshots }) - ) - ); + if (layout.positionElements) { + // position panel elements for print layout + await layout.positionElements(driver, logger); + } - return Rx.race(screenshot$, exit$); + await waitForRenderComplete(captureConfig, driver, layout, logger); + }), + mergeMap(() => getTimeRange(driver, layout, logger)), + mergeMap( + async (timeRange): Promise => { + const elementsPositionAndAttributes = await getElementPositionAndAttributes( + driver, + layout + ); + const screenshots = await getScreenshots({ + browser: driver, + elementsPositionAndAttributes, + logger, + }); + + return { timeRange, screenshots }; + } + ) + ); + + return Rx.race(screenshot$, exit$); + }) + ); }), - first() + first(), + toArray() ); }; } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/scan_page.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/scan_page.ts new file mode 100644 index 0000000000000..81ff01bb204b8 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/scan_page.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as Rx from 'rxjs'; +import { HeadlessChromiumDriver } from '../../../../server/browsers/chromium/driver'; +import { LevelLogger } from '../../../../server/lib'; +import { LayoutInstance } from '../../layouts/layout'; +import { checkForToastMessage } from './check_for_toast'; + +export function scanPage( + browser: HeadlessChromiumDriver, + layout: LayoutInstance, + logger: LevelLogger +) { + logger.debug('waiting for elements or items count attribute; or not found to interrupt'); + + // the dashboard is using the `itemsCountAttribute` attribute to let us + // know how many items to expect since gridster incrementally adds panels + // we have to use this hint to wait for all of them + const renderSuccess = browser.waitForSelector( + `${layout.selectors.renderComplete},[${layout.selectors.itemsCountAttribute}]`, + {}, + logger + ); + const renderError = checkForToastMessage(browser, layout, logger); + return Rx.race(Rx.from(renderSuccess), Rx.from(renderError)); +} diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts index 4f461ef4ec5f9..78cd42f0cae2f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts @@ -10,7 +10,7 @@ import { LayoutInstance } from '../../layouts/layout'; export interface ScreenshotObservableOpts { logger: LevelLogger; - url: string; + urls: string[]; conditionalHeaders: ConditionalHeaders; layout: LayoutInstance; browserTimezone: string; @@ -36,6 +36,6 @@ export interface Screenshot { } export interface ScreenshotResults { - timeRange: TimeRange; + timeRange: TimeRange | null; screenshots: Screenshot[]; } diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js index 0888d287af07c..1be65722fa668 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js @@ -27,7 +27,7 @@ beforeEach(() => { 'server.port': 5601, }; mockServer = { - expose: () => {}, + expose: () => {}, // NOTE: this is for oncePerServer config: memoize(() => ({ get: jest.fn() })), info: { protocol: 'http', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index b289ae45dde67..c2fda05fbe3e9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; -import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; +import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PLUGIN_ID, PNG_JOB_TYPE } from '../../../../common/constants'; import { ServerFacade, @@ -32,18 +32,14 @@ export const executeJobFactory: QueuedPngExecutorFactory = function executeJobFa const generatePngObservable = generatePngObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PNG_JOB_TYPE, 'execute']); - return function executeJob( - jobId: string, - jobToExecute: JobDocPayloadPNG, - cancellationToken: any - ) { + return function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) { const jobLogger = logger.clone([jobId]); - const process$ = Rx.of({ job: jobToExecute, server, logger }).pipe( - mergeMap(decryptJobHeaders), - map(omitBlacklistedHeaders), - map(getConditionalHeaders), - mergeMap(getFullUrls), - mergeMap(({ job, conditionalHeaders, urls }) => { + const process$ = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders({ server, job, logger })), + map(decryptedHeaders => omitBlacklistedHeaders({ job, decryptedHeaders })), + map(filteredHeaders => getConditionalHeaders({ server, job, filteredHeaders })), + mergeMap(conditionalHeaders => { + const urls = getFullUrls({ server, job }); const hashUrl = urls[0]; return generatePngObservable( jobLogger, diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts index e2b1474515786..600762c451a79 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts @@ -32,19 +32,17 @@ export function generatePngObservableFactory( const layout = new PreserveLayout(layoutParams.dimensions); const screenshots$ = screenshotsObservable({ logger, - url, + urls: [url], conditionalHeaders, layout, browserTimezone, }).pipe( - map(urlScreenshots => { - if (urlScreenshots.screenshots.length !== 1) { - throw new Error( - `Expected there to be 1 screenshot, but there are ${urlScreenshots.screenshots.length}` - ); + map(([{ screenshots }]) => { + if (screenshots.length !== 1) { + throw new Error(`Expected there to be 1 screenshot, but there are ${screenshots.length}`); } - return urlScreenshots.screenshots[0].base64EncodedData; + return screenshots[0].base64EncodedData; }) ); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index e2b3183464cf2..d85207e671212 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; -import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; +import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { ServerFacade, ExecuteJobFactory, @@ -33,33 +33,28 @@ export const executeJobFactory: QueuedPdfExecutorFactory = function executeJobFa const generatePdfObservable = generatePdfObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'execute']); - return function executeJob( - jobId: string, - jobToExecute: JobDocPayloadPDF, - cancellationToken: any - ) { + return function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) { const jobLogger = logger.clone([jobId]); - const process$ = Rx.of({ job: jobToExecute, server, logger }).pipe( - mergeMap(decryptJobHeaders), - map(omitBlacklistedHeaders), - map(getConditionalHeaders), - mergeMap(getCustomLogo), - mergeMap(getFullUrls), - mergeMap( - ({ job, conditionalHeaders, logo, urls }): Rx.Observable => { - const { browserTimezone, layout } = jobToExecute; - return generatePdfObservable( - jobLogger, - job.title, - urls, - browserTimezone, - conditionalHeaders, - layout, - logo - ); - } - ), + const process$ = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders({ server, job, logger })), + map(decryptedHeaders => omitBlacklistedHeaders({ job, decryptedHeaders })), + map(filteredHeaders => getConditionalHeaders({ server, job, filteredHeaders })), + mergeMap(conditionalHeaders => getCustomLogo({ server, job, conditionalHeaders })), + mergeMap(({ logo, conditionalHeaders }) => { + const urls = getFullUrls({ server, job }); + + const { browserTimezone, layout, title } = job; + return generatePdfObservable( + jobLogger, + title, + urls, + browserTimezone, + conditionalHeaders, + layout, + logo + ); + }), map((buffer: Buffer) => ({ content_type: 'application/pdf', content: buffer.toString('base64'), @@ -72,7 +67,6 @@ export const executeJobFactory: QueuedPdfExecutorFactory = function executeJobFa ); const stop$ = Rx.fromEventPattern(cancellationToken.on); - return process$.pipe(takeUntil(stop$)).toPromise(); }; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts index 898a13a2dfe80..9a8db308bea79 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; -import { toArray, mergeMap } from 'rxjs/operators'; +import { mergeMap } from 'rxjs/operators'; import { groupBy } from 'lodash'; import { LevelLogger } from '../../../../server/lib'; import { ServerFacade, HeadlessChromiumDriverFactory, ConditionalHeaders } from '../../../../types'; @@ -31,7 +31,6 @@ export function generatePdfObservableFactory( browserDriverFactory: HeadlessChromiumDriverFactory ) { const screenshotsObservable = screenshotsObservableFactory(server, browserDriverFactory); - const captureConcurrency = 1; return function generatePdfObservable( logger: LevelLogger, @@ -41,15 +40,16 @@ export function generatePdfObservableFactory( conditionalHeaders: ConditionalHeaders, layoutParams: LayoutParams, logo?: string - ) { + ): Rx.Observable { const layout = createLayout(server, layoutParams) as LayoutInstance; - const screenshots$ = Rx.from(urls).pipe( - mergeMap( - url => screenshotsObservable({ logger, url, conditionalHeaders, layout, browserTimezone }), - captureConcurrency - ), - toArray(), - mergeMap(async (urlScreenshots: ScreenshotResults[]) => { + const screenshots$ = screenshotsObservable({ + logger, + urls, + conditionalHeaders, + layout, + browserTimezone, + }).pipe( + mergeMap(async urlScreenshots => { const pdfOutput = pdf.create(layout, logo); if (title) { @@ -68,8 +68,7 @@ export function generatePdfObservableFactory( }); pdfOutput.generate(); - const buffer = await pdfOutput.getBuffer(); - return buffer; + return await pdfOutput.getBuffer(); }) ); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index daa7df343f8aa..6fa46b893de8c 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -31,10 +31,11 @@ type queueTimeout = number; export class HeadlessChromiumDriverFactory { private binaryPath: binaryPath; - private logger: Logger; private browserConfig: BrowserConfig; private queueTimeout: queueTimeout; private networkPolicy: NetworkPolicy; + private userDataDir: string; + private getChromiumArgs: (viewport: BrowserConfig['viewport']) => string[]; constructor( binaryPath: binaryPath, @@ -46,23 +47,30 @@ export class HeadlessChromiumDriverFactory { this.binaryPath = binaryPath; this.browserConfig = browserConfig; this.queueTimeout = queueTimeout; - this.logger = logger; this.networkPolicy = networkPolicy; + + this.userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); + this.getChromiumArgs = (viewport: BrowserConfig['viewport']) => + args({ + userDataDir: this.userDataDir, + viewport, + disableSandbox: this.browserConfig.disableSandbox, + proxy: this.browserConfig.proxy, + }); } type = 'chromium'; - test({ viewport }: { viewport: BrowserConfig['viewport'] }, logger: Logger) { - const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); + test(logger: Logger) { const chromiumArgs = args({ - userDataDir, - viewport, + userDataDir: this.userDataDir, + viewport: { width: 800, height: 600 }, disableSandbox: this.browserConfig.disableSandbox, proxy: this.browserConfig.proxy, }); return puppeteerLaunch({ - userDataDir, + userDataDir: this.userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, args: chromiumArgs, @@ -76,33 +84,25 @@ export class HeadlessChromiumDriverFactory { }); } - create({ - viewport, - browserTimezone, - }: { - viewport: BrowserConfig['viewport']; - browserTimezone: string; - }): Rx.Observable<{ - driver$: Rx.Observable; - exit$: Rx.Observable; - }> { + /* + * Return an observable to objects which will drive screenshot capture for a page + */ + createPage( + { viewport, browserTimezone }: { viewport: BrowserConfig['viewport']; browserTimezone: string }, + pLogger: Logger + ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> { return Rx.Observable.create(async (observer: InnerSubscriber) => { - this.logger.debug(`Creating browser driver factory`); + const logger = pLogger.clone(['browser-driver']); + logger.info(`Creating browser page driver`); - const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); - const chromiumArgs = args({ - userDataDir, - viewport, - disableSandbox: this.browserConfig.disableSandbox, - proxy: this.browserConfig.proxy, - }); + const chromiumArgs = this.getChromiumArgs(viewport); let browser: Browser; let page: Page; try { browser = await puppeteerLaunch({ pipe: !this.browserConfig.inspect, - userDataDir, + userDataDir: this.userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, args: chromiumArgs, @@ -119,7 +119,7 @@ export class HeadlessChromiumDriverFactory { // "TimeoutError: waiting for selector ".application" failed: timeout 30000ms exceeded" page.setDefaultTimeout(this.queueTimeout); - this.logger.debug(`Browser driver factory created`); + logger.debug(`Browser page driver created`); } catch (err) { observer.error(new Error(`Error spawning Chromium browser: [${err}]`)); throw err; @@ -130,12 +130,12 @@ export class HeadlessChromiumDriverFactory { await browser.close(); }, }; - const { terminate$ } = safeChildProcess(this.logger, childProcess); + const { terminate$ } = safeChildProcess(logger, childProcess); // this is adding unsubscribe logic to our observer // so that if our observer unsubscribes, we terminate our child-process observer.add(() => { - this.logger.debug(`The browser process observer has unsubscribed. Closing the browser...`); + logger.debug(`The browser process observer has unsubscribed. Closing the browser...`); childProcess.kill(); // ignore async }); @@ -144,7 +144,7 @@ export class HeadlessChromiumDriverFactory { terminate$ .pipe( tap(signal => { - this.logger.debug(`Termination signal received: ${signal}`); + logger.debug(`Termination signal received: ${signal}`); }), ignoreElements() ) @@ -152,33 +152,40 @@ export class HeadlessChromiumDriverFactory { ); // taps the browser log streams and combine them to Kibana logs - this.getBrowserLogger(page).subscribe(); - this.getProcessLogger(browser).subscribe(); + this.getBrowserLogger(page, logger).subscribe(); + this.getProcessLogger(browser, logger).subscribe(); + + // HeadlessChromiumDriver: object to "drive" a browser page + const driver = new HeadlessChromiumDriver(page, { + inspect: this.browserConfig.inspect, + networkPolicy: this.networkPolicy, + }); - const driver$ = Rx.of(new HeadlessChromiumDriver(page, { inspect: this.browserConfig.inspect, networkPolicy: this.networkPolicy })); // prettier-ignore + // Rx.Observable: stream to interrupt page capture const exit$ = this.getPageExit(browser, page); - observer.next({ driver$, exit$ }); + observer.next({ driver, exit$ }); // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium observer.add(() => { - this.logger.debug(`deleting chromium user data directory at [${userDataDir}]`); + const userDataDir = this.userDataDir; + logger.debug(`deleting chromium user data directory at [${userDataDir}]`); // the unsubscribe function isn't `async` so we're going to make our best effort at // deleting the userDataDir and if it fails log an error. del(userDataDir).catch(error => { - this.logger.error(`error deleting user data directory at [${userDataDir}]: [${error}]`); + logger.error(`error deleting user data directory at [${userDataDir}]: [${error}]`); }); }); }); } - getBrowserLogger(page: Page): Rx.Observable { + getBrowserLogger(page: Page, logger: Logger): Rx.Observable { const consoleMessages$ = Rx.fromEvent(page, 'console').pipe( map(line => { if (line.type() === 'error') { - this.logger.error(line.text(), ['headless-browser-console']); + logger.error(line.text(), ['headless-browser-console']); } else { - this.logger.debug(line.text(), [`headless-browser-console:${line.type()}`]); + logger.debug(line.text(), [`headless-browser-console:${line.type()}`]); } }) ); @@ -187,7 +194,7 @@ export class HeadlessChromiumDriverFactory { map(req => { const failure = req.failure && req.failure(); if (failure) { - this.logger.warning( + logger.warning( `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.` ); } @@ -197,7 +204,7 @@ export class HeadlessChromiumDriverFactory { return Rx.merge(consoleMessages$, pageRequestFailed$); } - getProcessLogger(browser: Browser) { + getProcessLogger(browser: Browser, logger: Logger): Rx.Observable { const childProcess = browser.process(); // NOTE: The browser driver can not observe stdout and stderr of the child process // Puppeteer doesn't give a handle to the original ChildProcess object @@ -206,7 +213,7 @@ export class HeadlessChromiumDriverFactory { // just log closing of the process const processClose$ = Rx.fromEvent(childProcess, 'close').pipe( tap(() => { - this.logger.debug('child process closed', ['headless-browser-process']); + logger.debug('child process closed', ['headless-browser-process']); }) ); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/index.ts index a5ecc405bf9c5..402fabea56c84 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/index.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import * as chromiumDefinition from './chromium'; export { ensureAllBrowsersDownloaded } from './download'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts index 031709c85284c..89c49123e85bf 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts @@ -18,14 +18,12 @@ export const validateBrowser = async ( logger: Logger ) => { if (browserFactory.type === BROWSER_TYPE) { - return browserFactory - .test({ viewport: { width: 800, height: 600 } }, logger) - .then((browser: Browser | null) => { - if (browser && browser.close) { - browser.close(); - } else { - throw new Error('Could not close browser client handle!'); - } - }); + return browserFactory.test(logger).then((browser: Browser | null) => { + if (browser && browser.close) { + browser.close(); + } else { + throw new Error('Could not close browser client handle!'); + } + }); } }; diff --git a/x-pack/test/reporting/configs/chromium_api.js b/x-pack/test/reporting/configs/chromium_api.js index f016738c0e052..95649dfb5d7a3 100644 --- a/x-pack/test/reporting/configs/chromium_api.js +++ b/x-pack/test/reporting/configs/chromium_api.js @@ -28,6 +28,7 @@ export default async function({ readConfigFile }) { '["info","warning","error","fatal","optimize","reporting"]', '--xpack.endpoint.enabled=true', '--xpack.reporting.csv.enablePanelActionDownload=true', + '--xpack.reporting.capture.maxAttempts=1', '--xpack.security.session.idleTimeout=3600000', '--xpack.spaces.enabled=false', ], From ecab2073ae8a730d9913d4b66c0e3f5181b1d92c Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Mon, 6 Jan 2020 23:58:55 +0200 Subject: [PATCH 13/18] =?UTF-8?q?[Telemetry]=20fix=20bug=20where=20uiStats?= =?UTF-8?q?Metrics=20is=20not=20getting=20report=E2=80=A6=20(#54045)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix bug where uiStatsMetrics is not getting reported * fix tests --- packages/kbn-analytics/src/report.ts | 24 ++++++++--------- .../apis/ui_metric/ui_metric.js | 26 +++++++------------ 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/packages/kbn-analytics/src/report.ts b/packages/kbn-analytics/src/report.ts index 1c0b37966355f..16c0a3069e5fd 100644 --- a/packages/kbn-analytics/src/report.ts +++ b/packages/kbn-analytics/src/report.ts @@ -78,6 +78,7 @@ export class ReportManager { } assignReports(newMetrics: Metric | Metric[]) { wrapArray(newMetrics).forEach(newMetric => this.assignReport(this.report, newMetric)); + return { report: this.report }; } static createMetricKey(metric: Metric): string { switch (metric.type) { @@ -101,7 +102,7 @@ export class ReportManager { case METRIC_TYPE.USER_AGENT: { const { appName, type, userAgent } = metric; if (userAgent) { - this.report.userAgent = { + report.userAgent = { [key]: { key, appName, @@ -110,23 +111,22 @@ export class ReportManager { }, }; } + return; } case METRIC_TYPE.CLICK: case METRIC_TYPE.LOADED: case METRIC_TYPE.COUNT: { const { appName, type, eventName, count } = metric; - if (report.uiStatsMetrics) { - const existingStats = (report.uiStatsMetrics[key] || {}).stats; - this.report.uiStatsMetrics = this.report.uiStatsMetrics || {}; - this.report.uiStatsMetrics[key] = { - key, - appName, - eventName, - type, - stats: this.incrementStats(count, existingStats), - }; - } + report.uiStatsMetrics = report.uiStatsMetrics || {}; + const existingStats = (report.uiStatsMetrics[key] || {}).stats; + report.uiStatsMetrics[key] = { + key, + appName, + eventName, + type, + stats: this.incrementStats(count, existingStats), + }; return; } default: diff --git a/test/api_integration/apis/ui_metric/ui_metric.js b/test/api_integration/apis/ui_metric/ui_metric.js index 5b02ba7e72430..5ddbd8649589c 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.js +++ b/test/api_integration/apis/ui_metric/ui_metric.js @@ -25,15 +25,13 @@ export default function({ getService }) { const es = getService('legacyEs'); const createStatsMetric = eventName => ({ - key: ReportManager.createMetricKey({ appName: 'myApp', type: METRIC_TYPE.CLICK, eventName }), eventName, appName: 'myApp', type: METRIC_TYPE.CLICK, - stats: { sum: 1, avg: 1, min: 1, max: 1 }, + count: 1, }); const createUserAgentMetric = appName => ({ - key: ReportManager.createMetricKey({ appName, type: METRIC_TYPE.USER_AGENT }), appName, type: METRIC_TYPE.USER_AGENT, userAgent: @@ -42,12 +40,9 @@ export default function({ getService }) { describe('ui_metric API', () => { it('increments the count field in the document defined by the {app}/{action_type} path', async () => { + const reportManager = new ReportManager(); const uiStatsMetric = createStatsMetric('myEvent'); - const report = { - uiStatsMetrics: { - [uiStatsMetric.key]: uiStatsMetric, - }, - }; + const { report } = reportManager.assignReports([uiStatsMetric]); await supertest .post('/api/ui_metric/report') .set('kbn-xsrf', 'kibana') @@ -61,21 +56,18 @@ export default function({ getService }) { }); it('supports multiple events', async () => { + const reportManager = new ReportManager(); const userAgentMetric = createUserAgentMetric('kibana'); const uiStatsMetric1 = createStatsMetric('myEvent'); const hrTime = process.hrtime(); const nano = hrTime[0] * 1000000000 + hrTime[1]; const uniqueEventName = `myEvent${nano}`; const uiStatsMetric2 = createStatsMetric(uniqueEventName); - const report = { - userAgent: { - [userAgentMetric.key]: userAgentMetric, - }, - uiStatsMetrics: { - [uiStatsMetric1.key]: uiStatsMetric1, - [uiStatsMetric2.key]: uiStatsMetric2, - }, - }; + const { report } = reportManager.assignReports([ + userAgentMetric, + uiStatsMetric1, + uiStatsMetric2, + ]); await supertest .post('/api/ui_metric/report') .set('kbn-xsrf', 'kibana') From 9481cbf36e359d6e60ffb3c1b00c1ad8ac6e8d17 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 6 Jan 2020 18:00:49 -0500 Subject: [PATCH 14/18] [Uptime] Prefer Kibana core http services (#53726) * Prefer Kibana core http service to other fetch functions, refactor helper functions to hooks. * Reintroduce newline deleted in previous commit. * Clean up obsolete import. * Clean up effect code in new hook. * Clean up enum usage in new hook. * Implement PR feedback on new hook. * Fix eslint error. Co-authored-by: Elastic Machine --- .../plugins/uptime/public/apps/plugin.ts | 8 +- .../components/functional/kuery_bar/index.tsx | 15 +- .../plugins/uptime/public/hooks/index.ts | 2 + .../uptime/public/hooks/use_index_pattern.ts | 20 +++ .../uptime/public/hooks/use_telemetry.ts | 41 +++++ .../framework/new_platform_adapter.tsx | 14 +- .../__tests__/get_index_pattern.test.ts | 50 ------- .../index_pattern/get_index_pattern.ts | 26 ---- .../lib/adapters/index_pattern/index.ts | 7 - .../public/lib/adapters/telemetry/index.ts | 8 - .../lib/adapters/telemetry/log_monitor.ts | 18 --- .../lib/adapters/telemetry/log_overview.ts | 18 --- .../plugins/uptime/public/pages/monitor.tsx | 14 +- .../plugins/uptime/public/pages/overview.tsx | 17 +-- .../plugins/uptime/public/uptime_app.tsx | 141 +++++++++--------- 15 files changed, 156 insertions(+), 243 deletions(-) create mode 100644 x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts create mode 100644 x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts delete mode 100644 x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/__tests__/get_index_pattern.test.ts delete mode 100644 x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/get_index_pattern.ts delete mode 100644 x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/index.ts delete mode 100644 x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/index.ts delete mode 100644 x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_monitor.ts delete mode 100644 x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_overview.ts diff --git a/x-pack/legacy/plugins/uptime/public/apps/plugin.ts b/x-pack/legacy/plugins/uptime/public/apps/plugin.ts index bc4e30b79cb15..c09fdf116e790 100644 --- a/x-pack/legacy/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/legacy/plugins/uptime/public/apps/plugin.ts @@ -30,14 +30,8 @@ export class Plugin { } public start(start: StartObject): void { - const { - core, - plugins: { - data: { autocomplete }, - }, - } = start; const libs: UMFrontendLibs = { - framework: getKibanaFrameworkAdapter(core, autocomplete), + framework: getKibanaFrameworkAdapter(start.core, start.plugins), }; // @ts-ignore improper type description this.chrome.setRootTemplate(template); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx index 72e88d2824073..731f560d315d6 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect } from 'react'; import { uniqueId, startsWith } from 'lodash'; import { EuiCallOut } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { Typeahead } from './typeahead'; -import { getIndexPattern } from '../../../lib/adapters/index_pattern'; -import { UptimeSettingsContext } from '../../../contexts'; import { useUrlParams } from '../../../hooks'; import { toStaticIndexPattern } from '../../../lib/helper'; import { @@ -20,6 +18,7 @@ import { esKuery, IIndexPattern, } from '../../../../../../../../src/plugins/data/public'; +import { useIndexPattern } from '../../../hooks'; const Container = styled.div` margin-bottom: 10px; @@ -71,16 +70,18 @@ export function KueryBar({ autocomplete }: Props) { suggestions: [], isLoadingIndexPattern: true, }); - const { basePath } = useContext(UptimeSettingsContext); const [indexPattern, setIndexPattern] = useState(undefined); const [isLoadingIndexPattern, setIsLoadingIndexPattern] = useState(true); const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false); let currentRequestCheck: string; + useIndexPattern((result: any) => setIndexPattern(toStaticIndexPattern(result))); + useEffect(() => { - getIndexPattern(basePath, (result: any) => setIndexPattern(toStaticIndexPattern(result))); - setIsLoadingIndexPattern(false); - }, [basePath]); + if (indexPattern !== undefined) { + setIsLoadingIndexPattern(false); + } + }, [indexPattern]); const [getUrlParams, updateUrlParams] = useUrlParams(); const { search: kuery } = getUrlParams(); diff --git a/x-pack/legacy/plugins/uptime/public/hooks/index.ts b/x-pack/legacy/plugins/uptime/public/hooks/index.ts index 22de59833b08d..aa7bb0a220357 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/index.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/index.ts @@ -5,3 +5,5 @@ */ export { useUrlParams } from './use_url_params'; +export { useIndexPattern } from './use_index_pattern'; +export * from './use_telemetry'; diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts new file mode 100644 index 0000000000000..eb9b475a35716 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, Dispatch } from 'react'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +export const useIndexPattern = (setIndexPattern: Dispatch) => { + const core = useKibana(); + useEffect(() => { + const fetch = core.services.http?.fetch; + async function getIndexPattern() { + if (!fetch) throw new Error('Http core services are not defined'); + setIndexPattern(await fetch('/api/uptime/index_pattern', { method: 'GET' })); + } + getIndexPattern(); + }, [core.services.http, setIndexPattern]); +}; diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts new file mode 100644 index 0000000000000..15f276174e2cf --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; +import { HttpHandler } from 'kibana/public'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +export enum UptimePage { + Overview = '/api/uptime/logOverview', + Monitor = '/api/uptime/logMonitor', + NotFound = '__not-found__', +} + +const getApiPath = (page?: UptimePage) => { + if (!page) throw new Error('Telemetry logging for this page not yet implemented'); + if (page === '__not-found__') + throw new Error('Telemetry logging for 404 page not yet implemented'); + return page.valueOf(); +}; + +const logPageLoad = async (fetch: HttpHandler, page?: UptimePage) => { + try { + await fetch(getApiPath(page), { + method: 'POST', + }); + } catch (e) { + throw e; + } +}; + +export const useUptimeTelemetry = (page?: UptimePage) => { + const kibana = useKibana(); + const fetch = kibana.services.http?.fetch; + useEffect(() => { + if (!fetch) throw new Error('Core http services are not defined'); + logPageLoad(fetch, page); + }, [fetch, page]); +}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx index b7ff3b2aa6264..28179c229013b 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx +++ b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ChromeBreadcrumb, CoreStart } from 'src/core/public'; +import { ChromeBreadcrumb, LegacyCoreStart } from 'src/core/public'; import React from 'react'; import ReactDOM from 'react-dom'; import { get } from 'lodash'; -import { AutocompleteProviderRegister } from 'src/plugins/data/public'; import { i18n as i18nFormatter } from '@kbn/i18n'; +import { PluginsStart } from 'ui/new_platform/new_platform'; import { CreateGraphQLClient } from './framework_adapter_types'; import { UptimeApp, UptimeAppProps } from '../../../uptime_app'; import { getIntegratedAppAvailability } from './capabilities_adapter'; @@ -19,13 +19,12 @@ import { DEFAULT_DARK_MODE, DEFAULT_TIMEPICKER_QUICK_RANGES, } from '../../../../common/constants'; -import { getTelemetryMonitorPageLogger, getTelemetryOverviewPageLogger } from '../telemetry'; import { UMFrameworkAdapter, BootstrapUptimeApp } from '../../lib'; import { createApolloClient } from './apollo_client_adapter'; export const getKibanaFrameworkAdapter = ( - core: CoreStart, - autocomplete: Pick + core: LegacyCoreStart, + plugins: PluginsStart ): UMFrameworkAdapter => { const { application: { capabilities }, @@ -44,10 +43,10 @@ export const getKibanaFrameworkAdapter = ( ); const canSave = get(capabilities, 'uptime.save', false); const props: UptimeAppProps = { - autocomplete, basePath: basePath.get(), canSave, client: createApolloClient(`${basePath.get()}/api/uptime/graphql`, 'true'), + core, darkMode: core.uiSettings.get(DEFAULT_DARK_MODE), commonlyUsedRanges: core.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES), i18n, @@ -55,8 +54,7 @@ export const getKibanaFrameworkAdapter = ( isInfraAvailable: infrastructure, isLogsAvailable: logs, kibanaBreadcrumbs: breadcrumbs, - logMonitorPageLoad: getTelemetryMonitorPageLogger('true', basePath.get()), - logOverviewPageLoad: getTelemetryOverviewPageLogger('true', basePath.get()), + plugins, renderGlobalHelpControls: () => setHelpExtension({ appName: i18nFormatter.translate('xpack.uptime.header.appName', { diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/__tests__/get_index_pattern.test.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/__tests__/get_index_pattern.test.ts deleted file mode 100644 index 6654def2f944b..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/__tests__/get_index_pattern.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios, { AxiosRequestConfig } from 'axios'; -import { getIndexPattern } from '../get_index_pattern'; - -describe('getIndexPattern', () => { - let axiosSpy: jest.SpyInstance, [string, (AxiosRequestConfig | undefined)?]>; - beforeEach(() => { - axiosSpy = jest.spyOn(axios, 'get'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('returns expected data', async () => { - expect.assertions(3); - axiosSpy.mockReturnValue(new Promise(r => r({ data: { foo: 'bar' } }))); - expect(await getIndexPattern()).toEqual({ foo: 'bar' }); - expect(axiosSpy.mock.calls).toHaveLength(1); - expect(axiosSpy.mock.calls[0]).toEqual(['/api/uptime/index_pattern']); - }); - - it('handles the supplied basePath', async () => { - expect.assertions(2); - await getIndexPattern('foo'); - expect(axiosSpy.mock.calls).toHaveLength(1); - expect(axiosSpy.mock.calls[0]).toEqual(['foo/api/uptime/index_pattern']); - }); - - it('supplies the returned data to the given setter function', async () => { - const mockSetter = jest.fn(); - axiosSpy.mockReturnValue(new Promise(r => r({ data: { foo: 'bar' } }))); - await getIndexPattern(undefined, mockSetter); - expect(mockSetter).toHaveBeenCalled(); - expect(mockSetter).toHaveBeenCalledWith({ foo: 'bar' }); - }); - - it('returns undefined when there is an error fetching', async () => { - expect.assertions(1); - axiosSpy.mockReturnValue( - new Promise((resolve, reject) => reject('Request timeout, server could not be reached')) - ); - expect(await getIndexPattern()).toBeUndefined(); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/get_index_pattern.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/get_index_pattern.ts deleted file mode 100644 index fd4161b35f7dd..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/get_index_pattern.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios from 'axios'; -import { getApiPath } from '../../helper'; - -/** - * Fetches and returns the uptime index pattern, optionally provides it to - * a given setter function. - * @param basePath - the base path, if any - * @param setter - a callback for use with non-async functions like `useEffect` - */ -export const getIndexPattern = async (basePath?: string, setter?: (data: unknown) => void) => { - try { - const { data } = await axios.get(getApiPath('/api/uptime/index_pattern', basePath)); - if (setter) { - setter(data); - } - return data; - } catch { - return undefined; - } -}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/index.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/index.ts deleted file mode 100644 index 1c84a7bc3b727..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getIndexPattern } from './get_index_pattern'; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/index.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/index.ts deleted file mode 100644 index 08d8d9a5d4069..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getTelemetryMonitorPageLogger } from './log_monitor'; -export { getTelemetryOverviewPageLogger } from './log_overview'; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_monitor.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_monitor.ts deleted file mode 100644 index 20328497d69a8..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_monitor.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios from 'axios'; -import { getApiPath } from '../../helper'; - -/** - * Generates a function to log a page load of the monitor page for Kibana telemetry. - * @returns a function that can log page loads - */ -export const getTelemetryMonitorPageLogger = (xsrf: string, basePath?: string) => async () => { - await axios.post(getApiPath('/api/uptime/logMonitor', basePath), undefined, { - headers: { 'kbn-xsrf': xsrf }, - }); -}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_overview.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_overview.ts deleted file mode 100644 index fd9fd773a18b9..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_overview.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios from 'axios'; -import { getApiPath } from '../../helper'; - -/** - * Generates a function to log a page load of the overview page for Kibana telemtry. - * @returns a function that can log page loads - */ -export const getTelemetryOverviewPageLogger = (xsrf: string, basePath?: string) => async () => { - await axios.post(getApiPath('/api/uptime/logOverview', basePath), undefined, { - headers: { 'kbn-xsrf': xsrf }, - }); -}; diff --git a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx index 8c5649f680fcb..c8334b2376bc1 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx @@ -12,14 +12,13 @@ import { getMonitorPageBreadcrumb } from '../breadcrumbs'; import { MonitorCharts, MonitorPageTitle, PingList } from '../components/functional'; import { UMUpdateBreadcrumbs } from '../lib/lib'; import { UptimeSettingsContext } from '../contexts'; -import { useUrlParams } from '../hooks'; +import { useUptimeTelemetry, useUrlParams, UptimePage } from '../hooks'; import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useTrackPageview } from '../../../infra/public'; import { getTitle } from '../lib/helper/get_title'; import { MonitorStatusDetails } from '../components/functional/monitor_status_details'; interface MonitorPageProps { - logMonitorPageLoad: () => void; match: { params: { monitorId: string } }; // this is the query function provided by Apollo's Client API query: ( @@ -28,12 +27,7 @@ interface MonitorPageProps { setBreadcrumbs: UMUpdateBreadcrumbs; } -export const MonitorPage = ({ - logMonitorPageLoad, - query, - setBreadcrumbs, - match, -}: MonitorPageProps) => { +export const MonitorPage = ({ query, setBreadcrumbs, match }: MonitorPageProps) => { // decode 64 base string, it was decoded to make it a valid url, since monitor id can be a url const monitorId = atob(match.params.monitorId); const [pingListPageCount, setPingListPageCount] = useState(10); @@ -74,9 +68,7 @@ export const MonitorPage = ({ monitorId, }; - useEffect(() => { - logMonitorPageLoad(); - }, [logMonitorPageLoad]); + useUptimeTelemetry(UptimePage.Monitor); useTrackPageview({ app: 'uptime', path: 'monitor' }); useTrackPageview({ app: 'uptime', path: 'monitor', delay: 15000 }); diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index 8e72f964ed128..34bcfb994cd48 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -19,10 +19,9 @@ import { } from '../components/functional'; import { UMUpdateBreadcrumbs } from '../lib/lib'; import { UptimeSettingsContext } from '../contexts'; -import { useUrlParams } from '../hooks'; +import { useIndexPattern, useUrlParams, useUptimeTelemetry, UptimePage } from '../hooks'; import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useTrackPageview } from '../../../infra/public'; -import { getIndexPattern } from '../lib/adapters/index_pattern'; import { combineFiltersAndUserSearch, stringifyKueries, toStaticIndexPattern } from '../lib/helper'; import { AutocompleteProviderRegister, esKuery } from '../../../../../../src/plugins/data/public'; @@ -34,7 +33,6 @@ interface OverviewPageProps { pathname: string; search: string; }; - logOverviewPageLoad: () => void; setBreadcrumbs: UMUpdateBreadcrumbs; } @@ -54,12 +52,7 @@ const EuiFlexItemStyled = styled(EuiFlexItem)` } `; -export const OverviewPage = ({ - basePath, - autocomplete, - logOverviewPageLoad, - setBreadcrumbs, -}: Props) => { +export const OverviewPage = ({ basePath, autocomplete, setBreadcrumbs }: Props) => { const { colors, setHeadingText } = useContext(UptimeSettingsContext); const [getUrlParams, updateUrl] = useUrlParams(); const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams(); @@ -72,11 +65,11 @@ export const OverviewPage = ({ filters: urlFilters, } = params; const [indexPattern, setIndexPattern] = useState(undefined); + useUptimeTelemetry(UptimePage.Overview); + useIndexPattern(setIndexPattern); useEffect(() => { - getIndexPattern(basePath, setIndexPattern); setBreadcrumbs(getOverviewPageBreadcrumbs()); - logOverviewPageLoad(); if (setHeadingText) { setHeadingText( i18n.translate('xpack.uptime.overviewPage.headerText', { @@ -85,7 +78,7 @@ export const OverviewPage = ({ }) ); } - }, [basePath, logOverviewPageLoad, setBreadcrumbs, setHeadingText]); + }, [basePath, setBreadcrumbs, setHeadingText]); useTrackPageview({ app: 'uptime', path: 'overview' }); useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 }); diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index f72055c52255d..cecbfe375f5fe 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -13,8 +13,9 @@ import React, { useEffect, useState } from 'react'; import { ApolloProvider } from 'react-apollo'; import { Provider as ReduxProvider } from 'react-redux'; import { BrowserRouter as Router, Route, RouteComponentProps, Switch } from 'react-router-dom'; -import { I18nStart, ChromeBreadcrumb } from 'src/core/public'; -import { AutocompleteProviderRegister } from 'src/plugins/data/public'; +import { I18nStart, ChromeBreadcrumb, LegacyCoreStart } from 'src/core/public'; +import { PluginsStart } from 'ui/new_platform/new_platform'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { UMGraphQLClient, UMUpdateBreadcrumbs, UMUpdateBadge } from './lib/lib'; import { MonitorPage, OverviewPage, NotFoundPage } from './pages'; import { UptimeRefreshContext, UptimeSettingsContext, UMSettingsContextValues } from './contexts'; @@ -37,15 +38,14 @@ export interface UptimeAppProps { basePath: string; canSave: boolean; client: UMGraphQLClient; + core: LegacyCoreStart; darkMode: boolean; - autocomplete: Pick; i18n: I18nStart; isApmAvailable: boolean; isInfraAvailable: boolean; isLogsAvailable: boolean; kibanaBreadcrumbs: ChromeBreadcrumb[]; - logMonitorPageLoad: () => void; - logOverviewPageLoad: () => void; + plugins: PluginsStart; routerBasename: string; setBreadcrumbs: UMUpdateBreadcrumbs; setBadge: UMUpdateBadge; @@ -55,18 +55,17 @@ export interface UptimeAppProps { const Application = (props: UptimeAppProps) => { const { - autocomplete, basePath, canSave, client, + core, darkMode, commonlyUsedRanges, i18n: i18nCore, isApmAvailable, isInfraAvailable, isLogsAvailable, - logMonitorPageLoad, - logOverviewPageLoad, + plugins, renderGlobalHelpControls, routerBasename, setBreadcrumbs, @@ -156,70 +155,70 @@ const Application = (props: UptimeAppProps) => { return ( - - { - return ( - - - - -

- - - -

{headingText}

-
-
- - - -
- - - ( - - )} - /> - ( - + + { + return ( + + + + +
+ + + +

{headingText}

+
+
+ + - )} - /> - - -
-
-
-
-
- ); - }} - /> -
+ + + + + ( + + )} + /> + ( + + )} + /> + + +
+ + + + + ); + }} + /> + + ); From fef8485f367785b78426430a08b4b0ddd1eb1ae8 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 6 Jan 2020 16:20:45 -0700 Subject: [PATCH 15/18] [Reporting/Legacy] Remove reporting legacy job params compatibility shim (#52539) * [Reporting/Tests] consolidate functional test configs * remove console.log * trash * Update * add more to the comment * restore historic archive for wip ui functional tests * [Reporting/Legacy] Remove reporting legacy job params compatibility shimmy shim shim * objectType: objectType * fix jest test and get_urls logging * simplify change Co-authored-by: Elastic Machine --- .../common/execute_job/get_full_urls.test.ts | 36 +-- .../common/execute_job/get_full_urls.ts | 6 +- .../server/create_job/compatibility_shim.js | 126 -------- .../create_job/compatibility_shim.test.js | 297 ------------------ .../printable_pdf/server/create_job/index.ts | 32 +- .../server/execute_job/index.test.js | 9 +- .../export_types/printable_pdf/types.d.ts | 6 +- .../public/components/report_listing.tsx | 2 +- .../lib/esqueue/helpers/create_index.js | 2 +- .../reporting/server/lib/esqueue/job.js | 2 +- .../reporting/server/routes/generation.ts | 2 - .../plugins/reporting/server/routes/legacy.ts | 73 ----- x-pack/test/reporting/README.md | 10 - .../reporting/api/bwc_existing_indexes.js | 78 ----- .../test/reporting/api/bwc_generation_urls.js | 54 ---- x-pack/test/reporting/api/chromium_tests.js | 2 - x-pack/test/reporting/api/generation_urls.js | 14 - 17 files changed, 38 insertions(+), 713 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.js delete mode 100644 x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.test.js delete mode 100644 x-pack/legacy/plugins/reporting/server/routes/legacy.ts delete mode 100644 x-pack/test/reporting/api/bwc_existing_indexes.js delete mode 100644 x-pack/test/reporting/api/bwc_generation_urls.js diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts index e25b94e98d020..9b2a065427f70 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts @@ -28,7 +28,7 @@ test(`fails if no URL is passed`, async () => { server: mockServer, } as FullUrlsOpts); expect(fn).toThrowErrorMatchingInlineSnapshot( - `"No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`"` + `"No valid URL fields found in Job Params! Expected \`job.relativeUrl: string\` or \`job.relativeUrls: string[]\`"` ); }); @@ -64,7 +64,10 @@ test(`fails if URLs are file-protocols for PDF`, async () => { const relativeUrl = 'file://etc/passwd/#/something'; const fn = () => getFullUrls({ - job: { objects: [{ relativeUrl }], forceNow }, + job: { + relativeUrls: [relativeUrl], + forceNow, + }, server: mockServer, } as FullUrlsOpts); expect(fn).toThrowErrorMatchingInlineSnapshot( @@ -79,11 +82,7 @@ test(`fails if URLs are absolute for PDF`, async () => { const fn = () => getFullUrls({ job: { - objects: [ - { - relativeUrl, - }, - ], + relativeUrls: [relativeUrl], forceNow, }, server: mockServer, @@ -95,18 +94,15 @@ test(`fails if URLs are absolute for PDF`, async () => { test(`fails if any URLs are absolute or file's for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const objects = [ - { relativeUrl: '/app/kibana#/something_aaa' }, - { - relativeUrl: - 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something', - }, - { relativeUrl: 'file://etc/passwd/#/something' }, + const relativeUrls = [ + '/app/kibana#/something_aaa', + 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something', + 'file://etc/passwd/#/something', ]; const fn = () => getFullUrls({ - job: { objects, forceNow }, + job: { relativeUrls, forceNow }, server: mockServer, } as FullUrlsOpts); expect(fn).toThrowErrorMatchingInlineSnapshot( @@ -163,11 +159,11 @@ test(`adds forceNow to each of multiple urls`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const urls = await getFullUrls({ job: { - objects: [ - { relativeUrl: '/app/kibana#/something_aaa' }, - { relativeUrl: '/app/kibana#/something_bbb' }, - { relativeUrl: '/app/kibana#/something_ccc' }, - { relativeUrl: '/app/kibana#/something_ddd' }, + relativeUrls: [ + '/app/kibana#/something_aaa', + '/app/kibana#/something_bbb', + '/app/kibana#/something_ccc', + '/app/kibana#/something_ddd', ], forceNow, }, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts index 90049e8e3aea4..ca64d8632dbfe 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts @@ -20,7 +20,7 @@ function isPngJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloa return (job as JobDocPayloadPNG).relativeUrl !== undefined; } function isPdfJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPDF { - return (job as JobDocPayloadPDF).objects !== undefined; + return (job as JobDocPayloadPDF).relativeUrls !== undefined; } export function getFullUrls({ @@ -45,10 +45,10 @@ export function getFullUrls({ if (isPngJob(job)) { relativeUrls = [job.relativeUrl]; } else if (isPdfJob(job)) { - relativeUrls = job.objects.map(obj => obj.relativeUrl); + relativeUrls = job.relativeUrls; } else { throw new Error( - `No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`` + `No valid URL fields found in Job Params! Expected \`job.relativeUrl: string\` or \`job.relativeUrls: string[]\`` ); } diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.js deleted file mode 100644 index a3afd070603b0..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uriEncode } from '../lib/uri_encode'; - -/* - * TODO: Kibana 8.0: - * Remove support for parsing Saved Object details from objectType / savedObjectId - * Including support for determining the Report title from objectType / savedObjectId - * - * - `objectType` is optional, but helps differentiate the type of report in the job listing - * - `title` must be explicitly passed - * - `relativeUrls` array OR `relativeUrl` string must be passed - */ - -const getSavedObjectTitle = async (objectType, savedObjectId, savedObjectsClient) => { - const savedObject = await savedObjectsClient.get(objectType, savedObjectId); - return savedObject.attributes.title; -}; - -const getSavedObjectRelativeUrl = (objectType, savedObjectId, queryString) => { - const appPrefixes = { - dashboard: '/dashboard/', - visualization: '/visualize/edit/', - search: '/discover/', - }; - - const appPrefix = appPrefixes[objectType]; - if (!appPrefix) throw new Error('Unexpected app type: ' + objectType); - - const hash = appPrefix + uriEncode.string(savedObjectId, true); - - return `/app/kibana#${hash}?${queryString || ''}`; -}; - -export function compatibilityShimFactory(server, logger) { - return function compatibilityShimFactory(createJobFn) { - return async function( - { - savedObjectId, // deprecating - queryString, // deprecating - browserTimezone, - objectType, - title, - relativeUrls, - layout, - }, - headers, - request - ) { - // input validation and deprecation logging - if (savedObjectId) { - if (typeof savedObjectId !== 'string') { - throw new Error('Invalid savedObjectId (deprecated). String is expected.'); - } - if (relativeUrls) { - throw new Error(`savedObjectId should not be provided if relativeUrls are provided`); - } - } else { - if (!relativeUrls) { - throw new Error(`Either relativeUrls or savedObjectId must be provided`); - } - if (!Array.isArray(relativeUrls)) { - throw new Error('Invalid relativeUrls. String[] is expected.'); - } - relativeUrls.forEach(url => { - if (typeof url !== 'string') { - throw new Error('Invalid Relative URL in relativeUrls. String is expected.'); - } - }); - } - - let kibanaRelativeUrls; - if (relativeUrls) { - kibanaRelativeUrls = relativeUrls; - } else { - kibanaRelativeUrls = [getSavedObjectRelativeUrl(objectType, savedObjectId, queryString)]; - logger.warning( - `The relativeUrls have been derived from saved object parameters. ` + - `This functionality will be removed with the next major version.` - ); - } - - let reportTitle; - try { - if (title) { - reportTitle = title; - } else { - if (objectType && savedObjectId) { - reportTitle = await getSavedObjectTitle( - objectType, - savedObjectId, - request.getSavedObjectsClient() - ); - logger.warning( - `The title has been derived from saved object parameters. This ` + - `functionality will be removed with the next major version.` - ); - } else { - logger.warning( - `A title parameter should be provided with the job generation ` + - `request. Please use Kibana to regenerate your POST URL to have a ` + - `title included in the PDF.` - ); - } - } - } catch (err) { - logger.error(err); // 404 for the savedObjectId, etc - throw err; - } - - const transformedJobParams = { - objectType, - title: reportTitle, - relativeUrls: kibanaRelativeUrls, - browserTimezone, - layout, - }; - - return await createJobFn(transformedJobParams, headers, request); - }; - }; -} diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.test.js deleted file mode 100644 index b3b8bca1d8955..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.test.js +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { compatibilityShimFactory } from './compatibility_shim'; - -const createMockServer = () => { - return { - expose: jest.fn(), //fool once_per_server - log: jest.fn(), - }; -}; - -const createMockLogger = () => ({ - warning: jest.fn(), - error: jest.fn(), -}); - -const createMockRequest = () => { - return { - getSavedObjectsClient: once(function() { - return { - get: jest.fn(), - }; - }), - }; -}; - -test(`passes title through if provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const title = 'test title'; - - const createJobMock = jest.fn(); - await compatibilityShim(createJobMock)( - { title, relativeUrls: ['/something'] }, - null, - createMockRequest() - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].title).toBe(title); -}); - -test(`gets the title from the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - const title = 'savedTitle'; - mockRequest.getSavedObjectsClient().get.mockReturnValue({ - attributes: { - title, - }, - }); - - await compatibilityShim(createJobMock)( - { objectType: 'search', savedObjectId: 'abc' }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(2); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].title).toBe(title); -}); - -test(`passes the objectType and savedObjectId to the savedObjectsClient`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - mockRequest.getSavedObjectsClient().get.mockReturnValue({ - attributes: { - title: '', - }, - }); - - const objectType = 'search'; - const savedObjectId = 'abc'; - await compatibilityShim(createJobMock)({ objectType, savedObjectId }, null, mockRequest); - - expect(mockLogger.warning.mock.calls.length).toBe(2); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.warning.mock.calls[1][0]).toEqual( - 'The title has been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - const getMock = mockRequest.getSavedObjectsClient().get.mock; - expect(getMock.calls.length).toBe(1); - expect(getMock.calls[0][0]).toBe(objectType); - expect(getMock.calls[0][1]).toBe(savedObjectId); -}); - -test(`logs no warnings when title and relativeUrls is passed`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - - await compatibilityShim(createJobMock)( - { title: 'Phenomenal Dashboard', relativeUrls: ['/abc', '/def'] }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); -}); - -test(`logs warning if title can not be provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - await compatibilityShim(createJobMock)({ relativeUrls: ['/abc'] }, null, mockRequest); - - expect(mockLogger.warning.mock.calls.length).toBe(1); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - `A title parameter should be provided with the job generation request. Please ` + - `use Kibana to regenerate your POST URL to have a title included in the PDF.` - ); -}); - -test(`logs deprecations when generating the title/relativeUrl using the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - mockRequest.getSavedObjectsClient().get.mockReturnValue({ - attributes: { - title: '', - }, - }); - - await compatibilityShim(createJobMock)( - { objectType: 'search', savedObjectId: 'abc' }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(2); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.warning.mock.calls[1][0]).toEqual( - 'The title has been derived from saved object parameters. This functionality will be removed with the next major version.' - ); -}); - -test(`passes objectType through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - - const objectType = 'foo'; - await compatibilityShim(createJobMock)( - { title: 'test', relativeUrls: ['/something'], objectType }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].objectType).toBe(objectType); -}); - -test(`passes the relativeUrls through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - - const relativeUrls = ['/app/kibana#something', '/app/kibana#something-else']; - await compatibilityShim(createJobMock)({ title: 'test', relativeUrls }, null, null); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].relativeUrls).toBe(relativeUrls); -}); - -const testSavedObjectRelativeUrl = (objectType, expectedUrl) => { - test(`generates the saved object relativeUrl for ${objectType}`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const createJobMock = jest.fn(); - - await compatibilityShim(createJobMock)( - { title: 'test', objectType, savedObjectId: 'abc' }, - null, - null - ); - - expect(mockLogger.warning.mock.calls.length).toBe(1); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual([expectedUrl]); - }); -}; - -testSavedObjectRelativeUrl('search', '/app/kibana#/discover/abc?'); -testSavedObjectRelativeUrl('visualization', '/app/kibana#/visualize/edit/abc?'); -testSavedObjectRelativeUrl('dashboard', '/app/kibana#/dashboard/abc?'); - -test(`appends the queryString to the relativeUrl when generating from the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const createJobMock = jest.fn(); - - await compatibilityShim(createJobMock)( - { title: 'test', objectType: 'search', savedObjectId: 'abc', queryString: 'foo=bar' }, - null, - null - ); - - expect(mockLogger.warning.mock.calls.length).toBe(1); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual([ - '/app/kibana#/discover/abc?foo=bar', - ]); -}); - -test(`throw an Error if the objectType, savedObjectId and relativeUrls are provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const createJobMock = jest.fn(); - - const promise = compatibilityShim(createJobMock)( - { - title: 'test', - objectType: 'something', - relativeUrls: ['/something'], - savedObjectId: 'abc', - }, - null, - null - ); - - await expect(promise).rejects.toBeDefined(); -}); - -test(`passes headers and request through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - - const headers = {}; - const request = createMockRequest(); - - await compatibilityShim(createJobMock)( - { title: 'test', relativeUrls: ['/something'] }, - headers, - request - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][1]).toBe(headers); - expect(createJobMock.mock.calls[0][2]).toBe(request); -}); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts index d17713057abc1..a8cc71175cffe 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PLUGIN_ID, PDF_JOB_TYPE } from '../../../../common/constants'; import { CreateJobFactory, ESQueueCreateJobFn, @@ -13,29 +12,16 @@ import { ConditionalHeaders, } from '../../../../types'; import { validateUrls } from '../../../../common/validate_urls'; -import { LevelLogger } from '../../../../server/lib'; import { cryptoFactory } from '../../../../server/lib/crypto'; import { JobParamsPDF } from '../../types'; -// @ts-ignore untyped module -import { compatibilityShimFactory } from './compatibility_shim'; - -interface CreateJobFnOpts { - objectType: any; - title: string; - relativeUrls: string[]; - browserTimezone: string; - layout: any; -} export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(server: ServerFacade) { - const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'create']); - const compatibilityShim = compatibilityShimFactory(server, logger); const crypto = cryptoFactory(server); - return compatibilityShim(async function createJobFn( - { objectType, title, relativeUrls, browserTimezone, layout }: CreateJobFnOpts, + return async function createJobFn( + { title, relativeUrls, browserTimezone, layout, objectType }: JobParamsPDF, headers: ConditionalHeaders['headers'], request: RequestFacade ) { @@ -44,14 +30,14 @@ export const createJobFactory: CreateJobFactory ({ relativeUrl: u })), - headers: serializedEncryptedHeaders, - browserTimezone, - layout, basePath: request.getBasePath(), + browserTimezone, forceNow: new Date().toISOString(), + headers: serializedEncryptedHeaders, + layout, + relativeUrls, + title, + objectType, }; - }); + }; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js index db7b599a1aaab..ddbee6e9d54a4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js @@ -27,7 +27,8 @@ beforeEach(() => { 'server.port': 5601, }; mockServer = { - expose: () => {}, + expose: jest.fn(), + log: jest.fn(), config: memoize(() => ({ get: jest.fn() })), info: { protocol: 'http', @@ -71,7 +72,7 @@ test(`passes browserTimezone to generatePdf`, async () => { const browserTimezone = 'UTC'; await executeJob( 'pdfJobId', - { objects: [], browserTimezone, headers: encryptedHeaders }, + { relativeUrls: [], browserTimezone, headers: encryptedHeaders }, cancellationToken ); @@ -96,7 +97,7 @@ test(`returns content_type of application/pdf`, async () => { const { content_type: contentType } = await executeJob( 'pdfJobId', - { objects: [], timeRange: {}, headers: encryptedHeaders }, + { relativeUrls: [], timeRange: {}, headers: encryptedHeaders }, cancellationToken ); expect(contentType).toBe('application/pdf'); @@ -112,7 +113,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pdfJobId', - { objects: [], timeRange: {}, headers: encryptedHeaders }, + { relativeUrls: [], timeRange: {}, headers: encryptedHeaders }, cancellationToken ); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts index b51a2d4d5711c..0a9dcfe986ca6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts @@ -9,7 +9,7 @@ import { JobDocPayload, ServerFacade, RequestFacade } from '../../types'; // Job params: structure of incoming user request data, after being parsed from RISON export interface JobParamsPDF { - objectType: string; + objectType: string; // visualization, dashboard, etc. Used for job info & telemetry title: string; relativeUrls: string[]; browserTimezone: string; @@ -22,7 +22,5 @@ export interface JobDocPayloadPDF extends JobDocPayload { browserTimezone: string; forceNow?: string; layout: LayoutParams; - objects: Array<{ - relativeUrl: string; - }>; + relativeUrls: string[]; } diff --git a/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx b/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx index 9783372aa29c4..320f6220aa996 100644 --- a/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx @@ -425,7 +425,7 @@ class ReportListingUi extends Component { return { id: job._id, type: source.jobtype, - object_type: source.payload.type, + object_type: source.payload.objectType, object_title: source.payload.title, created_by: source.created_by, created_at: source.created_at, diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js index 9e00f0447e99e..670c2907fb832 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js @@ -15,7 +15,7 @@ const schema = { properties: { /** * Type of object that is triggering this report. Should be either search, visualization or dashboard. - * Used for phone home stats only. + * Used for job listing and telemetry stats only. */ objectType: { type: 'text', diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js index cded6d2ce89a8..a7d8f4df3fd54 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js @@ -57,7 +57,7 @@ export class Job extends events.EventEmitter { meta: { // We are copying these values out of payload because these fields are indexed and can be aggregated on // for tracking stats, while payload contents are not. - objectType: payload.type, + objectType: payload.objectType, layout: payload.layout ? payload.layout.id : 'none', }, payload: this.payload, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 7bed7bc5773e4..73450b7641c8e 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -17,7 +17,6 @@ import { import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; -import { registerLegacy } from './legacy'; import { createQueueFactory, enqueueJobFactory } from '../lib'; export function registerJobGenerationRoutes( @@ -73,7 +72,6 @@ export function registerJobGenerationRoutes( } registerGenerateFromJobParams(server, handler, handleError); - registerLegacy(server, handler, handleError); // Register beta panel-action download-related API's if (config.get('xpack.reporting.csv.enablePanelActionDownload')) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/legacy.ts b/x-pack/legacy/plugins/reporting/server/routes/legacy.ts deleted file mode 100644 index 011ac4a02bbf9..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/routes/legacy.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import querystring from 'querystring'; -import { API_BASE_URL } from '../../common/constants'; -import { ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; -import { - getRouteConfigFactoryReportingPre, - GetRouteConfigFactoryFn, -} from './lib/route_config_factories'; -import { HandlerErrorFunction, HandlerFunction } from './types'; - -const getStaticFeatureConfig = (getRouteConfig: GetRouteConfigFactoryFn, featureId: string) => - getRouteConfig(() => featureId); - -const BASE_GENERATE = `${API_BASE_URL}/generate`; - -export function registerLegacy( - server: ServerFacade, - handler: HandlerFunction, - handleError: HandlerErrorFunction -) { - const getRouteConfig = getRouteConfigFactoryReportingPre(server); - - function createLegacyPdfRoute({ path, objectType }: { path: string; objectType: string }) { - const exportTypeId = 'printablePdf'; - server.route({ - path, - method: 'POST', - options: getStaticFeatureConfig(getRouteConfig, exportTypeId), - handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { - const message = `The following URL is deprecated and will stop working in the next major version: ${request.url.path}`; - server.log(['warning', 'reporting', 'deprecation'], message); - - try { - const savedObjectId = request.params.savedId; - const queryString = querystring.stringify(request.query); - - return await handler( - exportTypeId, - { - objectType, - savedObjectId, - queryString, - }, - request, - h - ); - } catch (err) { - throw handleError(exportTypeId, err); - } - }, - }); - } - - createLegacyPdfRoute({ - path: `${BASE_GENERATE}/visualization/{savedId}`, - objectType: 'visualization', - }); - - createLegacyPdfRoute({ - path: `${BASE_GENERATE}/search/{savedId}`, - objectType: 'search', - }); - - createLegacyPdfRoute({ - path: `${BASE_GENERATE}/dashboard/{savedId}`, - objectType: 'dashboard', - }); -} diff --git a/x-pack/test/reporting/README.md b/x-pack/test/reporting/README.md index 30859fa96c015..d4a6c20a835e2 100644 --- a/x-pack/test/reporting/README.md +++ b/x-pack/test/reporting/README.md @@ -82,16 +82,6 @@ node scripts/functional_tests_server.js --config test/reporting/configs/chromium **Note:** Dashboard has some snapshot testing too, in `_dashboard_snapshots.js`. This test watches for a command line flag `--updateBaselines` which automates updating the baselines. Probably worthwhile to do some similar here in the long run. - ### Adding a new BWC test - - We have tests that ensure the latest version of Kibana will continue to generate reports from URLs generated in previous versions, to ensure backward compatibility. These tests are in `api/bwc_generation_urls.js`. It's important to update these every now and then and add new ones, especially if anything in the URL changed in a release. - - To add test coverage for a specific minor release,: -1. Checkout previous branch, e.g. `git checkout upstream/6.4` -2. Sync your environment via `yarn kbn bootstrap` (Note, if you run into problems you may want to first clean via `yarn kbn clean`) -3. Start up kibana and Elasticsearch (`yarn es snapshot --license trial` in one terminal, and `yarn start` in another) -4. Load the reporting test data that is used in the tests. Ensure you are in the `x-pack` directory and run: - ``` node ../scripts/es_archiver.js --es-url http://elastic:changeme@localhost:9200 load ../../../../test/functional/fixtures/es_archiver/dashboard/current/kibana ``` diff --git a/x-pack/test/reporting/api/bwc_existing_indexes.js b/x-pack/test/reporting/api/bwc_existing_indexes.js deleted file mode 100644 index ffcf123848bb2..0000000000000 --- a/x-pack/test/reporting/api/bwc_existing_indexes.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as GenerationUrls from './generation_urls'; - -/** - * This file tests the situation when a reporting index spans releases. By default reporting indexes are created - * on a weekly basis, but this is configurable so it is possible a user has this set to yearly. In that event, it - * is possible report data is getting posted to an index that was created by a very old version. We don't have a - * reporting index migration plan, so this test is important to ensure BWC, or that in the event we decide to make - * a major change in a major release, we handle it properly. - */ - -export default function({ getService }) { - const esArchiver = getService('esArchiver'); - const reportingAPI = getService('reportingAPI'); - const usageAPI = getService('usageAPI'); - - // FLAKY: https://github.com/elastic/kibana/issues/42725 - describe.skip('BWC report generation into existing indexes', () => { - let expectedCompletedReportCount; - let cleanupIndexAlias; - - describe('existing 6_2 index', () => { - before('load data and add index alias', async () => { - await reportingAPI.deleteAllReportingIndexes(); - await esArchiver.load('reporting/bwc/6_2'); - - // The index name in the 6_2 archive. - const ARCHIVED_REPORTING_INDEX = '.reporting-2018.03.11'; - cleanupIndexAlias = await reportingAPI.coerceReportsIntoExistingIndex( - ARCHIVED_REPORTING_INDEX - ); - - const stats = await usageAPI.getUsageStats(); - expectedCompletedReportCount = await reportingAPI.getCompletedReportCount(stats); - - await esArchiver.unload('reporting/bwc/6_2'); - }); - - after('remove index alias', async () => { - await cleanupIndexAlias(); - }); - - // Might not be great test practice to lump all these jobs together but reporting takes awhile and it'll be - // more efficient to post them all up front, then sequentially. - it('multiple jobs posted', async () => { - const reportPaths = []; - reportPaths.push( - await reportingAPI.postJob(GenerationUrls.CSV_DISCOVER_KUERY_AND_FILTER_6_3) - ); - reportPaths.push( - await reportingAPI.postJob(GenerationUrls.PDF_PRESERVE_DASHBOARD_FILTER_6_3) - ); - reportPaths.push( - await reportingAPI.postJob(GenerationUrls.PDF_PRESERVE_PIE_VISUALIZATION_6_3) - ); - reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_6_3)); - reportPaths.push( - await reportingAPI.postJob( - GenerationUrls.PDF_PRINT_PIE_VISUALIZATION_FILTER_AND_SAVED_SEARCH_6_3 - ) - ); - - await reportingAPI.expectAllJobsToFinishSuccessfully(reportPaths); - }).timeout(1540000); - - it('jobs completed successfully', async () => { - const stats = await usageAPI.getUsageStats(); - expectedCompletedReportCount += 5; - reportingAPI.expectCompletedReportCount(stats, expectedCompletedReportCount); - }); - }); - }); -} diff --git a/x-pack/test/reporting/api/bwc_generation_urls.js b/x-pack/test/reporting/api/bwc_generation_urls.js deleted file mode 100644 index 25b40ff3f74a8..0000000000000 --- a/x-pack/test/reporting/api/bwc_generation_urls.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as GenerationUrls from './generation_urls'; - -export default function({ getService }) { - const reportingAPI = getService('reportingAPI'); - const usageAPI = getService('usageAPI'); - - describe('BWC report generation urls', () => { - describe('Pre 6_2', () => { - before(async () => { - await reportingAPI.deleteAllReportingIndexes(); - }); - - // The URL being tested was captured from release 6.4 and then the layout section was removed to test structure before - // preserve_layout was introduced. See https://github.com/elastic/kibana/issues/23414 - it('job posted successfully', async () => { - const path = await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_PRE_6_2); - await reportingAPI.waitForJobToFinish(path); - const stats = await usageAPI.getUsageStats(); - reportingAPI.expectCompletedReportCount(stats, 1); - }).timeout(500000); - }); - - describe('6_2', () => { - before(async () => { - await reportingAPI.deleteAllReportingIndexes(); - }); - - // Might not be great test practice to lump all these jobs together but reporting takes awhile and it'll be - // more efficient to post them all up front, then sequentially. - it('multiple jobs posted', async () => { - const reportPaths = []; - reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_6_2)); - reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRESERVE_VISUALIZATION_6_2)); - reportPaths.push(await reportingAPI.postJob(GenerationUrls.CSV_DISCOVER_FILTER_QUERY_6_2)); - - await reportingAPI.expectAllJobsToFinishSuccessfully(reportPaths); - }).timeout(1540000); - - it('jobs completed successfully', async () => { - const stats = await usageAPI.getUsageStats(); - reportingAPI.expectCompletedReportCount(stats, 3); - }); - }); - - // 6.3 urls currently being tested as part of the "bwc_existing_indexes" test suite. Reports are time consuming, - // don't replicate tests if we don't need to, so no specific 6_3 url tests here. - }); -} diff --git a/x-pack/test/reporting/api/chromium_tests.js b/x-pack/test/reporting/api/chromium_tests.js index 6594a80db491b..2d5a31bb40da3 100644 --- a/x-pack/test/reporting/api/chromium_tests.js +++ b/x-pack/test/reporting/api/chromium_tests.js @@ -27,8 +27,6 @@ export default function({ loadTestFile, getService }) { await esArchiver.unload(OSS_DATA_ARCHIVE_PATH); }); - loadTestFile(require.resolve('./bwc_existing_indexes')); - loadTestFile(require.resolve('./bwc_generation_urls')); loadTestFile(require.resolve('./usage')); }); } diff --git a/x-pack/test/reporting/api/generation_urls.js b/x-pack/test/reporting/api/generation_urls.js index 182d395704a25..b98b1a26651a1 100644 --- a/x-pack/test/reporting/api/generation_urls.js +++ b/x-pack/test/reporting/api/generation_urls.js @@ -4,13 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// These all have the domain name portion stripped out. The api infrastructure assumes it when we post to it anyhow. - -// The URL below was captured from release 6.4 and then the layout section was removed to test structure before -// preserve_layout was introduced. See https://github.com/elastic/kibana/issues/23414 -export const PDF_PRINT_DASHBOARD_PRE_6_2 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(pause:!!t,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; - export const PDF_PRINT_DASHBOARD_6_3 = '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; export const PDF_PRESERVE_DASHBOARD_FILTER_6_3 = @@ -21,10 +14,3 @@ export const PDF_PRINT_PIE_VISUALIZATION_FILTER_AND_SAVED_SEARCH_6_3 = '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:visualization,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2Fbefdb6b0-3e59-11e8-9fc3-39e49624228e%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:animal.keyword,negate:!!f,params:(query:dog,type:phrase),type:phrase,value:dog),query:(match:(animal.keyword:(query:dog,type:phrase))))),linked:!!t,query:(language:lucene,query:!%27!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:name.keyword,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Filter%2BTest:%2Banimals:%2Blinked%2Bto%2Bsearch%2Bwith%2Bfilter!%27,type:pie))%27),title:%27Filter%20Test:%20animals:%20linked%20to%20search%20with%20filter%27)'; export const CSV_DISCOVER_KUERY_AND_FILTER_6_3 = '/api/reporting/generate/csv?jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,agent,bytes,clientip),indexPatternId:%270bf35f60-3dc9-11e8-8660-4d65aa086b3c%27,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,agent,bytes,clientip)),docvalue_fields:!(%27@timestamp%27),query:(bool:(filter:!((bool:(minimum_should_match:1,should:!((match:(clientip:%2773.14.212.83%27)))))),must:!((range:(bytes:(gte:100,lt:1000))),(range:(%27@timestamp%27:(format:epoch_millis,gte:1369165215770,lte:1526931615770)))),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,agent,bytes,clientip),version:!t),index:%27logstash-*%27),title:%27Bytes%20and%20kuery%20in%20saved%20search%20with%20filter%27,type:search)'; - -export const PDF_PRINT_DASHBOARD_6_2 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:dashboard,queryString:%27_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,field:isDog,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:isDog,negate:!!f,params:(value:!!t),type:phrase,value:true),script:(script:(inline:!%27boolean%2Bcompare(Supplier%2Bs,%2Bdef%2Bv)%2B%257Breturn%2Bs.get()%2B%253D%253D%2Bv%3B%257Dcompare(()%2B-%253E%2B%257B%2Breturn%2Bdoc%255B!!!%27animal.keyword!!!%27%255D.value%2B%253D%253D%2B!!!%27dog!!!%27%2B%257D,%2Bparams.value)%3B!%27,lang:painless,params:(value:!!t))))),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((gridData:(h:3,i:!%274!%27,w:6,x:6,y:0),id:edb65990-53ca-11e8-b481-c9426d020fcd,panelIndex:!%274!%27,type:visualization,version:!%276.2.4!%27),(gridData:(h:3,i:!%275!%27,w:6,x:0,y:0),id:!%270644f890-53cb-11e8-b481-c9426d020fcd!%27,panelIndex:!%275!%27,type:visualization,version:!%276.2.4!%27)),query:(language:lucene,query:!%27weightLbs:%253E15!%27),timeRestore:!!t,title:!%27Animal%2BWeights%2B(created%2Bin%2B6.2)!%27,viewMode:view)%27,savedObjectId:%271b2f47b0-53cb-11e8-b481-c9426d020fcd%27)'; -export const PDF_PRESERVE_VISUALIZATION_6_2 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(dimensions:(height:441,width:1002),id:preserve_layout),objectType:visualization,queryString:%27_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!(),linked:!!f,query:(language:lucene,query:!%27weightLbs:%253E10!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:weightLbs,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Weight%2Bin%2Blbs%2Bpie%2Bcreated%2Bin%2B6.2!%27,type:pie))%27,savedObjectId:%270644f890-53cb-11e8-b481-c9426d020fcd%27)'; -export const CSV_DISCOVER_FILTER_QUERY_6_2 = - '/api/reporting/generate/csv?jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,animal,sound,weightLbs),indexPatternId:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,animal,sound,weightLbs)),docvalue_fields:!(%27@timestamp%27),query:(bool:(filter:!(),must:!((query_string:(analyze_wildcard:!t,default_field:%27*%27,query:%27weightLbs:%3E10%27)),(match_phrase:(sound.keyword:(query:growl))),(range:(%27@timestamp%27:(format:epoch_millis,gte:1523310968000,lte:1523483768000)))),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,animal,sound,weightLbs),version:!t),index:%27animals-*%27),title:%27Search%20created%20in%206.2%27,type:search)'; From 9aa23410d52b306d0550ffae6a3cfc4e22ce1eb1 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 6 Jan 2020 19:50:55 -0500 Subject: [PATCH 16/18] convert modelMemoryLimit nums to strings before validation check (#54011) --- x-pack/legacy/plugins/ml/common/util/job_utils.js | 2 +- .../hooks/use_create_analytics_form/reducer.test.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/ml/common/util/job_utils.js b/x-pack/legacy/plugins/ml/common/util/job_utils.js index 757dfbd7a9a77..8982cebed522e 100644 --- a/x-pack/legacy/plugins/ml/common/util/job_utils.js +++ b/x-pack/legacy/plugins/ml/common/util/job_utils.js @@ -521,7 +521,7 @@ export function validateModelMemoryLimitUnits(modelMemoryLimit) { let valid = true; if (modelMemoryLimit !== undefined) { - const mml = modelMemoryLimit.toUpperCase(); + const mml = String(modelMemoryLimit).toUpperCase(); const mmlSplit = mml.match(/\d+(\w+)$/); const unit = mmlSplit && mmlSplit.length === 2 ? mmlSplit[1] : null; diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts index fcb99ea83548d..7ea2f74908e0e 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts @@ -138,5 +138,11 @@ describe('useCreateAnalyticsForm', () => { validateAdvancedEditor(getMockState({ index: 'the-source-index', modelMemoryLimit: '' })) .isValid ).toBe(false); + // can still run validation check on model_memory_limit if number type + expect( + // @ts-ignore number is not assignable to type string - mml gets converted to string prior to creation + validateAdvancedEditor(getMockState({ index: 'the-source-index', modelMemoryLimit: 100 })) + .isValid + ).toBe(false); }); }); From 6398e22b4f9bbc6d01f053187e52e86738a57c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 7 Jan 2020 08:33:49 +0000 Subject: [PATCH 17/18] adding message to transaction and span metadata (#54017) --- .../__test__/SpanMetadata.test.tsx | 18 +++++++++++++----- .../MetadataTable/SpanMetadata/sections.ts | 4 +++- .../__test__/TransactionMetadata.test.tsx | 18 ++++++++++++++---- .../TransactionMetadata/sections.ts | 4 +++- .../shared/MetadataTable/sections.ts | 17 +++++++++++++++++ .../apm/typings/es_schemas/raw/SpanRaw.ts | 6 ++++++ .../typings/es_schemas/raw/TransactionRaw.ts | 6 ++++++ 7 files changed, 62 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 4b6355034f16a..99d8a0790a816 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -31,11 +31,15 @@ describe('SpanMetadata', () => { name: 'opbeans-java' }, span: { - id: '7efbc7056b746fcb' + id: '7efbc7056b746fcb', + message: { + age: { ms: 1577958057123 }, + queue: { name: 'queue name' } + } } } as unknown) as Span; const output = render(, renderOptions); - expectTextsInDocument(output, ['Service', 'Agent']); + expectTextsInDocument(output, ['Service', 'Agent', 'Message']); }); }); describe('when a span is presented', () => { @@ -55,11 +59,15 @@ describe('SpanMetadata', () => { response: { status_code: 200 } }, subtype: 'http', - type: 'external' + type: 'external', + message: { + age: { ms: 1577958057123 }, + queue: { name: 'queue name' } + } } } as unknown) as Span; const output = render(, renderOptions); - expectTextsInDocument(output, ['Service', 'Agent', 'Span']); + expectTextsInDocument(output, ['Service', 'Agent', 'Span', 'Message']); }); }); describe('when there is no id inside span', () => { @@ -83,7 +91,7 @@ describe('SpanMetadata', () => { } as unknown) as Span; const output = render(, renderOptions); expectTextsInDocument(output, ['Service', 'Agent']); - expectTextsNotInDocument(output, ['Span']); + expectTextsNotInDocument(output, ['Span', 'Message']); }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts index 7012bbcc8fcea..5a83a9bf4ef9e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts @@ -11,7 +11,8 @@ import { SPAN, LABELS, TRANSACTION, - TRACE + TRACE, + MESSAGE_SPAN } from '../sections'; export const SPAN_METADATA_SECTIONS: Section[] = [ @@ -20,5 +21,6 @@ export const SPAN_METADATA_SECTIONS: Section[] = [ TRANSACTION, TRACE, SERVICE, + MESSAGE_SPAN, AGENT ]; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index 1fcb093fa0354..93e87e884ea76 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -35,6 +35,10 @@ function getTransaction() { notIncluded: 'transaction not included value', custom: { someKey: 'custom value' + }, + message: { + age: { ms: 1577958057123 }, + queue: { name: 'queue name' } } } } as unknown) as Transaction; @@ -59,7 +63,8 @@ describe('TransactionMetadata', () => { 'Agent', 'URL', 'User', - 'Custom' + 'Custom', + 'Message' ]); }); @@ -81,7 +86,9 @@ describe('TransactionMetadata', () => { 'agent.someKey', 'url.someKey', 'user.someKey', - 'transaction.custom.someKey' + 'transaction.custom.someKey', + 'transaction.message.age.ms', + 'transaction.message.queue.name' ]); // excluded keys @@ -109,7 +116,9 @@ describe('TransactionMetadata', () => { 'agent value', 'url value', 'user value', - 'custom value' + 'custom value', + '1577958057123', + 'queue name' ]); // excluded values @@ -138,7 +147,8 @@ describe('TransactionMetadata', () => { 'Process', 'Agent', 'URL', - 'Custom' + 'Custom', + 'Message' ]); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts index 6b30c82bc35a0..18751efc6e1c1 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts @@ -18,7 +18,8 @@ import { PAGE, USER, USER_AGENT, - CUSTOM_TRANSACTION + CUSTOM_TRANSACTION, + MESSAGE_TRANSACTION } from '../sections'; export const TRANSACTION_METADATA_SECTIONS: Section[] = [ @@ -29,6 +30,7 @@ export const TRANSACTION_METADATA_SECTIONS: Section[] = [ CONTAINER, SERVICE, PROCESS, + MESSAGE_TRANSACTION, AGENT, URL, { ...PAGE, key: 'transaction.page' }, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts index 403663ce2095a..ac8e9559357e3 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts @@ -136,3 +136,20 @@ export const CUSTOM_TRANSACTION: Section = { key: 'transaction.custom', label: customLabel }; + +const messageLabel = i18n.translate( + 'xpack.apm.metadataTable.section.messageLabel', + { + defaultMessage: 'Message' + } +); + +export const MESSAGE_TRANSACTION: Section = { + key: 'transaction.message', + label: messageLabel +}; + +export const MESSAGE_SPAN: Section = { + key: 'span.message', + label: messageLabel +}; diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/SpanRaw.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/SpanRaw.ts index 5ba480221c997..60e523f1aa043 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/SpanRaw.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/SpanRaw.ts @@ -40,6 +40,12 @@ export interface SpanRaw extends APMBaseDoc { statement?: string; type?: string; }; + message?: { + queue?: { name: string }; + age?: { ms: number }; + body?: string; + headers?: Record; + }; }; transaction?: { id: string; diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts index ce7c11f34a220..4dc5f8c897c26 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/TransactionRaw.ts @@ -43,6 +43,12 @@ export interface TransactionRaw extends APMBaseDoc { }; type: string; custom?: Record; + message?: { + queue?: { name: string }; + age?: { ms: number }; + body?: string; + headers?: Record; + }; }; // Shared by errors and transactions From e687fc63dfe815877378299f2b9f7c443ccf6088 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 7 Jan 2020 11:01:21 +0100 Subject: [PATCH 18/18] [Console] Telemetry (part 1) (#52893) * Saving anonymised data to SO * Add new files * Hook up usage collector * Added app start up ui metric tracking * Only use client side track metrics functionality * Added comment regarding use of `patterns`, renamed trackMetric -> trackUiMetric * Fix jest tests * Slight refactor and fix for functional tests. More defensive tracking logic * Fix types in test * Minor refactor to get endpoint description - removed SenseEditor from autocomplete. Fix bug where cursor at end of line does not get endpoint informaiton * Send request to es: do not mutate args Always move cursor to end of line when getting endpoint description * Create an interface a simple interface to the metrics tracker Use the new createUiStatsReporter function to create the tracker Co-authored-by: Elastic Machine --- .../core_plugins/console/public/kibana.json | 3 +- .../core_plugins/console/public/legacy.ts | 10 +++-- .../console_editor/editor.test.mock.tsx | 4 ++ .../legacy/console_editor/editor.test.tsx | 16 ++++++-- .../editor/legacy/console_menu_actions.ts | 5 +-- .../np_ready/application/contexts/index.ts | 2 +- .../application/contexts/services_context.tsx | 4 +- .../send_request_to_es.ts | 3 +- .../use_send_current_request_to_es/track.ts | 40 ++++++++++++++++++ .../use_send_current_request_to_es.ts | 16 ++++---- .../public/np_ready/application/index.tsx | 12 +++++- .../application/models/sense_editor/index.ts | 1 + .../np_ready/lib/autocomplete/autocomplete.ts | 35 ++++++---------- .../get_endpoint_from_position.ts | 41 +++++++++++++++++++ .../console/public/np_ready/lib/es/es.js | 1 - .../public/np_ready/services/tracker.ts | 31 ++++++++++++++ .../console/public/np_ready/types/common.ts | 5 +++ .../console/public/np_ready/types/index.ts | 1 + .../spec/overrides/sql.query.json | 3 +- 19 files changed, 186 insertions(+), 47 deletions(-) create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/track.ts create mode 100644 src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/get_endpoint_from_position.ts create mode 100644 src/legacy/core_plugins/console/public/np_ready/services/tracker.ts diff --git a/src/legacy/core_plugins/console/public/kibana.json b/src/legacy/core_plugins/console/public/kibana.json index 3363af353912a..c58a5a90fb9f2 100644 --- a/src/legacy/core_plugins/console/public/kibana.json +++ b/src/legacy/core_plugins/console/public/kibana.json @@ -3,5 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["home"] + "requiredPlugins": ["home"], + "optionalPlugins": ["usageCollection"] } diff --git a/src/legacy/core_plugins/console/public/legacy.ts b/src/legacy/core_plugins/console/public/legacy.ts index c456d777187aa..d151a27d27e5c 100644 --- a/src/legacy/core_plugins/console/public/legacy.ts +++ b/src/legacy/core_plugins/console/public/legacy.ts @@ -22,7 +22,13 @@ import { I18nContext } from 'ui/i18n'; import chrome from 'ui/chrome'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { plugin } from './np_ready'; +import { DevToolsSetup } from '../../../../plugins/dev_tools/public'; +import { HomePublicPluginSetup } from '../../../../plugins/home/public'; +import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; + export interface XPluginSet { + usageCollection: UsageCollectionSetup; dev_tools: DevToolsSetup; home: HomePublicPluginSetup; __LEGACY: { @@ -32,10 +38,6 @@ export interface XPluginSet { }; } -import { plugin } from './np_ready'; -import { DevToolsSetup } from '../../../../plugins/dev_tools/public'; -import { HomePublicPluginSetup } from '../../../../plugins/home/public'; - const pluginInstance = plugin({} as any); (async () => { diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx index 5df72c0f03496..0ee7998d331f5 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx @@ -22,6 +22,7 @@ jest.mock('../../../../contexts/editor_context/editor_registry.ts', () => ({ setInputEditor: () => {}, getInputEditor: () => ({ getRequestsInRange: async () => [{ test: 'test' }], + getCoreEditor: () => ({ getCurrentPosition: jest.fn() }), }), }, })); @@ -52,3 +53,6 @@ jest.mock('../../../../models/sense_editor', () => { jest.mock('../../../../hooks/use_send_current_request_to_es/send_request_to_es', () => ({ sendRequestToES: jest.fn(), })); +jest.mock('../../../../../lib/autocomplete/get_endpoint_from_position', () => ({ + getEndpointFromPosition: jest.fn(), +})); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx index 6162397ce0650..73ee6d160613f 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -32,14 +32,18 @@ import { ServicesContextProvider, EditorContextProvider, RequestContextProvider, + ContextValue, } from '../../../../contexts'; +// Mocked functions import { sendRequestToES } from '../../../../hooks/use_send_current_request_to_es/send_request_to_es'; +import { getEndpointFromPosition } from '../../../../../lib/autocomplete/get_endpoint_from_position'; + import * as consoleMenuActions from '../console_menu_actions'; import { Editor } from './editor'; describe('Legacy (Ace) Console Editor Component Smoke Test', () => { - let mockedAppContextValue: any; + let mockedAppContextValue: ContextValue; const sandbox = sinon.createSandbox(); const doMount = () => @@ -58,11 +62,15 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { beforeEach(() => { document.queryCommandSupported = sinon.fake(() => true); mockedAppContextValue = { + elasticsearchUrl: 'test', services: { + trackUiMetric: { count: () => {}, load: () => {} }, + settings: {} as any, + storage: {} as any, history: { - getSavedEditorState: () => null, + getSavedEditorState: () => ({} as any), updateCurrentState: jest.fn(), - }, + } as any, notifications: notificationServiceMock.createSetupContract(), }, docLinkVersion: 'NA', @@ -70,10 +78,12 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { }); afterEach(() => { + jest.clearAllMocks(); sandbox.restore(); }); it('calls send current request to ES', async () => { + (getEndpointFromPosition as jest.Mock).mockReturnValue({ patterns: [] }); (sendRequestToES as jest.Mock).mockRejectedValue({}); const editor = doMount(); act(() => { diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts index 797ff5744eec3..2bbe49cd53eac 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts @@ -17,8 +17,7 @@ * under the License. */ -// @ts-ignore -import { getEndpointFromPosition } from '../../../../lib/autocomplete/autocomplete'; +import { getEndpointFromPosition } from '../../../../lib/autocomplete/get_endpoint_from_position'; import { SenseEditor } from '../../../models/sense_editor'; export async function autoIndent(editor: SenseEditor, event: Event) { @@ -40,7 +39,7 @@ export function getDocumentation( } const position = requests[0].range.end; position.column = position.column - 1; - const endpoint = getEndpointFromPosition(editor, position, editor.parser); + const endpoint = getEndpointFromPosition(editor.getCoreEditor(), position, editor.parser); if (endpoint && endpoint.documentation && endpoint.documentation.indexOf('http') !== -1) { return endpoint.documentation .replace('/master/', `/${docLinkVersion}/`) diff --git a/src/legacy/core_plugins/console/public/np_ready/application/contexts/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/contexts/index.ts index 18234acf15957..e489bd50c9ce0 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/contexts/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/contexts/index.ts @@ -17,7 +17,7 @@ * under the License. */ -export { useServicesContext, ServicesContextProvider } from './services_context'; +export { useServicesContext, ServicesContextProvider, ContextValue } from './services_context'; export { useRequestActionContext, diff --git a/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx index 77f0924a51842..f14685ecd4ac7 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx @@ -20,13 +20,15 @@ import React, { createContext, useContext } from 'react'; import { NotificationsSetup } from 'kibana/public'; import { History, Storage, Settings } from '../../services'; +import { MetricsTracker } from '../../types'; -interface ContextValue { +export interface ContextValue { services: { history: History; storage: Storage; settings: Settings; notifications: NotificationsSetup; + trackUiMetric: MetricsTracker; }; elasticsearchUrl: string; docLinkVersion: string; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/send_request_to_es.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/send_request_to_es.ts index 11c1f6638e9cf..10dab65b61d44 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/send_request_to_es.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/send_request_to_es.ts @@ -39,7 +39,8 @@ export interface ESRequestResult { } let CURRENT_REQ_ID = 0; -export function sendRequestToES({ requests }: EsRequestArgs): Promise { +export function sendRequestToES(args: EsRequestArgs): Promise { + const requests = args.requests.slice(); return new Promise((resolve, reject) => { const reqId = ++CURRENT_REQ_ID; const results: ESRequestResult[] = []; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/track.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/track.ts new file mode 100644 index 0000000000000..4d993512c8fa7 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/track.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SenseEditor } from '../../models/sense_editor'; +import { getEndpointFromPosition } from '../../../lib/autocomplete/get_endpoint_from_position'; +import { MetricsTracker } from '../../../types'; + +export const track = (requests: any[], editor: SenseEditor, trackUiMetric: MetricsTracker) => { + const coreEditor = editor.getCoreEditor(); + // `getEndpointFromPosition` gets values from the server-side generated JSON files which + // are a combination of JS, automatically generated JSON and manual overrides. That means + // the metrics reported from here will be tied to the definitions in those files. + // See src/legacy/core_plugins/console/server/api_server/spec + const endpointDescription = getEndpointFromPosition( + coreEditor, + coreEditor.getCurrentPosition(), + editor.parser + ); + + if (requests[0] && endpointDescription) { + const eventName = `${requests[0].method}_${endpointDescription.id ?? 'unknown'}`; + trackUiMetric.count(eventName); + } +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts index b51c29f8e9db6..6bf0b5024376b 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts @@ -19,15 +19,16 @@ import { i18n } from '@kbn/i18n'; import { useCallback } from 'react'; import { instance as registry } from '../../contexts/editor_context/editor_registry'; -import { useServicesContext } from '../../contexts'; +import { useRequestActionContext, useServicesContext } from '../../contexts'; import { sendRequestToES } from './send_request_to_es'; -import { useRequestActionContext } from '../../contexts'; +import { track } from './track'; + // @ts-ignore import mappings from '../../../lib/mappings/mappings'; export const useSendCurrentRequestToES = () => { const { - services: { history, settings, notifications }, + services: { history, settings, notifications, trackUiMetric }, } = useServicesContext(); const dispatch = useRequestActionContext(); @@ -45,9 +46,10 @@ export const useSendCurrentRequestToES = () => { return; } - const results = await sendRequestToES({ - requests, - }); + // Fire and forget + setTimeout(() => track(requests, editor, trackUiMetric), 0); + + const results = await sendRequestToES({ requests }); results.forEach(({ request: { path, method, data } }) => { history.addToHistory(path, method, data); @@ -82,5 +84,5 @@ export const useSendCurrentRequestToES = () => { }); } } - }, [dispatch, settings, history, notifications]); + }, [dispatch, settings, history, notifications, trackUiMetric]); }; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/index.tsx b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx index 239e4320f00f8..89756513b2b22 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/index.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx @@ -22,6 +22,7 @@ import { NotificationsSetup } from 'kibana/public'; import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts'; import { Main } from './containers'; import { createStorage, createHistory, createSettings, Settings } from '../services'; +import { createUsageTracker } from '../services/tracker'; let settingsRef: Settings; export function legacyBackDoorToSettings() { @@ -36,6 +37,9 @@ export function boot(deps: { }) { const { I18nContext, notifications, docLinkVersion, elasticsearchUrl } = deps; + const trackUiMetric = createUsageTracker(); + trackUiMetric.load('opened_app'); + const storage = createStorage({ engine: window.localStorage, prefix: 'sense:', @@ -50,7 +54,13 @@ export function boot(deps: { value={{ elasticsearchUrl, docLinkVersion, - services: { storage, history, settings, notifications }, + services: { + storage, + history, + settings, + notifications, + trackUiMetric, + }, }} > diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts index 9310de2724fbe..f2102d75685fd 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts @@ -21,3 +21,4 @@ export * from './create'; export * from '../legacy_core_editor/create_readonly'; export { MODE } from '../../../lib/row_parser'; export { SenseEditor } from './sense_editor'; +export { getEndpointFromPosition } from '../../../lib/autocomplete/get_endpoint_from_position'; diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts index 7520807ca77f5..ac8fa1ea48caa 100644 --- a/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts @@ -38,7 +38,6 @@ import { URL_PATH_END_MARKER } from './components/index'; import { createTokenIterator } from '../../application/factories'; import { Position, Token, Range, CoreEditor } from '../../types'; -import { SenseEditor } from '../../application/models/sense_editor'; let LAST_EVALUATED_TOKEN: any = null; @@ -54,11 +53,20 @@ function isUrlParamsToken(token: any) { return false; } } -function getCurrentMethodAndTokenPaths( + +/** + * Get the method and token paths for a specific position in the current editor buffer. + * + * This function can be used for getting autocomplete information or for getting more information + * about the endpoint associated with autocomplete. In future, these concerns should be better + * separated. + * + */ +export function getCurrentMethodAndTokenPaths( editor: CoreEditor, pos: Position, parser: any, - forceEndOfUrl?: boolean + forceEndOfUrl?: boolean /* Flag for indicating whether we want to avoid early escape optimization. */ ) { const tokenIter = createTokenIterator({ editor, @@ -186,7 +194,7 @@ function getCurrentMethodAndTokenPaths( } } - if (walkedSomeBody && (!bodyTokenPath || bodyTokenPath.length === 0)) { + if (walkedSomeBody && (!bodyTokenPath || bodyTokenPath.length === 0) && !forceEndOfUrl) { // we had some content and still no path -> the cursor is position after a closed body -> no auto complete return {}; } @@ -298,20 +306,6 @@ function getCurrentMethodAndTokenPaths( } return ret; } -export function getEndpointFromPosition(senseEditor: SenseEditor, pos: Position, parser: any) { - const editor = senseEditor.getCoreEditor(); - const context = { - ...getCurrentMethodAndTokenPaths( - editor, - { column: pos.column, lineNumber: pos.lineNumber }, - parser, - true - ), - }; - const components = getTopLevelUrlCompleteComponents(context.method); - populateContext(context.urlTokenPath, context, editor, true, components); - return context.endpoint; -} // eslint-disable-next-line export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor; parser: any }) { @@ -812,7 +806,6 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor if (!ret.urlTokenPath) { // zero length tokenPath is true - // console.log("Can't extract a valid url token path."); return context; } @@ -825,13 +818,11 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor ); if (!context.endpoint) { - // console.log("couldn't resolve an endpoint."); return context; } if (!ret.urlParamsTokenPath) { // zero length tokenPath is true - // console.log("Can't extract a valid urlParams token path."); return context; } let tokenPath: any[] = []; @@ -859,7 +850,6 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor context.requestStartRow = ret.requestStartRow; if (!ret.urlTokenPath) { // zero length tokenPath is true - // console.log("Can't extract a valid url token path."); return context; } @@ -875,7 +865,6 @@ export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor if (!ret.bodyTokenPath) { // zero length tokenPath is true - // console.log("Can't extract a valid body token path."); return context; } diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/get_endpoint_from_position.ts b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/get_endpoint_from_position.ts new file mode 100644 index 0000000000000..cb037e29e33f6 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/get_endpoint_from_position.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreEditor, Position } from '../../types'; +import { getCurrentMethodAndTokenPaths } from './autocomplete'; + +// @ts-ignore +import { getTopLevelUrlCompleteComponents } from '../kb/kb'; +// @ts-ignore +import { populateContext } from './engine'; + +export function getEndpointFromPosition(editor: CoreEditor, pos: Position, parser: any) { + const lineValue = editor.getLineValue(pos.lineNumber); + const context = { + ...getCurrentMethodAndTokenPaths( + editor, + { column: lineValue.length, lineNumber: pos.lineNumber }, + parser, + true + ), + }; + const components = getTopLevelUrlCompleteComponents(context.method); + populateContext(context.urlTokenPath, context, editor, true, components); + return context.endpoint; +} diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/es/es.js b/src/legacy/core_plugins/console/public/np_ready/lib/es/es.js index 9012b875e0f2b..e36976fb7acee 100644 --- a/src/legacy/core_plugins/console/public/np_ready/lib/es/es.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/es/es.js @@ -18,7 +18,6 @@ */ import { stringify as formatQueryString } from 'querystring'; - import $ from 'jquery'; const esVersion = []; diff --git a/src/legacy/core_plugins/console/public/np_ready/services/tracker.ts b/src/legacy/core_plugins/console/public/np_ready/services/tracker.ts new file mode 100644 index 0000000000000..13d5f875b3c6f --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/services/tracker.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { METRIC_TYPE } from '@kbn/analytics'; +import { MetricsTracker } from '../types'; +import { createUiStatsReporter } from '../../../../ui_metric/public'; + +const APP_TRACKER_NAME = 'console'; +export const createUsageTracker = (): MetricsTracker => { + const track = createUiStatsReporter(APP_TRACKER_NAME); + return { + count: (eventName: string) => track(METRIC_TYPE.COUNT, eventName), + load: (eventName: string) => track(METRIC_TYPE.LOADED, eventName), + }; +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/types/common.ts b/src/legacy/core_plugins/console/public/np_ready/types/common.ts index ad9ed10d4188f..e44969cd9e80a 100644 --- a/src/legacy/core_plugins/console/public/np_ready/types/common.ts +++ b/src/legacy/core_plugins/console/public/np_ready/types/common.ts @@ -17,6 +17,11 @@ * under the License. */ +export interface MetricsTracker { + count: (eventName: string) => void; + load: (eventName: string) => void; +} + export type BaseResponseType = | 'application/json' | 'text/csv' diff --git a/src/legacy/core_plugins/console/public/np_ready/types/index.ts b/src/legacy/core_plugins/console/public/np_ready/types/index.ts index 9d82237d667b3..78c6b6c8f55cc 100644 --- a/src/legacy/core_plugins/console/public/np_ready/types/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/types/index.ts @@ -20,3 +20,4 @@ export * from './core_editor'; export * from './token'; export * from './tokens_provider'; +export * from './common'; diff --git a/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json b/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json index 843fba30bb489..c78cfeea8473d 100644 --- a/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json +++ b/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json @@ -19,6 +19,7 @@ "smile" ] }, - "template": "_sql?format=json\n{\n \"query\": \"\"\"\n SELECT * FROM \"${1:TABLE}\"\n \"\"\"\n}\n" + "template": "_sql?format=json\n{\n \"query\": \"\"\"\n SELECT * FROM \"${1:TABLE}\"\n \"\"\"\n}\n", + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest-overview.html" } }