From b30d96e7f2d0b7b27f54ff3d8150dd6bf73dfcda Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 25 May 2021 13:25:18 -0600 Subject: [PATCH 01/18] [Maps] convert LayerPanel to typescript (#100481) * [Maps] convert LayerPanel to typescript * layer_errors * style panel * layer_panel component * rename to EditLayerPanel * clean up * fix scss imports for rename * one more scss path clean up * fix EditLayerPanel errors --- .../layers/vector_layer/vector_layer.tsx | 1 + .../ems_file_source/update_source_editor.tsx | 2 +- .../update_source_editor.tsx | 2 +- .../sources/es_search_source/scaling_form.tsx | 2 +- .../top_hits/create_source_editor.tsx | 2 +- .../top_hits/top_hits_form.tsx | 2 +- .../top_hits/update_source_editor.tsx | 2 +- .../update_source_editor.tsx | 2 +- .../maps/public/classes/sources/source.ts | 9 +- ...checkbox.js => global_filter_checkbox.tsx} | 12 +- .../public/connected_components/_index.scss | 2 +- .../edit_layer_panel.test.tsx.snap} | 7 +- .../_index.scss | 0 .../_layer_panel.scss | 0 .../edit_layer_panel.test.tsx} | 25 ++-- .../edit_layer_panel.tsx} | 131 ++++++++++++------ .../filter_editor/_filter_editor.scss | 0 .../filter_editor/filter_editor.tsx} | 32 ++++- .../filter_editor/index.ts} | 15 +- .../flyout_footer/flyout_footer.tsx} | 9 +- .../flyout_footer/index.ts} | 9 +- .../index.js => edit_layer_panel/index.ts} | 16 ++- .../__snapshots__/join_editor.test.tsx.snap | 0 .../join_editor/index.ts} | 1 + .../join_editor/join_editor.test.tsx | 0 .../join_editor/join_editor.tsx | 8 +- .../metrics_expression.test.js.snap | 0 .../join_editor/resources/_join.scss | 0 .../join_editor/resources/join.js | 0 .../join_editor/resources/join_expression.js | 0 .../resources/metrics_expression.js | 0 .../resources/metrics_expression.test.js | 0 .../join_editor/resources/where_expression.js | 0 .../attribution_form_row.test.tsx.snap | 0 .../layer_settings/_attribution_form_row.scss | 0 .../layer_settings/_attribution_popover.scss | 0 .../layer_settings/_index.scss | 0 .../attribution_form_row.test.tsx | 0 .../layer_settings/attribution_form_row.tsx | 0 .../layer_settings/attribution_popover.tsx | 0 .../layer_settings/index.tsx | 0 .../layer_settings/layer_settings.tsx | 0 .../style_settings/_style_settings.scss | 0 .../style_settings/index.ts} | 12 +- .../style_settings/style_settings.tsx} | 9 +- .../__snapshots__/layer_errors.test.js.snap | 21 --- .../layer_panel/layer_errors/index.js | 19 --- .../layer_panel/layer_errors/layer_errors.js | 30 ---- .../layer_errors/layer_errors.test.js | 36 ----- .../layer_panel/view.d.ts | 16 --- .../map_container/map_container.tsx | 5 +- .../routes/map_page/map_app/map_app.tsx | 4 +- 52 files changed, 216 insertions(+), 227 deletions(-) rename x-pack/plugins/maps/public/components/{global_filter_checkbox.js => global_filter_checkbox.tsx} (70%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/__snapshots__/view.test.js.snap => edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap} (93%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/_index.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/_layer_panel.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/view.test.js => edit_layer_panel/edit_layer_panel.test.tsx} (83%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/view.js => edit_layer_panel/edit_layer_panel.tsx} (64%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/filter_editor/_filter_editor.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/filter_editor/filter_editor.js => edit_layer_panel/filter_editor/filter_editor.tsx} (87%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/filter_editor/index.js => edit_layer_panel/filter_editor/index.ts} (56%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/flyout_footer/view.js => edit_layer_panel/flyout_footer/flyout_footer.tsx} (92%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/flyout_footer/index.js => edit_layer_panel/flyout_footer/index.ts} (78%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/index.js => edit_layer_panel/index.ts} (53%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/__snapshots__/join_editor.test.tsx.snap (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/join_editor/index.tsx => edit_layer_panel/join_editor/index.ts} (96%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/join_editor.test.tsx (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/join_editor.tsx (97%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/resources/__snapshots__/metrics_expression.test.js.snap (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/resources/_join.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/resources/join.js (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/resources/join_expression.js (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/resources/metrics_expression.js (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/resources/metrics_expression.test.js (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/join_editor/resources/where_expression.js (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/__snapshots__/attribution_form_row.test.tsx.snap (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/_attribution_form_row.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/_attribution_popover.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/_index.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/attribution_form_row.test.tsx (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/attribution_form_row.tsx (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/attribution_popover.tsx (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/index.tsx (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/layer_settings/layer_settings.tsx (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel => edit_layer_panel}/style_settings/_style_settings.scss (100%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/style_settings/index.js => edit_layer_panel/style_settings/index.ts} (62%) rename x-pack/plugins/maps/public/connected_components/{layer_panel/style_settings/style_settings.js => edit_layer_panel/style_settings/style_settings.tsx} (78%) delete mode 100644 x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap delete mode 100644 x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/index.js delete mode 100644 x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.js delete mode 100644 x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.test.js delete mode 100644 x-pack/plugins/maps/public/connected_components/layer_panel/view.d.ts diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index a1474237f6c9cc..a4d913979cf1b5 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -89,6 +89,7 @@ export interface IVectorLayer extends ILayer { getPropertiesForTooltip(properties: GeoJsonProperties): Promise; hasJoins(): boolean; canShowTooltip(): boolean; + getLeftJoinFields(): Promise; } export class VectorLayer extends AbstractLayer implements IVectorLayer { diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx index abac9cbe5d0267..265a0f67f959c2 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx @@ -12,7 +12,7 @@ import { TooltipSelector } from '../../../components/tooltip_selector'; import { getEmsFileLayers } from '../../../util'; import { IEmsFileSource } from './ems_file_source'; import { IField } from '../../fields/field'; -import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; +import { OnSourceChangeArgs } from '../source'; interface Props { layerId: string; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx index 81c5d8d6d89df4..2688ef1d29686f 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx @@ -18,7 +18,7 @@ import { MetricsEditor } from '../../../components/metrics_editor'; import { getIndexPatternService } from '../../../kibana_services'; import { GeoLineForm } from './geo_line_form'; import { AggDescriptor } from '../../../../common/descriptor_types'; -import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; +import { OnSourceChangeArgs } from '../source'; interface Props { indexPatternId: string; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx index b9ce43dbbdad42..5d095035331f8c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx @@ -22,7 +22,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { getIndexPatternService } from '../../../kibana_services'; import { DEFAULT_MAX_RESULT_WINDOW, LAYER_TYPE, SCALING_TYPES } from '../../../../common/constants'; import { loadIndexSettings } from './load_index_settings'; -import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; +import { OnSourceChangeArgs } from '../source'; interface Props { filterByMapBounds: boolean; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx index ec656be3efeaea..41c9076c114360 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx @@ -19,7 +19,7 @@ import { SortDirection, } from '../../../../../../../../src/plugins/data/common'; import { TopHitsForm } from './top_hits_form'; -import { OnSourceChangeArgs } from '../../../../connected_components/layer_panel/view'; +import { OnSourceChangeArgs } from '../../source'; interface Props { onSourceConfigChange: (sourceConfig: Partial | null) => void; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx index e4f196e5e8a858..83831f84e77f98 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx @@ -14,7 +14,7 @@ import { getIndexPatternService } from '../../../../kibana_services'; import { ValidatedRange } from '../../../../components/validated_range'; import { DEFAULT_MAX_INNER_RESULT_WINDOW } from '../../../../../common/constants'; import { loadIndexSettings } from '../load_index_settings'; -import { OnSourceChangeArgs } from '../../../../connected_components/layer_panel/view'; +import { OnSourceChangeArgs } from '../../source'; import { IFieldType, SortDirection } from '../../../../../../../../src/plugins/data/public'; interface Props { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx index 90553d47e644a5..6ca38f3be22f48 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx @@ -16,7 +16,7 @@ import { getIndexPatternService } from '../../../../kibana_services'; import { getTermsFields, getSortFields, getSourceFields } from '../../../../index_pattern_util'; import { SortDirection, IFieldType } from '../../../../../../../../src/plugins/data/public'; import { ESDocField } from '../../../fields/es_doc_field'; -import { OnSourceChangeArgs } from '../../../../connected_components/layer_panel/view'; +import { OnSourceChangeArgs } from '../../source'; import { TopHitsForm } from './top_hits_form'; import { ESSearchSource } from '../es_search_source'; import { IField } from '../../../fields/field'; diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.tsx index a20aadd3c12bc0..62ebdb789daebc 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.tsx @@ -14,7 +14,7 @@ import { TooltipSelector } from '../../../components/tooltip_selector'; import { MVTField } from '../../fields/mvt_field'; import { MVTSingleLayerVectorSource } from './mvt_single_layer_vector_source'; import { MVTSettings, MVTSingleLayerSourceSettings } from './mvt_single_layer_source_settings'; -import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; +import { OnSourceChangeArgs } from '../source'; import { MVTFieldDescriptor } from '../../../../common/descriptor_types'; interface Props { diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 28a68dda64cb6b..1f683068e132bf 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -14,12 +14,17 @@ import { GeoJsonProperties } from 'geojson'; import { copyPersistentState } from '../../reducers/copy_persistent_state'; import { IField } from '../fields/field'; -import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; +import { FieldFormatter, LAYER_TYPE, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; import { AbstractSourceDescriptor, Attribution } from '../../../common/descriptor_types'; -import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view'; import { LICENSED_FEATURES } from '../../licensed_features'; import { PreIndexedShape } from '../../../common/elasticsearch_util'; +export type OnSourceChangeArgs = { + propName: string; + value: unknown; + newLayerType?: LAYER_TYPE; +}; + export type SourceEditorArgs = { onChange: (...args: OnSourceChangeArgs[]) => void; currentLayerType?: string; diff --git a/x-pack/plugins/maps/public/components/global_filter_checkbox.js b/x-pack/plugins/maps/public/components/global_filter_checkbox.tsx similarity index 70% rename from x-pack/plugins/maps/public/components/global_filter_checkbox.js rename to x-pack/plugins/maps/public/components/global_filter_checkbox.tsx index df31cc5c40cdf4..bddb1cfd9cfcd5 100644 --- a/x-pack/plugins/maps/public/components/global_filter_checkbox.js +++ b/x-pack/plugins/maps/public/components/global_filter_checkbox.tsx @@ -6,10 +6,16 @@ */ import React from 'react'; -import { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; -export function GlobalFilterCheckbox({ applyGlobalQuery, label, setApplyGlobalQuery }) { - const onApplyGlobalQueryChange = (event) => { +interface Props { + applyGlobalQuery: boolean; + label: string; + setApplyGlobalQuery: (applyGlobalQuery: boolean) => void; +} + +export function GlobalFilterCheckbox({ applyGlobalQuery, label, setApplyGlobalQuery }: Props) { + const onApplyGlobalQueryChange = (event: EuiSwitchEvent) => { setApplyGlobalQuery(event.target.checked); }; diff --git a/x-pack/plugins/maps/public/connected_components/_index.scss b/x-pack/plugins/maps/public/connected_components/_index.scss index c4438a9312bceb..9a49dbe550efb2 100644 --- a/x-pack/plugins/maps/public/connected_components/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/_index.scss @@ -1,5 +1,5 @@ @import 'map_container/map_container'; -@import 'layer_panel/index'; +@import 'edit_layer_panel/index'; @import 'right_side_controls/index'; @import 'toolbar_overlay/index'; @import 'mb_map/tooltip_control/features_tooltip/index'; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap similarity index 93% rename from x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap index bbec1643cb55df..24f15674a05043 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`LayerPanel is rendered 1`] = ` +exports[`EditLayerPanel is rendered 1`] = ` - `; -exports[`LayerPanel should render empty panel when selectedLayer is null 1`] = `""`; +exports[`EditLayerPanel should render empty panel when selectedLayer is null 1`] = `""`; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/_index.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/_index.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/_index.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/_index.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/_layer_panel.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/_layer_panel.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/_layer_panel.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/_layer_panel.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/view.test.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.test.tsx similarity index 83% rename from x-pack/plugins/maps/public/connected_components/layer_panel/view.test.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.test.tsx index ae5368c4749859..fa5cdc77982b85 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/view.test.js +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.test.tsx @@ -29,12 +29,6 @@ jest.mock('./flyout_footer', () => ({ }, })); -jest.mock('./layer_errors', () => ({ - LayerErrors: () => { - return
mockLayerErrors
; - }, -})); - jest.mock('./layer_settings', () => ({ LayerSettings: () => { return
mockLayerSettings
; @@ -53,11 +47,11 @@ jest.mock('../../kibana_services', () => { }); import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; - -import { LayerPanel } from './view'; +import { shallow } from 'enzyme'; +import { ILayer } from '../../classes/layers/layer'; +import { EditLayerPanel } from './edit_layer_panel'; -const mockLayer = { +const mockLayer = ({ getId: () => { return '1'; }, @@ -79,7 +73,10 @@ const mockLayer = { renderSourceSettingsEditor: () => { return
mockSourceSettings
; }, -}; + hasErrors: () => { + return false; + }, +} as unknown) as ILayer; const defaultProps = { selectedLayer: mockLayer, @@ -87,9 +84,9 @@ const defaultProps = { updateSourceProp: () => {}, }; -describe('LayerPanel', () => { +describe('EditLayerPanel', () => { test('is rendered', async () => { - const component = shallowWithIntl(); + const component = shallow(); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -100,7 +97,7 @@ describe('LayerPanel', () => { }); test('should render empty panel when selectedLayer is null', async () => { - const component = shallowWithIntl(); + const component = shallow(); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/view.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx similarity index 64% rename from x-pack/plugins/maps/public/connected_components/layer_panel/view.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx index 9438d55ac18bc1..eb3c472252252a 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/view.js +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx @@ -5,15 +5,10 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React, { Component, Fragment } from 'react'; -import { FilterEditor } from './filter_editor'; -import { JoinEditor } from './join_editor'; -import { FlyoutFooter } from './flyout_footer'; -import { LayerErrors } from './layer_errors'; -import { LayerSettings } from './layer_settings'; -import { StyleSettings } from './style_settings'; import { + EuiCallOut, EuiIcon, EuiFlexItem, EuiTitle, @@ -26,20 +21,47 @@ import { EuiText, EuiLink, } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; +import { FilterEditor } from './filter_editor'; +import { JoinEditor, JoinField } from './join_editor'; +import { FlyoutFooter } from './flyout_footer'; +import { LayerSettings } from './layer_settings'; +import { StyleSettings } from './style_settings'; + import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; - +import { LAYER_TYPE } from '../../../common/constants'; import { getData, getCore } from '../../kibana_services'; +import { ILayer } from '../../classes/layers/layer'; +import { IVectorLayer } from '../../classes/layers/vector_layer'; +import { ImmutableSourceProperty, OnSourceChangeArgs } from '../../classes/sources/source'; +import { IField } from '../../classes/fields/field'; const localStorage = new Storage(window.localStorage); -export class LayerPanel extends React.Component { - state = { +export interface Props { + selectedLayer?: ILayer; + updateSourceProp: ( + layerId: string, + propName: string, + value: unknown, + newLayerType?: LAYER_TYPE + ) => void; +} + +interface State { + displayName: string; + immutableSourceProps: ImmutableSourceProperty[]; + leftJoinFields: JoinField[]; + supportsFitToBounds: boolean; +} + +export class EditLayerPanel extends Component { + private _isMounted = false; + state: State = { displayName: '', immutableSourceProps: [], - leftJoinFields: null, + leftJoinFields: [], supportsFitToBounds: false, }; @@ -89,14 +111,19 @@ export class LayerPanel extends React.Component { }; async _loadLeftJoinFields() { - if (!this.props.selectedLayer || !this.props.selectedLayer.showJoinEditor()) { + if ( + !this.props.selectedLayer || + !this.props.selectedLayer.showJoinEditor() || + (this.props.selectedLayer as IVectorLayer).getLeftJoinFields === undefined + ) { return; } - let leftJoinFields; + let leftJoinFields: JoinField[] = []; try { - const leftFieldsInstances = await this.props.selectedLayer.getLeftJoinFields(); - const leftFieldPromises = leftFieldsInstances.map(async (field) => { + const leftFieldsInstances = await (this.props + .selectedLayer as IVectorLayer).getLeftJoinFields(); + const leftFieldPromises = leftFieldsInstances.map(async (field: IField) => { return { name: field.getName(), label: await field.getLabel(), @@ -104,22 +131,42 @@ export class LayerPanel extends React.Component { }); leftJoinFields = await Promise.all(leftFieldPromises); } catch (error) { - leftJoinFields = []; + // ignore exceptions getting fields, will bubble up in layer errors panel } if (this._isMounted) { this.setState({ leftJoinFields }); } } - _onSourceChange = (...args) => { + _onSourceChange = (...args: OnSourceChangeArgs[]) => { for (let i = 0; i < args.length; i++) { const { propName, value, newLayerType } = args[i]; - this.props.updateSourceProp(this.props.selectedLayer.getId(), propName, value, newLayerType); + this.props.updateSourceProp(this.props.selectedLayer!.getId(), propName, value, newLayerType); } }; + _renderLayerErrors() { + if (!this.props.selectedLayer || !this.props.selectedLayer.hasErrors()) { + return null; + } + + return ( + + +

{this.props.selectedLayer.getErrors()}

+
+ +
+ ); + } + _renderFilterSection() { - if (!this.props.selectedLayer.supportsElasticsearchFilters()) { + if (!this.props.selectedLayer || !this.props.selectedLayer.supportsElasticsearchFilters()) { return null; } @@ -134,7 +181,7 @@ export class LayerPanel extends React.Component { } _renderJoinSection() { - if (!this.props.selectedLayer.showJoinEditor()) { + if (!this.props.selectedLayer || !this.props.selectedLayer.showJoinEditor()) { return null; } @@ -153,29 +200,29 @@ export class LayerPanel extends React.Component { } _renderSourceProperties() { - return this.state.immutableSourceProps.map(({ label, value, link }) => { - function renderValue() { - if (link) { - return ( - - {value} - - ); + return this.state.immutableSourceProps.map( + ({ label, value, link }: ImmutableSourceProperty) => { + function renderValue() { + if (link) { + return ( + + {value} + + ); + } + return {value}; } - return {value}; + return ( +

+ {label} {renderValue()} +

+ ); } - return ( -

- {label} {renderValue()} -

- ); - }); + ); } render() { - const { selectedLayer } = this.props; - - if (!selectedLayer) { + if (!this.props.selectedLayer) { return null; } @@ -192,7 +239,7 @@ export class LayerPanel extends React.Component { - + @@ -218,10 +265,10 @@ export class LayerPanel extends React.Component {
- + {this._renderLayerErrors()} diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/_filter_editor.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/_filter_editor.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/_filter_editor.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/_filter_editor.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx similarity index 87% rename from x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx index 654ce5a4e9ddbf..6e258e679b96f9 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx @@ -21,12 +21,28 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import type { IndexPattern, Query } from 'src/plugins/data/public'; +import { APP_ID } from '../../../../common/constants'; import { getIndexPatternService, getData } from '../../../kibana_services'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; import { GlobalTimeCheckbox } from '../../../components/global_time_checkbox'; +import { ILayer } from '../../../classes/layers/layer'; -export class FilterEditor extends Component { - state = { +export interface Props { + layer: ILayer; + setLayerQuery: (id: string, query: Query) => void; + updateSourceProp: (layerId: string, propName: string, value: unknown) => void; +} + +interface State { + isPopoverOpen: boolean; + indexPatterns: IndexPattern[]; + isSourceTimeAware: boolean; +} + +export class FilterEditor extends Component { + private _isMounted = false; + state: State = { isPopoverOpen: false, indexPatterns: [], isSourceTimeAware: false, @@ -45,7 +61,7 @@ export class FilterEditor extends Component { async _loadIndexPatterns() { // Filter only effects source so only load source indices. const indexPatternIds = this.props.layer.getSource().getIndexPatternIds(); - const indexPatterns = []; + const indexPatterns: IndexPattern[] = []; const getIndexPatternPromises = indexPatternIds.map(async (indexPatternId) => { try { const indexPattern = await getIndexPatternService().get(indexPatternId); @@ -81,16 +97,19 @@ export class FilterEditor extends Component { this.setState({ isPopoverOpen: false }); }; - _onQueryChange = ({ query }) => { + _onQueryChange = ({ query }: { query?: Query }) => { + if (!query) { + return; + } this.props.setLayerQuery(this.props.layer.getId(), query); this._close(); }; - _onApplyGlobalQueryChange = (applyGlobalQuery) => { + _onApplyGlobalQueryChange = (applyGlobalQuery: boolean) => { this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalQuery', applyGlobalQuery); }; - _onApplyGlobalTimeChange = (applyGlobalTime) => { + _onApplyGlobalTimeChange = (applyGlobalTime: boolean) => { this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalTime', applyGlobalTime); }; @@ -109,6 +128,7 @@ export class FilterEditor extends Component { >
) { return { - setLayerQuery: (layerId, query) => { + setLayerQuery: (layerId: string, query: Query) => { dispatch(setLayerQuery(layerId, query)); }, - updateSourceProp: (id, propName, value) => dispatch(updateSourceProp(id, propName, value)), + updateSourceProp: (id: string, propName: string, value: unknown) => + dispatch(updateSourceProp(id, propName, value)), }; } diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/flyout_footer/view.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx similarity index 92% rename from x-pack/plugins/maps/public/connected_components/layer_panel/flyout_footer/view.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx index 7070a875715f47..b55f9488e1f767 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/flyout_footer/view.js +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx @@ -10,12 +10,19 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +export interface Props { + cancelLayerPanel: () => void; + saveLayerEdits: () => void; + removeLayer: () => void; + hasStateChanged: boolean; +} + export const FlyoutFooter = ({ cancelLayerPanel, saveLayerEdits, removeLayer, hasStateChanged, -}) => { +}: Props) => { const removeBtn = ( { +const mapDispatchToProps = (dispatch: ThunkDispatch) => { return { cancelLayerPanel: () => { dispatch(updateFlyout(FLYOUT_STATE.NONE)); diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/index.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/index.ts similarity index 53% rename from x-pack/plugins/maps/public/connected_components/layer_panel/index.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/index.ts index d47ebc98a3fe3b..b78ffb3874e304 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/index.js +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/index.ts @@ -5,12 +5,16 @@ * 2.0. */ +import { AnyAction } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; -import { LayerPanel } from './view'; +import { EditLayerPanel } from './edit_layer_panel'; +import { LAYER_TYPE } from '../../../common/constants'; import { getSelectedLayer } from '../../selectors/map_selectors'; import { updateSourceProp } from '../../actions'; +import { MapStoreState } from '../../reducers/store'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { const selectedLayer = getSelectedLayer(state); return { key: selectedLayer ? `${selectedLayer.getId()}${selectedLayer.showJoinEditor()}` : '', @@ -18,12 +22,12 @@ function mapStateToProps(state = {}) { }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: ThunkDispatch) { return { - updateSourceProp: (id, propName, value, newLayerType) => + updateSourceProp: (id: string, propName: string, value: unknown, newLayerType?: LAYER_TYPE) => dispatch(updateSourceProp(id, propName, value, newLayerType)), }; } -const connectedLayerPanel = connect(mapStateToProps, mapDispatchToProps)(LayerPanel); -export { connectedLayerPanel as LayerPanel }; +const connected = connect(mapStateToProps, mapDispatchToProps)(EditLayerPanel); +export { connected as EditLayerPanel }; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/__snapshots__/join_editor.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/index.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/index.ts similarity index 96% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/index.tsx rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/index.ts index fb8e27205368a9..7971017a3d52b6 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/index.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/index.ts @@ -31,3 +31,4 @@ function mapDispatchToProps(dispatch: ThunkDispatch void; } diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/__snapshots__/metrics_expression.test.js.snap b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/__snapshots__/metrics_expression.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/__snapshots__/metrics_expression.test.js.snap rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/__snapshots__/metrics_expression.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/_join.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/_join.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/_join.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/_join.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.js diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join_expression.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join_expression.js diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.js diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.test.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.test.js diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/where_expression.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/where_expression.js diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/__snapshots__/attribution_form_row.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/__snapshots__/attribution_form_row.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/__snapshots__/attribution_form_row.test.tsx.snap rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/__snapshots__/attribution_form_row.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/_attribution_form_row.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/_attribution_form_row.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/_attribution_form_row.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/_attribution_form_row.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/_attribution_popover.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/_attribution_popover.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/_attribution_popover.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/_attribution_popover.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/_index.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/_index.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/_index.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/_index.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/attribution_form_row.test.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.test.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/attribution_form_row.test.tsx rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.test.tsx diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/attribution_form_row.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/attribution_form_row.tsx rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.tsx diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/attribution_popover.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_popover.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/attribution_popover.tsx rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_popover.tsx diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/index.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/index.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/index.tsx rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/index.tsx diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/_style_settings.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/_style_settings.scss diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/index.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/index.ts similarity index 62% rename from x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/index.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/index.ts index a25477880e4654..d52689cda141af 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/index.js +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/index.ts @@ -5,20 +5,24 @@ * 2.0. */ +import { AnyAction } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; import { StyleSettings } from './style_settings'; import { getSelectedLayer } from '../../../selectors/map_selectors'; import { updateLayerStyleForSelectedLayer } from '../../../actions'; +import { MapStoreState } from '../../../reducers/store'; +import { StyleDescriptor } from '../../../../common/descriptor_types'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { - layer: getSelectedLayer(state), + layer: getSelectedLayer(state)!, }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: ThunkDispatch) { return { - updateStyleDescriptor: (styleDescriptor) => { + updateStyleDescriptor: (styleDescriptor: StyleDescriptor) => { dispatch(updateLayerStyleForSelectedLayer(styleDescriptor)); }, }; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/style_settings.tsx similarity index 78% rename from x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js rename to x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/style_settings.tsx index b37e58925195c1..06e38079403d12 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/style_settings/style_settings.tsx @@ -10,8 +10,15 @@ import React, { Fragment } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { StyleDescriptor } from '../../../../common/descriptor_types'; +import { ILayer } from '../../../classes/layers/layer'; -export function StyleSettings({ layer, updateStyleDescriptor }) { +export interface Props { + layer: ILayer; + updateStyleDescriptor: (styleDescriptor: StyleDescriptor) => void; +} + +export function StyleSettings({ layer, updateStyleDescriptor }: Props) { const settingsEditor = layer.renderStyleEditor(updateStyleDescriptor); if (!settingsEditor) { diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap deleted file mode 100644 index 1fdc3a1bfdf178..00000000000000 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Should render errors when layer has errors 1`] = ` - - -

- simulated layer error -

-
- -
-`; - -exports[`should render nothing when layer has no errors 1`] = `""`; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/index.js b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/index.js deleted file mode 100644 index 932034cc19da62..00000000000000 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { connect } from 'react-redux'; -import { LayerErrors } from './layer_errors'; -import { getSelectedLayer } from '../../../selectors/map_selectors'; - -function mapStateToProps(state = {}) { - return { - layer: getSelectedLayer(state), - }; -} - -const connectedLayerErrors = connect(mapStateToProps, null)(LayerErrors); -export { connectedLayerErrors as LayerErrors }; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.js b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.js deleted file mode 100644 index 13c802faf79e76..00000000000000 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { Fragment } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; - -export function LayerErrors({ layer }) { - if (!layer.hasErrors()) { - return null; - } - - return ( - - -

{layer.getErrors()}

-
- -
- ); -} diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.test.js b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.test.js deleted file mode 100644 index 333a1b2bd1e4ee..00000000000000 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_errors/layer_errors.test.js +++ /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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; - -import { LayerErrors } from './layer_errors'; - -test('Should render errors when layer has errors', () => { - const mockLayer = { - hasErrors: () => { - return true; - }, - getErrors: () => { - return 'simulated layer error'; - }, - }; - const component = shallow(); - - expect(component).toMatchSnapshot(); -}); - -test('should render nothing when layer has no errors', () => { - const mockLayer = { - hasErrors: () => { - return false; - }, - }; - const component = shallow(); - - expect(component).toMatchSnapshot(); -}); diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/view.d.ts b/x-pack/plugins/maps/public/connected_components/layer_panel/view.d.ts deleted file mode 100644 index c1c55ebe6040ba..00000000000000 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/view.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* eslint-disable @typescript-eslint/consistent-type-definitions */ - -import { LAYER_TYPE } from '../../../common/constants'; - -export type OnSourceChangeArgs = { - propName: string; - value: unknown; - newLayerType?: LAYER_TYPE; -}; diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 851fd583b4251f..9128cebf480ed2 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -16,8 +16,7 @@ import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; import { MBMap } from '../mb_map'; import { RightSideControls } from '../right_side_controls'; import { ToolbarOverlay } from '../toolbar_overlay'; -// @ts-expect-error -import { LayerPanel } from '../layer_panel'; +import { EditLayerPanel } from '../edit_layer_panel'; import { AddLayerPanel } from '../add_layer_panel'; import { ExitFullScreenButton } from '../../../../../../src/plugins/kibana_react/public'; import { getIndexPatternsFromIds } from '../../index_pattern_util'; @@ -222,7 +221,7 @@ export class MapContainer extends Component { if (flyoutDisplay === FLYOUT_STATE.ADD_LAYER_WIZARD) { flyoutPanel = ; } else if (flyoutDisplay === FLYOUT_STATE.LAYER_PANEL) { - flyoutPanel = ; + flyoutPanel = ; } else if (flyoutDisplay === FLYOUT_STATE.MAP_SETTINGS_PANEL) { flyoutPanel = ; } diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index 6a31e25197210d..21b50aba440f5a 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -42,7 +42,7 @@ import { getTopNavConfig } from '../top_nav_config'; import { MapRefreshConfig, MapQuery } from '../../../../common/descriptor_types'; import { goToSpecifiedPath } from '../../../render_app'; import { MapSavedObjectAttributes } from '../../../../common/map_saved_object_type'; -import { getExistingMapPath } from '../../../../common/constants'; +import { getExistingMapPath, APP_ID } from '../../../../common/constants'; import { getInitialQuery, getInitialRefreshConfig, @@ -355,7 +355,7 @@ export class MapApp extends React.Component { return ( Date: Tue, 25 May 2021 13:59:21 -0700 Subject: [PATCH 02/18] Update dependency @elastic/charts to v29.2.0 (#100587) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 73f3e5585faf74..3cdde5e52584af 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "dependencies": { "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", - "@elastic/charts": "29.1.0", + "@elastic/charts": "29.2.0", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath/npm_module", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.4", "@elastic/ems-client": "7.13.0", diff --git a/yarn.lock b/yarn.lock index 9967cedea9fde1..1f09ede5e7900b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1366,10 +1366,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@29.1.0": - version "29.1.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-29.1.0.tgz#2850aa30d5e00aa8a1ab4974ea36f3c960a8e457" - integrity sha512-/nHT8niLtvSwX3dyEeIQWXEEZrB3xgjLIdlnqZhQXEdHqDQnxlehOMsTqWWws7jS/5uRq/sg+8N2z1xEb+odDw== +"@elastic/charts@29.2.0": + version "29.2.0" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-29.2.0.tgz#cd65887c0ca1d420493aee1b570c862ca0df5311" + integrity sha512-gj3Gew9zy8XPNEkAAznOjncO5AF63jy/X1k1VIcNPqdqMi07YYCZwCQjMzUVoS4RN6X4GSzxhrYfGAeyZP8gqg== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" From fea499c8ab651a6dfc7f38633651670287771dff Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 26 May 2021 00:58:43 +0100 Subject: [PATCH 03/18] refact(NA): remove extra pkg_npm target and add specific target folders for @kbn/analytics on Bazel (#100569) * refact(NA): remove extra pkg_npm target and add specific target folders on @kbn/analytics * chore(NA): update import on target_types --- packages/kbn-analytics/BUILD.bazel | 30 +++---------------- packages/kbn-analytics/package.json | 6 ++-- packages/kbn-analytics/tsconfig.browser.json | 2 +- packages/kbn-analytics/tsconfig.json | 4 +-- .../apis/ui_metric/ui_metric.ts | 2 +- 5 files changed, 11 insertions(+), 33 deletions(-) diff --git a/packages/kbn-analytics/BUILD.bazel b/packages/kbn-analytics/BUILD.bazel index 1749a3dcdc8160..9eeaf4dc5842e9 100644 --- a/packages/kbn-analytics/BUILD.bazel +++ b/packages/kbn-analytics/BUILD.bazel @@ -56,10 +56,10 @@ ts_project( srcs = SRCS, deps = DEPS, declaration = True, - declaration_dir = "types", + declaration_dir = "target_types", declaration_map = True, incremental = True, - out_dir = "node", + out_dir = "target_node", source_map = True, root_dir = "src", tsconfig = ":tsconfig", @@ -72,38 +72,16 @@ ts_project( deps = DEPS, declaration = False, incremental = True, - out_dir = "web", + out_dir = "target_web", source_map = True, root_dir = "src", tsconfig = ":tsconfig_browser", ) -filegroup( - name = "tsc_types", - srcs = [":tsc"], - output_group = "types", -) - -filegroup( - name = "target_files", - srcs = [ - ":tsc", - ":tsc_browser", - ":tsc_types", - ], -) - -pkg_npm( - name = "target", - deps = [ - ":target_files", - ], -) - js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = DEPS + [":target"], + deps = DEPS + [":tsc", ":tsc_browser"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json index 726b62e1c61b80..177c0eb8157605 100644 --- a/packages/kbn-analytics/package.json +++ b/packages/kbn-analytics/package.json @@ -3,9 +3,9 @@ "private": true, "version": "1.0.0", "description": "Kibana Analytics tool", - "main": "target/node/index.js", - "types": "target/types/index.d.ts", - "browser": "target/web/index.js", + "main": "target_node/index.js", + "types": "target_types/index.d.ts", + "browser": "target_web/index.js", "author": "Ahmad Bamieh ", "license": "SSPL-1.0 OR Elastic License 2.0" } \ No newline at end of file diff --git a/packages/kbn-analytics/tsconfig.browser.json b/packages/kbn-analytics/tsconfig.browser.json index 12f70b77008e75..8a65c551d3bc41 100644 --- a/packages/kbn-analytics/tsconfig.browser.json +++ b/packages/kbn-analytics/tsconfig.browser.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.browser.json", "compilerOptions": { "incremental": true, - "outDir": "./target/web", + "outDir": "./target_web", "stripInternal": true, "declaration": false, "isolatedModules": true, diff --git a/packages/kbn-analytics/tsconfig.json b/packages/kbn-analytics/tsconfig.json index 165a8b695db572..2eaa88cd3ce5f6 100644 --- a/packages/kbn-analytics/tsconfig.json +++ b/packages/kbn-analytics/tsconfig.json @@ -2,8 +2,8 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "incremental": true, - "declarationDir": "./target/types", - "outDir": "./target/node", + "declarationDir": "./target_types", + "outDir": "./target_node", "stripInternal": true, "declaration": true, "declarationMap": true, diff --git a/test/api_integration/apis/ui_metric/ui_metric.ts b/test/api_integration/apis/ui_metric/ui_metric.ts index 47d10da9a1b29c..3f0a4c07789117 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.ts +++ b/test/api_integration/apis/ui_metric/ui_metric.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { ReportManager, METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; -import { UserAgentMetric } from '@kbn/analytics/target/types/metrics/user_agent'; +import { UserAgentMetric } from '@kbn/analytics/target_types/metrics/user_agent'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { From a591af2856e78ca0d1f3a66a99fdcb68ba0c41f7 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 25 May 2021 20:33:43 -0500 Subject: [PATCH 04/18] [build] Clean jest configs (#100594) --- src/dev/build/tasks/clean_tasks.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev/build/tasks/clean_tasks.ts b/src/dev/build/tasks/clean_tasks.ts index 3051579d2e6f81..d4b4f98ed295b9 100644 --- a/src/dev/build/tasks/clean_tasks.ts +++ b/src/dev/build/tasks/clean_tasks.ts @@ -62,6 +62,7 @@ export const CleanExtraFilesFromModules: Task = { // tests '**/test', '**/tests', + '**/jest.config.js', '**/__tests__', '**/*.test.js', '**/*.snap', From 5ebded21058146e1d757d42ca90572fe6002fc87 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 25 May 2021 19:00:29 -0700 Subject: [PATCH 05/18] [triggersActionsUi] Reduce page load bundle to under 100kB (#97770) * [triggersActionsUi] Reduce page load bundle to under 100kB * removed old code * removed fragment * changed svg logo to lazy react components * fixed type checks and translations * fixed type checks * fixed type checks * fixed type checks * fixed tests * fixed tests * fixed iconClass * fixed due to comments * added info about new IconType to readme file * fixed key errors --- packages/kbn-optimizer/limits.yml | 2 +- .../connectors_dropdown.test.tsx | 40 ++++- .../public/components/connectors/types.ts | 3 +- .../translations/translations/ja-JP.json | 28 --- .../translations/translations/zh-CN.json | 28 --- x-pack/plugins/triggers_actions_ui/README.md | 36 ++-- .../application/action_type_registry.mock.ts | 4 +- .../public/application/app.tsx | 2 +- .../components/add_message_variables.tsx | 4 +- .../email/email_params.tsx | 10 +- .../builtin_action_types/jira/jira.tsx | 3 +- .../builtin_action_types/jira/jira_params.tsx | 6 +- .../builtin_action_types/jira/logo.svg | 9 - .../builtin_action_types/jira/logo.tsx | 35 ++++ .../builtin_action_types/pagerduty/logo.tsx | 37 ++++ .../pagerduty/pagerduty.svg | 13 -- .../pagerduty/pagerduty.test.tsx | 2 +- .../pagerduty/pagerduty.tsx | 3 +- .../pagerduty/pagerduty_connectors.tsx | 10 +- .../pagerduty/pagerduty_params.tsx | 6 +- .../resilient/{logo.svg => logo.tsx} | 23 ++- .../resilient/resilient.tsx | 3 +- .../resilient/resilient_params.tsx | 6 +- .../server_log/server_log_params.tsx | 6 +- .../builtin_action_types/servicenow/logo.svg | 5 - .../builtin_action_types/servicenow/logo.tsx | 33 ++++ .../servicenow/servicenow.tsx | 5 +- .../servicenow/servicenow_itsm_params.tsx | 6 +- .../servicenow/servicenow_sir_params.tsx | 6 +- .../slack/slack_connectors.tsx | 10 +- .../teams/{teams.svg => logo.tsx} | 42 ++++- .../builtin_action_types/teams/teams.tsx | 3 +- .../webhook/webhook_connectors.tsx | 10 +- .../application/components/health_check.tsx | 10 +- .../prompts/empty_connectors_prompt.tsx | 6 +- .../public/application/home.tsx | 52 +++--- .../lib/check_action_type_enabled.test.tsx | 2 +- .../lib/check_action_type_enabled.tsx | 8 +- .../action_connector_form.tsx | 6 +- .../action_form.test.tsx | 6 +- .../action_connector_form/action_form.tsx | 110 ++++++------ .../action_type_form.tsx | 162 +++++++++--------- .../connector_add_flyout.tsx | 23 +-- .../connector_add_inline.tsx | 28 +-- .../connector_add_modal.test.tsx | 2 +- .../connector_add_modal.tsx | 10 +- .../connector_edit_flyout.tsx | 33 ++-- .../sections/action_connector_form/index.ts | 8 + .../test_connector_form.tsx | 10 +- .../actions_connectors_list.test.tsx | 7 +- .../components/actions_connectors_list.tsx | 20 ++- .../components/alert_details.tsx | 14 +- .../components/alert_instances.tsx | 10 +- .../sections/alert_form/alert_add.test.tsx | 3 +- .../sections/alert_form/alert_add.tsx | 17 +- .../alert_form/alert_conditions_group.tsx | 4 +- .../sections/alert_form/alert_edit.test.tsx | 2 +- .../sections/alert_form/alert_edit.tsx | 24 +-- .../sections/alert_form/alert_form.test.tsx | 12 +- .../sections/alert_form/alert_form.tsx | 73 ++++---- .../sections/alert_form/alert_notify_when.tsx | 22 +-- .../alerts_list/components/alerts_list.tsx | 9 +- .../components/bulk_operation_popover.tsx | 4 +- .../common/expression_items/group_by_over.tsx | 6 +- .../common/expression_items/threshold.tsx | 2 +- .../public/common/get_add_alert_flyout.tsx | 12 +- .../common/get_add_connector_flyout.tsx | 14 +- .../public/common/get_edit_alert_flyout.tsx | 12 +- .../common/get_edit_connector_flyout.tsx | 14 +- .../triggers_actions_ui/public/index.ts | 17 +- .../triggers_actions_ui/public/mocks.ts | 13 +- .../triggers_actions_ui/public/plugin.ts | 13 +- .../triggers_actions_ui/public/types.ts | 50 +++++- 73 files changed, 696 insertions(+), 593 deletions(-) delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.svg create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/logo.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.svg rename x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/{logo.svg => logo.tsx} (77%) delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.svg create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.tsx rename x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/{teams.svg => logo.tsx} (91%) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index a585a0fc7542f5..2e76c26dd7b389 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -77,7 +77,7 @@ pageLoadAssetSize: tileMap: 65337 timelion: 29920 transform: 41007 - triggersActionsUi: 186732 + triggersActionsUi: 100000 uiActions: 97717 uiActionsEnhanced: 313011 upgradeAssistant: 81241 diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx index 0070bc18dfe121..86f80f772944aa 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx @@ -73,7 +73,14 @@ describe('ConnectorsDropdown', () => { >
@@ -96,7 +103,14 @@ describe('ConnectorsDropdown', () => { > @@ -119,7 +133,14 @@ describe('ConnectorsDropdown', () => { > @@ -142,7 +163,14 @@ describe('ConnectorsDropdown', () => { > @@ -182,7 +210,9 @@ describe('ConnectorsDropdown', () => { wrappingComponent: TestProviders, }); - expect(newWrapper.find('button span:not([data-euiicon-type])').text()).toEqual('My Connector'); + expect(newWrapper.find('button span:not([data-euiicon-type])').at(1).text()).toBe( + 'My Connector' + ); }); test('if the props hideConnectorServiceNowSir is true, the connector should not be part of the list of options ', () => { diff --git a/x-pack/plugins/cases/public/components/connectors/types.ts b/x-pack/plugins/cases/public/components/connectors/types.ts index fc2f66d331700c..1657153ab645bd 100644 --- a/x-pack/plugins/cases/public/components/connectors/types.ts +++ b/x-pack/plugins/cases/public/components/connectors/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { IconType } from '@elastic/eui/src/components/icon/icon'; import React from 'react'; import { @@ -26,7 +27,7 @@ export interface ThirdPartyField { export interface ConnectorConfiguration { name: string; - logo: string; + logo: IconType; } export interface CaseConnector { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a68efbf379ad45..417c8360d0df37 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -23651,19 +23651,6 @@ "xpack.triggersActionsUI.sections.alertEdit.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertEdit.saveErrorNotificationText": "ルールを更新できません", "xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText": "'{ruleName}'を更新しました", - "xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel": "削除", - "xpack.triggersActionsUI.sections.alertForm.actionDisabledTitle": "このアクションは無効です", - "xpack.triggersActionsUI.sections.alertForm.actionIdLabel": "{connectorInstance} コネクター", - "xpack.triggersActionsUI.sections.alertForm.actionRunWhenInActionGroup": "次のときに実行", - "xpack.triggersActionsUI.sections.alertForm.actionSectionsTitle": "アクション", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByConfigMessageTitle": "この機能は Kibana の構成で無効になっています。", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByLicenseLinkTitle": "ライセンスオプションを表示", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByLicenseMessageDescription": "このアクションを再び有効にするには、ライセンスをアップグレードしてください。", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByLicenseMessageTitle": "この機能には {minimumLicenseRequired} ライセンスが必要です。", - "xpack.triggersActionsUI.sections.alertForm.addActionButtonLabel": "アクションの追加", - "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "コネクターを作成する", - "xpack.triggersActionsUI.sections.alertForm.addNewActionConnectorActionGroup.display": "{actionGroupName} (現在サポートされていません) ", - "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "コネクターの追加", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名前", "xpack.triggersActionsUI.sections.alertForm.alertNotifyWhen.label": "毎", "xpack.triggersActionsUI.sections.alertForm.alertNotifyWhen.onActionGroupChange.description": "アラートステータスが変更されるときにアクションを実行します。", @@ -23681,37 +23668,22 @@ "xpack.triggersActionsUI.sections.alertForm.conditions.addConditionLabel": "追加:", "xpack.triggersActionsUI.sections.alertForm.conditions.removeConditionLabel": "削除", "xpack.triggersActionsUI.sections.alertForm.conditions.title": "条件:", - "xpack.triggersActionsUI.sections.alertForm.connectorAddInline.actionIdLabel": "別の{connectorInstance}コネクターを使用", - "xpack.triggersActionsUI.sections.alertForm.connectorAddInline.addNewConnectorEmptyButton": "コネクターの追加", "xpack.triggersActionsUI.sections.alertForm.documentationLabel": "ドキュメント", - "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "{actionTypeName}コネクターがありません", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedRuleTypes": "ルールを{operation}するには、適切な権限が付与されている必要があります。", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedRuleTypesTitle": "ルールタイプを{operation}する権限がありません。", "xpack.triggersActionsUI.sections.alertForm.error.requiredActionConnector": "{actionTypeId}コネクターのアクションが必要です。", "xpack.triggersActionsUI.sections.alertForm.error.requiredIntervalText": "確認間隔が必要です。", "xpack.triggersActionsUI.sections.alertForm.error.requiredNameText": "名前が必要です。", "xpack.triggersActionsUI.sections.alertForm.error.requiredRuleTypeIdText": "ルールタイプは必須です。", - "xpack.triggersActionsUI.sections.alertForm.existingAlertActionTypeEditTitle": "{actionConnectorName}", - "xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription": "コネクターを読み込んでいます…", - "xpack.triggersActionsUI.sections.alertForm.loadingConnectorTypesDescription": "コネクタータイプを読み込んでいます...", "xpack.triggersActionsUI.sections.alertForm.loadingRuleTypeParamsDescription": "ルールタイプパラメーターを読み込んでいます…", "xpack.triggersActionsUI.sections.alertForm.loadingRuleTypesDescription": "ルールタイプを読み込んでいます…", - "xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle": "{actionConnectorName}", - "xpack.triggersActionsUI.sections.alertForm.preconfiguredTitleMessage": "構成済み", "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "ルールがアクティブな間にアクションを繰り返す頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.ruleTypeSelectLabel": "ルールタイプを選択", "xpack.triggersActionsUI.sections.alertForm.searchPlaceholderTitle": "検索", - "xpack.triggersActionsUI.sections.alertForm.selectConnectorTypeTitle": "コネクタータイプを選択", "xpack.triggersActionsUI.sections.alertForm.solutionFilterLabel": "ユースケースでフィルタリング", "xpack.triggersActionsUI.sections.alertForm.tagsFieldLabel": "タグ (任意) ", - "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "デフォルトアクショングループの定義がないのでアクションを追加できません", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "コネクターを読み込めません", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTitle": "コネクターを読み込めません。", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTitle'": "コネクターを読み込めません。", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTypesMessage": "コネクタータイプを読み込めません", "xpack.triggersActionsUI.sections.alertForm.unableToLoadRuleTypesMessage": "ルールタイプを読み込めません", - "xpack.triggersActionsUI.sections.alertForm.unauthorizedToCreateForEmptyConnectors": "許可されたユーザーのみがコネクターを構成できます。管理者にお問い合わせください。", "xpack.triggersActionsUI.sections.alertsList.actionTypeFilterLabel": "アクションタイプ", "xpack.triggersActionsUI.sections.alertsList.addRuleButtonLabel": "ルールを作成", "xpack.triggersActionsUI.sections.alertsList.alertErrorReasonDecrypting": "ルールの復号中にエラーが発生しました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 233bafeb71f3aa..8f0694e02ad91b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -24016,19 +24016,6 @@ "xpack.triggersActionsUI.sections.alertEdit.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertEdit.saveErrorNotificationText": "无法更新规则。", "xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText": "已更新“{ruleName}”", - "xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel": "删除", - "xpack.triggersActionsUI.sections.alertForm.actionDisabledTitle": "此操作已禁用", - "xpack.triggersActionsUI.sections.alertForm.actionIdLabel": "{connectorInstance} 连接器", - "xpack.triggersActionsUI.sections.alertForm.actionRunWhenInActionGroup": "运行条件", - "xpack.triggersActionsUI.sections.alertForm.actionSectionsTitle": "操作", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByConfigMessageTitle": "此功能已由 Kibana 配置禁用。", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByLicenseLinkTitle": "查看许可证选项", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByLicenseMessageDescription": "要重新启用此操作,请升级您的许可证。", - "xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByLicenseMessageTitle": "此功能需要{minimumLicenseRequired}许可证。", - "xpack.triggersActionsUI.sections.alertForm.addActionButtonLabel": "添加操作", - "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "创建连接器", - "xpack.triggersActionsUI.sections.alertForm.addNewActionConnectorActionGroup.display": "{actionGroupName} (当前不支持) ", - "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "添加连接器", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名称", "xpack.triggersActionsUI.sections.alertForm.alertNotifyWhen.label": "每", "xpack.triggersActionsUI.sections.alertForm.alertNotifyWhen.onActionGroupChange.description": "操作在告警状态更改时运行。", @@ -24046,37 +24033,22 @@ "xpack.triggersActionsUI.sections.alertForm.conditions.addConditionLabel": "添加:", "xpack.triggersActionsUI.sections.alertForm.conditions.removeConditionLabel": "移除", "xpack.triggersActionsUI.sections.alertForm.conditions.title": "条件:", - "xpack.triggersActionsUI.sections.alertForm.connectorAddInline.actionIdLabel": "使用其他 {connectorInstance} 连接器", - "xpack.triggersActionsUI.sections.alertForm.connectorAddInline.addNewConnectorEmptyButton": "添加连接器", "xpack.triggersActionsUI.sections.alertForm.documentationLabel": "文档", - "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "无 {actionTypeName} 连接器", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedRuleTypes": "为了{operation}规则,您需要获得相应的权限。", "xpack.triggersActionsUI.sections.alertForm.error.noAuthorizedRuleTypesTitle": "您尚无权{operation}任何规则类型", "xpack.triggersActionsUI.sections.alertForm.error.requiredActionConnector": "“{actionTypeId} 连接器的操作”必填。", "xpack.triggersActionsUI.sections.alertForm.error.requiredIntervalText": "“检查时间间隔”必填。", "xpack.triggersActionsUI.sections.alertForm.error.requiredNameText": "“名称”必填。", "xpack.triggersActionsUI.sections.alertForm.error.requiredRuleTypeIdText": "“规则类型”必填。", - "xpack.triggersActionsUI.sections.alertForm.existingAlertActionTypeEditTitle": "{actionConnectorName}", - "xpack.triggersActionsUI.sections.alertForm.loadingConnectorsDescription": "正在加载连接器……", - "xpack.triggersActionsUI.sections.alertForm.loadingConnectorTypesDescription": "正在加载连接器类型……", "xpack.triggersActionsUI.sections.alertForm.loadingRuleTypeParamsDescription": "正在加载规则类型参数……", "xpack.triggersActionsUI.sections.alertForm.loadingRuleTypesDescription": "正在加载规则类型……", - "xpack.triggersActionsUI.sections.alertForm.newAlertActionTypeEditTitle": "{actionConnectorName}", - "xpack.triggersActionsUI.sections.alertForm.preconfiguredTitleMessage": "预配置", "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义规则处于活动状态时重复操作的频率。", "xpack.triggersActionsUI.sections.alertForm.ruleTypeSelectLabel": "选择规则类型", "xpack.triggersActionsUI.sections.alertForm.searchPlaceholderTitle": "搜索", - "xpack.triggersActionsUI.sections.alertForm.selectConnectorTypeTitle": "选择连接器类型", "xpack.triggersActionsUI.sections.alertForm.solutionFilterLabel": "按用例筛选", "xpack.triggersActionsUI.sections.alertForm.tagsFieldLabel": "标签 (可选) ", - "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "无法添加操作,因为未定义默认操作组", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "无法加载连接器", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTitle": "无法加载连接器。", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTitle'": "无法加载连接器。", - "xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTypesMessage": "无法加载连接器类型", "xpack.triggersActionsUI.sections.alertForm.unableToLoadRuleTypesMessage": "无法加载规则类型", - "xpack.triggersActionsUI.sections.alertForm.unauthorizedToCreateForEmptyConnectors": "只有获得授权的用户才能配置连接器。请联系您的管理员。", "xpack.triggersActionsUI.sections.alertsList.actionTypeFilterLabel": "操作类型", "xpack.triggersActionsUI.sections.alertsList.addRuleButtonLabel": "创建规则", "xpack.triggersActionsUI.sections.alertsList.alertErrorReasonDecrypting": "解密规则时发生错误。", diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index dc1da533af374b..7d736218af2d99 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -206,13 +206,13 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent + <> {hasExpressionErrors ? ( - + <> - + ) : null} @@ -221,7 +221,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent .... - + ); }; @@ -322,7 +322,7 @@ Fields of this object `AlertTypeModel` will be mapped properly in the UI below. 2. Define `alertParamsExpression` as `React.FunctionComponent` - this is the form for filling Alert params based on the current Alert type. ``` -import React, { Fragment, useState } from 'react'; +import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { WhenExpression, OfExpression } from '../../../../common/expression_items'; import { builtInAggregationTypes } from '../../../../common/constants'; @@ -340,7 +340,7 @@ export const ExampleExpression: React.FunctionComponent = ({ }) => { const [aggType, setAggType] = useState('count'); return ( - + <> = ({ ) : null} - + ); }; @@ -653,7 +653,7 @@ const ThresholdSpecifier = ( }) => { if (!actionGroup) { // render empty if no condition action group is specified - return ; + return null; } return ( @@ -1073,7 +1073,7 @@ Action type model definition: export function getActionType(): ActionTypeModel { return { id: '.pagerduty', - iconClass: 'apps', + iconClass: lazy(() => import('./logo')), selectMessage: i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText', { @@ -1110,7 +1110,7 @@ and action params form available in Create Alert form: Each action type should be defined as an `ActionTypeModel` object with the following properties: ``` id: string; - iconClass: string; + iconClass: IconType; selectMessage: string; actionTypeTitle?: string; validateConnector: (connector: any) => ValidationResult; @@ -1121,7 +1121,7 @@ Each action type should be defined as an `ActionTypeModel` object with the follo |Property|Description| |---|---| |id|Action type id. Should be the same as on server side.| -|iconClass|Icon of action type, that will be displayed on the select card in UI.| +|iconClass|Setting for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or a lazy loaded React component, ReactElement.| |selectMessage|Short description of action type responsibility, that will be displayed on the select card in UI.| |validateConnector|Validation function for action connector.| |validateParams|Validation function for action params.| @@ -1157,7 +1157,7 @@ Below is a list of steps that should be done to build and register a new action 1. At any suitable place in Kibana, create a file, which will expose an object implementing interface [ActionTypeModel]: ``` -import React, { Fragment, lazy } from 'react'; +import React, { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { ActionTypeModel, @@ -1230,7 +1230,7 @@ export function getActionType(): ActionTypeModel { 2. Define `actionConnectorFields` as `React.FunctionComponent` - this is the form for action connector. ``` -import React, { Fragment } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFieldText } from '@elastic/eui'; import { EuiTextArea } from '@elastic/eui'; @@ -1252,7 +1252,7 @@ const ExampleConnectorFields: React.FunctionComponent> = ({ action, editActionConfig, errors }) => { const { someConnectorField } = action.config; return ( - + <> 0 && someConnectorField !== undefined} @@ -1267,7 +1267,7 @@ const ExampleConnectorFields: React.FunctionComponent - + ); }; @@ -1277,7 +1277,7 @@ export {ExampleConnectorFields as default}; 3. Define action type params fields using the property of `ActionTypeModel` `actionParamsFields`: ``` -import React, { Fragment } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFieldText } from '@elastic/eui'; import { EuiTextArea } from '@elastic/eui'; @@ -1300,7 +1300,7 @@ const ExampleParamsFields: React.FunctionComponent { const { message } = actionParams; return ( - + <> 0 && message !== undefined} @@ -1315,7 +1315,7 @@ const ExampleParamsFields: React.FunctionComponent - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts b/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts index c1d7a3281b410e..3c5c1b551028ea 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import React, { lazy, Fragment } from 'react'; +import React, { lazy } from 'react'; import uuid from 'uuid'; import { ActionTypeModel, ActionTypeRegistryContract } from '../types'; @@ -21,7 +21,7 @@ const createActionTypeRegistryMock = () => { const mockedActionParamsFields = lazy(async () => ({ default() { - return React.createElement(Fragment); + return React.createElement(React.Fragment); }, })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 2e0614e7ea9637..5acb99c684a961 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -26,7 +26,7 @@ import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common import { setSavedObjectsClient } from '../common/lib/data_apis'; import { KibanaContextProvider } from '../common/lib/kibana'; -const TriggersActionsUIHome = lazy(async () => import('./home')); +const TriggersActionsUIHome = lazy(() => import('./home')); const AlertDetailsRoute = lazy( () => import('./sections/alert_details/components/alert_details_route') ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx index 57b251fba0d454..2f220855f94743 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, Fragment } from 'react'; +import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiPopover, @@ -62,7 +62,7 @@ export const AddMessageVariables: React.FunctionComponent = ({ ); if ((messageVariables?.length ?? 0) === 0) { - return ; + return <>; } return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.tsx index 2c25220598b03e..e2d6237af85da2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiComboBox, EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -46,7 +46,7 @@ export const EmailParamsFields = ({ }, [defaultMessage]); return ( - + <> + <> {!addCC ? ( setAddCC(true)}> @@ -77,7 +77,7 @@ export const EmailParamsFields = ({ ) : null} - + } > - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx index ba6a5fa2079dca..ff7fd026f8e31b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx @@ -11,7 +11,6 @@ import { ActionTypeModel, ConnectorValidationResult, } from '../../../../types'; -import logo from './logo.svg'; import { JiraActionConnector, JiraConfig, JiraSecrets, JiraActionParams } from './types'; import * as i18n from './translations'; import { isValidUrl } from '../../../lib/value_validators'; @@ -63,7 +62,7 @@ const validateConnector = ( export function getActionType(): ActionTypeModel { return { id: '.jira', - iconClass: logo, + iconClass: lazy(() => import('./logo')), selectMessage: i18n.JIRA_DESC, actionTypeTitle: i18n.JIRA_TITLE, validateConnector, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx index cb2d637972cb82..11123a81440bba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -190,7 +190,7 @@ const JiraParamsFields: React.FunctionComponent + <> <> - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.svg b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.svg deleted file mode 100644 index 8560cf7e270c89..00000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.tsx new file mode 100644 index 00000000000000..2e8f1d5ef3bd70 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/logo.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +const Logo = () => ( + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export { Logo as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/logo.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/logo.tsx new file mode 100644 index 00000000000000..20db34351c6b12 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/logo.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +const Logo = () => ( + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export { Logo as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.svg b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.svg deleted file mode 100644 index b11dcb3570c266..00000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx index 7a60d79d331379..eae8690dbdd988 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx @@ -25,7 +25,7 @@ beforeAll(() => { describe('actionTypeRegistry.get() works', () => { test('action type static data is as expected', () => { expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('test-file-stub'); + expect(actionTypeModel.actionTypeTitle).toEqual('Send to PagerDuty'); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx index cae4221e5d7ce9..310c5cae24566c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx @@ -20,7 +20,6 @@ import { PagerDutyActionParams, EventActionOptions, } from '.././types'; -import pagerDutySvg from './pagerduty.svg'; import { hasMustacheTokens } from '../../../lib/has_mustache_tokens'; export function getActionType(): ActionTypeModel< @@ -30,7 +29,7 @@ export function getActionType(): ActionTypeModel< > { return { id: '.pagerduty', - iconClass: pagerDutySvg, + iconClass: lazy(() => import('./logo')), selectMessage: i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx index 8c9f809b974477..7e9a5770c2158e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -21,7 +21,7 @@ const PagerDutyActionConnectorFields: React.FunctionComponent< const { apiUrl } = action.config; const { routingKey } = action.secrets; return ( - + <> - + <> {getEncryptedFieldNotifyLabel( !action.id, 1, @@ -94,9 +94,9 @@ const PagerDutyActionConnectorFields: React.FunctionComponent< } }} /> - + - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx index 98dd9c6bf84315..4961a27fd0ac15 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isUndefined } from 'lodash'; @@ -102,7 +102,7 @@ const PagerDutyParamsFields: React.FunctionComponent + <> ) : null} - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.svg b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.tsx similarity index 77% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.svg rename to x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.tsx index 553c2c62b7191c..325893756e2f42 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.svg +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/logo.tsx @@ -1,3 +1,20 @@ - - - +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +const Logo = () => ( + + + +); + +// eslint-disable-next-line import/no-default-export +export { Logo as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx index a8fe5e8ae4b6ae..e7074b7506e7a6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx @@ -11,7 +11,6 @@ import { ActionTypeModel, ConnectorValidationResult, } from '../../../../types'; -import logo from './logo.svg'; import { ResilientActionConnector, ResilientConfig, @@ -72,7 +71,7 @@ export function getActionType(): ActionTypeModel< > { return { id: '.resilient', - iconClass: logo, + iconClass: lazy(() => import('./logo')), selectMessage: i18n.DESC, actionTypeTitle: i18n.TITLE, validateConnector, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx index 8444f5a2c5ca91..4642226d402221 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { EuiFormRow, EuiComboBox, @@ -165,7 +165,7 @@ const ResilientParamsFields: React.FunctionComponent + <>

Incident

@@ -251,7 +251,7 @@ const ResilientParamsFields: React.FunctionComponent - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx index 2a6d21f31973b9..6397ce7bc184ea 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSelect, EuiFormRow } from '@elastic/eui'; import { ActionParamsProps } from '../../../../types'; @@ -48,7 +48,7 @@ export const ServerLogParamsFields: React.FunctionComponent< }, [defaultMessage]); return ( - + <> - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.svg b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.svg deleted file mode 100644 index dcd022a8dca18d..00000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.tsx new file mode 100644 index 00000000000000..f7f79d387c62c4 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/logo.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +function Logo() { + return ( + + + + + + ); +} + +// eslint-disable-next-line import/no-default-export +export default Logo; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx index b1664656c0d14d..a6cc116d3d7b4b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx @@ -11,7 +11,6 @@ import { ActionTypeModel, ConnectorValidationResult, } from '../../../../types'; -import logo from './logo.svg'; import { ServiceNowActionConnector, ServiceNowConfig, @@ -68,7 +67,7 @@ export function getServiceNowITSMActionType(): ActionTypeModel< > { return { id: '.servicenow', - iconClass: logo, + iconClass: lazy(() => import('./logo')), selectMessage: i18n.SERVICENOW_ITSM_DESC, actionTypeTitle: i18n.SERVICENOW_ITSM_TITLE, validateConnector, @@ -103,7 +102,7 @@ export function getServiceNowSIRActionType(): ActionTypeModel< > { return { id: '.servicenow-sir', - iconClass: logo, + iconClass: lazy(() => import('./logo')), selectMessage: i18n.SERVICENOW_SIR_DESC, actionTypeTitle: i18n.SERVICENOW_SIR_TITLE, validateConnector, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx index 84326a7ae9be8b..dbd6fec3dad190 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { EuiFormRow, EuiSelect, @@ -146,7 +146,7 @@ const ServiceNowParamsFields: React.FunctionComponent< }, [actionParams]); return ( - + <>

{i18n.INCIDENT}

@@ -270,7 +270,7 @@ const ServiceNowParamsFields: React.FunctionComponent< inputTargetValue={comments && comments.length > 0 ? comments[0].comment : undefined} label={i18n.COMMENTS_LABEL} /> -
+ ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx index 95a17c205801c4..be6756b1c1049d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { EuiFormRow, EuiSelect, @@ -142,7 +142,7 @@ const ServiceNowSIRParamsFields: React.FunctionComponent< }, [actionParams]); return ( - + <>

{i18n.INCIDENT}

@@ -276,7 +276,7 @@ const ServiceNowSIRParamsFields: React.FunctionComponent< inputTargetValue={comments && comments.length > 0 ? comments[0].comment : undefined} label={i18n.COMMENTS_LABEL} /> -
+ ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx index 677eb8d7d05f95..ce6cda1294adc7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -21,7 +21,7 @@ const SlackActionFields: React.FunctionComponent< const { webhookUrl } = action.secrets; return ( - + <> - + <> {getEncryptedFieldNotifyLabel( !action.id, 1, @@ -68,9 +68,9 @@ const SlackActionFields: React.FunctionComponent< } }} /> - + - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.svg b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/logo.tsx similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.svg rename to x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/logo.tsx index ab07be8f1ef0a8..42b1fa2c2a0da3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.svg +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/logo.tsx @@ -1,7 +1,32 @@ - - - + - +MS0xMlQxOTo1Nzo0NSswMzowMIKUWWYAAAAASUVORK5CYII=" + /> + +); + +// eslint-disable-next-line import/no-default-export +export { Logo as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.tsx index 00d860fc54110b..e8c7be7311c1ce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.tsx @@ -7,7 +7,6 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import teamsSvg from './teams.svg'; import { ActionTypeModel, GenericValidationResult, @@ -19,7 +18,7 @@ import { isValidUrl } from '../../../lib/value_validators'; export function getActionType(): ActionTypeModel { return { id: '.teams', - iconClass: teamsSvg, + iconClass: lazy(() => import('./logo')), selectMessage: i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.selectMessageText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx index 9a93d29cfcb155..d3231f52b4d7bf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -110,7 +110,7 @@ const WebhookActionConnectorFields: React.FunctionComponent< let headerControl; if (hasHeaders) { headerControl = ( - + <>
- + ); } @@ -220,7 +220,7 @@ const WebhookActionConnectorFields: React.FunctionComponent< }); return ( - + <>
- + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index d75ab102a8e0ca..762526dfd7fa78 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { Option, none, some, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -69,16 +69,16 @@ export const HealthCheck: React.FunctionComponent = ({ fold( () => waitForCheck ? ( - + <> - + ) : ( - {children} + <>{children} ), (healthCheck) => { return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? ( - {children} + <>{children} ) : !healthCheck.isAlertsAvailable ? ( ) : !healthCheck.isSufficientlySecure && !healthCheck.hasPermanentEncryptionKey ? ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx index e56fad409d98f4..84ac46605905e5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx @@ -6,7 +6,7 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React, { Fragment } from 'react'; +import React from 'react'; import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiSpacer, EuiTitle } from '@elastic/eui'; import './empty_connectors_prompt.scss'; @@ -14,7 +14,7 @@ export const EmptyConnectorsPrompt = ({ onCTAClicked }: { onCTAClicked: () => vo + <> @@ -27,7 +27,7 @@ export const EmptyConnectorsPrompt = ({ onCTAClicked }: { onCTAClicked: () => vo /> - + } body={

diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index b77593c9905509..20aec6974d395b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React, { lazy, useEffect } from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -26,11 +26,15 @@ import { getAlertingSectionBreadcrumb } from './lib/breadcrumb'; import { getCurrentDocTitle } from './lib/doc_title'; import { hasShowActionsCapability } from './lib/capabilities'; -import { ActionsConnectorsList } from './sections/actions_connectors_list/components/actions_connectors_list'; -import { AlertsList } from './sections/alerts_list/components/alerts_list'; import { HealthCheck } from './components/health_check'; import { HealthContextProvider } from './context/health_context'; import { useKibana } from '../common/lib/kibana'; +import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; + +const ActionsConnectorsList = lazy( + () => import('./sections/actions_connectors_list/components/actions_connectors_list') +); +const AlertsList = lazy(() => import('./sections/alerts_list/components/alerts_list')); export interface MatchParams { section: Section; @@ -137,32 +141,24 @@ export const TriggersActionsUIHome: React.FunctionComponent - - {canShowActions && ( - ( - - - - - + + + + {canShowActions && ( + )} - /> - )} - ( - - - - - - )} - /> - + + + + ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx index f9855cc9d71308..6b115abc590ccc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx @@ -61,7 +61,7 @@ describe('checkActionTypeEnabled', () => { > diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx index e4e67002298ee7..1c2a56f4cccaad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx @@ -39,7 +39,7 @@ const getLicenseCheckResult = (actionType: ActionType) => { { // The "re-enable" terminology is used here because this message is used when an alert // action was previously enabled and needs action to be re-enabled. description={i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.actionTypeDisabledByLicenseMessageDescription', + 'xpack.triggersActionsUI.licenseCheck.actionTypeDisabledByLicenseMessageDescription', { defaultMessage: 'To re-enable this action, please upgrade your license.' } )} className="actCheckActionTypeEnabled__disabledActionWarningCard" @@ -58,7 +58,7 @@ const getLicenseCheckResult = (actionType: ActionType) => { } @@ -76,7 +76,7 @@ const configurationCheckResult = { messageCard: ( + <> - + ); const FieldsComponent = actionTypeRegistered.actionConnectorFields; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 174407e7edec50..ad727be58280f4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, lazy } from 'react'; +import React, { lazy } from 'react'; import { mountWithIntl, nextTick } from '@kbn/test/jest'; import { EuiAccordion } from '@elastic/eui'; import { coreMock } from '../../../../../../../src/core/public/mocks'; @@ -34,7 +34,7 @@ const setHasActionsWithBrokenConnector = jest.fn(); describe('action_form', () => { const mockedActionParamsFields = lazy(async () => ({ default() { - return ; + return <>; }, })); @@ -45,7 +45,7 @@ describe('action_form', () => { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: () => <>, requiresAppContext: false, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 55ebbbc6f3edd1..e9f79633ef5207 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -30,7 +30,6 @@ import { ActionTypeRegistryContract, } from '../../../types'; import { SectionLoading } from '../../components/section_loading'; -import { ConnectorAddModal } from './connector_add_modal'; import { ActionTypeForm, ActionTypeFormProps } from './action_type_form'; import { AddConnectorInline } from './connector_add_inline'; import { actionTypeCompare } from '../../lib/action_type_compare'; @@ -43,6 +42,8 @@ import { import { ActionGroup, AlertActionParam } from '../../../../../alerting/common'; import { useKibana } from '../../../common/lib/kibana'; import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params'; +import { ConnectorAddModal } from '.'; +import { suspendedComponentWithProps } from '../../lib/suspended_component_with_props'; export interface ActionGroupWithMessageVariables extends ActionGroup { omitOptionalMessageVariables?: boolean; @@ -124,7 +125,7 @@ export const ActionForm = ({ } catch (e) { toasts.addDanger({ title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.unableToLoadConnectorTypesMessage', + 'xpack.triggersActionsUI.sections.actionForm.unableToLoadConnectorTypesMessage', { defaultMessage: 'Unable to load connector types' } ), }); @@ -145,7 +146,7 @@ export const ActionForm = ({ } catch (e) { toasts.addDanger({ title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage', + 'xpack.triggersActionsUI.sections.actionForm.unableToLoadActionsMessage', { defaultMessage: 'Unable to load connectors', } @@ -193,7 +194,7 @@ export const ActionForm = ({ function addActionType(actionTypeModel: ActionTypeModel) { if (!defaultActionGroupId) { toasts!.addDanger({ - title: i18n.translate('xpack.triggersActionsUI.sections.alertForm.unableToAddAction', { + title: i18n.translate('xpack.triggersActionsUI.sections.actionForm.unableToAddAction', { defaultMessage: 'Unable to add action, because default action group is not defined', }), }); @@ -271,7 +272,14 @@ export const ActionForm = ({ label={actionTypesIndex[item.id].name} onClick={() => addActionType(item)} > - + ); @@ -291,17 +299,17 @@ export const ActionForm = ({ return isLoadingConnectors ? ( ) : ( - + <>

@@ -354,54 +362,56 @@ export const ActionForm = ({ ?.validateParams(actionItem.params); return ( - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, indices: [index] }); - setAddModalVisibility(true); - }} - onConnectorSelected={(id: string) => { - setActionIdByIndex(id, index); - }} - actionTypeRegistry={actionTypeRegistry} - onDeleteAction={() => { - const updatedActions = actions.filter( - (_item: AlertAction, i: number) => i !== index - ); - setActions(updatedActions); - setIsAddActionPanelOpen( - updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === - 0 - ); - setActiveActionItem(undefined); - }} - /> + + { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, indices: [index] }); + setAddModalVisibility(true); + }} + onConnectorSelected={(id: string) => { + setActionIdByIndex(id, index); + }} + actionTypeRegistry={actionTypeRegistry} + onDeleteAction={() => { + const updatedActions = actions.filter( + (_item: AlertAction, i: number) => i !== index + ); + setActions(updatedActions); + setIsAddActionPanelOpen( + updatedActions.filter((item: AlertAction) => item.id !== actionItem.id) + .length === 0 + ); + setActiveActionItem(undefined); + }} + /> + + ); })} {isAddActionPanelOpen ? ( - + <>
@@ -431,7 +441,7 @@ export const ActionForm = ({ {isLoadingActionTypes ? ( @@ -439,7 +449,7 @@ export const ActionForm = ({ actionTypeNodes )}
-
+ ) : ( @@ -449,7 +459,7 @@ export const ActionForm = ({ onClick={() => setIsAddActionPanelOpen(true)} > @@ -468,7 +478,7 @@ export const ActionForm = ({ actionTypeRegistry={actionTypeRegistry} /> ) : null} - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index 48c6c1b42d7af7..2690aeaffad324 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, Suspense, useEffect, useState } from 'react'; +import React, { Suspense, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -166,7 +166,7 @@ export const ActionTypeForm = ({ isActionGroupDisabledForActionType ? isActionGroupDisabledForActionType(actionGroupId, actionTypeId) ? i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.addNewActionConnectorActionGroup.display', + 'xpack.triggersActionsUI.sections.actionTypeForm.addNewActionConnectorActionGroup.display', { defaultMessage: '{actionGroupName} (Not Currently Supported)', values: { actionGroupName }, @@ -202,9 +202,9 @@ export const ActionTypeForm = ({ ); const accordionContent = checkEnabledResult.isEnabled ? ( - + <> {actionGroups && selectedActionGroup && setActionGroupIdByIndex && ( - + <> @@ -240,7 +240,7 @@ export const ActionTypeForm = ({ - + )} @@ -248,7 +248,7 @@ export const ActionTypeForm = ({ fullWidth label={ ) : null @@ -305,88 +305,86 @@ export const ActionTypeForm = ({ ) : null} - + ) : ( checkEnabledResult.messageCard ); return ( - - - - - - - -
- + + + + + + +
+ + + + + {selectedActionGroup && !isOpen && ( - + {selectedActionGroup.name} - {selectedActionGroup && !isOpen && ( - - {selectedActionGroup.name} - + )} + + {checkEnabledResult.isEnabled === false && ( + <> + + )} - - {checkEnabledResult.isEnabled === false && ( - - - - )} - - -
-
-
-
- } - extraAction={ - - } - > - {accordionContent} - - - + + +
+
+
+
+ } + extraAction={ + + } + > + {accordionContent} + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 6dc75b318a8f07..d3a6d662720ca9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useState, Fragment, useReducer } from 'react'; +import React, { useCallback, useState, useReducer } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -29,8 +29,8 @@ import { ActionConnectorForm, getConnectorErrors } from './action_connector_form import { ActionType, ActionConnector, - ActionTypeRegistryContract, UserConfiguredActionConnector, + ConnectorAddFlyoutProps, } from '../../../types'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { createActionConnector } from '../../lib/action_connector_api'; @@ -39,15 +39,6 @@ import { useKibana } from '../../../common/lib/kibana'; import { createConnectorReducer, InitialConnector, ConnectorReducer } from './connector_reducer'; import { getConnectorWithInvalidatedFields } from '../../lib/value_validators'; -export interface ConnectorAddFlyoutProps { - onClose: () => void; - actionTypes?: ActionType[]; - onTestConnector?: (connector: ActionConnector) => void; - reloadConnectors?: () => Promise; - consumer?: string; - actionTypeRegistry: ActionTypeRegistryContract; -} - const ConnectorAddFlyout: React.FunctionComponent = ({ onClose, actionTypes, @@ -199,7 +190,7 @@ const ConnectorAddFlyout: React.FunctionComponent = ({ }; saveButton = ( - + <> {onTestConnector && ( = ({ /> - + ); } @@ -251,7 +242,7 @@ const ConnectorAddFlyout: React.FunctionComponent = ({ ) : null} {actionTypeModel && actionType ? ( - + <>

= ({ {actionTypeModel.selectMessage} - + ) : (

@@ -285,7 +276,7 @@ const ConnectorAddFlyout: React.FunctionComponent = ({ !actionType && hasActionsUpgradeableByTrial ? ( ) : ( - + <> ) } > diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx index 9a9583313bcdb7..0cdcf8bd44413b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -68,7 +68,7 @@ export const AddConnectorInline = ({ const noConnectorsLabel = ( @@ -117,7 +117,7 @@ export const AddConnectorInline = ({ fullWidth label={ } @@ -162,8 +162,9 @@ export const AddConnectorInline = ({ ); return ( - + <> } /> @@ -211,7 +212,7 @@ export const AddConnectorInline = ({ color="danger" className="actAccordionActionForm__extraAction" aria-label={i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel', + 'xpack.triggersActionsUI.sections.connectorAddInline.accordion.deleteIconAriaLabel', { defaultMessage: 'Delete', } @@ -236,7 +237,7 @@ export const AddConnectorInline = ({ onClick={onAddConnector} > @@ -247,7 +248,7 @@ export const AddConnectorInline = ({

@@ -255,6 +256,9 @@ export const AddConnectorInline = ({ )}
-
+ ); }; + +// eslint-disable-next-line import/no-default-export +export { AddConnectorInline as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx index ccff4f5853b1b6..c18f6955d12177 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { mountWithIntl } from '@kbn/test/jest'; -import { ConnectorAddModal } from './connector_add_modal'; +import ConnectorAddModal from './connector_add_modal'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ActionType, ConnectorValidationResult, GenericValidationResult } from '../../../types'; import { useKibana } from '../../../common/lib/kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 8732727b9a77a8..d01ee08df23946 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -35,15 +35,16 @@ import { import { useKibana } from '../../../common/lib/kibana'; import { getConnectorWithInvalidatedFields } from '../../lib/value_validators'; -interface ConnectorAddModalProps { +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +type ConnectorAddModalProps = { actionType: ActionType; onClose: () => void; postSaveEventHandler?: (savedAction: ActionConnector) => void; consumer?: string; actionTypeRegistry: ActionTypeRegistryContract; -} +}; -export const ConnectorAddModal = ({ +const ConnectorAddModal = ({ actionType, onClose, postSaveEventHandler, @@ -216,3 +217,6 @@ export const ConnectorAddModal = ({ ); }; + +// eslint-disable-next-line import/no-default-export +export { ConnectorAddModal as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index 6c08b0b0b1ac53..66a4dcc452c518 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useReducer, useState, Fragment } from 'react'; +import React, { useCallback, useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -30,7 +30,8 @@ import { ActionConnectorForm, getConnectorErrors } from './action_connector_form import { TestConnectorForm } from './test_connector_form'; import { ActionConnector, - ActionTypeRegistryContract, + ConnectorEditFlyoutProps, + EditConectorTabs, UserConfiguredActionConnector, } from '../../../types'; import { ConnectorReducer, createConnectorReducer } from './connector_reducer'; @@ -44,21 +45,7 @@ import './connector_edit_flyout.scss'; import { useKibana } from '../../../common/lib/kibana'; import { getConnectorWithInvalidatedFields } from '../../lib/value_validators'; -export interface ConnectorEditFlyoutProps { - initialConnector: ActionConnector; - onClose: () => void; - tab?: EditConectorTabs; - reloadConnectors?: () => Promise; - consumer?: string; - actionTypeRegistry: ActionTypeRegistryContract; -} - -export enum EditConectorTabs { - Configuration = 'configuration', - Test = 'test', -} - -export const ConnectorEditFlyout = ({ +const ConnectorEditFlyout = ({ initialConnector, onClose, tab = EditConectorTabs.Configuration, @@ -173,7 +160,7 @@ export const ConnectorEditFlyout = ({ }); const flyoutTitle = connector.isPreconfigured ? ( - + <>

- + ) : (

@@ -313,7 +300,7 @@ export const ConnectorEditFlyout = ({ consumer={consumer} /> ) : ( - + <> {i18n.translate( 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText', @@ -328,7 +315,7 @@ export const ConnectorEditFlyout = ({ defaultMessage="Learn more about preconfigured connectors." /> - + ) ) : ( {canSave && actionTypeModel && !connector.isPreconfigured ? ( - + <> - + ) : null} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts index 6ff8b5ae1d5002..75d29fd4b0c094 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts @@ -15,3 +15,11 @@ export const ConnectorEditFlyout = suspendedComponentWithProps( lazy(() => import('./connector_edit_flyout')) ); export const ActionForm = suspendedComponentWithProps(lazy(() => import('./action_form'))); + +export const ConnectorAddModal = suspendedComponentWithProps( + lazy(() => import('./connector_add_modal')) +); + +export const AddConnectorInline = suspendedComponentWithProps( + lazy(() => import('./connector_add_inline')) +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx index 8afa2d2b575299..92a17a2e4cfae8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, Suspense } from 'react'; +import React, { Suspense } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -102,9 +102,9 @@ export const TestConnectorForm = ({ defaultMessage: 'Run the test', }), children: ( - + <> {executeEnabled ? null : ( - + <>

- + )} - + ), }, { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 9102e73690cacf..7b6453e705ec38 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import { mountWithIntl, nextTick } from '@kbn/test/jest'; -import { ActionsConnectorsList } from './actions_connectors_list'; +import ActionsConnectorsList from './actions_connectors_list'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; @@ -154,7 +154,7 @@ describe('actions_connectors_list component with items', () => { const mockedActionParamsFields = React.lazy(async () => ({ default() { - return ; + return <>; }, })); @@ -260,7 +260,8 @@ describe('actions_connectors_list component with items', () => { await setup(); await wrapper.find('[data-test-subj="edit1"]').first().simulate('click'); - expect(wrapper.find('ConnectorEditFlyout')).toHaveLength(1); + const edit = await wrapper.find('ConnectorEditFlyout'); + expect(edit).toHaveLength(1); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index a322460cde4448..c237bbda486584 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -25,10 +25,6 @@ import { i18n } from '@kbn/i18n'; import { omit } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api'; -import ConnectorAddFlyout from '../../action_connector_form/connector_add_flyout'; -import ConnectorEditFlyout, { - EditConectorTabs, -} from '../../action_connector_form/connector_edit_flyout'; import { hasDeleteActionsCapability, hasSaveActionsCapability, @@ -37,13 +33,20 @@ import { import { DeleteModalConfirmation } from '../../../components/delete_modal_confirmation'; import { checkActionTypeEnabled } from '../../../lib/check_action_type_enabled'; import './actions_connectors_list.scss'; -import { ActionConnector, ActionConnectorTableItem, ActionTypeIndex } from '../../../../types'; +import { + ActionConnector, + ActionConnectorTableItem, + ActionTypeIndex, + EditConectorTabs, +} from '../../../../types'; import { EmptyConnectorsPrompt } from '../../../components/prompts/empty_connectors_prompt'; import { useKibana } from '../../../../common/lib/kibana'; import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../../'; import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; +import ConnectorEditFlyout from '../../action_connector_form/connector_edit_flyout'; +import ConnectorAddFlyout from '../../action_connector_form/connector_add_flyout'; -export const ActionsConnectorsList: React.FunctionComponent = () => { +const ActionsConnectorsList: React.FunctionComponent = () => { const { http, notifications: { toasts }, @@ -440,6 +443,9 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { ); }; +// eslint-disable-next-line import/no-default-export +export { ActionsConnectorsList as default }; + function getActionsCountByActionType(actions: ActionConnector[], actionTypeId: string) { return actions.filter((action) => action.actionTypeId === actionTypeId).length; } @@ -455,7 +461,7 @@ const DeleteOperation: React.FunctionComponent<{ = ({ {hasEditButton ? ( - + <> {' '} = ({ onSave={setAlert} /> )} - + ) : null} @@ -201,7 +201,7 @@ export const AlertDetails: React.FunctionComponent = ({ {uniqueActions && uniqueActions.length ? ( - + <>

= ({ ))} - + ) : null} @@ -336,7 +336,7 @@ export const AlertDetails: React.FunctionComponent = ({ readOnly={!canSaveAlert} /> ) : ( - + <> = ({ />

-
+ )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx index 5ba4c466f6fadf..29290af0d02852 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useState } from 'react'; +import React, { useState } from 'react'; import moment, { Duration } from 'moment'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiHealth, EuiSpacer, EuiSwitch, EuiToolTip } from '@elastic/eui'; @@ -112,7 +112,7 @@ export const alertInstancesTableColumns = ( ), render: (alertInstance: AlertInstanceListItem) => { return ( - + <> onMuteAction(alertInstance)} /> - + ); }, sortable: false, @@ -167,7 +167,7 @@ export function AlertInstances({ }; return ( - + <> - + ); } export const AlertInstancesWithApi = withBulkAlertOperations(AlertInstances); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index a2463d785a3ebf..cb43c168aa999d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -12,11 +12,12 @@ import { act } from 'react-dom/test-utils'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormLabel } from '@elastic/eui'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import AlertAdd, { AlertAddProps } from './alert_add'; +import AlertAdd from './alert_add'; import { createAlert } from '../../lib/alert_api'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { Alert, + AlertAddProps, AlertFlyoutCloseReason, ConnectorValidationResult, GenericValidationResult, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index bcae77f896b71e..a40f77998d6ee4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -11,12 +11,11 @@ import { EuiTitle, EuiFlyoutHeader, EuiFlyout, EuiFlyoutBody, EuiPortal } from ' import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import { - ActionTypeRegistryContract, Alert, - AlertTypeRegistryContract, AlertTypeParams, AlertUpdates, AlertFlyoutCloseReason, + AlertAddProps, } from '../../../types'; import { AlertForm, getAlertErrors, isValidAlert } from './alert_form'; import { alertReducer, InitialAlert, InitialAlertReducer } from './alert_reducer'; @@ -31,20 +30,6 @@ import { useKibana } from '../../../common/lib/kibana'; import { hasAlertChanged, haveAlertParamsChanged } from './has_alert_changed'; import { getAlertWithInvalidatedFields } from '../../lib/value_validators'; -export interface AlertAddProps> { - consumer: string; - alertTypeRegistry: AlertTypeRegistryContract; - actionTypeRegistry: ActionTypeRegistryContract; - onClose: (reason: AlertFlyoutCloseReason) => void; - alertTypeId?: string; - canChangeTrigger?: boolean; - initialValues?: Partial; - /** @deprecated use `onSave` as a callback after an alert is saved*/ - reloadAlerts?: () => Promise; - onSave?: () => Promise; - metadata?: MetaData; -} - const AlertAdd = ({ consumer, alertTypeRegistry, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.tsx index 6c2f5aecfcb7c7..dd0a7df38eb629 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, PropsWithChildren } from 'react'; +import React, { PropsWithChildren } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiButtonIcon, EuiTitle } from '@elastic/eui'; import { AlertConditionsProps, ActionGroupWithCondition } from './alert_conditions'; @@ -55,7 +55,7 @@ export const AlertConditionsGroup = ({ ...otherProps, }) ) : ( - + <> )} ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 0736e28f193b81..49dd92b67ee41c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -98,7 +98,7 @@ describe('alert_edit', () => { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: () => <>, requiresAppContext: false, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index d704111858e4fd..f6569f32088eec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useReducer, useState } from 'react'; +import React, { useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -23,12 +23,7 @@ import { } from '@elastic/eui'; import { cloneDeep } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - ActionTypeRegistryContract, - Alert, - AlertFlyoutCloseReason, - AlertTypeRegistryContract, -} from '../../../types'; +import { Alert, AlertEditProps, AlertFlyoutCloseReason } from '../../../types'; import { AlertForm, getAlertErrors, isValidAlert } from './alert_form'; import { alertReducer, ConcreteAlertReducer } from './alert_reducer'; import { updateAlert } from '../../lib/alert_api'; @@ -39,17 +34,6 @@ import { ConfirmAlertClose } from './confirm_alert_close'; import { hasAlertChanged } from './has_alert_changed'; import { getAlertWithInvalidatedFields } from '../../lib/value_validators'; -export interface AlertEditProps> { - initialAlert: Alert; - alertTypeRegistry: AlertTypeRegistryContract; - actionTypeRegistry: ActionTypeRegistryContract; - onClose: (reason: AlertFlyoutCloseReason) => void; - /** @deprecated use `onSave` as a callback after an alert is saved*/ - reloadAlerts?: () => Promise; - onSave?: () => Promise; - metadata?: MetaData; -} - export const AlertEdit = ({ initialAlert, onClose, @@ -149,7 +133,7 @@ export const AlertEdit = ({ {hasActionsDisabled && ( - + <> - + )} { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: () => <>, requiresAppContext: false, }; @@ -72,7 +72,7 @@ describe('alert_form', () => { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: () => <>, requiresAppContext: true, }; @@ -84,7 +84,7 @@ describe('alert_form', () => { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: () => <>, requiresAppContext: false, }; @@ -322,7 +322,7 @@ describe('alert_form', () => { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: () => <>, requiresAppContext: true, }, { @@ -333,7 +333,7 @@ describe('alert_form', () => { validate: (): ValidationResult => { return { errors: {} }; }, - alertParamsExpression: () => , + alertParamsExpression: () => <>, requiresAppContext: false, }, ]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 6bb485bc7fbb84..b4b6477fd59470 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -470,35 +470,34 @@ export const AlertForm = ({ ); return ( - - - {alertTypeListItemHtml} - - ) + + {alertTypeListItemHtml} + + ) + } + isDisabled={!item.checkEnabledResult.isEnabled} + onClick={() => { + setAlertProperty('alertTypeId', item.id); + setActions([]); + setAlertTypeModel(item.alertTypeItem); + setAlertProperty('params', {}); + if (alertTypesIndex && alertTypesIndex.has(item.id)) { + setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId); } - isDisabled={!item.checkEnabledResult.isEnabled} - onClick={() => { - setAlertProperty('alertTypeId', item.id); - setActions([]); - setAlertTypeModel(item.alertTypeItem); - setAlertProperty('params', {}); - if (alertTypesIndex && alertTypesIndex.has(item.id)) { - setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId); - } - }} - /> - + }} + /> ); })} @@ -507,7 +506,7 @@ export const AlertForm = ({ )); const alertTypeDetails = ( - + <> @@ -605,11 +604,11 @@ export const AlertForm = ({ selectedAlertType ? ( <> {errors.actionConnectors.length >= 1 ? ( - + <> - + ) : null} ) : null} - + ); const labelForAlertChecked = ( @@ -793,9 +792,9 @@ export const AlertForm = ({ {alertTypeModel ? ( - {alertTypeDetails} + <>{alertTypeDetails} ) : availableAlertTypes.length ? ( - + <> {errors.alertTypeId.length >= 1 && alert.alertTypeId !== undefined ? ( - + <> - + ) : null} {alertTypeNodes} - + ) : alertTypesIndex ? ( ) : ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx index b774fd702fadc4..15b228467cf2d5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -39,7 +39,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [ ), 'data-test-subj': 'onActionGroupChange', dropdownDisplay: ( - + <> > = [ />

-
+ ), }, { @@ -67,7 +67,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [ ), 'data-test-subj': 'onActiveAlert', dropdownDisplay: ( - + <> > = [ />

-
+ ), }, { @@ -95,7 +95,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [ ), 'data-test-subj': 'onThrottleInterval', dropdownDisplay: ( - + <> > = [ />

-
+ ), }, ]; @@ -173,7 +173,7 @@ export const AlertNotifyWhen = ({ ); return ( - + <> @@ -184,7 +184,7 @@ export const AlertNotifyWhen = ({ onChange={onNotifyWhenValueChange} /> {showCustomThrottleOpts && ( - + <> @@ -227,11 +227,11 @@ export const AlertNotifyWhen = ({ - + )} -
+ ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index d43dd9f05344fe..1fb688c4dd6bf4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { capitalize, sortBy } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useEffect, useState, Fragment } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiBasicTable, EuiBadge, @@ -479,7 +479,7 @@ export const AlertsList: React.FunctionComponent = () => { : false; const table = ( - + <> {selectedIds.length > 0 && authorizedToModifySelectedAlerts && ( @@ -713,7 +713,7 @@ export const AlertsList: React.FunctionComponent = () => { onCancel={() => setManageLicenseModalOpts(null)} /> )} - + ); const loadedItems = convertAlertsToTableItems( @@ -782,6 +782,9 @@ export const AlertsList: React.FunctionComponent = () => { ); }; +// eslint-disable-next-line import/no-default-export +export { AlertsList as default }; + const noPermissionPrompt = ( { > {children && React.Children.map(children, (child) => - React.isValidElement(child) ? {React.cloneElement(child, {})} : child + React.isValidElement(child) ? <>{React.cloneElement(child, {})} : child )} ); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index a6ec9d1b39665e..1430c403407719 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, Fragment } from 'react'; +import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { @@ -151,7 +151,7 @@ export const GroupByExpression = ({ {groupByTypes[groupBy].sizeRequired ? ( - + <> 0} error={errors.termSize}> - + ) : null}

diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx index d650162816f2bb..5c44b6f29178b3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState, Fragment } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiExpression, diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_add_alert_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_add_alert_flyout.tsx index 42444f4b54e866..2698f4ee2e428e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_add_alert_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_add_alert_flyout.tsx @@ -5,14 +5,10 @@ * 2.0. */ -import React, { lazy, Suspense } from 'react'; -import type { AlertAddProps } from '../application/sections/alert_form/alert_add'; +import React from 'react'; +import { AlertAdd } from '../application/sections/alert_form'; +import type { AlertAddProps } from '../types'; export const getAddAlertFlyoutLazy = (props: AlertAddProps) => { - const AlertAddFlyoutLazy = lazy(() => import('../application/sections/alert_form/alert_add')); - return ( - - - - ); + return ; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_add_connector_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_add_connector_flyout.tsx index 2c211572f28509..09261714cf8cf2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_add_connector_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_add_connector_flyout.tsx @@ -5,16 +5,10 @@ * 2.0. */ -import React, { lazy, Suspense } from 'react'; -import type { ConnectorAddFlyoutProps } from '../application/sections/action_connector_form/connector_add_flyout'; +import React from 'react'; +import { ConnectorAddFlyout } from '../application/sections/action_connector_form'; +import type { ConnectorAddFlyoutProps } from '../types'; export const getAddConnectorFlyoutLazy = (props: ConnectorAddFlyoutProps) => { - const ConnectorAddFlyoutLazy = lazy( - () => import('../application/sections/action_connector_form/connector_add_flyout') - ); - return ( - - - - ); + return ; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_edit_alert_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_edit_alert_flyout.tsx index 89b17f5bb15960..26cc1159e5afdd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_edit_alert_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_edit_alert_flyout.tsx @@ -5,14 +5,10 @@ * 2.0. */ -import React, { lazy, Suspense } from 'react'; -import type { AlertEditProps } from '../application/sections/alert_form/alert_edit'; +import React from 'react'; +import { AlertEdit } from '../application/sections/alert_form'; +import type { AlertEditProps } from '../types'; export const getEditAlertFlyoutLazy = (props: AlertEditProps) => { - const AlertEditFlyoutLazy = lazy(() => import('../application/sections/alert_form/alert_edit')); - return ( - - - - ); + return ; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_edit_connector_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_edit_connector_flyout.tsx index 38002cfe14a150..90ecea56856f57 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_edit_connector_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_edit_connector_flyout.tsx @@ -5,16 +5,10 @@ * 2.0. */ -import React, { lazy, Suspense } from 'react'; -import type { ConnectorEditFlyoutProps } from '../application/sections/action_connector_form/connector_edit_flyout'; +import React from 'react'; +import { ConnectorEditFlyout } from '../application/sections/action_connector_form'; +import type { ConnectorEditFlyoutProps } from '../types'; export const getEditConnectorFlyoutLazy = (props: ConnectorEditFlyoutProps) => { - const ConnectorEditFlyoutLazy = lazy( - () => import('../application/sections/action_connector_form/connector_edit_flyout') - ); - return ( - - - - ); + return ; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index b65086cd6f3e7d..134627929e4a02 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -7,14 +7,6 @@ import { Plugin } from './plugin'; -export { AlertAdd } from './application/sections/alert_form'; -export { - AlertEdit, - AlertConditions, - AlertConditionsGroup, - ActionGroupWithCondition, -} from './application/sections'; - export type { AlertAction, Alert, @@ -37,7 +29,10 @@ export { ConnectorEditFlyout, } from './application/sections/action_connector_form'; -export { loadActionTypes } from './application/lib/action_connector_api'; +export type { ActionGroupWithCondition } from './application/sections'; + +export { AlertConditions, AlertConditionsGroup } from './application/sections'; + export * from './common'; export function plugin() { @@ -47,6 +42,8 @@ export function plugin() { export { Plugin }; export * from './plugin'; -export { TIME_UNITS } from './application/constants'; +export { loadActionTypes } from './application/lib/action_connector_api/connector_types'; + +export type { TIME_UNITS } from './application/constants'; export { getTimeUnitLabel } from './common/lib/get_time_unit_label'; export type { TriggersAndActionsUiServices } from '../public/application/app'; diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 9666f8ab1b16b5..dfc1cc88e15bcf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -5,10 +5,6 @@ * 2.0. */ -import type { ConnectorAddFlyoutProps } from './application/sections/action_connector_form/connector_add_flyout'; -import type { ConnectorEditFlyoutProps } from './application/sections/action_connector_form/connector_edit_flyout'; -import type { AlertAddProps } from './application/sections/alert_form/alert_add'; -import type { AlertEditProps } from './application/sections/alert_form/alert_edit'; import type { TriggersAndActionsUIPublicPluginStart } from './plugin'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; @@ -17,7 +13,14 @@ import { getAddAlertFlyoutLazy } from './common/get_add_alert_flyout'; import { getEditAlertFlyoutLazy } from './common/get_edit_alert_flyout'; import { TypeRegistry } from './application/type_registry'; -import { ActionTypeModel, AlertTypeModel } from './types'; +import { + ActionTypeModel, + AlertAddProps, + AlertEditProps, + AlertTypeModel, + ConnectorAddFlyoutProps, + ConnectorEditFlyoutProps, +} from './types'; function createStartMock(): TriggersAndActionsUIPublicPluginStart { const actionTypeRegistry = new TypeRegistry(); diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index a027f25d15eb7b..62daf2ad198f36 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -32,11 +32,14 @@ import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; import { getAddAlertFlyoutLazy } from './common/get_add_alert_flyout'; import { getEditAlertFlyoutLazy } from './common/get_edit_alert_flyout'; -import type { ActionTypeModel, AlertTypeModel } from './types'; -import type { ConnectorAddFlyoutProps } from './application/sections/action_connector_form/connector_add_flyout'; -import type { ConnectorEditFlyoutProps } from './application/sections/action_connector_form/connector_edit_flyout'; -import type { AlertAddProps } from './application/sections/alert_form/alert_add'; -import type { AlertEditProps } from './application/sections/alert_form/alert_edit'; +import type { + ActionTypeModel, + AlertAddProps, + AlertEditProps, + AlertTypeModel, + ConnectorAddFlyoutProps, + ConnectorEditFlyoutProps, +} from './types'; export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry; diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 6db5634be2221a..0f2b961b1f2da8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -10,6 +10,7 @@ import type { DocLinksStart } from 'kibana/public'; import { ComponentType } from 'react'; import { ChartsPluginSetup } from 'src/plugins/charts/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { IconType } from '@elastic/eui'; import { ActionType, AlertHistoryEsIndexConnectorId, @@ -103,7 +104,7 @@ export interface Sorting { export interface ActionTypeModel { id: string; - iconClass: string; + iconClass: IconType; selectMessage: string; actionTypeTitle?: string; validateConnector: ( @@ -247,3 +248,50 @@ export interface AlertTypeModel void; + actionTypes?: ActionType[]; + onTestConnector?: (connector: ActionConnector) => void; + reloadConnectors?: () => Promise; + consumer?: string; + actionTypeRegistry: ActionTypeRegistryContract; +} +export enum EditConectorTabs { + Configuration = 'configuration', + Test = 'test', +} + +export interface ConnectorEditFlyoutProps { + initialConnector: ActionConnector; + onClose: () => void; + tab?: EditConectorTabs; + reloadConnectors?: () => Promise; + consumer?: string; + actionTypeRegistry: ActionTypeRegistryContract; +} + +export interface AlertEditProps> { + initialAlert: Alert; + alertTypeRegistry: AlertTypeRegistryContract; + actionTypeRegistry: ActionTypeRegistryContract; + onClose: (reason: AlertFlyoutCloseReason) => void; + /** @deprecated use `onSave` as a callback after an alert is saved*/ + reloadAlerts?: () => Promise; + onSave?: () => Promise; + metadata?: MetaData; +} + +export interface AlertAddProps> { + consumer: string; + alertTypeRegistry: AlertTypeRegistryContract; + actionTypeRegistry: ActionTypeRegistryContract; + onClose: (reason: AlertFlyoutCloseReason) => void; + alertTypeId?: string; + canChangeTrigger?: boolean; + initialValues?: Partial; + /** @deprecated use `onSave` as a callback after an alert is saved*/ + reloadAlerts?: () => Promise; + onSave?: () => Promise; + metadata?: MetaData; +} From 656ff1ca272bbf4e11262fefbbb255a057dfee60 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 25 May 2021 20:12:12 -0600 Subject: [PATCH 06/18] [ftr] migrate "filterBar" service to FtrService class (#100601) Co-authored-by: spalger --- test/functional/services/filter_bar.ts | 350 ++++++++++++------------- test/functional/services/index.ts | 4 +- 2 files changed, 175 insertions(+), 179 deletions(-) diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index 1ffa5c94b5fc45..5f20d3d4f8b7b5 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -7,200 +7,196 @@ */ import classNames from 'classnames'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function FilterBarProvider({ getService, getPageObjects }: FtrProviderContext) { - const comboBox = getService('comboBox'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header']); - - class FilterBar { - /** - * Checks if specified filter exists - * - * @param key field name - * @param value filter value - * @param enabled filter status - * @param pinned filter pinned status - * @param negated filter including or excluding value - */ - public async hasFilter( - key: string, - value: string, - enabled: boolean = true, - pinned: boolean = false, - negated: boolean = false - ): Promise { - const filterActivationState = enabled ? 'enabled' : 'disabled'; - const filterPinnedState = pinned ? 'pinned' : 'unpinned'; - const filterNegatedState = negated ? 'filter-negated' : ''; - return testSubjects.exists( - classNames( - 'filter', - `filter-${filterActivationState}`, - key !== '' && `filter-key-${key}`, - value !== '' && `filter-value-${value}`, - `filter-${filterPinnedState}`, - filterNegatedState - ), - { - allowHidden: true, - } - ); - } +import { FtrService } from '../ftr_provider_context'; + +export class FilterBarService extends FtrService { + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly PageObjects = this.ctx.getPageObjects(['common', 'header']); + + /** + * Checks if specified filter exists + * + * @param key field name + * @param value filter value + * @param enabled filter status + * @param pinned filter pinned status + * @param negated filter including or excluding value + */ + public async hasFilter( + key: string, + value: string, + enabled: boolean = true, + pinned: boolean = false, + negated: boolean = false + ): Promise { + const filterActivationState = enabled ? 'enabled' : 'disabled'; + const filterPinnedState = pinned ? 'pinned' : 'unpinned'; + const filterNegatedState = negated ? 'filter-negated' : ''; + return this.testSubjects.exists( + classNames( + 'filter', + `filter-${filterActivationState}`, + key !== '' && `filter-key-${key}`, + value !== '' && `filter-value-${value}`, + `filter-${filterPinnedState}`, + filterNegatedState + ), + { + allowHidden: true, + } + ); + } - /** - * Removes specified filter - * - * @param key field name - */ - public async removeFilter(key: string): Promise { - await testSubjects.click(`~filter & ~filter-key-${key}`); - await testSubjects.click(`deleteFilter`); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } + /** + * Removes specified filter + * + * @param key field name + */ + public async removeFilter(key: string): Promise { + await this.testSubjects.click(`~filter & ~filter-key-${key}`); + await this.testSubjects.click(`deleteFilter`); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } - /** - * Removes all filters - */ - public async removeAllFilters(): Promise { - await testSubjects.click('showFilterActions'); - await testSubjects.click('removeAllFilters'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.waitUntilUrlIncludes('filters:!()'); - } + /** + * Removes all filters + */ + public async removeAllFilters(): Promise { + await this.testSubjects.click('showFilterActions'); + await this.testSubjects.click('removeAllFilters'); + await this.PageObjects.header.waitUntilLoadingHasFinished(); + await this.PageObjects.common.waitUntilUrlIncludes('filters:!()'); + } - /** - * Changes filter active status - * - * @param key field name - */ - public async toggleFilterEnabled(key: string): Promise { - await testSubjects.click(`~filter & ~filter-key-${key}`); - await testSubjects.click(`disableFilter`); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } + /** + * Changes filter active status + * + * @param key field name + */ + public async toggleFilterEnabled(key: string): Promise { + await this.testSubjects.click(`~filter & ~filter-key-${key}`); + await this.testSubjects.click(`disableFilter`); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } - public async toggleFilterPinned(key: string): Promise { - await testSubjects.click(`~filter & ~filter-key-${key}`); - await testSubjects.click(`pinFilter`); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } + public async toggleFilterPinned(key: string): Promise { + await this.testSubjects.click(`~filter & ~filter-key-${key}`); + await this.testSubjects.click(`pinFilter`); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } - public async isFilterPinned(key: string): Promise { - const filter = await testSubjects.find(`~filter & ~filter-key-${key}`); - return (await filter.getAttribute('data-test-subj')).includes('filter-pinned'); - } + public async isFilterPinned(key: string): Promise { + const filter = await this.testSubjects.find(`~filter & ~filter-key-${key}`); + return (await filter.getAttribute('data-test-subj')).includes('filter-pinned'); + } - public async getFilterCount(): Promise { - const filters = await testSubjects.findAll('~filter'); - return filters.length; - } + public async getFilterCount(): Promise { + const filters = await this.testSubjects.findAll('~filter'); + return filters.length; + } - /** - * Adds a filter to the filter bar. - * - * @param {string} field The name of the field the filter should be applied for. - * @param {string} operator A valid operator for that fields, e.g. "is one of", "is", "exists", etc. - * @param {string[]|string} values The remaining parameters are the values passed into the individual - * value input fields, i.e. the third parameter into the first input field, the fourth into the second, etc. - * Each value itself can be an array, in case you want to enter multiple values into one field (e.g. for "is one of"): - * @example - * // Add a plain single value - * filterBar.addFilter('country', 'is', 'NL'); - * // Add an exists filter - * filterBar.addFilter('country', 'exists'); - * // Add a range filter for a numeric field - * filterBar.addFilter('bytes', 'is between', '500', '1000'); - * // Add a filter containing multiple values - * filterBar.addFilter('extension', 'is one of', ['jpg', 'png']); - */ - public async addFilter(field: string, operator: string, ...values: any): Promise { - await testSubjects.click('addFilter'); - await comboBox.set('filterFieldSuggestionList', field); - await comboBox.set('filterOperatorList', operator); - const params = await testSubjects.find('filterParams'); - const paramsComboBoxes = await params.findAllByCssSelector( - '[data-test-subj~="filterParamsComboBox"]', - 1000 - ); - const paramFields = await params.findAllByTagName('input', 1000); - for (let i = 0; i < values.length; i++) { - let fieldValues = values[i]; - if (!Array.isArray(fieldValues)) { - fieldValues = [fieldValues]; - } + /** + * Adds a filter to the filter bar. + * + * @param {string} field The name of the field the filter should be applied for. + * @param {string} operator A valid operator for that fields, e.g. "is one of", "is", "exists", etc. + * @param {string[]|string} values The remaining parameters are the values passed into the individual + * value input fields, i.e. the third parameter into the first input field, the fourth into the second, etc. + * Each value itself can be an array, in case you want to enter multiple values into one field (e.g. for "is one of"): + * @example + * // Add a plain single value + * filterBar.addFilter('country', 'is', 'NL'); + * // Add an exists filter + * filterBar.addFilter('country', 'exists'); + * // Add a range filter for a numeric field + * filterBar.addFilter('bytes', 'is between', '500', '1000'); + * // Add a filter containing multiple values + * filterBar.addFilter('extension', 'is one of', ['jpg', 'png']); + */ + public async addFilter(field: string, operator: string, ...values: any): Promise { + await this.testSubjects.click('addFilter'); + await this.comboBox.set('filterFieldSuggestionList', field); + await this.comboBox.set('filterOperatorList', operator); + const params = await this.testSubjects.find('filterParams'); + const paramsComboBoxes = await params.findAllByCssSelector( + '[data-test-subj~="filterParamsComboBox"]', + 1000 + ); + const paramFields = await params.findAllByTagName('input', 1000); + for (let i = 0; i < values.length; i++) { + let fieldValues = values[i]; + if (!Array.isArray(fieldValues)) { + fieldValues = [fieldValues]; + } - if (paramsComboBoxes && paramsComboBoxes.length > 0) { - for (let j = 0; j < fieldValues.length; j++) { - await comboBox.setElement(paramsComboBoxes[i], fieldValues[j]); - } - } else if (paramFields && paramFields.length > 0) { - for (let j = 0; j < fieldValues.length; j++) { - await paramFields[i].type(fieldValues[j]); - } + if (paramsComboBoxes && paramsComboBoxes.length > 0) { + for (let j = 0; j < fieldValues.length; j++) { + await this.comboBox.setElement(paramsComboBoxes[i], fieldValues[j]); + } + } else if (paramFields && paramFields.length > 0) { + for (let j = 0; j < fieldValues.length; j++) { + await paramFields[i].type(fieldValues[j]); } } - await testSubjects.click('saveFilter'); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } - - /** - * Activates filter editing - * @param key field name - * @param value field value - */ - public async clickEditFilter(key: string, value: string): Promise { - await testSubjects.click(`~filter & ~filter-key-${key} & ~filter-value-${value}`); - await testSubjects.click(`editFilter`); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); } + await this.testSubjects.click('saveFilter'); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } - /** - * Returns available phrases in the filter - */ - public async getFilterEditorSelectedPhrases(): Promise { - return await comboBox.getComboBoxSelectedOptions('~filterParamsComboBox'); - } + /** + * Activates filter editing + * @param key field name + * @param value field value + */ + public async clickEditFilter(key: string, value: string): Promise { + await this.testSubjects.click(`~filter & ~filter-key-${key} & ~filter-value-${value}`); + await this.testSubjects.click(`editFilter`); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } - /** - * Returns available fields in the filter - */ - public async getFilterEditorFields(): Promise { - const optionsString = await comboBox.getOptionsList('filterFieldSuggestionList'); - return optionsString.split('\n'); - } + /** + * Returns available phrases in the filter + */ + public async getFilterEditorSelectedPhrases(): Promise { + return await this.comboBox.getComboBoxSelectedOptions('~filterParamsComboBox'); + } - /** - * Closes field editor modal window - */ - public async ensureFieldEditorModalIsClosed(): Promise { - const cancelSaveFilterModalButtonExists = await testSubjects.exists('cancelSaveFilter'); - if (cancelSaveFilterModalButtonExists) { - await testSubjects.click('cancelSaveFilter'); - } - await testSubjects.waitForDeleted('cancelSaveFilter'); - } + /** + * Returns available fields in the filter + */ + public async getFilterEditorFields(): Promise { + const optionsString = await this.comboBox.getOptionsList('filterFieldSuggestionList'); + return optionsString.split('\n'); + } - /** - * Returns comma-separated list of index patterns - */ - public async getIndexPatterns(): Promise { - await testSubjects.click('addFilter'); - const indexPatterns = await comboBox.getOptionsList('filterIndexPatternsSelect'); - await this.ensureFieldEditorModalIsClosed(); - return indexPatterns.trim().split('\n').join(','); + /** + * Closes field editor modal window + */ + public async ensureFieldEditorModalIsClosed(): Promise { + const cancelSaveFilterModalButtonExists = await this.testSubjects.exists('cancelSaveFilter'); + if (cancelSaveFilterModalButtonExists) { + await this.testSubjects.click('cancelSaveFilter'); } + await this.testSubjects.waitForDeleted('cancelSaveFilter'); + } - /** - * Adds new index pattern filter - * @param indexPatternTitle - */ - public async selectIndexPattern(indexPatternTitle: string): Promise { - await testSubjects.click('addFilter'); - await comboBox.set('filterIndexPatternsSelect', indexPatternTitle); - } + /** + * Returns comma-separated list of index patterns + */ + public async getIndexPatterns(): Promise { + await this.testSubjects.click('addFilter'); + const indexPatterns = await this.comboBox.getOptionsList('filterIndexPatternsSelect'); + await this.ensureFieldEditorModalIsClosed(); + return indexPatterns.trim().split('\n').join(','); } - return new FilterBar(); + /** + * Adds new index pattern filter + * @param indexPatternTitle + */ + public async selectIndexPattern(indexPatternTitle: string): Promise { + await this.testSubjects.click('addFilter'); + await this.comboBox.set('filterIndexPatternsSelect', indexPatternTitle); + } } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 0dd7f20debcbdf..b6887bc38b93e1 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -27,7 +27,7 @@ import { } from './dashboard'; import { DocTableProvider } from './doc_table'; import { EmbeddingProvider } from './embedding'; -import { FilterBarProvider } from './filter_bar'; +import { FilterBarService } from './filter_bar'; import { FlyoutProvider } from './flyout'; import { GlobalNavProvider } from './global_nav'; import { InspectorProvider } from './inspector'; @@ -53,7 +53,7 @@ export const services = { ...commonServiceProviders, __webdriver__: RemoteProvider, - filterBar: FilterBarProvider, + filterBar: FilterBarService, queryBar: QueryBarProvider, find: FindProvider, testSubjects: TestSubjectsProvider, From 090e0beb65e42b601b54b8f89afc701a3e1ec33d Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 25 May 2021 20:14:00 -0600 Subject: [PATCH 07/18] [ftr] migrate "fieldEditor" to FtrService class (#100597) Co-authored-by: spalger --- test/functional/services/field_editor.ts | 78 +++++++++++------------- test/functional/services/index.ts | 4 +- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/test/functional/services/field_editor.ts b/test/functional/services/field_editor.ts index 342e2afec28d3c..c74c229cd11c7c 100644 --- a/test/functional/services/field_editor.ts +++ b/test/functional/services/field_editor.ts @@ -6,51 +6,47 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function FieldEditorProvider({ getService }: FtrProviderContext) { - const browser = getService('browser'); - const testSubjects = getService('testSubjects'); +export class FieldEditorService extends FtrService { + private readonly browser = this.ctx.getService('browser'); + private readonly testSubjects = this.ctx.getService('testSubjects'); - class FieldEditor { - public async setName(name: string) { - await testSubjects.setValue('nameField > input', name); - } - public async enableCustomLabel() { - await testSubjects.setEuiSwitch('customLabelRow > toggle', 'check'); - } - public async setCustomLabel(name: string) { - await testSubjects.setValue('customLabelRow > input', name); - } - public async enableValue() { - await testSubjects.setEuiSwitch('valueRow > toggle', 'check'); - } - public async disableValue() { - await testSubjects.setEuiSwitch('valueRow > toggle', 'uncheck'); - } - public async typeScript(script: string) { - const editor = await (await testSubjects.find('valueRow')).findByClassName( - 'react-monaco-editor-container' - ); - const textarea = await editor.findByClassName('monaco-mouse-cursor-text'); - - await textarea.click(); - await browser.pressKeys(script); - } - public async save() { - await testSubjects.click('fieldSaveButton'); - } + public async setName(name: string) { + await this.testSubjects.setValue('nameField > input', name); + } + public async enableCustomLabel() { + await this.testSubjects.setEuiSwitch('customLabelRow > toggle', 'check'); + } + public async setCustomLabel(name: string) { + await this.testSubjects.setValue('customLabelRow > input', name); + } + public async enableValue() { + await this.testSubjects.setEuiSwitch('valueRow > toggle', 'check'); + } + public async disableValue() { + await this.testSubjects.setEuiSwitch('valueRow > toggle', 'uncheck'); + } + public async typeScript(script: string) { + const editor = await (await this.testSubjects.find('valueRow')).findByClassName( + 'react-monaco-editor-container' + ); + const textarea = await editor.findByClassName('monaco-mouse-cursor-text'); - public async confirmSave() { - await testSubjects.setValue('saveModalConfirmText', 'change'); - await testSubjects.click('confirmModalConfirmButton'); - } + await textarea.click(); + await this.browser.pressKeys(script); + } + public async save() { + await this.testSubjects.click('fieldSaveButton'); + } - public async confirmDelete() { - await testSubjects.setValue('deleteModalConfirmText', 'remove'); - await testSubjects.click('confirmModalConfirmButton'); - } + public async confirmSave() { + await this.testSubjects.setValue('saveModalConfirmText', 'change'); + await this.testSubjects.click('confirmModalConfirmButton'); } - return new FieldEditor(); + public async confirmDelete() { + await this.testSubjects.setValue('deleteModalConfirmText', 'remove'); + await this.testSubjects.click('confirmModalConfirmButton'); + } } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index b6887bc38b93e1..f37b0b544fd660 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -31,7 +31,7 @@ import { FilterBarService } from './filter_bar'; import { FlyoutProvider } from './flyout'; import { GlobalNavProvider } from './global_nav'; import { InspectorProvider } from './inspector'; -import { FieldEditorProvider } from './field_editor'; +import { FieldEditorService } from './field_editor'; import { ManagementMenuProvider } from './management'; import { QueryBarProvider } from './query_bar'; import { RemoteProvider } from './remote'; @@ -75,7 +75,7 @@ export const services = { browser: BrowserProvider, pieChart: PieChartProvider, inspector: InspectorProvider, - fieldEditor: FieldEditorProvider, + fieldEditor: FieldEditorService, vegaDebugInspector: VegaDebugInspectorViewProvider, appsMenu: AppsMenuProvider, globalNav: GlobalNavProvider, From 29b7d1d448ba5d15f0f3ebfb9773f1758b8205ee Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 May 2021 01:58:05 -0600 Subject: [PATCH 08/18] [ftr] migrate "dataGrid" service to FtrService class (#100593) Co-authored-by: spalger --- test/functional/services/data_grid.ts | 443 +++++++++++++------------- test/functional/services/index.ts | 4 +- 2 files changed, 220 insertions(+), 227 deletions(-) diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index ee50185db8c688..a00587c9789778 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -7,7 +7,7 @@ */ import { chunk } from 'lodash'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from './lib/web_element_wrapper'; interface TabbedGridData { @@ -19,261 +19,254 @@ interface SelectOptions { rowIndex: number; } -export function DataGridProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header']); - const retry = getService('retry'); - - class DataGrid { - async getDataGridTableData(): Promise { - const table = await find.byCssSelector('.euiDataGrid'); - const $ = await table.parseDomContent(); - - const columns = $('.euiDataGridHeaderCell__content') - .toArray() - .map((cell) => $(cell).text()); - const cells = $.findTestSubjects('dataGridRowCell') - .toArray() - .map((cell) => $(cell).text()); - - const rows = chunk(cells, columns.length); - - return { - columns, - rows, - }; - } - - /** - * Converts the data grid data into nested array - * [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ] - * @param element table - */ - public async getDataFromElement( - element: WebElementWrapper, - cellDataTestSubj: string - ): Promise { - const $ = await element.parseDomContent(); - const columnNumber = $('.euiDataGridHeaderCell__content').length; - const cells = $.findTestSubjects('dataGridRowCell') - .toArray() - .map((cell) => - $(cell) - .findTestSubject(cellDataTestSubj) - .text() - .replace(/ /g, '') - .trim() - ); - - return chunk(cells, columnNumber); - } - - /** - * Returns an array of data grid headers names - */ - public async getHeaders() { - const header = await testSubjects.find('dataGridWrapper > dataGridHeader'); - const $ = await header.parseDomContent(); - return $('.euiDataGridHeaderCell__content') - .toArray() - .map((cell) => $(cell).text()); - } +export class DataGridService extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly PageObjects = this.ctx.getPageObjects(['common', 'header']); + private readonly retry = this.ctx.getService('retry'); + + async getDataGridTableData(): Promise { + const table = await this.find.byCssSelector('.euiDataGrid'); + const $ = await table.parseDomContent(); + + const columns = $('.euiDataGridHeaderCell__content') + .toArray() + .map((cell) => $(cell).text()); + const cells = $.findTestSubjects('dataGridRowCell') + .toArray() + .map((cell) => $(cell).text()); + + const rows = chunk(cells, columns.length); + + return { + columns, + rows, + }; + } - /** - * Returns a grid cell element by row & column indexes. - * The row offset equals 1 since the first row of data grid is the header row. - * @param rowIndex data row index starting from 1 (1 means 1st row) - * @param columnIndex column index starting from 1 (1 means 1st column) - */ - public async getCellElement(rowIndex: number, columnIndex: number) { - const table = await find.byCssSelector('.euiDataGrid'); - const $ = await table.parseDomContent(); - const columnNumber = $('.euiDataGridHeaderCell__content').length; - return await find.byCssSelector( - `[data-test-subj="dataGridWrapper"] [data-test-subj="dataGridRowCell"]:nth-of-type(${ - columnNumber * (rowIndex - 1) + columnIndex + 1 - })` + /** + * Converts the data grid data into nested array + * [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ] + * @param element table + */ + public async getDataFromElement( + element: WebElementWrapper, + cellDataTestSubj: string + ): Promise { + const $ = await element.parseDomContent(); + const columnNumber = $('.euiDataGridHeaderCell__content').length; + const cells = $.findTestSubjects('dataGridRowCell') + .toArray() + .map((cell) => + $(cell) + .findTestSubject(cellDataTestSubj) + .text() + .replace(/ /g, '') + .trim() ); - } - public async getDocCount(): Promise { - const grid = await find.byCssSelector('[data-document-number]'); - return Number(await grid.getAttribute('data-document-number')); - } + return chunk(cells, columnNumber); + } - public async getFields() { - const cells = await find.allByCssSelector('.euiDataGridRowCell'); - - const rows: string[][] = []; - let rowIdx = -1; - for (const cell of cells) { - if (await cell.elementHasClass('euiDataGridRowCell--firstColumn')) { - // first column contains expand icon - rowIdx++; - rows[rowIdx] = []; - } - if (!(await cell.elementHasClass('euiDataGridRowCell--controlColumn'))) { - rows[rowIdx].push(await cell.getVisibleText()); - } - } - return rows; - } + /** + * Returns an array of data grid headers names + */ + public async getHeaders() { + const header = await this.testSubjects.find('dataGridWrapper > dataGridHeader'); + const $ = await header.parseDomContent(); + return $('.euiDataGridHeaderCell__content') + .toArray() + .map((cell) => $(cell).text()); + } - public async getTable(selector: string = 'docTable') { - return await testSubjects.find(selector); - } + /** + * Returns a grid cell element by row & column indexes. + * The row offset equals 1 since the first row of data grid is the header row. + * @param rowIndex data row index starting from 1 (1 means 1st row) + * @param columnIndex column index starting from 1 (1 means 1st column) + */ + public async getCellElement(rowIndex: number, columnIndex: number) { + const table = await this.find.byCssSelector('.euiDataGrid'); + const $ = await table.parseDomContent(); + const columnNumber = $('.euiDataGridHeaderCell__content').length; + return await this.find.byCssSelector( + `[data-test-subj="dataGridWrapper"] [data-test-subj="dataGridRowCell"]:nth-of-type(${ + columnNumber * (rowIndex - 1) + columnIndex + 1 + })` + ); + } - public async getBodyRows(): Promise { - return this.getDocTableRows(); - } + public async getDocCount(): Promise { + const grid = await this.find.byCssSelector('[data-document-number]'); + return Number(await grid.getAttribute('data-document-number')); + } - /** - * Returns an array of rows (which are array of cells) - */ - public async getDocTableRows() { - const table = await this.getTable(); - if (!table) { - return []; + public async getFields() { + const cells = await this.find.allByCssSelector('.euiDataGridRowCell'); + + const rows: string[][] = []; + let rowIdx = -1; + for (const cell of cells) { + if (await cell.elementHasClass('euiDataGridRowCell--firstColumn')) { + // first column contains expand icon + rowIdx++; + rows[rowIdx] = []; } - const cells = await table.findAllByCssSelector('.euiDataGridRowCell'); - - const rows: WebElementWrapper[][] = []; - let rowIdx = -1; - for (const cell of cells) { - if (await cell.elementHasClass('euiDataGridRowCell--firstColumn')) { - rowIdx++; - rows[rowIdx] = []; - } - rows[rowIdx].push(cell); + if (!(await cell.elementHasClass('euiDataGridRowCell--controlColumn'))) { + rows[rowIdx].push(await cell.getVisibleText()); } - return rows; - } - - /** - * Returns an array of cells for that row - */ - public async getRow(options: SelectOptions): Promise { - return (await this.getBodyRows())[options.rowIndex]; } + return rows; + } - public async clickRowToggle( - options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } - ): Promise { - const row = await this.getRow(options); - const toggle = await row[0]; - await toggle.click(); - } + public async getTable(selector: string = 'docTable') { + return await this.testSubjects.find(selector); + } - public async getDetailsRows(): Promise { - return await testSubjects.findAll('docTableDetailsFlyout'); - } + public async getBodyRows(): Promise { + return this.getDocTableRows(); + } - public async closeFlyout() { - await testSubjects.click('euiFlyoutCloseButton'); + /** + * Returns an array of rows (which are array of cells) + */ + public async getDocTableRows() { + const table = await this.getTable(); + if (!table) { + return []; } - - public async getHeaderFields(): Promise { - const result = await find.allByCssSelector('.euiDataGridHeaderCell__content'); - const textArr = []; - let idx = 0; - for (const cell of result) { - if (idx > 1) { - textArr.push(await cell.getVisibleText()); - } - idx++; + const cells = await table.findAllByCssSelector('.euiDataGridRowCell'); + + const rows: WebElementWrapper[][] = []; + let rowIdx = -1; + for (const cell of cells) { + if (await cell.elementHasClass('euiDataGridRowCell--firstColumn')) { + rowIdx++; + rows[rowIdx] = []; } - return Promise.resolve(textArr); + rows[rowIdx].push(cell); } + return rows; + } - public async getRowActions( - options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } - ): Promise { - const detailsRow = (await this.getDetailsRows())[options.rowIndex]; - return await detailsRow.findAllByTestSubject('~docTableRowAction'); - } + /** + * Returns an array of cells for that row + */ + public async getRow(options: SelectOptions): Promise { + return (await this.getBodyRows())[options.rowIndex]; + } - public async openColMenuByField(field: string) { - await retry.waitFor('header cell action being displayed', async () => { - // to prevent flakiness - await testSubjects.click(`dataGridHeaderCell-${field}`); - return await testSubjects.exists(`dataGridHeaderCellActionGroup-${field}`); - }); - } + public async clickRowToggle( + options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } + ): Promise { + const row = await this.getRow(options); + const toggle = await row[0]; + await toggle.click(); + } - public async clickDocSortAsc(field?: string, sortText = 'Sort New-Old') { - if (field) { - await this.openColMenuByField(field); - } else { - await find.clickByCssSelector('.euiDataGridHeaderCell__button'); - } - await find.clickByButtonText(sortText); - } + public async getDetailsRows(): Promise { + return await this.testSubjects.findAll('docTableDetailsFlyout'); + } - public async clickDocSortDesc(field?: string, sortText = 'Sort Old-New') { - if (field) { - await this.openColMenuByField(field); - } else { - await find.clickByCssSelector('.euiDataGridHeaderCell__button'); - } - await find.clickByButtonText(sortText); - } + public async closeFlyout() { + await this.testSubjects.click('euiFlyoutCloseButton'); + } - public async clickRemoveColumn(field?: string) { - if (field) { - await this.openColMenuByField(field); - } else { - await find.clickByCssSelector('.euiDataGridHeaderCell__button'); + public async getHeaderFields(): Promise { + const result = await this.find.allByCssSelector('.euiDataGridHeaderCell__content'); + const textArr = []; + let idx = 0; + for (const cell of result) { + if (idx > 1) { + textArr.push(await cell.getVisibleText()); } - await find.clickByButtonText('Remove column'); - } - public async getDetailsRow(): Promise { - const detailRows = await this.getDetailsRows(); - return detailRows[0]; - } - public async addInclusiveFilter( - detailsRow: WebElementWrapper, - fieldName: string - ): Promise { - const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); - const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow); - await addInclusiveFilterButton.click(); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + idx++; } + return Promise.resolve(textArr); + } - public async getAddInclusiveFilterButton( - tableDocViewRow: WebElementWrapper - ): Promise { - return await tableDocViewRow.findByTestSubject(`~addInclusiveFilterButton`); - } + public async getRowActions( + options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } + ): Promise { + const detailsRow = (await this.getDetailsRows())[options.rowIndex]; + return await detailsRow.findAllByTestSubject('~docTableRowAction'); + } - public async getTableDocViewRow( - detailsRow: WebElementWrapper, - fieldName: string - ): Promise { - return await detailsRow.findByTestSubject(`~tableDocViewRow-${fieldName}`); - } + public async openColMenuByField(field: string) { + await this.retry.waitFor('header cell action being displayed', async () => { + // to prevent flakiness + await this.testSubjects.click(`dataGridHeaderCell-${field}`); + return await this.testSubjects.exists(`dataGridHeaderCellActionGroup-${field}`); + }); + } - public async getRemoveInclusiveFilterButton( - tableDocViewRow: WebElementWrapper - ): Promise { - return await tableDocViewRow.findByTestSubject(`~removeInclusiveFilterButton`); + public async clickDocSortAsc(field?: string, sortText = 'Sort New-Old') { + if (field) { + await this.openColMenuByField(field); + } else { + await this.find.clickByCssSelector('.euiDataGridHeaderCell__button'); } + await this.find.clickByButtonText(sortText); + } - public async removeInclusiveFilter( - detailsRow: WebElementWrapper, - fieldName: string - ): Promise { - const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); - const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow); - await addInclusiveFilterButton.click(); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + public async clickDocSortDesc(field?: string, sortText = 'Sort Old-New') { + if (field) { + await this.openColMenuByField(field); + } else { + await this.find.clickByCssSelector('.euiDataGridHeaderCell__button'); } + await this.find.clickByButtonText(sortText); + } - public async hasNoResults() { - return await find.existsByCssSelector('.euiDataGrid__noResults'); + public async clickRemoveColumn(field?: string) { + if (field) { + await this.openColMenuByField(field); + } else { + await this.find.clickByCssSelector('.euiDataGridHeaderCell__button'); } + await this.find.clickByButtonText('Remove column'); + } + public async getDetailsRow(): Promise { + const detailRows = await this.getDetailsRows(); + return detailRows[0]; + } + public async addInclusiveFilter(detailsRow: WebElementWrapper, fieldName: string): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } + + public async getAddInclusiveFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByTestSubject(`~addInclusiveFilterButton`); } - return new DataGrid(); + public async getTableDocViewRow( + detailsRow: WebElementWrapper, + fieldName: string + ): Promise { + return await detailsRow.findByTestSubject(`~tableDocViewRow-${fieldName}`); + } + + public async getRemoveInclusiveFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByTestSubject(`~removeInclusiveFilterButton`); + } + + public async removeInclusiveFilter( + detailsRow: WebElementWrapper, + fieldName: string + ): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } + + public async hasNoResults() { + return await this.find.existsByCssSelector('.euiDataGrid__noResults'); + } } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index f37b0b544fd660..f5415e34c29e88 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -37,7 +37,7 @@ import { QueryBarProvider } from './query_bar'; import { RemoteProvider } from './remote'; import { RenderableProvider } from './renderable'; import { ToastsProvider } from './toasts'; -import { DataGridProvider } from './data_grid'; +import { DataGridService } from './data_grid'; import { PieChartProvider, ElasticChartProvider, @@ -69,7 +69,7 @@ export const services = { dashboardPanelActions: DashboardPanelActionsProvider, flyout: FlyoutProvider, comboBox: ComboBoxProvider, - dataGrid: DataGridProvider, + dataGrid: DataGridService, embedding: EmbeddingProvider, renderable: RenderableProvider, browser: BrowserProvider, From c42f6c3063d41b626e9711645a7ea4f7b2ba6e4a Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Wed, 26 May 2021 11:34:08 +0300 Subject: [PATCH 09/18] Fixed comparing real value with formatted according to mode. (#100456) Before this part of code was comparing clean data, which came from dataset, with X/Y values. They were true according to normal mode, but in percentage mode, for example, it was comparing absolute value with percentage value. To avoid it, need to compare datum (feature #822 from elastic/elastic-charts) of geometry with clean value from row info. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../charts/public/static/utils/transform_click_event.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/charts/public/static/utils/transform_click_event.ts b/src/plugins/charts/public/static/utils/transform_click_event.ts index 0c303b92bf1a1c..844e2c3b301fa1 100644 --- a/src/plugins/charts/public/static/utils/transform_click_event.ts +++ b/src/plugins/charts/public/static/utils/transform_click_event.ts @@ -152,9 +152,9 @@ const rowFindPredicate = ( ) => (row: Datatable['rows'][number]): boolean => (geometry === null || (xAccessor !== null && - getAccessorValue(row, xAccessor) === geometry.x && + getAccessorValue(row, xAccessor) === getAccessorValue(geometry.datum, xAccessor) && yAccessor !== null && - getAccessorValue(row, yAccessor) === geometry.y && + getAccessorValue(row, yAccessor) === getAccessorValue(geometry.datum, yAccessor) && (splitChartAccessor === undefined || (splitChartValue !== undefined && getAccessorValue(row, splitChartAccessor) === splitChartValue)))) && From 749c69b93601f6daab6a0fd162136ae1a9136bf0 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 May 2021 01:37:34 -0700 Subject: [PATCH 10/18] [ftr] migrate "listingTable" service to FtrService class (#100606) Co-authored-by: spalger --- test/functional/services/index.ts | 4 +- test/functional/services/listing_table.ts | 339 +++++++++++----------- 2 files changed, 170 insertions(+), 173 deletions(-) diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index f5415e34c29e88..b0ac8f50624a3d 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -43,7 +43,7 @@ import { ElasticChartProvider, VegaDebugInspectorViewProvider, } from './visualizations'; -import { ListingTableProvider } from './listing_table'; +import { ListingTableService } from './listing_table'; import { SavedQueryManagementComponentProvider } from './saved_query_management_component'; import { KibanaSupertestProvider } from './supertest'; import { MenuToggleProvider } from './menu_toggle'; @@ -63,7 +63,7 @@ export const services = { dashboardVisualizations: DashboardVisualizationProvider, dashboardExpect: DashboardExpectProvider, failureDebugging: FailureDebuggingProvider, - listingTable: ListingTableProvider, + listingTable: ListingTableService, dashboardAddPanel: DashboardAddPanelProvider, dashboardReplacePanel: DashboardReplacePanelProvider, dashboardPanelActions: DashboardPanelActionsProvider, diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index 0e1fa4a7b2117f..79678cf7a812b8 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -7,202 +7,199 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -type AppName = 'visualize' | 'dashboard' | 'map'; +type AppName = keyof typeof PREFIX_MAP; +const PREFIX_MAP = { visualize: 'vis', dashboard: 'dashboard', map: 'map' }; -export function ListingTableProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const log = getService('log'); - const retry = getService('retry'); - const { common, header } = getPageObjects(['common', 'header']); - const prefixMap = { visualize: 'vis', dashboard: 'dashboard', map: 'map' }; +export class ListingTableService extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly common = this.ctx.getPageObjects(['common']).common; + private readonly header = this.ctx.getPageObjects(['header']).header; - class ListingTable { - private async getSearchFilter() { - return await testSubjects.find('tableListSearchBox'); - } + private async getSearchFilter() { + return await this.testSubjects.find('tableListSearchBox'); + } - /** - * Returns search input value on landing page - */ - public async getSearchFilterValue() { - const searchFilter = await this.getSearchFilter(); - return await searchFilter.getAttribute('value'); - } + /** + * Returns search input value on landing page + */ + public async getSearchFilterValue() { + const searchFilter = await this.getSearchFilter(); + return await searchFilter.getAttribute('value'); + } - /** - * Clears search input on landing page - */ - public async clearSearchFilter() { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await searchFilter.click(); - } + /** + * Clears search input on landing page + */ + public async clearSearchFilter() { + const searchFilter = await this.getSearchFilter(); + await searchFilter.clearValue(); + await searchFilter.click(); + } - private async getAllItemsNamesOnCurrentPage(): Promise { - const visualizationNames = []; - const links = await find.allByCssSelector('.euiTableRow .euiLink'); - for (let i = 0; i < links.length; i++) { - visualizationNames.push(await links[i].getVisibleText()); - } - log.debug(`Found ${visualizationNames.length} visualizations on current page`); - return visualizationNames; + private async getAllItemsNamesOnCurrentPage(): Promise { + const visualizationNames = []; + const links = await this.find.allByCssSelector('.euiTableRow .euiLink'); + for (let i = 0; i < links.length; i++) { + visualizationNames.push(await links[i].getVisibleText()); } + this.log.debug(`Found ${visualizationNames.length} visualizations on current page`); + return visualizationNames; + } - public async waitUntilTableIsLoaded() { - return retry.try(async () => { - const isLoaded = await find.existsByDisplayedByCssSelector( - '[data-test-subj="itemsInMemTable"]:not(.euiBasicTable-loading)' - ); - - if (isLoaded) { - return true; - } else { - throw new Error('Waiting'); - } - }); - } + public async waitUntilTableIsLoaded() { + return this.retry.try(async () => { + const isLoaded = await this.find.existsByDisplayedByCssSelector( + '[data-test-subj="itemsInMemTable"]:not(.euiBasicTable-loading)' + ); - /** - * Navigates through all pages on Landing page and returns array of items names - */ - public async getAllItemsNames(): Promise { - log.debug('ListingTable.getAllItemsNames'); - let morePages = true; - let visualizationNames: string[] = []; - while (morePages) { - visualizationNames = visualizationNames.concat(await this.getAllItemsNamesOnCurrentPage()); - morePages = !( - (await testSubjects.getAttribute('pagination-button-next', 'disabled')) === 'true' - ); - if (morePages) { - await testSubjects.click('pagerNextButton'); - await header.waitUntilLoadingHasFinished(); - } + if (isLoaded) { + return true; + } else { + throw new Error('Waiting'); } - return visualizationNames; - } + }); + } - /** - * Returns items count on landing page - */ - public async expectItemsCount(appName: AppName, count: number) { - await retry.try(async () => { - const elements = await find.allByCssSelector( - `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` - ); - expect(elements.length).to.equal(count); - }); + /** + * Navigates through all pages on Landing page and returns array of items names + */ + public async getAllItemsNames(): Promise { + this.log.debug('ListingTable.getAllItemsNames'); + let morePages = true; + let visualizationNames: string[] = []; + while (morePages) { + visualizationNames = visualizationNames.concat(await this.getAllItemsNamesOnCurrentPage()); + morePages = !( + (await this.testSubjects.getAttribute('pagination-button-next', 'disabled')) === 'true' + ); + if (morePages) { + await this.testSubjects.click('pagerNextButton'); + await this.header.waitUntilLoadingHasFinished(); + } } + return visualizationNames; + } - /** - * Types name into search field on Landing page and waits till search completed - * @param name item name - */ - public async searchForItemWithName(name: string, { escape = true }: { escape?: boolean } = {}) { - log.debug(`searchForItemWithName: ${name}`); - - await retry.try(async () => { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await searchFilter.click(); - - if (escape) { - name = name - // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. - .replace('-', ' ') - // Remove `[*]` from search as it is not supported by EUI Query's syntax. - .replace(/ *\[[^)]*\] */g, ''); - } - - await searchFilter.type(name); - await common.pressEnterKey(); - }); + /** + * Returns items count on landing page + */ + public async expectItemsCount(appName: AppName, count: number) { + await this.retry.try(async () => { + const elements = await this.find.allByCssSelector( + `[data-test-subj^="${PREFIX_MAP[appName]}ListingTitleLink"]` + ); + expect(elements.length).to.equal(count); + }); + } - await header.waitUntilLoadingHasFinished(); - } + /** + * Types name into search field on Landing page and waits till search completed + * @param name item name + */ + public async searchForItemWithName(name: string, { escape = true }: { escape?: boolean } = {}) { + this.log.debug(`searchForItemWithName: ${name}`); - /** - * Searches for item on Landing page and retruns items count that match `ListingTitleLink-${name}` pattern - */ - public async searchAndExpectItemsCount(appName: AppName, name: string, count: number) { - await this.searchForItemWithName(name); - await retry.try(async () => { - const links = await testSubjects.findAll( - `${prefixMap[appName]}ListingTitleLink-${name.replace(/ /g, '-')}` - ); - expect(links.length).to.equal(count); - }); - } + await this.retry.try(async () => { + const searchFilter = await this.getSearchFilter(); + await searchFilter.clearValue(); + await searchFilter.click(); - public async clickDeleteSelected() { - await testSubjects.click('deleteSelectedItems'); - } + if (escape) { + name = name + // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. + .replace('-', ' ') + // Remove `[*]` from search as it is not supported by EUI Query's syntax. + .replace(/ *\[[^)]*\] */g, ''); + } - public async clickItemCheckbox(id: string) { - await testSubjects.click(`checkboxSelectRow-${id}`); - } + await searchFilter.type(name); + await this.common.pressEnterKey(); + }); - /** - * Searches for item by name, selects checbox and deletes it - * @param name item name - * @param id row id - */ - public async deleteItem(name: string, id: string) { - await this.searchForItemWithName(name); - await this.clickItemCheckbox(id); - await this.clickDeleteSelected(); - await common.clickConfirmOnModal(); - } + await this.header.waitUntilLoadingHasFinished(); + } - /** - * Clicks item on Landing page by link name if it is present - */ - public async clickItemLink(appName: AppName, name: string) { - await testSubjects.click( - `${prefixMap[appName]}ListingTitleLink-${name.split(' ').join('-')}` + /** + * Searches for item on Landing page and retruns items count that match `ListingTitleLink-${name}` pattern + */ + public async searchAndExpectItemsCount(appName: AppName, name: string, count: number) { + await this.searchForItemWithName(name); + await this.retry.try(async () => { + const links = await this.testSubjects.findAll( + `${PREFIX_MAP[appName]}ListingTitleLink-${name.replace(/ /g, '-')}` ); - } + expect(links.length).to.equal(count); + }); + } - /** - * Checks 'SelectAll' checkbox on - */ - public async checkListingSelectAllCheckbox() { - const element = await testSubjects.find('checkboxSelectAll'); - const isSelected = await element.isSelected(); - if (!isSelected) { - log.debug(`checking checkbox "checkboxSelectAll"`); - await testSubjects.click('checkboxSelectAll'); - } - } + public async clickDeleteSelected() { + await this.testSubjects.click('deleteSelectedItems'); + } - /** - * Clicks NewItem button on Landing page - * @param promptBtnTestSubj testSubj locator for Prompt button - */ - public async clickNewButton(promptBtnTestSubj: string): Promise { - await retry.tryForTime(20000, async () => { - // newItemButton button is only visible when there are items in the listing table is displayed. - const isnNewItemButtonPresent = await testSubjects.exists('newItemButton', { - timeout: 10000, - }); - if (isnNewItemButtonPresent) { - await testSubjects.click('newItemButton'); - } else { - // no items exist, click createPromptButton to create new dashboard/visualization - await testSubjects.click(promptBtnTestSubj); - } - }); + public async clickItemCheckbox(id: string) { + await this.testSubjects.click(`checkboxSelectRow-${id}`); + } + + /** + * Searches for item by name, selects checbox and deletes it + * @param name item name + * @param id row id + */ + public async deleteItem(name: string, id: string) { + await this.searchForItemWithName(name); + await this.clickItemCheckbox(id); + await this.clickDeleteSelected(); + await this.common.clickConfirmOnModal(); + } + + /** + * Clicks item on Landing page by link name if it is present + */ + public async clickItemLink(appName: AppName, name: string) { + await this.testSubjects.click( + `${PREFIX_MAP[appName]}ListingTitleLink-${name.split(' ').join('-')}` + ); + } + + /** + * Checks 'SelectAll' checkbox on + */ + public async checkListingSelectAllCheckbox() { + const element = await this.testSubjects.find('checkboxSelectAll'); + const isSelected = await element.isSelected(); + if (!isSelected) { + this.log.debug(`checking checkbox "checkboxSelectAll"`); + await this.testSubjects.click('checkboxSelectAll'); } + } - public async onListingPage(appName: AppName) { - return await testSubjects.exists(`${appName}LandingPage`, { - timeout: 5000, + /** + * Clicks NewItem button on Landing page + * @param promptBtnTestSubj testSubj locator for Prompt button + */ + public async clickNewButton(promptBtnTestSubj: string): Promise { + await this.retry.tryForTime(20000, async () => { + // newItemButton button is only visible when there are items in the listing table is displayed. + const isnNewItemButtonPresent = await this.testSubjects.exists('newItemButton', { + timeout: 10000, }); - } + if (isnNewItemButtonPresent) { + await this.testSubjects.click('newItemButton'); + } else { + // no items exist, click createPromptButton to create new dashboard/visualization + await this.testSubjects.click(promptBtnTestSubj); + } + }); } - return new ListingTable(); + public async onListingPage(appName: AppName) { + return await this.testSubjects.exists(`${appName}LandingPage`, { + timeout: 5000, + }); + } } From 987c7369578fbc7382979ce80713bfa327bc5e3b Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 May 2021 01:42:45 -0700 Subject: [PATCH 11/18] [ftr] migrate "docTable" service to FtrService class (#100595) Co-authored-by: spalger --- test/functional/services/doc_table.ts | 323 +++++++++++++------------- test/functional/services/index.ts | 4 +- 2 files changed, 160 insertions(+), 167 deletions(-) diff --git a/test/functional/services/doc_table.ts b/test/functional/services/doc_table.ts index 35c3531c70c417..6c73faec16b1a9 100644 --- a/test/functional/services/doc_table.ts +++ b/test/functional/services/doc_table.ts @@ -6,177 +6,170 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from './lib/web_element_wrapper'; -export function DocTableProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'header']); +interface SelectOptions { + isAnchorRow?: boolean; + rowIndex?: number; +} + +export class DocTableService extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly PageObjects = this.ctx.getPageObjects(['common', 'header']); - interface SelectOptions { - isAnchorRow?: boolean; - rowIndex?: number; + public async getTable(selector?: string) { + return await this.testSubjects.find(selector ? selector : 'docTable'); } - class DocTable { - public async getTable(selector?: string) { - return await testSubjects.find(selector ? selector : 'docTable'); - } + public async getRowsText() { + const table = await this.getTable(); + const $ = await table.parseDomContent(); + return $.findTestSubjects('~docTableRow') + .toArray() + .map((row: any) => $(row).text().trim()); + } - public async getRowsText() { - const table = await this.getTable(); - const $ = await table.parseDomContent(); - return $.findTestSubjects('~docTableRow') - .toArray() - .map((row: any) => $(row).text().trim()); - } - - public async getBodyRows(): Promise { - const table = await this.getTable(); - return await table.findAllByTestSubject('~docTableRow'); - } - - public async getAnchorRow(): Promise { - const table = await this.getTable(); - return await table.findByTestSubject('~docTableAnchorRow'); - } - - public async getRow({ - isAnchorRow = false, - rowIndex = 0, - }: SelectOptions = {}): Promise { - return isAnchorRow ? await this.getAnchorRow() : (await this.getBodyRows())[rowIndex]; - } - - public async getDetailsRow(): Promise { - const table = await this.getTable(); - return await table.findByCssSelector('[data-test-subj~="docTableDetailsRow"]'); - } - - public async getAnchorDetailsRow(): Promise { - const table = await this.getTable(); - return await table.findByCssSelector( - '[data-test-subj~="docTableAnchorRow"] + [data-test-subj~="docTableDetailsRow"]' - ); - } - - public async clickRowToggle( - options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } - ): Promise { - const row = await this.getRow(options); - const toggle = await row.findByTestSubject('~docTableExpandToggleColumn'); - await toggle.click(); - } - - public async getDetailsRows(): Promise { - const table = await this.getTable(); - return await table.findAllByCssSelector( - '[data-test-subj~="docTableRow"] + [data-test-subj~="docTableDetailsRow"]' - ); - } - - public async getRowActions({ isAnchorRow = false, rowIndex = 0 }: SelectOptions = {}): Promise< - WebElementWrapper[] - > { - const detailsRow = isAnchorRow - ? await this.getAnchorDetailsRow() - : (await this.getDetailsRows())[rowIndex]; - return await detailsRow.findAllByTestSubject('~docTableRowAction'); - } - - public async getFields(options: { isAnchorRow: boolean } = { isAnchorRow: false }) { - const table = await this.getTable(); - const $ = await table.parseDomContent(); - const rowLocator = options.isAnchorRow ? '~docTableAnchorRow' : '~docTableRow'; - const rows = $.findTestSubjects(rowLocator).toArray(); - return rows.map((row: any) => - $(row) - .find('[data-test-subj~="docTableField"]') - .toArray() - .map((field: any) => $(field).text()) - ); - } + public async getBodyRows(): Promise { + const table = await this.getTable(); + return await table.findAllByTestSubject('~docTableRow'); + } + + public async getAnchorRow(): Promise { + const table = await this.getTable(); + return await table.findByTestSubject('~docTableAnchorRow'); + } + + public async getRow({ + isAnchorRow = false, + rowIndex = 0, + }: SelectOptions = {}): Promise { + return isAnchorRow ? await this.getAnchorRow() : (await this.getBodyRows())[rowIndex]; + } + + public async getDetailsRow(): Promise { + const table = await this.getTable(); + return await table.findByCssSelector('[data-test-subj~="docTableDetailsRow"]'); + } + + public async getAnchorDetailsRow(): Promise { + const table = await this.getTable(); + return await table.findByCssSelector( + '[data-test-subj~="docTableAnchorRow"] + [data-test-subj~="docTableDetailsRow"]' + ); + } + + public async clickRowToggle( + options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } + ): Promise { + const row = await this.getRow(options); + const toggle = await row.findByTestSubject('~docTableExpandToggleColumn'); + await toggle.click(); + } + + public async getDetailsRows(): Promise { + const table = await this.getTable(); + return await table.findAllByCssSelector( + '[data-test-subj~="docTableRow"] + [data-test-subj~="docTableDetailsRow"]' + ); + } + + public async getRowActions({ isAnchorRow = false, rowIndex = 0 }: SelectOptions = {}): Promise< + WebElementWrapper[] + > { + const detailsRow = isAnchorRow + ? await this.getAnchorDetailsRow() + : (await this.getDetailsRows())[rowIndex]; + return await detailsRow.findAllByTestSubject('~docTableRowAction'); + } - public async getHeaderFields(selector?: string): Promise { - const table = await this.getTable(selector); - const $ = await table.parseDomContent(); - return $.findTestSubjects('~docTableHeaderField') + public async getFields(options: { isAnchorRow: boolean } = { isAnchorRow: false }) { + const table = await this.getTable(); + const $ = await table.parseDomContent(); + const rowLocator = options.isAnchorRow ? '~docTableAnchorRow' : '~docTableRow'; + const rows = $.findTestSubjects(rowLocator).toArray(); + return rows.map((row: any) => + $(row) + .find('[data-test-subj~="docTableField"]') .toArray() - .map((field: any) => $(field).text().trim()); - } - - public async getHeaders(selector?: string): Promise { - return this.getHeaderFields(selector); - } - - public async getTableDocViewRow( - detailsRow: WebElementWrapper, - fieldName: string - ): Promise { - return await detailsRow.findByTestSubject(`~tableDocViewRow-${fieldName}`); - } - - public async getAddInclusiveFilterButton( - tableDocViewRow: WebElementWrapper - ): Promise { - return await tableDocViewRow.findByTestSubject(`~addInclusiveFilterButton`); - } - - public async addInclusiveFilter( - detailsRow: WebElementWrapper, - fieldName: string - ): Promise { - const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); - const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow); - await addInclusiveFilterButton.click(); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } - - public async getRemoveInclusiveFilterButton( - tableDocViewRow: WebElementWrapper - ): Promise { - return await tableDocViewRow.findByTestSubject(`~removeInclusiveFilterButton`); - } - - public async removeInclusiveFilter( - detailsRow: WebElementWrapper, - fieldName: string - ): Promise { - const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); - const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow); - await addInclusiveFilterButton.click(); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } - - public async getAddExistsFilterButton( - tableDocViewRow: WebElementWrapper - ): Promise { - return await tableDocViewRow.findByTestSubject(`~addExistsFilterButton`); - } - - public async addExistsFilter(detailsRow: WebElementWrapper, fieldName: string): Promise { - const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); - const addInclusiveFilterButton = await this.getAddExistsFilterButton(tableDocViewRow); - await addInclusiveFilterButton.click(); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } - - public async toggleRowExpanded({ - isAnchorRow = false, - rowIndex = 0, - }: SelectOptions = {}): Promise { - await this.clickRowToggle({ isAnchorRow, rowIndex }); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - return await retry.try(async () => { - const row = isAnchorRow ? await this.getAnchorRow() : (await this.getBodyRows())[rowIndex]; - const detailsRow = await row.findByXpath( - './following-sibling::*[@data-test-subj="docTableDetailsRow"]' - ); - return detailsRow.findByTestSubject('~docViewer'); - }); - } - } - - return new DocTable(); + .map((field: any) => $(field).text()) + ); + } + + public async getHeaderFields(selector?: string): Promise { + const table = await this.getTable(selector); + const $ = await table.parseDomContent(); + return $.findTestSubjects('~docTableHeaderField') + .toArray() + .map((field: any) => $(field).text().trim()); + } + + public async getHeaders(selector?: string): Promise { + return this.getHeaderFields(selector); + } + + public async getTableDocViewRow( + detailsRow: WebElementWrapper, + fieldName: string + ): Promise { + return await detailsRow.findByTestSubject(`~tableDocViewRow-${fieldName}`); + } + + public async getAddInclusiveFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByTestSubject(`~addInclusiveFilterButton`); + } + + public async addInclusiveFilter(detailsRow: WebElementWrapper, fieldName: string): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } + + public async getRemoveInclusiveFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByTestSubject(`~removeInclusiveFilterButton`); + } + + public async removeInclusiveFilter( + detailsRow: WebElementWrapper, + fieldName: string + ): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } + + public async getAddExistsFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByTestSubject(`~addExistsFilterButton`); + } + + public async addExistsFilter(detailsRow: WebElementWrapper, fieldName: string): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getAddExistsFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } + + public async toggleRowExpanded({ + isAnchorRow = false, + rowIndex = 0, + }: SelectOptions = {}): Promise { + await this.clickRowToggle({ isAnchorRow, rowIndex }); + await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + return await this.retry.try(async () => { + const row = isAnchorRow ? await this.getAnchorRow() : (await this.getBodyRows())[rowIndex]; + const detailsRow = await row.findByXpath( + './following-sibling::*[@data-test-subj="docTableDetailsRow"]' + ); + return detailsRow.findByTestSubject('~docViewer'); + }); + } } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index b0ac8f50624a3d..43f891ee4644fe 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -25,7 +25,7 @@ import { DashboardPanelActionsProvider, DashboardVisualizationProvider, } from './dashboard'; -import { DocTableProvider } from './doc_table'; +import { DocTableService } from './doc_table'; import { EmbeddingProvider } from './embedding'; import { FilterBarService } from './filter_bar'; import { FlyoutProvider } from './flyout'; @@ -57,7 +57,7 @@ export const services = { queryBar: QueryBarProvider, find: FindProvider, testSubjects: TestSubjectsProvider, - docTable: DocTableProvider, + docTable: DocTableService, screenshots: ScreenshotsProvider, snapshots: SnapshotsProvider, dashboardVisualizations: DashboardVisualizationProvider, From f915b6fe73f21eeb82e00c60641fa46c9a474acc Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 26 May 2021 10:57:01 +0200 Subject: [PATCH 12/18] [telemetry] report config deprecations (#99887) * return the list of changes config keys during deprecation * gather changed config keys in the core * adjust Security plugin deprecations tests * update docs * update interface * update telemetry schema * update spaces tests * update tests in other x-pack plugins * remove testing instruction * improve tests. get rid of snapshots --- .../kbn-config/src/config_service.mock.ts | 2 + .../src/config_service.test.mocks.ts | 11 +- .../kbn-config/src/config_service.test.ts | 25 +- packages/kbn-config/src/config_service.ts | 12 +- .../deprecation/apply_deprecations.test.ts | 29 ++- .../src/deprecation/apply_deprecations.ts | 19 +- packages/kbn-config/src/deprecation/index.ts | 1 + packages/kbn-config/src/deprecation/types.ts | 10 + packages/kbn-config/src/index.ts | 1 + src/core/server/config/test_utils.ts | 2 +- .../core_usage_data_service.mock.ts | 4 + .../core_usage_data_service.test.ts | 225 ++++++++---------- .../core_usage_data_service.ts | 14 +- src/core/server/core_usage_data/types.ts | 5 + src/core/server/server.api.md | 5 + src/core/server/server.ts | 2 + .../collectors/core/core_usage_collector.ts | 17 ++ src/plugins/telemetry/schema/oss_plugins.json | 22 ++ .../reporting/server/config/index.test.ts | 2 +- .../server/config_deprecations.test.ts | 2 +- x-pack/plugins/spaces/server/config.test.ts | 2 +- .../plugins/task_manager/server/index.test.ts | 2 +- 22 files changed, 269 insertions(+), 145 deletions(-) diff --git a/packages/kbn-config/src/config_service.mock.ts b/packages/kbn-config/src/config_service.mock.ts index 83fbf20b5c0b3d..68adbba7c0ed73 100644 --- a/packages/kbn-config/src/config_service.mock.ts +++ b/packages/kbn-config/src/config_service.mock.ts @@ -26,11 +26,13 @@ const createConfigServiceMock = ({ addDeprecationProvider: jest.fn(), validate: jest.fn(), getHandledDeprecatedConfigs: jest.fn(), + getDeprecatedConfigPath$: jest.fn(), }; mocked.atPath.mockReturnValue(new BehaviorSubject(atPath)); mocked.atPathSync.mockReturnValue(atPath); mocked.getConfig$.mockReturnValue(new BehaviorSubject(new ObjectToConfigAdapter(getConfig$))); + mocked.getDeprecatedConfigPath$.mockReturnValue(new BehaviorSubject({ set: [], unset: [] })); mocked.getUsedPaths.mockResolvedValue([]); mocked.getUnusedPaths.mockResolvedValue([]); mocked.isEnabledAtPath.mockResolvedValue(true); diff --git a/packages/kbn-config/src/config_service.test.mocks.ts b/packages/kbn-config/src/config_service.test.mocks.ts index d8da2852b92515..39aa551ae85f95 100644 --- a/packages/kbn-config/src/config_service.test.mocks.ts +++ b/packages/kbn-config/src/config_service.test.mocks.ts @@ -11,10 +11,17 @@ import type { applyDeprecations } from './deprecation/apply_deprecations'; jest.mock('../../../package.json', () => mockPackage); +const changedPaths = { + set: ['foo'], + unset: ['bar.baz'], +}; + +export { changedPaths as mockedChangedPaths }; + export const mockApplyDeprecations = jest.fn< - Record, + ReturnType, Parameters ->((config, deprecations, createAddDeprecation) => config); +>((config, deprecations, createAddDeprecation) => ({ config, changedPaths })); jest.mock('./deprecation/apply_deprecations', () => ({ applyDeprecations: mockApplyDeprecations, diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index 64404341bc64d0..c2d4f15b6d9153 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -9,7 +9,7 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { first, take } from 'rxjs/operators'; -import { mockApplyDeprecations } from './config_service.test.mocks'; +import { mockApplyDeprecations, mockedChangedPaths } from './config_service.test.mocks'; import { rawConfigServiceMock } from './raw/raw_config_service.mock'; import { schema } from '@kbn/config-schema'; @@ -420,7 +420,7 @@ test('logs deprecation warning during validation', async () => { const addDeprecation = createAddDeprecation!(''); addDeprecation({ message: 'some deprecation message' }); addDeprecation({ message: 'another deprecation message' }); - return config; + return { config, changedPaths: mockedChangedPaths }; }); loggerMock.clear(logger); @@ -446,12 +446,12 @@ test('does not log warnings for silent deprecations during validation', async () const addDeprecation = createAddDeprecation!(''); addDeprecation({ message: 'some deprecation message', silent: true }); addDeprecation({ message: 'another deprecation message' }); - return config; + return { config, changedPaths: mockedChangedPaths }; }) .mockImplementationOnce((config, deprecations, createAddDeprecation) => { const addDeprecation = createAddDeprecation!(''); addDeprecation({ message: 'I am silent', silent: true }); - return config; + return { config, changedPaths: mockedChangedPaths }; }); loggerMock.clear(logger); @@ -521,7 +521,7 @@ describe('getHandledDeprecatedConfigs', () => { const addDeprecation = createAddDeprecation!(deprecation.path); addDeprecation({ message: `some deprecation message`, documentationUrl: 'some-url' }); }); - return config; + return { config, changedPaths: mockedChangedPaths }; }); await configService.validate(); @@ -541,3 +541,18 @@ describe('getHandledDeprecatedConfigs', () => { `); }); }); + +describe('getDeprecatedConfigPath$', () => { + it('returns all config paths changes during deprecation', async () => { + const rawConfig$ = new BehaviorSubject>({ key: 'value' }); + const rawConfigProvider = rawConfigServiceMock.create({ rawConfig$ }); + + const configService = new ConfigService(rawConfigProvider, defaultEnv, logger); + await configService.setSchema('key', schema.string()); + await configService.validate(); + + const deprecatedConfigPath$ = configService.getDeprecatedConfigPath$(); + const deprecatedConfigPath = await deprecatedConfigPath$.pipe(first()).toPromise(); + expect(deprecatedConfigPath).toEqual(mockedChangedPaths); + }); +}); diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index 91927b4c7b5c96..a80680bd46dfca 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -22,6 +22,7 @@ import { ConfigDeprecationProvider, configDeprecationFactory, DeprecatedConfigDetails, + ChangedDeprecatedPaths, } from './deprecation'; import { LegacyObjectToConfigAdapter } from './legacy'; @@ -36,6 +37,10 @@ export class ConfigService { private validated = false; private readonly config$: Observable; private lastConfig?: Config; + private readonly deprecatedConfigPaths = new BehaviorSubject({ + set: [], + unset: [], + }); /** * Whenever a config if read at a path, we mark that path as 'handled'. We can @@ -57,7 +62,8 @@ export class ConfigService { this.config$ = combineLatest([this.rawConfigProvider.getConfig$(), this.deprecations]).pipe( map(([rawConfig, deprecations]) => { const migrated = applyDeprecations(rawConfig, deprecations); - return new LegacyObjectToConfigAdapter(migrated); + this.deprecatedConfigPaths.next(migrated.changedPaths); + return new LegacyObjectToConfigAdapter(migrated.config); }), tap((config) => { this.lastConfig = config; @@ -191,6 +197,10 @@ export class ConfigService { return config.getFlattenedPaths().filter((path) => isPathHandled(path, handledPaths)); } + public getDeprecatedConfigPath$() { + return this.deprecatedConfigPaths.asObservable(); + } + private async logDeprecation() { const rawConfig = await this.rawConfigProvider.getConfig$().pipe(take(1)).toPromise(); const deprecations = await this.deprecations.pipe(take(1)).toPromise(); diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts index 47746967bbe5f1..8ad1491c19c9b1 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts @@ -82,7 +82,7 @@ describe('applyDeprecations', () => { it('returns the migrated config', () => { const initialConfig = { foo: 'bar', deprecated: 'deprecated', renamed: 'renamed' }; - const migrated = applyDeprecations(initialConfig, [ + const { config: migrated } = applyDeprecations(initialConfig, [ wrapHandler(deprecations.unused('deprecated')), wrapHandler(deprecations.rename('renamed', 'newname')), ]); @@ -93,7 +93,7 @@ describe('applyDeprecations', () => { it('does not alter the initial config', () => { const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; - const migrated = applyDeprecations(initialConfig, [ + const { config: migrated } = applyDeprecations(initialConfig, [ wrapHandler(deprecations.unused('deprecated')), ]); @@ -110,7 +110,7 @@ describe('applyDeprecations', () => { return { unset: [{ path: 'unknown' }] }; }); - const migrated = applyDeprecations( + const { config: migrated } = applyDeprecations( initialConfig, [wrapHandler(handler, 'pathA')], createAddDeprecation @@ -128,7 +128,7 @@ describe('applyDeprecations', () => { return { rewrite: [{ path: 'foo' }] }; }); - const migrated = applyDeprecations( + const { config: migrated } = applyDeprecations( initialConfig, [wrapHandler(handler, 'pathA')], createAddDeprecation @@ -136,4 +136,25 @@ describe('applyDeprecations', () => { expect(migrated).toEqual(initialConfig); }); + + it('returns a list of changes config paths', () => { + const addDeprecation = jest.fn(); + const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); + const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; + + const handler = jest.fn().mockImplementation((config) => { + return { set: [{ path: 'foo', value: 'bar' }], unset: [{ path: 'baz' }] }; + }); + + const { changedPaths } = applyDeprecations( + initialConfig, + [wrapHandler(handler, 'pathA')], + createAddDeprecation + ); + + expect(changedPaths).toEqual({ + set: ['foo'], + unset: ['baz'], + }); + }); }); diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.ts b/packages/kbn-config/src/deprecation/apply_deprecations.ts index 092a5ced28371d..d38ae988358313 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.ts @@ -8,7 +8,11 @@ import { cloneDeep, unset } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; -import { ConfigDeprecationWithContext, AddConfigDeprecation } from './types'; +import type { + AddConfigDeprecation, + ChangedDeprecatedPaths, + ConfigDeprecationWithContext, +} from './types'; const noopAddDeprecationFactory: () => AddConfigDeprecation = () => () => undefined; /** @@ -22,22 +26,31 @@ export const applyDeprecations = ( config: Record, deprecations: ConfigDeprecationWithContext[], createAddDeprecation: (pluginId: string) => AddConfigDeprecation = noopAddDeprecationFactory -) => { +): { config: Record; changedPaths: ChangedDeprecatedPaths } => { const result = cloneDeep(config); + const changedPaths: ChangedDeprecatedPaths = { + set: [], + unset: [], + }; deprecations.forEach(({ deprecation, path }) => { const commands = deprecation(result, path, createAddDeprecation(path)); if (commands) { if (commands.set) { + changedPaths.set.push(...commands.set.map((c) => c.path)); commands.set.forEach(function ({ path: commandPath, value }) { set(result, commandPath, value); }); } if (commands.unset) { + changedPaths.unset.push(...commands.unset.map((c) => c.path)); commands.unset.forEach(function ({ path: commandPath }) { unset(result, commandPath); }); } } }); - return result; + return { + config: result, + changedPaths, + }; }; diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 48576e6d830bef..ce10bafd9c575b 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -14,6 +14,7 @@ export type { AddConfigDeprecation, ConfigDeprecationProvider, DeprecatedConfigDetails, + ChangedDeprecatedPaths, } from './types'; export { configDeprecationFactory } from './deprecation_factory'; export { applyDeprecations } from './apply_deprecations'; diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts index 6944f45c1e1d22..0522365ad76c11 100644 --- a/packages/kbn-config/src/deprecation/types.ts +++ b/packages/kbn-config/src/deprecation/types.ts @@ -55,6 +55,16 @@ export type ConfigDeprecation = ( addDeprecation: AddConfigDeprecation ) => void | ConfigDeprecationCommand; +/** + * List of config paths changed during deprecation. + * + * @public + */ +export interface ChangedDeprecatedPaths { + set: string[]; + unset: string[]; +} + /** * Outcome of deprecation operation. Allows mutating config values in a declarative way. * diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index cf875d3daa4a21..294caba4e7048b 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -13,6 +13,7 @@ export type { ConfigDeprecationWithContext, ConfigDeprecation, ConfigDeprecationCommand, + ChangedDeprecatedPaths, } from './deprecation'; export { applyDeprecations, configDeprecationFactory } from './deprecation'; diff --git a/src/core/server/config/test_utils.ts b/src/core/server/config/test_utils.ts index 8e20e87e6f7d8e..ab06ff50012b7b 100644 --- a/src/core/server/config/test_utils.ts +++ b/src/core/server/config/test_utils.ts @@ -16,7 +16,7 @@ function collectDeprecations( ) { const deprecations = provider(configDeprecationFactory); const deprecationMessages: string[] = []; - const migrated = applyDeprecations( + const { config: migrated } = applyDeprecations( settings, deprecations.map((deprecation) => ({ deprecation, diff --git a/src/core/server/core_usage_data/core_usage_data_service.mock.ts b/src/core/server/core_usage_data/core_usage_data_service.mock.ts index e09f595747c30b..5fa67fecb2a8ae 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.mock.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.mock.ts @@ -116,6 +116,10 @@ const createStartContractMock = () => { maxImportExportSize: 10000, maxImportPayloadBytes: 26214400, }, + deprecatedKeys: { + set: ['path.to.a.prop'], + unset: [], + }, }, environment: { memory: { diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index dc74b65c8dcfc2..95dd392016c179 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -91,7 +91,8 @@ describe('CoreUsageDataService', () => { const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - service.setup({ http, metrics, savedObjectsStartPromise }); + const changedDeprecatedConfigPath$ = configServiceMock.create().getDeprecatedConfigPath$(); + service.setup({ http, metrics, savedObjectsStartPromise, changedDeprecatedConfigPath$ }); const savedObjects = await savedObjectsStartPromise; expect(savedObjects.createInternalRepository).toHaveBeenCalledTimes(1); @@ -105,7 +106,13 @@ describe('CoreUsageDataService', () => { const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - const coreUsageData = service.setup({ http, metrics, savedObjectsStartPromise }); + const changedDeprecatedConfigPath$ = configServiceMock.create().getDeprecatedConfigPath$(); + const coreUsageData = service.setup({ + http, + metrics, + savedObjectsStartPromise, + changedDeprecatedConfigPath$, + }); const typeRegistry = typeRegistryMock.create(); coreUsageData.registerType(typeRegistry); @@ -126,7 +133,13 @@ describe('CoreUsageDataService', () => { const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - const coreUsageData = service.setup({ http, metrics, savedObjectsStartPromise }); + const changedDeprecatedConfigPath$ = configServiceMock.create().getDeprecatedConfigPath$(); + const coreUsageData = service.setup({ + http, + metrics, + savedObjectsStartPromise, + changedDeprecatedConfigPath$, + }); const usageStatsClient = coreUsageData.getClient(); expect(usageStatsClient).toBeInstanceOf(CoreUsageStatsClient); @@ -142,7 +155,11 @@ describe('CoreUsageDataService', () => { const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - service.setup({ http, metrics, savedObjectsStartPromise }); + const changedDeprecatedConfigPath$ = new BehaviorSubject({ + set: ['new.path'], + unset: ['deprecated.path'], + }); + service.setup({ http, metrics, savedObjectsStartPromise, changedDeprecatedConfigPath$ }); const elasticsearch = elasticsearchServiceMock.createStart(); elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({ body: [ @@ -180,6 +197,14 @@ describe('CoreUsageDataService', () => { expect(getCoreUsageData()).resolves.toMatchInlineSnapshot(` Object { "config": Object { + "deprecatedKeys": Object { + "set": Array [ + "new.path", + ], + "unset": Array [ + "deprecated.path", + ], + }, "elasticsearch": Object { "apiVersion": "master", "customHeadersConfigured": false, @@ -381,12 +406,10 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginA.enabled": "[redacted]", - "pluginAB.enabled": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginA.enabled': '[redacted]', + 'pluginAB.enabled': '[redacted]', + }); }); it('returns an object of plugin config usage', async () => { @@ -418,23 +441,21 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "elasticsearch.password": "[redacted]", - "elasticsearch.username": "[redacted]", - "logging.json": false, - "pluginA.arrayOfNumbers": "[redacted]", - "pluginA.enabled": true, - "pluginA.objectConfig.debug": true, - "pluginA.objectConfig.username": "[redacted]", - "pluginAB.enabled": false, - "pluginB.arrayOfObjects": "[redacted]", - "plugins.paths": "[redacted]", - "server.basePath": "/zvt", - "server.port": 5603, - "server.rewriteBasePath": true, - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'elasticsearch.password': '[redacted]', + 'elasticsearch.username': '[redacted]', + 'logging.json': false, + 'pluginA.arrayOfNumbers': '[redacted]', + 'pluginA.enabled': true, + 'pluginA.objectConfig.debug': true, + 'pluginA.objectConfig.username': '[redacted]', + 'pluginAB.enabled': false, + 'pluginB.arrayOfObjects': '[redacted]', + 'plugins.paths': '[redacted]', + 'server.basePath': '/zvt', + 'server.port': 5603, + 'server.rewriteBasePath': true, + }); }); describe('config explicitly exposed to usage', () => { @@ -457,12 +478,10 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginA.objectConfig.debug": "[redacted]", - "server.basePath": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginA.objectConfig.debug': '[redacted]', + 'server.basePath': '[redacted]', + }); }); it('returns config value on safe complete match', async () => { @@ -478,11 +497,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "server.basePath": "/zvt", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'server.basePath': '/zvt', + }); }); it('returns [redacted] on unsafe parent match', async () => { @@ -501,12 +518,10 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginA.objectConfig.debug": "[redacted]", - "pluginA.objectConfig.username": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginA.objectConfig.debug': '[redacted]', + 'pluginA.objectConfig.username': '[redacted]', + }); }); it('returns config value on safe parent match', async () => { @@ -525,12 +540,10 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginA.objectConfig.debug": true, - "pluginA.objectConfig.username": "some_user", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginA.objectConfig.debug': true, + 'pluginA.objectConfig.username': 'some_user', + }); }); it('returns [redacted] on explicitly marked as safe array of objects', async () => { @@ -546,11 +559,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginB.arrayOfObjects": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginB.arrayOfObjects': '[redacted]', + }); }); it('returns values on explicitly marked as safe array of numbers', async () => { @@ -566,15 +577,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginA.arrayOfNumbers": Array [ - 1, - 2, - 3, - ], - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginA.arrayOfNumbers': [1, 2, 3], + }); }); it('returns values on explicitly marked as safe array of strings', async () => { @@ -590,15 +595,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "plugins.paths": Array [ - "pluginA", - "pluginAB", - "pluginB", - ], - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'plugins.paths': ['pluginA', 'pluginAB', 'pluginB'], + }); }); }); @@ -619,12 +618,10 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginA.objectConfig.debug": "[redacted]", - "pluginA.objectConfig.username": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginA.objectConfig.debug': '[redacted]', + 'pluginA.objectConfig.username': '[redacted]', + }); }); it('returns config value on safe parent match', async () => { @@ -640,13 +637,11 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "elasticsearch.password": "[redacted]", - "elasticsearch.username": "[redacted]", - "pluginA.objectConfig.username": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'elasticsearch.password': '[redacted]', + 'elasticsearch.username': '[redacted]', + 'pluginA.objectConfig.username': '[redacted]', + }); }); it('returns [redacted] on implicit array of objects', async () => { @@ -658,11 +653,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginB.arrayOfObjects": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginB.arrayOfObjects': '[redacted]', + }); }); it('returns values on implicit array of numbers', async () => { @@ -674,16 +667,11 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "pluginA.arrayOfNumbers": Array [ - 1, - 2, - 3, - ], - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'pluginA.arrayOfNumbers': [1, 2, 3], + }); }); + it('returns [redacted] on implicit array of strings', async () => { configService.getUsedPaths.mockResolvedValue(['plugins.paths']); @@ -693,11 +681,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "plugins.paths": "[redacted]", - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'plugins.paths': '[redacted]', + }); }); it('returns config value for numbers', async () => { @@ -709,11 +695,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "server.port": 5603, - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'server.port': 5603, + }); }); it('returns config value for booleans', async () => { @@ -728,12 +712,10 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "logging.json": false, - "pluginA.objectConfig.debug": true, - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'logging.json': false, + 'pluginA.objectConfig.debug': true, + }); }); it('ignores exposed to usage configs but not used', async () => { @@ -749,11 +731,9 @@ describe('CoreUsageDataService', () => { elasticsearch, }); - await expect(getConfigsUsageData()).resolves.toMatchInlineSnapshot(` - Object { - "logging.json": false, - } - `); + await expect(getConfigsUsageData()).resolves.toEqual({ + 'logging.json': false, + }); }); }); }); @@ -779,7 +759,8 @@ describe('CoreUsageDataService', () => { savedObjectsServiceMock.createStartContract() ); - service.setup({ http, metrics, savedObjectsStartPromise }); + const changedDeprecatedConfigPath$ = configServiceMock.create().getDeprecatedConfigPath$(); + service.setup({ http, metrics, savedObjectsStartPromise, changedDeprecatedConfigPath$ }); // Use the stopTimer$ to delay calling stop() until the third frame const stopTimer$ = cold('---a|'); diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index 85abdca9ea5dc3..dc24f889cd8dd4 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { Subject } from 'rxjs'; +import { Subject, Observable } from 'rxjs'; import { takeUntil, first } from 'rxjs/operators'; import { get } from 'lodash'; -import { hasConfigPathIntersection } from '@kbn/config'; +import { hasConfigPathIntersection, ChangedDeprecatedPaths } from '@kbn/config'; import { CoreService } from 'src/core/types'; import { Logger, SavedObjectsServiceStart, SavedObjectTypeRegistry } from 'src/core/server'; @@ -39,6 +39,7 @@ export interface SetupDeps { http: InternalHttpServiceSetup; metrics: MetricsServiceSetup; savedObjectsStartPromise: Promise; + changedDeprecatedConfigPath$: Observable; } export interface StartDeps { @@ -89,6 +90,7 @@ export class CoreUsageDataService implements CoreService); } - setup({ http, metrics, savedObjectsStartPromise }: SetupDeps) { + setup({ http, metrics, savedObjectsStartPromise, changedDeprecatedConfigPath$ }: SetupDeps) { metrics .getOpsMetrics$() .pipe(takeUntil(this.stop$)) @@ -417,6 +421,10 @@ export class CoreUsageDataService implements CoreService (this.deprecatedConfigPaths = deprecatedConfigPaths)); + const internalRepositoryPromise = savedObjectsStartPromise.then((savedObjects) => savedObjects.createInternalRepository([CORE_USAGE_STATS_TYPE]) ); diff --git a/src/core/server/core_usage_data/types.ts b/src/core/server/core_usage_data/types.ts index 1d5ef6d893f53e..affd3d5c66ab7a 100644 --- a/src/core/server/core_usage_data/types.ts +++ b/src/core/server/core_usage_data/types.ts @@ -254,6 +254,11 @@ export interface CoreConfigUsageData { // uiSettings: { // overridesCount: number; // }; + + deprecatedKeys: { + set: string[]; + unset: string[]; + }; } /** @internal */ diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d9ad24a4a2c0c0..7f108dbeb0086f 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -399,6 +399,11 @@ export interface ContextSetup { // @internal export interface CoreConfigUsageData { + // (undocumented) + deprecatedKeys: { + set: string[]; + unset: string[]; + }; // (undocumented) elasticsearch: { sniffOnStart: boolean; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index fcfca3a5e0e2fa..4d99368f9bf708 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -153,6 +153,7 @@ export class Server { http: httpSetup, metrics: metricsSetup, savedObjectsStartPromise: this.savedObjectsStartPromise, + changedDeprecatedConfigPath$: this.configService.getDeprecatedConfigPath$(), }); const savedObjectsSetup = await this.savedObjects.setup({ @@ -265,6 +266,7 @@ export class Server { await this.http.start(); startTransaction?.end(); + return this.coreStart; } diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index 3f39b5563ebc00..bf51e21bb9bf4e 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -308,6 +308,23 @@ export function getCoreUsageCollector( }, }, }, + + deprecatedKeys: { + set: { + type: 'array', + items: { + type: 'keyword', + _meta: { description: 'Config path added during config deprecation.' }, + }, + }, + unset: { + type: 'array', + items: { + type: 'keyword', + _meta: { description: 'Config path removed during config deprecation.' }, + }, + }, + }, }, environment: { memory: { diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 230d2052f089ed..693957057f1084 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -6950,6 +6950,28 @@ } } } + }, + "deprecatedKeys": { + "properties": { + "set": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "Config path added during config deprecation." + } + } + }, + "unset": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "Config path removed during config deprecation." + } + } + } + } } } }, diff --git a/x-pack/plugins/reporting/server/config/index.test.ts b/x-pack/plugins/reporting/server/config/index.test.ts index cba64500575aaf..8f13fe8b538108 100644 --- a/x-pack/plugins/reporting/server/config/index.test.ts +++ b/x-pack/plugins/reporting/server/config/index.test.ts @@ -15,7 +15,7 @@ const applyReportingDeprecations = (settings: Record = {}) => { const deprecationMessages: string[] = []; const _config: any = {}; _config[CONFIG_PATH] = settings; - const migrated = applyDeprecations( + const { config: migrated } = applyDeprecations( _config, deprecations.map((deprecation) => ({ deprecation, diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts index 80173dd42a49e4..a233d760359e5b 100644 --- a/x-pack/plugins/security/server/config_deprecations.test.ts +++ b/x-pack/plugins/security/server/config_deprecations.test.ts @@ -14,7 +14,7 @@ import { securityConfigDeprecationProvider } from './config_deprecations'; const applyConfigDeprecations = (settings: Record = {}) => { const deprecations = securityConfigDeprecationProvider(configDeprecationFactory); const deprecationMessages: string[] = []; - const migrated = applyDeprecations( + const { config: migrated } = applyDeprecations( settings, deprecations.map((deprecation) => ({ deprecation, diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts index 1e60c1b6353209..a6f8c37b293ef8 100644 --- a/x-pack/plugins/spaces/server/config.test.ts +++ b/x-pack/plugins/spaces/server/config.test.ts @@ -13,7 +13,7 @@ import { spacesConfigDeprecationProvider } from './config'; const applyConfigDeprecations = (settings: Record = {}) => { const deprecations = spacesConfigDeprecationProvider(configDeprecationFactory); const deprecationMessages: string[] = []; - const migrated = applyDeprecations( + const { config: migrated } = applyDeprecations( settings, deprecations.map((deprecation) => ({ deprecation, diff --git a/x-pack/plugins/task_manager/server/index.test.ts b/x-pack/plugins/task_manager/server/index.test.ts index 3fce5f7bdfdf48..8eb98c39a2ccd2 100644 --- a/x-pack/plugins/task_manager/server/index.test.ts +++ b/x-pack/plugins/task_manager/server/index.test.ts @@ -16,7 +16,7 @@ const applyTaskManagerDeprecations = (settings: Record = {}) => const _config = { [CONFIG_PATH]: settings, }; - const migrated = applyDeprecations( + const { config: migrated } = applyDeprecations( _config, deprecations.map((deprecation) => ({ deprecation, From 28d2343fce11892a21f332b6fd0298f1e7bb52e2 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 May 2021 02:00:24 -0700 Subject: [PATCH 13/18] [ftr] migrate "find" service to FtrService class (#100509) Co-authored-by: spalger --- test/functional/services/common/find.ts | 863 ++++++++++++------------ 1 file changed, 413 insertions(+), 450 deletions(-) diff --git a/test/functional/services/common/find.ts b/test/functional/services/common/find.ts index 0cd4c14683f6e9..8d037e2df2109d 100644 --- a/test/functional/services/common/find.ts +++ b/test/functional/services/common/find.ts @@ -7,511 +7,474 @@ */ import { WebDriver, WebElement, By, until } from 'selenium-webdriver'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { WebElementWrapper } from '../lib/web_element_wrapper'; -export async function FindProvider({ getService }: FtrProviderContext) { - const log = getService('log'); - const config = getService('config'); - const { driver, browserType } = await getService('__webdriver__').init(); - const retry = getService('retry'); +import { Browsers } from '../remote/browsers'; +import { FtrService, FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../lib/web_element_wrapper'; - const WAIT_FOR_EXISTS_TIME = config.get('timeouts.waitForExists'); - const POLLING_TIME = 500; - const defaultFindTimeout = config.get('timeouts.find'); - const fixedHeaderHeight = config.get('layout.fixedHeaderHeight'); +export class FindService extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly config = this.ctx.getService('config'); + private readonly retry = this.ctx.getService('retry'); - const wrap = (webElement: WebElement | WebElementWrapper, locator: By | null = null) => - WebElementWrapper.create( - webElement, - locator, - driver, - defaultFindTimeout, - fixedHeaderHeight, - log, - browserType - ); + private readonly WAIT_FOR_EXISTS_TIME = this.config.get('timeouts.waitForExists'); + private readonly POLLING_TIME = 500; + private readonly defaultFindTimeout = this.config.get('timeouts.find'); + private readonly fixedHeaderHeight = this.config.get('layout.fixedHeaderHeight'); - const wrapAll = (webElements: Array) => - webElements.map((e) => wrap(e)); + public currentWait = this.defaultFindTimeout; - const findAndWrap = async (locator: By, timeout: number): Promise => { - const webElement = await driver.wait(until.elementLocated(locator), timeout); - return wrap(webElement, locator); - }; + constructor( + ctx: FtrProviderContext, + private readonly browserType: Browsers, + private readonly driver: WebDriver + ) { + super(ctx); + } - class Find { - public currentWait = defaultFindTimeout; + public async byName( + selector: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.byName('${selector}') with timeout=${timeout}`); + return await this.findAndWrap(By.name(selector), timeout); + } - public async byName( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.byName('${selector}') with timeout=${timeout}`); - return await findAndWrap(By.name(selector), timeout); - } + public async byCssSelector( + selector: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.findByCssSelector('${selector}') with timeout=${timeout}`); + return this.findAndWrap(By.css(selector), timeout); + } - public async byCssSelector( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.findByCssSelector('${selector}') with timeout=${timeout}`); - return findAndWrap(By.css(selector), timeout); - } + public async byXPath( + selector: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.byXPath('${selector}') with timeout=${timeout}`); + return this.findAndWrap(By.xpath(selector), timeout); + } - public async byXPath( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.byXPath('${selector}') with timeout=${timeout}`); - return findAndWrap(By.xpath(selector), timeout); - } + public async byClassName( + selector: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.findByClassName('${selector}') with timeout=${timeout}`); + return this.findAndWrap(By.className(selector), timeout); + } - public async byClassName( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.findByClassName('${selector}') with timeout=${timeout}`); - return findAndWrap(By.className(selector), timeout); - } + public async activeElement(): Promise { + return this.wrap(await this.driver.switchTo().activeElement()); + } - public async activeElement(): Promise { - return wrap(await driver.switchTo().activeElement()); - } + public async setValue(selector: string, text: string, topOffset?: number): Promise { + this.log.debug(`Find.setValue('${selector}', '${text}')`); + return await this.retry.try(async () => { + const element = await this.byCssSelector(selector); + await element.click(topOffset); + + // in case the input element is actually a child of the testSubject, we + // call clearValue() and type() on the element that is focused after + // clicking on the testSubject + const input = await this.activeElement(); + if (input) { + await input.clearValue(); + await input.type(text); + } else { + await element.clearValue(); + await element.type(text); + } + }); + } - public async setValue(selector: string, text: string, topOffset?: number): Promise { - log.debug(`Find.setValue('${selector}', '${text}')`); - return await retry.try(async () => { - const element = await this.byCssSelector(selector); - await element.click(topOffset); + public async selectValue(selector: string, value: string): Promise { + this.log.debug(`Find.selectValue('${selector}', option[value="${value}"]')`); + const combobox = await this.byCssSelector(selector); + const $ = await combobox.parseDomContent(); + const text = $(`option[value="${value}"]`).text(); + await combobox.type(text); + } - // in case the input element is actually a child of the testSubject, we - // call clearValue() and type() on the element that is focused after - // clicking on the testSubject - const input = await this.activeElement(); - if (input) { - await input.clearValue(); - await input.type(text); - } else { - await element.clearValue(); - await element.type(text); + public async filterElementIsDisplayed(elements: WebElementWrapper[]) { + if (elements.length === 0) { + return []; + } else { + const displayed = []; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < elements.length; i++) { + const isDisplayed = await elements[i].isDisplayed(); + if (isDisplayed) { + displayed.push(elements[i]); } - }); + } + return displayed; } + } - public async selectValue(selector: string, value: string): Promise { - log.debug(`Find.selectValue('${selector}', option[value="${value}"]')`); - const combobox = await this.byCssSelector(selector); - const $ = await combobox.parseDomContent(); - const text = $(`option[value="${value}"]`).text(); - await combobox.type(text); - } + public async allByCssSelector( + selector: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.allByCssSelector('${selector}') with timeout=${timeout}`); + await this._withTimeout(timeout); + const elements = await this.driver.findElements(By.css(selector)); + await this._withTimeout(this.defaultFindTimeout); + return this.wrapAll(elements); + } - public async filterElementIsDisplayed(elements: WebElementWrapper[]) { - if (elements.length === 0) { - return []; - } else { - const displayed = []; - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < elements.length; i++) { - const isDisplayed = await elements[i].isDisplayed(); - if (isDisplayed) { - displayed.push(elements[i]); - } - } - return displayed; - } - } + public async descendantExistsByCssSelector( + selector: string, + parentElement: WebElementWrapper, + timeout: number = this.WAIT_FOR_EXISTS_TIME + ): Promise { + this.log.debug(`Find.descendantExistsByCssSelector('${selector}') with timeout=${timeout}`); + const els = await parentElement._webElement.findElements(By.css(selector)); + return await this.exists(async () => this.wrapAll(els), timeout); + } - public async allByCustom( - findAllFunction: (drive: WebDriver) => WebElementWrapper[], - timeout = defaultFindTimeout - ): Promise { - await this._withTimeout(timeout); - return await retry.try(async () => { - let elements = await findAllFunction(driver); - if (!elements) { - elements = []; - } - // Force isStale checks for all the retrieved elements. - await Promise.all(elements.map(async (element) => await element.isEnabled())); - await this._withTimeout(defaultFindTimeout); - return elements; - }); + public async descendantDisplayedByCssSelector( + selector: string, + parentElement: WebElementWrapper + ): Promise { + this.log.debug(`Find.descendantDisplayedByCssSelector('${selector}')`); + const element = await parentElement._webElement.findElement(By.css(selector)); + const descendant = this.wrap(element, By.css(selector)); + const isDisplayed = await descendant.isDisplayed(); + if (isDisplayed) { + return descendant; + } else { + throw new Error(`Element "${selector}" is not displayed`); } + } - public async allByLinkText( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.allByLinkText('${selector}') with timeout=${timeout}`); - await this._withTimeout(timeout); - const elements = await driver.findElements(By.linkText(selector)); - await this._withTimeout(defaultFindTimeout); - return wrapAll(elements); - } + public async allDescendantDisplayedByCssSelector( + selector: string, + parentElement: WebElementWrapper + ): Promise { + this.log.debug(`Find.allDescendantDisplayedByCssSelector('${selector}')`); + const allElements = await this.wrapAll( + await parentElement._webElement.findElements(By.css(selector)) + ); + return await this.filterElementIsDisplayed(allElements); + } - public async allByButtonText( - buttonText: string, - element: WebDriver | WebElement | WebElementWrapper = driver, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`); - return await retry.tryForTime(timeout, async () => { - // tslint:disable-next-line:variable-name - const _element = element instanceof WebElementWrapper ? element._webElement : element; - await this._withTimeout(0); - const allButtons = wrapAll(await _element.findElements(By.tagName('button'))); - await this._withTimeout(defaultFindTimeout); - const buttonTexts = await Promise.all( - allButtons.map(async (el) => { - return el.getVisibleText(); - }) - ); - return buttonTexts.filter((text) => text.trim() === buttonText.trim()); - }); - } + public async allDescendantDisplayedByTagName( + tagName: string, + parentElement: WebElementWrapper + ): Promise { + this.log.debug(`Find.allDescendantDisplayedByTagName('${tagName}')`); + const allElements = await this.wrapAll( + await parentElement._webElement.findElements(By.tagName(tagName)) + ); + return await this.filterElementIsDisplayed(allElements); + } - public async allByCssSelector( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.allByCssSelector('${selector}') with timeout=${timeout}`); - await this._withTimeout(timeout); - const elements = await driver.findElements(By.css(selector)); - await this._withTimeout(defaultFindTimeout); - return wrapAll(elements); - } + public async displayedByLinkText( + linkText: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.displayedByLinkText('${linkText}') with timeout=${timeout}`); + const element = await this.byLinkText(linkText, timeout); + this.log.debug(`Wait for element become visible: ${linkText} with timeout=${timeout}`); + await this.driver.wait(until.elementIsVisible(element._webElement), timeout); + return this.wrap(element, By.linkText(linkText)); + } - public async descendantExistsByCssSelector( - selector: string, - parentElement: WebElementWrapper, - timeout: number = WAIT_FOR_EXISTS_TIME - ): Promise { - log.debug(`Find.descendantExistsByCssSelector('${selector}') with timeout=${timeout}`); - const els = await parentElement._webElement.findElements(By.css(selector)); - return await this.exists(async () => wrapAll(els), timeout); - } + public async displayedByCssSelector( + selector: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.displayedByCssSelector(${selector})`); + const element = await this.byCssSelector(selector, timeout); + this.log.debug(`Wait for element become visible: ${selector} with timeout=${timeout}`); + await this.driver.wait(until.elementIsVisible(element._webElement), timeout); + return this.wrap(element, By.css(selector)); + } + + public async byLinkText( + selector: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.byLinkText('${selector}') with timeout=${timeout}`); + return this.findAndWrap(By.linkText(selector), timeout); + } - public async descendantDisplayedByCssSelector( - selector: string, - parentElement: WebElementWrapper - ): Promise { - log.debug(`Find.descendantDisplayedByCssSelector('${selector}')`); - const element = await parentElement._webElement.findElement(By.css(selector)); - const descendant = wrap(element, By.css(selector)); - const isDisplayed = await descendant.isDisplayed(); - if (isDisplayed) { - return descendant; + public async byPartialLinkText( + partialLinkText: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.byPartialLinkText('${partialLinkText}') with timeout=${timeout}`); + return this.findAndWrap(By.partialLinkText(partialLinkText), timeout); + } + + public async exists( + findFunction: ( + el: WebDriver + ) => + | WebElementWrapper + | WebElementWrapper[] + | Promise + | Promise, + timeout: number = this.WAIT_FOR_EXISTS_TIME + ): Promise { + await this._withTimeout(timeout); + try { + const found = await findFunction(this.driver); + await this._withTimeout(this.defaultFindTimeout); + if (Array.isArray(found)) { + return found.length > 0; } else { - throw new Error(`Element "${selector}" is not displayed`); + return found instanceof WebElementWrapper; } + } catch (err) { + await this._withTimeout(this.defaultFindTimeout); + return false; } + } - public async allDescendantDisplayedByCssSelector( - selector: string, - parentElement: WebElementWrapper - ): Promise { - log.debug(`Find.allDescendantDisplayedByCssSelector('${selector}')`); - const allElements = await wrapAll( - await parentElement._webElement.findElements(By.css(selector)) - ); - return await this.filterElementIsDisplayed(allElements); - } - - public async allDescendantDisplayedByTagName( - tagName: string, - parentElement: WebElementWrapper - ): Promise { - log.debug(`Find.allDescendantDisplayedByTagName('${tagName}')`); - const allElements = await wrapAll( - await parentElement._webElement.findElements(By.tagName(tagName)) - ); - return await this.filterElementIsDisplayed(allElements); - } + public async existsByLinkText( + linkText: string, + timeout: number = this.WAIT_FOR_EXISTS_TIME + ): Promise { + this.log.debug(`Find.existsByLinkText('${linkText}') with timeout=${timeout}`); + return await this.exists( + async (drive) => this.wrapAll(await drive.findElements(By.linkText(linkText))), + timeout + ); + } - public async displayedByLinkText( - linkText: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.displayedByLinkText('${linkText}') with timeout=${timeout}`); - const element = await this.byLinkText(linkText, timeout); - log.debug(`Wait for element become visible: ${linkText} with timeout=${timeout}`); - await driver.wait(until.elementIsVisible(element._webElement), timeout); - return wrap(element, By.linkText(linkText)); + public async existsByDisplayedByCssSelector( + selector: string, + timeout: number = this.WAIT_FOR_EXISTS_TIME + ): Promise { + this.log.debug(`Find.existsByDisplayedByCssSelector('${selector}') with timeout=${timeout}`); + try { + await this.retry.tryForTime(timeout, async () => { + // make sure that the find timeout is not longer than the retry timeout + await this._withTimeout(Math.min(timeout, this.WAIT_FOR_EXISTS_TIME)); + const elements = await this.driver.findElements(By.css(selector)); + await this._withTimeout(this.defaultFindTimeout); + const displayed = await this.filterElementIsDisplayed(this.wrapAll(elements)); + if (displayed.length === 0) { + throw new Error(`${selector} is not displayed`); + } + }); + } catch (err) { + await this._withTimeout(this.defaultFindTimeout); + return false; } + return true; + } - public async displayedByCssSelector( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.displayedByCssSelector(${selector})`); - const element = await this.byCssSelector(selector, timeout); - log.debug(`Wait for element become visible: ${selector} with timeout=${timeout}`); - await driver.wait(until.elementIsVisible(element._webElement), timeout); - return wrap(element, By.css(selector)); - } + public async existsByCssSelector( + selector: string, + timeout: number = this.WAIT_FOR_EXISTS_TIME + ): Promise { + this.log.debug(`Find.existsByCssSelector('${selector}') with timeout=${timeout}`); + return await this.exists(async (drive) => { + return this.wrapAll(await drive.findElements(By.css(selector))); + }, timeout); + } - public async byLinkText( - selector: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.byLinkText('${selector}') with timeout=${timeout}`); - return findAndWrap(By.linkText(selector), timeout); - } + public async clickByCssSelectorWhenNotDisabled( + selector: string, + { timeout } = { timeout: this.defaultFindTimeout } + ): Promise { + this.log.debug(`Find.clickByCssSelectorWhenNotDisabled('${selector}') with timeout=${timeout}`); + + // Don't wrap this code in a retry, or stale element checks may get caught here and the element + // will never be re-grabbed. Let errors bubble, but continue checking for disabled property until + // it's gone. + const element = await this.byCssSelector(selector, timeout); + await element.moveMouseTo(); + await this.driver.wait(until.elementIsEnabled(element._webElement), timeout); + await element.click(); + } - public async byPartialLinkText( - partialLinkText: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.byPartialLinkText('${partialLinkText}') with timeout=${timeout}`); - return findAndWrap(By.partialLinkText(partialLinkText), timeout); - } + public async clickByPartialLinkText( + linkText: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.clickByPartialLinkText('${linkText}') with timeout=${timeout}`); + await this.retry.try(async () => { + const element = await this.byPartialLinkText(linkText, timeout); + await element.moveMouseTo(); + await element.click(); + }); + } - public async exists( - findFunction: ( - el: WebDriver - ) => - | WebElementWrapper - | WebElementWrapper[] - | Promise - | Promise, - timeout: number = WAIT_FOR_EXISTS_TIME - ): Promise { - await this._withTimeout(timeout); - try { - const found = await findFunction(driver); - await this._withTimeout(defaultFindTimeout); - if (Array.isArray(found)) { - return found.length > 0; - } else { - return found instanceof WebElementWrapper; - } - } catch (err) { - await this._withTimeout(defaultFindTimeout); - return false; - } - } + public async clickByLinkText( + linkText: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.clickByLinkText('${linkText}') with timeout=${timeout}`); + await this.retry.try(async () => { + const element = await this.byLinkText(linkText, timeout); + await element.moveMouseTo(); + await element.click(); + }); + } - public async existsByLinkText( - linkText: string, - timeout: number = WAIT_FOR_EXISTS_TIME - ): Promise { - log.debug(`Find.existsByLinkText('${linkText}') with timeout=${timeout}`); - return await this.exists( - async (drive) => wrapAll(await drive.findElements(By.linkText(linkText))), - timeout + public async byButtonText( + buttonText: string, + element: WebDriver | WebElement | WebElementWrapper = this.driver, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`); + return await this.retry.tryForTime(timeout, async () => { + // tslint:disable-next-line:variable-name + const _element = element instanceof WebElementWrapper ? element._webElement : element; + const allButtons = this.wrapAll(await _element.findElements(By.tagName('button'))); + const buttonTexts = await Promise.all( + allButtons.map(async (el) => { + return el.getVisibleText(); + }) ); - } - - public async existsByDisplayedByCssSelector( - selector: string, - timeout: number = WAIT_FOR_EXISTS_TIME - ): Promise { - log.debug(`Find.existsByDisplayedByCssSelector('${selector}') with timeout=${timeout}`); - try { - await retry.tryForTime(timeout, async () => { - // make sure that the find timeout is not longer than the retry timeout - await this._withTimeout(Math.min(timeout, WAIT_FOR_EXISTS_TIME)); - const elements = await driver.findElements(By.css(selector)); - await this._withTimeout(defaultFindTimeout); - const displayed = await this.filterElementIsDisplayed(wrapAll(elements)); - if (displayed.length === 0) { - throw new Error(`${selector} is not displayed`); - } - }); - } catch (err) { - await this._withTimeout(defaultFindTimeout); - return false; + const index = buttonTexts.findIndex((text) => text.trim() === buttonText.trim()); + if (index === -1) { + throw new Error('Button not found'); } - return true; - } - - public async existsByCssSelector( - selector: string, - timeout: number = WAIT_FOR_EXISTS_TIME - ): Promise { - log.debug(`Find.existsByCssSelector('${selector}') with timeout=${timeout}`); - return await this.exists(async (drive) => { - return wrapAll(await drive.findElements(By.css(selector))); - }, timeout); - } + return allButtons[index]; + }); + } - public async clickByCssSelectorWhenNotDisabled( - selector: string, - { timeout } = { timeout: defaultFindTimeout } - ): Promise { - log.debug(`Find.clickByCssSelectorWhenNotDisabled('${selector}') with timeout=${timeout}`); + public async clickByButtonText( + buttonText: string, + element: WebDriver | WebElement | WebElementWrapper = this.driver, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.clickByButtonText('${buttonText}') with timeout=${timeout}`); + await this.retry.try(async () => { + const button = await this.byButtonText(buttonText, element, timeout); + await button.click(); + }); + } - // Don't wrap this code in a retry, or stale element checks may get caught here and the element - // will never be re-grabbed. Let errors bubble, but continue checking for disabled property until - // it's gone. + public async clickByCssSelector( + selector: string, + timeout: number = this.defaultFindTimeout, + topOffset?: number + ): Promise { + this.log.debug(`Find.clickByCssSelector('${selector}') with timeout=${timeout}`); + await this.retry.try(async () => { const element = await this.byCssSelector(selector, timeout); - await element.moveMouseTo(); - await driver.wait(until.elementIsEnabled(element._webElement), timeout); - await element.click(); - } + if (element) { + // await element.moveMouseTo(); + await element.click(topOffset); + } else { + throw new Error(`Element with css='${selector}' is not found`); + } + }); + } - public async clickByPartialLinkText( - linkText: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.clickByPartialLinkText('${linkText}') with timeout=${timeout}`); - await retry.try(async () => { - const element = await this.byPartialLinkText(linkText, timeout); + public async clickByDisplayedLinkText( + linkText: string, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.clickByDisplayedLinkText('${linkText}') with timeout=${timeout}`); + await this.retry.try(async () => { + const element = await this.displayedByLinkText(linkText, timeout); + if (element) { await element.moveMouseTo(); await element.click(); - }); - } + } else { + throw new Error(`Element with linkText='${linkText}' is not found`); + } + }); + } - public async clickByLinkText( - linkText: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.clickByLinkText('${linkText}') with timeout=${timeout}`); - await retry.try(async () => { - const element = await this.byLinkText(linkText, timeout); + public async clickDisplayedByCssSelector( + selector: string, + timeout: number = this.defaultFindTimeout + ) { + this.log.debug(`Find.clickDisplayedByCssSelector('${selector}') with timeout=${timeout}`); + await this.retry.try(async () => { + const element = await this.displayedByCssSelector(selector, timeout); + if (element) { await element.moveMouseTo(); await element.click(); - }); - } - - public async byButtonText( - buttonText: string, - element: WebDriver | WebElement | WebElementWrapper = driver, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`); - return await retry.tryForTime(timeout, async () => { - // tslint:disable-next-line:variable-name - const _element = element instanceof WebElementWrapper ? element._webElement : element; - const allButtons = wrapAll(await _element.findElements(By.tagName('button'))); - const buttonTexts = await Promise.all( - allButtons.map(async (el) => { - return el.getVisibleText(); - }) - ); - const index = buttonTexts.findIndex((text) => text.trim() === buttonText.trim()); - if (index === -1) { - throw new Error('Button not found'); - } - return allButtons[index]; - }); - } - - public async clickByButtonText( - buttonText: string, - element: WebDriver | WebElement | WebElementWrapper = driver, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.clickByButtonText('${buttonText}') with timeout=${timeout}`); - await retry.try(async () => { - const button = await this.byButtonText(buttonText, element, timeout); - await button.click(); - }); - } + } else { + throw new Error(`Element with css='${selector}' is not found`); + } + }); + } - public async clickByCssSelector( - selector: string, - timeout: number = defaultFindTimeout, - topOffset?: number - ): Promise { - log.debug(`Find.clickByCssSelector('${selector}') with timeout=${timeout}`); - await retry.try(async () => { - const element = await this.byCssSelector(selector, timeout); - if (element) { - // await element.moveMouseTo(); - await element.click(topOffset); - } else { - throw new Error(`Element with css='${selector}' is not found`); - } - }); - } + public async waitForDeletedByCssSelector( + selector: string, + timeout: number = this.defaultFindTimeout + ) { + this.log.debug(`Find.waitForDeletedByCssSelector('${selector}') with timeout=${timeout}`); + await this._withTimeout(this.POLLING_TIME); + await this.driver.wait( + async () => { + const found = await this.driver.findElements(By.css(selector)); + return found.length === 0; + }, + timeout, + `The element ${selector} was still present when it should have disappeared.` + ); + await this._withTimeout(this.defaultFindTimeout); + } - public async clickByDisplayedLinkText( - linkText: string, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.clickByDisplayedLinkText('${linkText}') with timeout=${timeout}`); - await retry.try(async () => { - const element = await this.displayedByLinkText(linkText, timeout); - if (element) { - await element.moveMouseTo(); - await element.click(); - } else { - throw new Error(`Element with linkText='${linkText}' is not found`); - } - }); - } + public async waitForAttributeToChange( + selector: string, + attribute: string, + value: string + ): Promise { + this.log.debug(`Find.waitForAttributeToChange('${selector}', '${attribute}', '${value}')`); + await this.retry.waitFor(`${attribute} to equal "${value}"`, async () => { + const el = await this.byCssSelector(selector); + return value === (await el.getAttribute(attribute)); + }); + } - public async clickDisplayedByCssSelector( - selector: string, - timeout: number = defaultFindTimeout - ) { - log.debug(`Find.clickDisplayedByCssSelector('${selector}') with timeout=${timeout}`); - await retry.try(async () => { - const element = await this.displayedByCssSelector(selector, timeout); - if (element) { - await element.moveMouseTo(); - await element.click(); - } else { - throw new Error(`Element with css='${selector}' is not found`); - } - }); - } + public async waitForElementStale( + element: WebElementWrapper, + timeout: number = this.defaultFindTimeout + ): Promise { + this.log.debug(`Find.waitForElementStale with timeout=${timeout}`); + await this.driver.wait(until.stalenessOf(element._webElement), timeout); + } - public async waitForDeletedByCssSelector( - selector: string, - timeout: number = defaultFindTimeout - ) { - log.debug(`Find.waitForDeletedByCssSelector('${selector}') with timeout=${timeout}`); - await this._withTimeout(POLLING_TIME); - await driver.wait( - async () => { - const found = await driver.findElements(By.css(selector)); - return found.length === 0; - }, - timeout, - `The element ${selector} was still present when it should have disappeared.` - ); - await this._withTimeout(defaultFindTimeout); - } + public async waitForElementHidden( + element: WebElementWrapper, + timeout: number = this.defaultFindTimeout + ) { + this.log.debug(`Find.waitForElementHidden with timeout=${timeout}`); + await this.driver.wait(until.elementIsNotVisible(element._webElement), timeout); + } - public async waitForAttributeToChange( - selector: string, - attribute: string, - value: string - ): Promise { - log.debug(`Find.waitForAttributeToChange('${selector}', '${attribute}', '${value}')`); - await retry.waitFor(`${attribute} to equal "${value}"`, async () => { - const el = await this.byCssSelector(selector); - return value === (await el.getAttribute(attribute)); - }); + private async _withTimeout(timeout: number) { + if (timeout !== this.currentWait) { + this.currentWait = timeout; + await this.driver.manage().setTimeouts({ implicit: timeout }); } + } - public async waitForElementStale( - element: WebElementWrapper, - timeout: number = defaultFindTimeout - ): Promise { - log.debug(`Find.waitForElementStale with timeout=${timeout}`); - await driver.wait(until.stalenessOf(element._webElement), timeout); - } + private wrap(webElement: WebElement | WebElementWrapper, locator: By | null = null) { + return WebElementWrapper.create( + webElement, + locator, + this.driver, + this.defaultFindTimeout, + this.fixedHeaderHeight, + this.log, + this.browserType + ); + } - public async waitForElementHidden( - element: WebElementWrapper, - timeout: number = defaultFindTimeout - ) { - log.debug(`Find.waitForElementHidden with timeout=${timeout}`); - await driver.wait(until.elementIsNotVisible(element._webElement), timeout); - } + private wrapAll(webElements: Array) { + return webElements.map((e) => this.wrap(e)); + } - private async _withTimeout(timeout: number) { - if (timeout !== this.currentWait) { - this.currentWait = timeout; - await driver.manage().setTimeouts({ implicit: timeout }); - } - } + private async findAndWrap(locator: By, timeout: number): Promise { + const webElement = await this.driver.wait(until.elementLocated(locator), timeout); + return this.wrap(webElement, locator); } +} - return new Find(); +export async function FindProvider(ctx: FtrProviderContext) { + const { browserType, driver } = await ctx.getService('__webdriver__').init(); + return new FindService(ctx, browserType, driver); } From 1f02c48d3cf57dd921cfe64186a695f8375e3954 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 May 2021 02:02:42 -0700 Subject: [PATCH 14/18] [ftr] migrate "browser" to FtrService class (#100507) Co-authored-by: spalger --- test/functional/services/common/browser.ts | 1109 ++++++++++---------- 1 file changed, 554 insertions(+), 555 deletions(-) diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index 4dfd30c3b3b68f..d38203d5d07d36 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -8,586 +8,585 @@ import { delay } from 'bluebird'; import { cloneDeepWith } from 'lodash'; -import { Key, Origin } from 'selenium-webdriver'; +import { Key, Origin, WebDriver } from 'selenium-webdriver'; // @ts-ignore internal modules are not typed import { LegacyActionSequence } from 'selenium-webdriver/lib/actions'; -import { ProvidedType } from '@kbn/test'; import { modifyUrl } from '@kbn/std'; import Jimp from 'jimp'; import { WebElementWrapper } from '../lib/web_element_wrapper'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext, FtrService } from '../../ftr_provider_context'; import { Browsers } from '../remote/browsers'; -export type Browser = ProvidedType; -export async function BrowserProvider({ getService }: FtrProviderContext) { - const log = getService('log'); - const { driver, browserType } = await getService('__webdriver__').init(); - - return new (class BrowserService { - /** - * Keyboard events - */ - public readonly keys = Key; - - /** - * Browser name - */ - public readonly browserType: string = browserType; - - public readonly isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes( - browserType +export type Browser = BrowserService; + +class BrowserService extends FtrService { + /** + * Keyboard events + */ + public readonly keys = Key; + public readonly isFirefox: boolean = this.browserType === Browsers.Firefox; + public readonly isChromium: boolean = + this.browserType === Browsers.Chrome || this.browserType === Browsers.ChromiumEdge; + + private readonly log = this.ctx.getService('log'); + + constructor( + ctx: FtrProviderContext, + public readonly browserType: string, + private readonly driver: WebDriver + ) { + super(ctx); + } + + /** + * Returns instance of Actions API based on driver w3c flag + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#actions + */ + public getActions() { + return this.driver.actions(); + } + + /** + * Get handle for an alert, confirm, or prompt dialog. (if any). + * @return {Promise} + */ + public async getAlert() { + try { + return await this.driver.switchTo().alert(); + } catch (e) { + return null; + } + } + + /** + * Retrieves the a rect describing the current top-level window's size and position. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Window.html + * + * @return {Promise<{height: number, width: number, x: number, y: number}>} + */ + public async getWindowSize(): Promise<{ height: number; width: number; x: number; y: number }> { + return await this.driver.manage().window().getRect(); + } + + /** + * Sets the dimensions of a window. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Window.html + * + * @param {number} width + * @param {number} height + * @return {Promise} + */ + public async setWindowSize(width: number, height: number) { + await this.driver.manage().window().setRect({ width, height }); + } + + /** + * Gets a screenshot of the focused window and returns it as a Bitmap object + */ + public async getScreenshotAsBitmap() { + const screenshot = await this.takeScreenshot(); + const buffer = Buffer.from(screenshot, 'base64'); + const session = (await Jimp.read(buffer)).clone(); + return session.bitmap; + } + + /** + * Sets the dimensions of a window to get the right size screenshot. + * + * @param {number} width + * @param {number} height + * @return {Promise} + */ + public async setScreenshotSize(width: number, height: number) { + this.log.debug(`======browser======== setWindowSize ${width} ${height}`); + // We really want to set the Kibana app to a specific size without regard to the browser chrome (borders) + // But that means we first need to figure out the display scaling factor. + // NOTE: None of this is required when running Chrome headless because there's no scaling and no borders. + await this.setWindowSize(1200, 800); + const bitmap1 = await this.getScreenshotAsBitmap(); + this.log.debug( + `======browser======== actual initial screenshot size width=${bitmap1.width}, height=${bitmap1.height}` ); - public readonly isFirefox: boolean = browserType === Browsers.Firefox; - - /** - * Returns instance of Actions API based on driver w3c flag - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#actions - */ - public getActions() { - return driver.actions(); - } - - /** - * Get handle for an alert, confirm, or prompt dialog. (if any). - * @return {Promise} - */ - public async getAlert() { - try { - return await driver.switchTo().alert(); - } catch (e) { - return null; - } - } - - /** - * Retrieves the a rect describing the current top-level window's size and position. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Window.html - * - * @return {Promise<{height: number, width: number, x: number, y: number}>} - */ - public async getWindowSize(): Promise<{ height: number; width: number; x: number; y: number }> { - return await driver.manage().window().getRect(); - } - - /** - * Sets the dimensions of a window. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Window.html - * - * @param {number} width - * @param {number} height - * @return {Promise} - */ - public async setWindowSize(width: number, height: number) { - await driver.manage().window().setRect({ width, height }); - } - - /** - * Gets a screenshot of the focused window and returns it as a Bitmap object - */ - public async getScreenshotAsBitmap() { - const screenshot = await this.takeScreenshot(); - const buffer = Buffer.from(screenshot, 'base64'); - const session = (await Jimp.read(buffer)).clone(); - return session.bitmap; - } + // drasticly change the window size so we can calculate the scaling + await this.setWindowSize(600, 400); + const bitmap2 = await this.getScreenshotAsBitmap(); + this.log.debug( + `======browser======== actual second screenshot size width= ${bitmap2.width}, height=${bitmap2.height}` + ); - /** - * Sets the dimensions of a window to get the right size screenshot. - * - * @param {number} width - * @param {number} height - * @return {Promise} - */ - public async setScreenshotSize(width: number, height: number) { - log.debug(`======browser======== setWindowSize ${width} ${height}`); - // We really want to set the Kibana app to a specific size without regard to the browser chrome (borders) - // But that means we first need to figure out the display scaling factor. - // NOTE: None of this is required when running Chrome headless because there's no scaling and no borders. - await this.setWindowSize(1200, 800); - const bitmap1 = await this.getScreenshotAsBitmap(); - log.debug( - `======browser======== actual initial screenshot size width=${bitmap1.width}, height=${bitmap1.height}` - ); - - // drasticly change the window size so we can calculate the scaling - await this.setWindowSize(600, 400); - const bitmap2 = await this.getScreenshotAsBitmap(); - log.debug( - `======browser======== actual second screenshot size width= ${bitmap2.width}, height=${bitmap2.height}` - ); - - const xScaling = (bitmap1.width - bitmap2.width) / 600; - const yScaling = (bitmap1.height - bitmap2.height) / 400; - const xBorder = Math.round(600 - bitmap2.width / xScaling); - const yBorder = Math.round(400 - bitmap2.height / yScaling); - log.debug( - `======browser======== calculated values xBorder= ${xBorder}, yBorder=${yBorder}, xScaling=${xScaling}, yScaling=${yScaling}` - ); - log.debug( - `======browser======== setting browser size to ${width + xBorder} x ${height + yBorder}` - ); - await this.setWindowSize(width + xBorder, height + yBorder); - - const bitmap3 = await this.getScreenshotAsBitmap(); - // when there is display scaling this won't show the expected size. It will show expected size * scaling factor - log.debug( - `======browser======== final screenshot size width=${bitmap3.width}, height=${bitmap3.height}` - ); - } + const xScaling = (bitmap1.width - bitmap2.width) / 600; + const yScaling = (bitmap1.height - bitmap2.height) / 400; + const xBorder = Math.round(600 - bitmap2.width / xScaling); + const yBorder = Math.round(400 - bitmap2.height / yScaling); + this.log.debug( + `======browser======== calculated values xBorder= ${xBorder}, yBorder=${yBorder}, xScaling=${xScaling}, yScaling=${yScaling}` + ); + this.log.debug( + `======browser======== setting browser size to ${width + xBorder} x ${height + yBorder}` + ); + await this.setWindowSize(width + xBorder, height + yBorder); - /** - * Gets the URL that is loaded in the focused window/frame. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#getCurrentUrl - * - * @return {Promise} - */ - public async getCurrentUrl() { - // strip _t=Date query param when url is read - const current = await driver.getCurrentUrl(); - const currentWithoutTime = modifyUrl(current, (parsed) => { - delete (parsed.query as any)._t; + const bitmap3 = await this.getScreenshotAsBitmap(); + // when there is display scaling this won't show the expected size. It will show expected size * scaling factor + this.log.debug( + `======browser======== final screenshot size width=${bitmap3.width}, height=${bitmap3.height}` + ); + } + + /** + * Gets the URL that is loaded in the focused window/frame. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#getCurrentUrl + * + * @return {Promise} + */ + public async getCurrentUrl() { + // strip _t=Date query param when url is read + const current = await this.driver.getCurrentUrl(); + const currentWithoutTime = modifyUrl(current, (parsed) => { + delete (parsed.query as any)._t; + return void 0; + }); + return currentWithoutTime; + } + + /** + * Gets the page/document title of the focused window/frame. + * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#getTitle + */ + public async getTitle() { + return await this.driver.getTitle(); + } + + /** + * Navigates the focused window/frame to a new URL. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#get + * + * @param {string} url + * @param {boolean} insertTimestamp Optional + * @return {Promise} + */ + public async get(url: string, insertTimestamp: boolean = true) { + if (insertTimestamp) { + const urlWithTime = modifyUrl(url, (parsed) => { + (parsed.query as any)._t = Date.now(); return void 0; }); - return currentWithoutTime; - } - - /** - * Gets the page/document title of the focused window/frame. - * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#getTitle - */ - public async getTitle() { - return await driver.getTitle(); - } - - /** - * Navigates the focused window/frame to a new URL. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#get - * - * @param {string} url - * @param {boolean} insertTimestamp Optional - * @return {Promise} - */ - public async get(url: string, insertTimestamp: boolean = true) { - if (insertTimestamp) { - const urlWithTime = modifyUrl(url, (parsed) => { - (parsed.query as any)._t = Date.now(); - return void 0; - }); - - return await driver.get(urlWithTime); - } - return await driver.get(url); - } - /** - * Retrieves the cookie with the given name. Returns null if there is no such cookie. The cookie will be returned as - * a JSON object as described by the WebDriver wire protocol. - * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Options.html - * - * @param {string} cookieName - * @return {Promise} - */ - public async getCookie(cookieName: string) { - return await driver.manage().getCookie(cookieName); - } - - /** - * Pauses the execution in the browser, similar to setting a breakpoint for debugging. - * @return {Promise} - */ - public async pause() { - await driver.executeAsyncScript(`(async () => { debugger; return Promise.resolve(); })()`); - } - - /** - * Moves the remote environment’s mouse cursor to the specified point {x, y} which is - * offset to browser page top left corner. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#move - * - * @param {x: number, y: number} point on browser page - * @return {Promise} - */ - public async moveMouseTo(point: { x: number; y: number }): Promise { - await this.getActions().move({ x: 0, y: 0 }).perform(); - await this.getActions().move({ x: point.x, y: point.y, origin: Origin.POINTER }).perform(); - } - - /** - * Does a drag-and-drop action from one point to another - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#dragAndDrop - * - * @return {Promise} - */ - public async dragAndDrop( - from: { - location: WebElementWrapper | { x?: number; y?: number }; - offset?: { x?: number; y?: number }; - }, - to: { - location: WebElementWrapper | { x?: number; y?: number }; - offset?: { x?: number; y?: number }; + return await this.driver.get(urlWithTime); + } + return await this.driver.get(url); + } + + /** + * Retrieves the cookie with the given name. Returns null if there is no such cookie. The cookie will be returned as + * a JSON object as described by the WebDriver wire protocol. + * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Options.html + * + * @param {string} cookieName + * @return {Promise} + */ + public async getCookie(cookieName: string) { + return await this.driver.manage().getCookie(cookieName); + } + + /** + * Pauses the execution in the browser, similar to setting a breakpoint for debugging. + * @return {Promise} + */ + public async pause() { + await this.driver.executeAsyncScript(`(async () => { debugger; return Promise.resolve(); })()`); + } + + /** + * Moves the remote environment’s mouse cursor to the specified point {x, y} which is + * offset to browser page top left corner. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#move + * + * @param {x: number, y: number} point on browser page + * @return {Promise} + */ + public async moveMouseTo(point: { x: number; y: number }): Promise { + await this.getActions().move({ x: 0, y: 0 }).perform(); + await this.getActions().move({ x: point.x, y: point.y, origin: Origin.POINTER }).perform(); + } + + /** + * Does a drag-and-drop action from one point to another + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#dragAndDrop + * + * @return {Promise} + */ + public async dragAndDrop( + from: { + location: WebElementWrapper | { x?: number; y?: number }; + offset?: { x?: number; y?: number }; + }, + to: { + location: WebElementWrapper | { x?: number; y?: number }; + offset?: { x?: number; y?: number }; + } + ) { + // The offset should be specified in pixels relative to the center of the element's bounding box + const getW3CPoint = (data: any) => { + if (!data.offset) { + data.offset = {}; } - ) { - // The offset should be specified in pixels relative to the center of the element's bounding box - const getW3CPoint = (data: any) => { - if (!data.offset) { - data.offset = {}; - } - return data.location instanceof WebElementWrapper - ? { - x: data.offset.x || 0, - y: data.offset.y || 0, - origin: data.location._webElement, - } - : { x: data.location.x, y: data.location.y, origin: Origin.POINTER }; - }; - - const startPoint = getW3CPoint(from); - const endPoint = getW3CPoint(to); - await this.getActions().move({ x: 0, y: 0 }).perform(); - return await this.getActions().move(startPoint).press().move(endPoint).release().perform(); - } - - /** - * Performs drag and drop for html5 native drag and drop implementation - * There's a bug in Chromedriver for html5 dnd that doesn't allow to use the method `dragAndDrop` defined above - * https://github.com/SeleniumHQ/selenium/issues/6235 - * This implementation simulates user's action by calling the drag and drop specific events directly. - * - * @param {string} from html selector - * @param {string} to html selector - * @return {Promise} - */ - public async html5DragAndDrop(from: string, to: string) { - await this.execute( - ` - function createEvent(typeOfEvent) { - const event = document.createEvent("CustomEvent"); - event.initCustomEvent(typeOfEvent, true, true, null); - event.dataTransfer = { - data: {}, - setData: function (key, value) { - this.data[key] = value; - }, - getData: function (key) { - return this.data[key]; - } - }; - return event; + return data.location instanceof WebElementWrapper + ? { + x: data.offset.x || 0, + y: data.offset.y || 0, + origin: data.location._webElement, } - function dispatchEvent(element, event, transferData) { - if (transferData !== undefined) { - event.dataTransfer = transferData; - } - if (element.dispatchEvent) { - element.dispatchEvent(event); - } else if (element.fireEvent) { - element.fireEvent("on" + event.type, event); + : { x: data.location.x, y: data.location.y, origin: Origin.POINTER }; + }; + + const startPoint = getW3CPoint(from); + const endPoint = getW3CPoint(to); + await this.getActions().move({ x: 0, y: 0 }).perform(); + return await this.getActions().move(startPoint).press().move(endPoint).release().perform(); + } + + /** + * Performs drag and drop for html5 native drag and drop implementation + * There's a bug in Chromedriver for html5 dnd that doesn't allow to use the method `dragAndDrop` defined above + * https://github.com/SeleniumHQ/selenium/issues/6235 + * This implementation simulates user's action by calling the drag and drop specific events directly. + * + * @param {string} from html selector + * @param {string} to html selector + * @return {Promise} + */ + public async html5DragAndDrop(from: string, to: string) { + await this.execute( + ` + function createEvent(typeOfEvent) { + const event = document.createEvent("CustomEvent"); + event.initCustomEvent(typeOfEvent, true, true, null); + event.dataTransfer = { + data: {}, + setData: function (key, value) { + this.data[key] = value; + }, + getData: function (key) { + return this.data[key]; } + }; + return event; + } + function dispatchEvent(element, event, transferData) { + if (transferData !== undefined) { + event.dataTransfer = transferData; } - - const origin = document.querySelector(arguments[0]); - - const dragStartEvent = createEvent('dragstart'); - dispatchEvent(origin, dragStartEvent); - - setTimeout(() => { - const dropEvent = createEvent('drop'); - const target = document.querySelector(arguments[1]); - dispatchEvent(target, dropEvent, dragStartEvent.dataTransfer); - const dragEndEvent = createEvent('dragend'); - dispatchEvent(origin, dragEndEvent, dropEvent.dataTransfer); - }, 100); - `, - from, - to - ); - // wait for 150ms to make sure the script has run - await delay(150); - } - - /** - * Reloads the current browser window/frame. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#refresh - * - * @return {Promise} - */ - public async refresh() { - await driver.navigate().refresh(); - } - - /** - * Navigates the focused window/frame back one page using the browser’s navigation history. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#back - * - * @return {Promise} - */ - public async goBack() { - await driver.navigate().back(); - } - - /** - * Moves forwards in the browser history. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#forward - * - * @return {Promise} - */ - public async goForward() { - await driver.navigate().forward(); - } - - /** - * Navigates to a URL via the browser history. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#to - * - * @return {Promise} - */ - public async navigateTo(url: string) { - await driver.navigate().to(url); - } - - /** - * Sends a sequance of keyboard keys. For each key, this will record a pair of keyDown and keyUp actions - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#sendKeys - * - * @param {string|string[]} keys - * @return {Promise} - */ - public async pressKeys(keys: string | string[]): Promise; - public async pressKeys(...args: string[]): Promise; - public async pressKeys(...args: string[]): Promise { - const chord = this.keys.chord(...args); - await this.getActions().sendKeys(chord).perform(); - } - - /** - * Moves the remote environment’s mouse cursor to the specified point {x, y} which is - * offset to browser page top left corner. - * Then adds an action for left-click (down/up) with the mouse. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#click - * - * @param {x: number, y: number} point on browser page - * @return {Promise} - */ - public async clickMouseButton(point: { x: number; y: number }) { - await this.getActions().move({ x: 0, y: 0 }).perform(); - await this.getActions() - .move({ x: point.x, y: point.y, origin: Origin.POINTER }) - .click() - .perform(); - } - - /** - * Gets the HTML loaded in the focused window/frame. This markup is serialised by the remote - * environment so may not exactly match the HTML provided by the Web server. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#getPageSource - * - * @return {Promise} - */ - public async getPageSource() { - return await driver.getPageSource(); - } - - /** - * Gets a screenshot of the focused window and returns it as a base-64 encoded PNG - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#takeScreenshot - * - * @return {Promise} - */ - public async takeScreenshot() { - return await driver.takeScreenshot(); - } - - /** - * Inserts action for performing a double left-click with the mouse. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#doubleClick - * @param {WebElementWrapper} element - * @return {Promise} - */ - public async doubleClick() { - await this.getActions().doubleClick().perform(); - } - - /** - * Changes the focus of all future commands to another window. Windows may be specified - * by their window.name attributeor by its handle (as returned by WebDriver#getWindowHandles). - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_TargetLocator.html - * - * @param {string} handle - * @return {Promise} - */ - public async switchToWindow(nameOrHandle: string) { - await driver.switchTo().window(nameOrHandle); - } - - /** - * Gets a list of identifiers for all currently open windows. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#getAllWindowHandles - * - * @return {Promise} - */ - public async getAllWindowHandles() { - return await driver.getAllWindowHandles(); - } - - /** - * Switches driver to specific browser tab by index - * - * @param {string} tabIndex - * @return {Promise} - */ - public async switchTab(tabIndex: number) { - const tabs = await driver.getAllWindowHandles(); - if (tabs.length <= tabIndex) { - throw new Error(`Out of existing tabs bounds`); - } - await driver.switchTo().window(tabs[tabIndex]); - } - - /** - * Sets a value in local storage for the focused window/frame. - * - * @param {string} key - * @param {string} value - * @return {Promise} - */ - public async setLocalStorageItem(key: string, value: string): Promise { - await driver.executeScript( - 'return window.localStorage.setItem(arguments[0], arguments[1]);', - key, - value - ); - } - - /** - * Removes a value in local storage for the focused window/frame. - * - * @param {string} key - * @return {Promise} - */ - public async removeLocalStorageItem(key: string): Promise { - await driver.executeScript('return window.localStorage.removeItem(arguments[0]);', key); - } - - /** - * Clears session storage for the focused window/frame. - * - * @return {Promise} - */ - public async clearSessionStorage(): Promise { - await driver.executeScript('return window.sessionStorage.clear();'); - } - - /** - * Closes the currently focused window. In most environments, after the window has been - * closed, it is necessary to explicitly switch to whatever window is now focused. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#close - * - * @return {Promise} - */ - public async closeCurrentWindow() { - await driver.close(); - } - - /** - * Executes JavaScript code within the focused window/frame. The code should return a value synchronously. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#executeScript - * - * @param {string|function} fn - * @param {...any[]} args - */ - public async execute( - fn: string | ((...args: A) => R), - ...args: A - ): Promise { - return await driver.executeScript( - fn, - ...cloneDeepWith(args, (arg) => { - if (arg instanceof WebElementWrapper) { - return arg._webElement; - } - }) - ); - } - - public async executeAsync(fn: (cb: (value?: T) => void) => void): Promise; - public async executeAsync( - fn: (a1: A1, cb: (value?: T) => void) => void, - a1: A1 - ): Promise; - public async executeAsync( - fn: (a1: A1, a2: A2, cb: (value?: T) => void) => void, - a1: A1, - a2: A2 - ): Promise; - public async executeAsync( - fn: (a1: A1, a2: A2, a3: A3, cb: (value?: T) => void) => void, - a1: A1, - a2: A2, - a3: A3 - ): Promise; - public async executeAsync( - fn: (...args: any[]) => void, - ...args: any[] - ): Promise { - return await driver.executeAsyncScript( - fn, - ...cloneDeepWith(args, (arg) => { - if (arg instanceof WebElementWrapper) { - return arg._webElement; + if (element.dispatchEvent) { + element.dispatchEvent(event); + } else if (element.fireEvent) { + element.fireEvent("on" + event.type, event); } - }) - ); - } - - public async getScrollTop() { - const scrollSize = await driver.executeScript('return document.body.scrollTop'); - return parseInt(scrollSize, 10); - } - - public async getScrollLeft() { - const scrollSize = await driver.executeScript('return document.body.scrollLeft'); - return parseInt(scrollSize, 10); - } - - public async scrollTop() { - await driver.executeScript('document.documentElement.scrollTop = 0'); - } + } - // return promise with REAL scroll position - public async setScrollTop(scrollSize: number | string) { - await driver.executeScript('document.body.scrollTop = ' + scrollSize); - return this.getScrollTop(); - } + const origin = document.querySelector(arguments[0]); + + const dragStartEvent = createEvent('dragstart'); + dispatchEvent(origin, dragStartEvent); + + setTimeout(() => { + const dropEvent = createEvent('drop'); + const target = document.querySelector(arguments[1]); + dispatchEvent(target, dropEvent, dragStartEvent.dataTransfer); + const dragEndEvent = createEvent('dragend'); + dispatchEvent(origin, dragEndEvent, dropEvent.dataTransfer); + }, 100); + `, + from, + to + ); + // wait for 150ms to make sure the script has run + await delay(150); + } + + /** + * Reloads the current browser window/frame. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#refresh + * + * @return {Promise} + */ + public async refresh() { + await this.driver.navigate().refresh(); + } + + /** + * Navigates the focused window/frame back one page using the browser’s navigation history. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#back + * + * @return {Promise} + */ + public async goBack() { + await this.driver.navigate().back(); + } + + /** + * Moves forwards in the browser history. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#forward + * + * @return {Promise} + */ + public async goForward() { + await this.driver.navigate().forward(); + } + + /** + * Navigates to a URL via the browser history. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#to + * + * @return {Promise} + */ + public async navigateTo(url: string) { + await this.driver.navigate().to(url); + } + + /** + * Sends a sequance of keyboard keys. For each key, this will record a pair of keyDown and keyUp actions + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#sendKeys + * + * @param {string|string[]} keys + * @return {Promise} + */ + public async pressKeys(keys: string | string[]): Promise; + public async pressKeys(...args: string[]): Promise; + public async pressKeys(...args: string[]): Promise { + const chord = this.keys.chord(...args); + await this.getActions().sendKeys(chord).perform(); + } + + /** + * Moves the remote environment’s mouse cursor to the specified point {x, y} which is + * offset to browser page top left corner. + * Then adds an action for left-click (down/up) with the mouse. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#click + * + * @param {x: number, y: number} point on browser page + * @return {Promise} + */ + public async clickMouseButton(point: { x: number; y: number }) { + await this.getActions().move({ x: 0, y: 0 }).perform(); + await this.getActions() + .move({ x: point.x, y: point.y, origin: Origin.POINTER }) + .click() + .perform(); + } + + /** + * Gets the HTML loaded in the focused window/frame. This markup is serialised by the remote + * environment so may not exactly match the HTML provided by the Web server. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#getPageSource + * + * @return {Promise} + */ + public async getPageSource() { + return await this.driver.getPageSource(); + } + + /** + * Gets a screenshot of the focused window and returns it as a base-64 encoded PNG + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#takeScreenshot + * + * @return {Promise} + */ + public async takeScreenshot() { + return await this.driver.takeScreenshot(); + } + + /** + * Inserts action for performing a double left-click with the mouse. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#doubleClick + * @param {WebElementWrapper} element + * @return {Promise} + */ + public async doubleClick() { + await this.getActions().doubleClick().perform(); + } + + /** + * Changes the focus of all future commands to another window. Windows may be specified + * by their window.name attributeor by its handle (as returned by WebDriver#getWindowHandles). + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_TargetLocator.html + * + * @param {string} handle + * @return {Promise} + */ + public async switchToWindow(nameOrHandle: string) { + await this.driver.switchTo().window(nameOrHandle); + } + + /** + * Gets a list of identifiers for all currently open windows. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#getAllWindowHandles + * + * @return {Promise} + */ + public async getAllWindowHandles() { + return await this.driver.getAllWindowHandles(); + } + + /** + * Switches driver to specific browser tab by index + * + * @param {string} tabIndex + * @return {Promise} + */ + public async switchTab(tabIndex: number) { + const tabs = await this.driver.getAllWindowHandles(); + if (tabs.length <= tabIndex) { + throw new Error(`Out of existing tabs bounds`); + } + await this.driver.switchTo().window(tabs[tabIndex]); + } + + /** + * Sets a value in local storage for the focused window/frame. + * + * @param {string} key + * @param {string} value + * @return {Promise} + */ + public async setLocalStorageItem(key: string, value: string): Promise { + await this.driver.executeScript( + 'return window.localStorage.setItem(arguments[0], arguments[1]);', + key, + value + ); + } + + /** + * Removes a value in local storage for the focused window/frame. + * + * @param {string} key + * @return {Promise} + */ + public async removeLocalStorageItem(key: string): Promise { + await this.driver.executeScript('return window.localStorage.removeItem(arguments[0]);', key); + } + + /** + * Clears session storage for the focused window/frame. + * + * @return {Promise} + */ + public async clearSessionStorage(): Promise { + await this.driver.executeScript('return window.sessionStorage.clear();'); + } + + /** + * Closes the currently focused window. In most environments, after the window has been + * closed, it is necessary to explicitly switch to whatever window is now focused. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#close + * + * @return {Promise} + */ + public async closeCurrentWindow() { + await this.driver.close(); + } + + /** + * Executes JavaScript code within the focused window/frame. The code should return a value synchronously. + * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#executeScript + * + * @param {string|function} fn + * @param {...any[]} args + */ + public async execute( + fn: string | ((...args: A) => R), + ...args: A + ): Promise { + return await this.driver.executeScript( + fn, + ...cloneDeepWith(args, (arg) => { + if (arg instanceof WebElementWrapper) { + return arg._webElement; + } + }) + ); + } + + public async executeAsync(fn: (cb: (value?: T) => void) => void): Promise; + public async executeAsync( + fn: (a1: A1, cb: (value?: T) => void) => void, + a1: A1 + ): Promise; + public async executeAsync( + fn: (a1: A1, a2: A2, cb: (value?: T) => void) => void, + a1: A1, + a2: A2 + ): Promise; + public async executeAsync( + fn: (a1: A1, a2: A2, a3: A3, cb: (value?: T) => void) => void, + a1: A1, + a2: A2, + a3: A3 + ): Promise; + public async executeAsync(fn: (...args: any[]) => void, ...args: any[]): Promise { + return await this.driver.executeAsyncScript( + fn, + ...cloneDeepWith(args, (arg) => { + if (arg instanceof WebElementWrapper) { + return arg._webElement; + } + }) + ); + } + + public async getScrollTop() { + const scrollSize = await this.driver.executeScript('return document.body.scrollTop'); + return parseInt(scrollSize, 10); + } + + public async getScrollLeft() { + const scrollSize = await this.driver.executeScript('return document.body.scrollLeft'); + return parseInt(scrollSize, 10); + } + + public async scrollTop() { + await this.driver.executeScript('document.documentElement.scrollTop = 0'); + } + + // return promise with REAL scroll position + public async setScrollTop(scrollSize: number | string) { + await this.driver.executeScript('document.body.scrollTop = ' + scrollSize); + return this.getScrollTop(); + } + + public async setScrollToById(elementId: string, xCoord: number, yCoord: number) { + await this.driver.executeScript( + `document.getElementById("${elementId}").scrollTo(${xCoord},${yCoord})` + ); + } - public async setScrollToById(elementId: string, xCoord: number, yCoord: number) { - await driver.executeScript( - `document.getElementById("${elementId}").scrollTo(${xCoord},${yCoord})` - ); - } + public async setScrollLeft(scrollSize: number | string) { + await this.driver.executeScript('document.body.scrollLeft = ' + scrollSize); + return this.getScrollLeft(); + } - public async setScrollLeft(scrollSize: number | string) { - await driver.executeScript('document.body.scrollLeft = ' + scrollSize); - return this.getScrollLeft(); - } + public async switchToFrame(idOrElement: number | WebElementWrapper) { + const _id = idOrElement instanceof WebElementWrapper ? idOrElement._webElement : idOrElement; + await this.driver.switchTo().frame(_id); + } - public async switchToFrame(idOrElement: number | WebElementWrapper) { - const _id = idOrElement instanceof WebElementWrapper ? idOrElement._webElement : idOrElement; - await driver.switchTo().frame(_id); - } + public async checkBrowserPermission(permission: string): Promise { + const result: any = await this.driver.executeAsyncScript( + `navigator.permissions.query({name:'${permission}'}).then(arguments[0])` + ); - public async checkBrowserPermission(permission: string): Promise { - const result: any = await driver.executeAsyncScript( - `navigator.permissions.query({name:'${permission}'}).then(arguments[0])` - ); + return Boolean(result?.state === 'granted'); + } - return Boolean(result?.state === 'granted'); - } + public getClipboardValue(): Promise { + return this.driver.executeAsyncScript('navigator.clipboard.readText().then(arguments[0])'); + } +} - public getClipboardValue(): Promise { - return driver.executeAsyncScript('navigator.clipboard.readText().then(arguments[0])'); - } - })(); +export async function BrowserProvider(ctx: FtrProviderContext) { + const { driver, browserType } = await ctx.getService('__webdriver__').init(); + return new BrowserService(ctx, browserType, driver); } From 93acfb4d43ddfdf29a8261de19e9ee8083c99105 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Wed, 26 May 2021 11:36:06 +0200 Subject: [PATCH 15/18] [Ingest pipelines] add support for ip type in convert processor (#100531) * add ip option type to convert processor * remove duped option * small CR changes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__jest__/processors/convert.test.tsx | 123 ++++++++++++++++++ .../__jest__/processors/processor.helpers.tsx | 1 + .../processor_form/processors/convert.tsx | 7 + 3 files changed, 131 insertions(+) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx new file mode 100644 index 00000000000000..5a58a7b595c90d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from 'react-dom/test-utils'; +import { setup, SetupResult, getProcessorValue } from './processor.helpers'; + +// Default parameter values automatically added to the `convert processor` when saved +const defaultConvertParameters = { + if: undefined, + tag: undefined, + description: undefined, + target_field: undefined, + ignore_missing: undefined, + ignore_failure: undefined, +}; + +const CONVERT_TYPE = 'convert'; + +describe('Processor: Convert', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + + await act(async () => { + testBed = await setup({ + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + testBed.component.update(); + + // Open flyout to add new processor + testBed.actions.addProcessor(); + // Add type (the other fields are not visible until a type is selected) + await testBed.actions.addProcessorType(CONVERT_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the processor type defined + await saveNewProcessor(); + + // Expect form error as "field" and "type" are required parameters + expect(form.getErrorsMessages()).toEqual([ + 'A field value is required.', + 'A type value is required.', + ]); + }); + + test('saves with default parameter values', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + // Add "type" value (required) + form.setSelectValue('typeSelectorField', 'ip'); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, CONVERT_TYPE); + expect(processors[0][CONVERT_TYPE]).toEqual({ + ...defaultConvertParameters, + field: 'field_1', + type: 'ip', + }); + }); + + test('allows optional parameters to be set', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + // Add "type" value (required) + form.setSelectValue('typeSelectorField', 'ip'); + + // Set optional parameteres + form.setInputValue('targetField.input', 'target_field'); + form.toggleEuiSwitch('ignoreMissingSwitch.input'); + form.toggleEuiSwitch('ignoreFailureSwitch.input'); + + // Save the field with new changes + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, CONVERT_TYPE); + expect(processors[0][CONVERT_TYPE]).toEqual({ + ...defaultConvertParameters, + type: 'ip', + field: 'field_1', + target_field: 'target_field', + ignore_failure: true, + ignore_missing: true, + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index 193e94c7aeb9e6..d69ceb385ddd71 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -143,6 +143,7 @@ type TestSubject = | 'messageField.input' | 'mockCodeEditor' | 'tagField.input' + | 'typeSelectorField' | 'ignoreMissingSwitch.input' | 'ignoreFailureSwitch.input' | 'ifField.textarea' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/convert.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/convert.tsx index 46aee71fdee427..af671dce295599 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/convert.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/convert.tsx @@ -58,6 +58,7 @@ export const Convert: FunctionComponent = () => { { { defaultMessage: 'Boolean' } ), }, + { + value: 'ip', + text: i18n.translate('xpack.ingestPipelines.pipelineEditor.convertForm.ipOption', { + defaultMessage: 'IP', + }), + }, { value: 'auto', text: i18n.translate( From 5d5cc55b3a946faf735f18aa0165bd7d4deffaf2 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Wed, 26 May 2021 06:47:28 -0400 Subject: [PATCH 16/18] Fix spaces test flakyness (#100605) --- .../spaces/public/nav_control/nav_control_popover.tsx | 5 +---- x-pack/test/functional/apps/spaces/enter_space.ts | 3 +-- x-pack/test/functional/apps/spaces/spaces_selection.ts | 3 +-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx index 392219d480e671..6f743b3a308e25 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -71,9 +71,6 @@ export class NavControlPopover extends Component { public render() { const button = this.getActiveSpaceButton(); - if (!button) { - return null; - } let element: React.ReactNode; if (!this.state.loading && this.state.spaces.length < 2) { @@ -102,7 +99,7 @@ export class NavControlPopover extends Component { return ( { await esArchiver.load('spaces/enter_space'); diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index f3d3665bf9f613..99efdf29eecb97 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -22,8 +22,7 @@ export default function spaceSelectorFunctionalTests({ 'spaceSelector', ]); - // FLAKY: https://github.com/elastic/kibana/issues/99581 - describe.skip('Spaces', function () { + describe('Spaces', function () { this.tags('includeFirefox'); describe('Space Selector', () => { before(async () => { From 2d6ee26223aa49d108423a7cc948df75cfa5e412 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 May 2021 03:48:41 -0700 Subject: [PATCH 17/18] [ftr] migrate "testSubjects" to FtrService class (#100512) Co-authored-by: spalger --- test/functional/services/common/index.ts | 2 +- .../services/common/test_subjects.ts | 560 +++++++++--------- test/functional/services/index.ts | 4 +- 3 files changed, 284 insertions(+), 282 deletions(-) diff --git a/test/functional/services/common/index.ts b/test/functional/services/common/index.ts index e491b964c8ff60..a928b3ddf93788 100644 --- a/test/functional/services/common/index.ts +++ b/test/functional/services/common/index.ts @@ -11,4 +11,4 @@ export { FailureDebuggingProvider } from './failure_debugging'; export { FindProvider } from './find'; export { ScreenshotsProvider } from './screenshots'; export { SnapshotsProvider } from './snapshots'; -export { TestSubjectsProvider, TestSubjects } from './test_subjects'; +export { TestSubjects } from './test_subjects'; diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index d0050859cbb32a..ae04fe5d2b9390 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -8,9 +8,8 @@ import testSubjSelector from '@kbn/test-subj-selector'; import { map as mapAsync } from 'bluebird'; -import { ProvidedType } from '@kbn/test'; import { WebElementWrapper } from '../lib/web_element_wrapper'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrService } from '../../ftr_provider_context'; interface ExistsOptions { timeout?: number; @@ -22,326 +21,329 @@ interface SetValueOptions { typeCharByChar?: boolean; } -export type TestSubjects = ProvidedType; -export function TestSubjectsProvider({ getService }: FtrProviderContext) { - const log = getService('log'); - const retry = getService('retry'); - const find = getService('find'); - const config = getService('config'); - - const FIND_TIME = config.get('timeouts.find'); - const TRY_TIME = config.get('timeouts.try'); - const WAIT_FOR_EXISTS_TIME = config.get('timeouts.waitForExists'); - - class TestSubjects { - public async exists(selector: string, options: ExistsOptions = {}): Promise { - const { timeout = WAIT_FOR_EXISTS_TIME, allowHidden = false } = options; - - log.debug(`TestSubjects.exists(${selector})`); - return await (allowHidden - ? find.existsByCssSelector(testSubjSelector(selector), timeout) - : find.existsByDisplayedByCssSelector(testSubjSelector(selector), timeout)); - } - - public async existOrFail( - selector: string, - existsOptions?: ExistsOptions - ): Promise { - if (!(await this.exists(selector, { timeout: TRY_TIME, ...existsOptions }))) { - throw new Error(`expected testSubject(${selector}) to exist`); - } - } +export class TestSubjects extends FtrService { + public readonly log = this.ctx.getService('log'); + public readonly retry = this.ctx.getService('retry'); + public readonly findService = this.ctx.getService('find'); + public readonly config = this.ctx.getService('config'); - public async missingOrFail( - selector: string, - options: ExistsOptions = {} - ): Promise { - const { timeout = WAIT_FOR_EXISTS_TIME, allowHidden = false } = options; + public readonly FIND_TIME = this.config.get('timeouts.find'); + public readonly TRY_TIME = this.config.get('timeouts.try'); + public readonly WAIT_FOR_EXISTS_TIME = this.config.get('timeouts.waitForExists'); - log.debug(`TestSubjects.missingOrFail(${selector})`); - return await (allowHidden - ? this.waitForHidden(selector, timeout) - : find.waitForDeletedByCssSelector(testSubjSelector(selector), timeout)); - } + public async exists(selector: string, options: ExistsOptions = {}): Promise { + const { timeout = this.WAIT_FOR_EXISTS_TIME, allowHidden = false } = options; - async stringExistsInCodeBlockOrFail(codeBlockSelector: string, stringToFind: string) { - await retry.try(async () => { - const responseCodeBlock = await this.find(codeBlockSelector); - const spans = await find.allDescendantDisplayedByTagName('span', responseCodeBlock); - const foundInSpans = await Promise.all( - spans.map(async (span) => { - const text = await span.getVisibleText(); - if (text === stringToFind) { - log.debug(`"${text}" matched "${stringToFind}"!`); - return true; - } else { - log.debug(`"${text}" did not match "${stringToFind}"`); - } - }) - ); - if (!foundInSpans.find((foundInSpan) => foundInSpan)) { - throw new Error(`"${stringToFind}" was not found. Trying again...`); - } - }); - } + this.log.debug(`TestSubjects.exists(${selector})`); + return await (allowHidden + ? this.findService.existsByCssSelector(testSubjSelector(selector), timeout) + : this.findService.existsByDisplayedByCssSelector(testSubjSelector(selector), timeout)); + } - public async append(selector: string, text: string): Promise { - log.debug(`TestSubjects.append(${selector}, ${text})`); - const input = await this.find(selector); - await input.click(); - await input.type(text); + public async existOrFail(selector: string, existsOptions?: ExistsOptions): Promise { + if (!(await this.exists(selector, { timeout: this.TRY_TIME, ...existsOptions }))) { + throw new Error(`expected testSubject(${selector}) to exist`); } + } - public async clickWhenNotDisabled( - selector: string, - { timeout = FIND_TIME }: { timeout?: number } = {} - ): Promise { - log.debug(`TestSubjects.clickWhenNotDisabled(${selector})`); - await find.clickByCssSelectorWhenNotDisabled(testSubjSelector(selector), { timeout }); - } + public async missingOrFail(selector: string, options: ExistsOptions = {}): Promise { + const { timeout = this.WAIT_FOR_EXISTS_TIME, allowHidden = false } = options; - public async click( - selector: string, - timeout: number = FIND_TIME, - topOffset?: number - ): Promise { - log.debug(`TestSubjects.click(${selector})`); - await find.clickByCssSelector(testSubjSelector(selector), timeout, topOffset); - } + this.log.debug(`TestSubjects.missingOrFail(${selector})`); + return await (allowHidden + ? this.waitForHidden(selector, timeout) + : this.findService.waitForDeletedByCssSelector(testSubjSelector(selector), timeout)); + } - public async doubleClick(selector: string, timeout: number = FIND_TIME): Promise { - log.debug(`TestSubjects.doubleClick(${selector})`); - const element = await this.find(selector, timeout); - await element.moveMouseTo(); - await element.doubleClick(); - } + async stringExistsInCodeBlockOrFail(codeBlockSelector: string, stringToFind: string) { + await this.retry.try(async () => { + const responseCodeBlock = await this.find(codeBlockSelector); + const spans = await this.findService.allDescendantDisplayedByTagName( + 'span', + responseCodeBlock + ); + const foundInSpans = await Promise.all( + spans.map(async (span) => { + const text = await span.getVisibleText(); + if (text === stringToFind) { + this.log.debug(`"${text}" matched "${stringToFind}"!`); + return true; + } else { + this.log.debug(`"${text}" did not match "${stringToFind}"`); + } + }) + ); + if (!foundInSpans.find((foundInSpan) => foundInSpan)) { + throw new Error(`"${stringToFind}" was not found. Trying again...`); + } + }); + } - async descendantExists(selector: string, parentElement: WebElementWrapper): Promise { - log.debug(`TestSubjects.descendantExists(${selector})`); - return await find.descendantExistsByCssSelector(testSubjSelector(selector), parentElement); - } + public async append(selector: string, text: string): Promise { + this.log.debug(`TestSubjects.append(${selector}, ${text})`); + const input = await this.find(selector); + await input.click(); + await input.type(text); + } - public async findDescendant( - selector: string, - parentElement: WebElementWrapper - ): Promise { - log.debug(`TestSubjects.findDescendant(${selector})`); - return await find.descendantDisplayedByCssSelector(testSubjSelector(selector), parentElement); - } + public async clickWhenNotDisabled( + selector: string, + { timeout = this.FIND_TIME }: { timeout?: number } = {} + ): Promise { + this.log.debug(`TestSubjects.clickWhenNotDisabled(${selector})`); + await this.findService.clickByCssSelectorWhenNotDisabled(testSubjSelector(selector), { + timeout, + }); + } - public async findAllDescendant( - selector: string, - parentElement: WebElementWrapper - ): Promise { - log.debug(`TestSubjects.findAllDescendant(${selector})`); - return await find.allDescendantDisplayedByCssSelector( - testSubjSelector(selector), - parentElement - ); - } + public async click( + selector: string, + timeout: number = this.FIND_TIME, + topOffset?: number + ): Promise { + this.log.debug(`TestSubjects.click(${selector})`); + await this.findService.clickByCssSelector(testSubjSelector(selector), timeout, topOffset); + } - public async find(selector: string, timeout: number = FIND_TIME): Promise { - log.debug(`TestSubjects.find(${selector})`); - return await find.byCssSelector(testSubjSelector(selector), timeout); - } + public async doubleClick(selector: string, timeout: number = this.FIND_TIME): Promise { + this.log.debug(`TestSubjects.doubleClick(${selector})`); + const element = await this.find(selector, timeout); + await element.moveMouseTo(); + await element.doubleClick(); + } - public async findAll(selector: string, timeout?: number): Promise { - return await retry.try(async () => { - log.debug(`TestSubjects.findAll(${selector})`); - const all = await find.allByCssSelector(testSubjSelector(selector), timeout); - return await find.filterElementIsDisplayed(all); - }); - } + async descendantExists(selector: string, parentElement: WebElementWrapper): Promise { + this.log.debug(`TestSubjects.descendantExists(${selector})`); + return await this.findService.descendantExistsByCssSelector( + testSubjSelector(selector), + parentElement + ); + } - public async getAttributeAll(selector: string, attribute: string): Promise { - log.debug(`TestSubjects.getAttributeAll(${selector}, ${attribute})`); - return await this._mapAll(selector, async (element: WebElementWrapper) => { - return await element.getAttribute(attribute); - }); - } + public async findDescendant( + selector: string, + parentElement: WebElementWrapper + ): Promise { + this.log.debug(`TestSubjects.findDescendant(${selector})`); + return await this.findService.descendantDisplayedByCssSelector( + testSubjSelector(selector), + parentElement + ); + } - public async getAttribute( - selector: string, - attribute: string, - options?: - | number - | { - findTimeout?: number; - tryTimeout?: number; - } - ): Promise { - const findTimeout = - (typeof options === 'number' ? options : options?.findTimeout) ?? - config.get('timeouts.find'); + public async findAllDescendant( + selector: string, + parentElement: WebElementWrapper + ): Promise { + this.log.debug(`TestSubjects.findAllDescendant(${selector})`); + return await this.findService.allDescendantDisplayedByCssSelector( + testSubjSelector(selector), + parentElement + ); + } - const tryTimeout = - (typeof options !== 'number' ? options?.tryTimeout : undefined) ?? - config.get('timeouts.try'); + public async find( + selector: string, + timeout: number = this.FIND_TIME + ): Promise { + this.log.debug(`TestSubjects.find(${selector})`); + return await this.findService.byCssSelector(testSubjSelector(selector), timeout); + } - log.debug( - `TestSubjects.getAttribute(${selector}, ${attribute}, tryTimeout=${tryTimeout}, findTimeout=${findTimeout})` - ); + public async findAll(selector: string, timeout?: number): Promise { + return await this.retry.try(async () => { + this.log.debug(`TestSubjects.findAll(${selector})`); + const all = await this.findService.allByCssSelector(testSubjSelector(selector), timeout); + return await this.findService.filterElementIsDisplayed(all); + }); + } - return await retry.tryForTime(tryTimeout, async () => { - const element = await this.find(selector, findTimeout); - return await element.getAttribute(attribute); - }); - } + public async getAttributeAll(selector: string, attribute: string): Promise { + this.log.debug(`TestSubjects.getAttributeAll(${selector}, ${attribute})`); + return await this._mapAll(selector, async (element: WebElementWrapper) => { + return await element.getAttribute(attribute); + }); + } - public async setValue( - selector: string, - text: string, - options: SetValueOptions = {}, - topOffset?: number - ): Promise { - return await retry.try(async () => { - const { clearWithKeyboard = false, typeCharByChar = false } = options; - log.debug(`TestSubjects.setValue(${selector}, ${text})`); - await this.click(selector, undefined, topOffset); - // in case the input element is actually a child of the testSubject, we - // call clearValue() and type() on the element that is focused after - // clicking on the testSubject - const input = await find.activeElement(); - if (clearWithKeyboard === true) { - await input.clearValueWithKeyboard(); - } else { - await input.clearValue(); + public async getAttribute( + selector: string, + attribute: string, + options?: + | number + | { + findTimeout?: number; + tryTimeout?: number; } - await input.type(text, { charByChar: typeCharByChar }); - }); - } + ): Promise { + const findTimeout = + (typeof options === 'number' ? options : options?.findTimeout) ?? + this.config.get('timeouts.find'); + + const tryTimeout = + (typeof options !== 'number' ? options?.tryTimeout : undefined) ?? + this.config.get('timeouts.try'); + + this.log.debug( + `TestSubjects.getAttribute(${selector}, ${attribute}, tryTimeout=${tryTimeout}, findTimeout=${findTimeout})` + ); + + return await this.retry.tryForTime(tryTimeout, async () => { + const element = await this.find(selector, findTimeout); + return await element.getAttribute(attribute); + }); + } - public async selectValue(selector: string, value: string): Promise { - await find.selectValue(`[data-test-subj="${selector}"]`, value); - } + public async setValue( + selector: string, + text: string, + options: SetValueOptions = {}, + topOffset?: number + ): Promise { + return await this.retry.try(async () => { + const { clearWithKeyboard = false, typeCharByChar = false } = options; + this.log.debug(`TestSubjects.setValue(${selector}, ${text})`); + await this.click(selector, undefined, topOffset); + // in case the input element is actually a child of the testSubject, we + // call clearValue() and type() on the element that is focused after + // clicking on the testSubject + const input = await this.findService.activeElement(); + if (clearWithKeyboard === true) { + await input.clearValueWithKeyboard(); + } else { + await input.clearValue(); + } + await input.type(text, { charByChar: typeCharByChar }); + }); + } - public async isEnabled(selector: string): Promise { - log.debug(`TestSubjects.isEnabled(${selector})`); - const element = await this.find(selector); - return await element.isEnabled(); - } + public async selectValue(selector: string, value: string): Promise { + await this.findService.selectValue(`[data-test-subj="${selector}"]`, value); + } - public async isDisplayed(selector: string): Promise { - log.debug(`TestSubjects.isDisplayed(${selector})`); - const element = await this.find(selector); - return await element.isDisplayed(); - } + public async isEnabled(selector: string): Promise { + this.log.debug(`TestSubjects.isEnabled(${selector})`); + const element = await this.find(selector); + return await element.isEnabled(); + } - public async isSelected(selector: string): Promise { - log.debug(`TestSubjects.isSelected(${selector})`); - const element = await this.find(selector); + public async isDisplayed(selector: string): Promise { + this.log.debug(`TestSubjects.isDisplayed(${selector})`); + const element = await this.find(selector); + return await element.isDisplayed(); + } + + public async isSelected(selector: string): Promise { + this.log.debug(`TestSubjects.isSelected(${selector})`); + const element = await this.find(selector); + return await element.isSelected(); + } + + public async isSelectedAll(selectorAll: string): Promise { + this.log.debug(`TestSubjects.isSelectedAll(${selectorAll})`); + return await this._mapAll(selectorAll, async (element: WebElementWrapper) => { return await element.isSelected(); - } + }); + } - public async isSelectedAll(selectorAll: string): Promise { - log.debug(`TestSubjects.isSelectedAll(${selectorAll})`); - return await this._mapAll(selectorAll, async (element: WebElementWrapper) => { - return await element.isSelected(); - }); - } + public async getVisibleText(selector: string): Promise { + this.log.debug(`TestSubjects.getVisibleText(${selector})`); + const element = await this.find(selector); + return await element.getVisibleText(); + } - public async getVisibleText(selector: string): Promise { - log.debug(`TestSubjects.getVisibleText(${selector})`); - const element = await this.find(selector); + async getVisibleTextAll(selectorAll: string): Promise { + this.log.debug(`TestSubjects.getVisibleTextAll(${selectorAll})`); + return await this._mapAll(selectorAll, async (element: WebElementWrapper) => { return await element.getVisibleText(); - } + }); + } - async getVisibleTextAll(selectorAll: string): Promise { - log.debug(`TestSubjects.getVisibleTextAll(${selectorAll})`); - return await this._mapAll(selectorAll, async (element: WebElementWrapper) => { - return await element.getVisibleText(); - }); - } + public async moveMouseTo(selector: string): Promise { + // Wrapped in a retry because even though the find should do a stale element check of it's own, we seem to + // have run into a case where the element becomes stale after the find succeeds, throwing an error during the + // moveMouseTo function. + await this.retry.try(async () => { + this.log.debug(`TestSubjects.moveMouseTo(${selector})`); + const element = await this.find(selector); + await element.moveMouseTo(); + }); + } - public async moveMouseTo(selector: string): Promise { - // Wrapped in a retry because even though the find should do a stale element check of it's own, we seem to - // have run into a case where the element becomes stale after the find succeeds, throwing an error during the - // moveMouseTo function. - await retry.try(async () => { - log.debug(`TestSubjects.moveMouseTo(${selector})`); - const element = await this.find(selector); - await element.moveMouseTo(); - }); - } + private async _mapAll( + selectorAll: string, + mapFn: (element: WebElementWrapper, index?: number, arrayLength?: number) => Promise + ): Promise { + return await this.retry.try(async () => { + const elements = await this.findAll(selectorAll); + return await mapAsync(elements, mapFn); + }); + } - private async _mapAll( - selectorAll: string, - mapFn: (element: WebElementWrapper, index?: number, arrayLength?: number) => Promise - ): Promise { - return await retry.try(async () => { - const elements = await this.findAll(selectorAll); - return await mapAsync(elements, mapFn); - }); + public async waitForDeleted(selectorOrElement: string | WebElementWrapper): Promise { + if (typeof selectorOrElement === 'string') { + await this.findService.waitForDeletedByCssSelector(testSubjSelector(selectorOrElement)); + } else { + await this.findService.waitForElementStale(selectorOrElement); } + } - public async waitForDeleted(selectorOrElement: string | WebElementWrapper): Promise { - if (typeof selectorOrElement === 'string') { - await find.waitForDeletedByCssSelector(testSubjSelector(selectorOrElement)); - } else { - await find.waitForElementStale(selectorOrElement); - } - } + public async waitForAttributeToChange( + selector: string, + attribute: string, + value: string + ): Promise { + await this.findService.waitForAttributeToChange(testSubjSelector(selector), attribute, value); + } - public async waitForAttributeToChange( - selector: string, - attribute: string, - value: string - ): Promise { - await find.waitForAttributeToChange(testSubjSelector(selector), attribute, value); - } + public async waitForHidden(selector: string, timeout?: number): Promise { + this.log.debug(`TestSubjects.waitForHidden(${selector})`); + const element = await this.find(selector); + await this.findService.waitForElementHidden(element, timeout); + } - public async waitForHidden(selector: string, timeout?: number): Promise { - log.debug(`TestSubjects.waitForHidden(${selector})`); + public async waitForEnabled(selector: string, timeout: number = this.TRY_TIME): Promise { + await this.retry.tryForTime(timeout, async () => { const element = await this.find(selector); - await find.waitForElementHidden(element, timeout); - } - - public async waitForEnabled(selector: string, timeout: number = TRY_TIME): Promise { - await retry.tryForTime(timeout, async () => { - const element = await this.find(selector); - return (await element.isDisplayed()) && (await element.isEnabled()); - }); - } + return (await element.isDisplayed()) && (await element.isEnabled()); + }); + } - public getCssSelector(selector: string): string { - return testSubjSelector(selector); - } + public getCssSelector(selector: string): string { + return testSubjSelector(selector); + } - public async scrollIntoView(selector: string) { - const element = await this.find(selector); - await element.scrollIntoViewIfNecessary(); - } + public async scrollIntoView(selector: string) { + const element = await this.find(selector); + await element.scrollIntoViewIfNecessary(); + } - // isChecked always returns false when run on an euiSwitch, because they use the aria-checked attribute - public async isChecked(selector: string) { - const checkbox = await this.find(selector); - return await checkbox.isSelected(); - } + // isChecked always returns false when run on an euiSwitch, because they use the aria-checked attribute + public async isChecked(selector: string) { + const checkbox = await this.find(selector); + return await checkbox.isSelected(); + } - public async setCheckbox(selector: string, state: 'check' | 'uncheck') { - const isChecked = await this.isChecked(selector); - const states = { check: true, uncheck: false }; - if (isChecked !== states[state]) { - log.debug(`updating checkbox ${selector} from ${isChecked} to ${states[state]}`); - await this.click(selector); - } + public async setCheckbox(selector: string, state: 'check' | 'uncheck') { + const isChecked = await this.isChecked(selector); + const states = { check: true, uncheck: false }; + if (isChecked !== states[state]) { + this.log.debug(`updating checkbox ${selector} from ${isChecked} to ${states[state]}`); + await this.click(selector); } + } - public async isEuiSwitchChecked(selector: string) { - const euiSwitch = await this.find(selector); - const isChecked = await euiSwitch.getAttribute('aria-checked'); - return isChecked === 'true'; - } + public async isEuiSwitchChecked(selector: string) { + const euiSwitch = await this.find(selector); + const isChecked = await euiSwitch.getAttribute('aria-checked'); + return isChecked === 'true'; + } - public async setEuiSwitch(selector: string, state: 'check' | 'uncheck') { - const isChecked = await this.isEuiSwitchChecked(selector); - const states = { check: true, uncheck: false }; - if (isChecked !== states[state]) { - log.debug(`updating checkbox ${selector} from ${isChecked} to ${states[state]}`); - await this.click(selector); - } + public async setEuiSwitch(selector: string, state: 'check' | 'uncheck') { + const isChecked = await this.isEuiSwitchChecked(selector); + const states = { check: true, uncheck: false }; + if (isChecked !== states[state]) { + this.log.debug(`updating checkbox ${selector} from ${isChecked} to ${states[state]}`); + await this.click(selector); } } - - return new TestSubjects(); } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 43f891ee4644fe..fc90fcdb2d52e4 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -15,7 +15,7 @@ import { FindProvider, ScreenshotsProvider, SnapshotsProvider, - TestSubjectsProvider, + TestSubjects, } from './common'; import { ComboBoxProvider } from './combo_box'; import { @@ -56,7 +56,7 @@ export const services = { filterBar: FilterBarService, queryBar: QueryBarProvider, find: FindProvider, - testSubjects: TestSubjectsProvider, + testSubjects: TestSubjects, docTable: DocTableService, screenshots: ScreenshotsProvider, snapshots: SnapshotsProvider, From 6d48a50c2a4ecff9188e4d2c9f7d426ba4398294 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 May 2021 05:18:13 -0700 Subject: [PATCH 18/18] [ftr] migrate "globalNav" service to FtrService class (#100604) Co-authored-by: spalger --- test/functional/services/global_nav.ts | 71 +++++++++++++------------- test/functional/services/index.ts | 4 +- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/test/functional/services/global_nav.ts b/test/functional/services/global_nav.ts index ec404bcb068e94..4ce8ed6cb79dde 100644 --- a/test/functional/services/global_nav.ts +++ b/test/functional/services/global_nav.ts @@ -7,50 +7,49 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function GlobalNavProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); +export class GlobalNavService extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); - class GlobalNav { - public async moveMouseToLogo(): Promise { - await testSubjects.moveMouseTo('headerGlobalNav > logo'); - } - - public async clickLogo(): Promise { - return await testSubjects.click('headerGlobalNav > logo'); - } + public async moveMouseToLogo(): Promise { + await this.testSubjects.moveMouseTo('headerGlobalNav > logo'); + } - public async clickNewsfeed(): Promise { - return await testSubjects.click('headerGlobalNav > newsfeed'); - } + public async clickLogo(): Promise { + return await this.testSubjects.click('headerGlobalNav > logo'); + } - public async exists(): Promise { - return await testSubjects.exists('headerGlobalNav'); - } + public async clickNewsfeed(): Promise { + return await this.testSubjects.click('headerGlobalNav > newsfeed'); + } - public async getFirstBreadcrumb(): Promise { - return await testSubjects.getVisibleText( - 'headerGlobalNav > breadcrumbs > ~breadcrumb & ~first' - ); - } + public async exists(): Promise { + return await this.testSubjects.exists('headerGlobalNav'); + } - public async getLastBreadcrumb(): Promise { - return await testSubjects.getVisibleText( - 'headerGlobalNav > breadcrumbs > ~breadcrumb & ~last' - ); - } + public async getFirstBreadcrumb(): Promise { + return await this.testSubjects.getVisibleText( + 'headerGlobalNav > breadcrumbs > ~breadcrumb & ~first' + ); + } - public async badgeExistsOrFail(expectedLabel: string): Promise { - await testSubjects.existOrFail('headerBadge'); - const actualLabel = await testSubjects.getAttribute('headerBadge', 'data-test-badge-label'); - expect(actualLabel.toUpperCase()).to.equal(expectedLabel.toUpperCase()); - } + public async getLastBreadcrumb(): Promise { + return await this.testSubjects.getVisibleText( + 'headerGlobalNav > breadcrumbs > ~breadcrumb & ~last' + ); + } - public async badgeMissingOrFail(): Promise { - await testSubjects.missingOrFail('headerBadge'); - } + public async badgeExistsOrFail(expectedLabel: string): Promise { + await this.testSubjects.existOrFail('headerBadge'); + const actualLabel = await this.testSubjects.getAttribute( + 'headerBadge', + 'data-test-badge-label' + ); + expect(actualLabel.toUpperCase()).to.equal(expectedLabel.toUpperCase()); } - return new GlobalNav(); + public async badgeMissingOrFail(): Promise { + await this.testSubjects.missingOrFail('headerBadge'); + } } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index fc90fcdb2d52e4..b1443244d2c5a7 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -29,7 +29,7 @@ import { DocTableService } from './doc_table'; import { EmbeddingProvider } from './embedding'; import { FilterBarService } from './filter_bar'; import { FlyoutProvider } from './flyout'; -import { GlobalNavProvider } from './global_nav'; +import { GlobalNavService } from './global_nav'; import { InspectorProvider } from './inspector'; import { FieldEditorService } from './field_editor'; import { ManagementMenuProvider } from './management'; @@ -78,7 +78,7 @@ export const services = { fieldEditor: FieldEditorService, vegaDebugInspector: VegaDebugInspectorViewProvider, appsMenu: AppsMenuProvider, - globalNav: GlobalNavProvider, + globalNav: GlobalNavService, toasts: ToastsProvider, savedQueryManagementComponent: SavedQueryManagementComponentProvider, elasticChart: ElasticChartProvider,