From 4f66dfd661b5bf5e279d07486703a793dbed9724 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 5 May 2020 10:20:38 +0200 Subject: [PATCH] [ML] Transforms: Single Column Wizard. (#64436) Rearranges the layout of the transform wizard pivot configuration step into a single-column. This allows us to have the data grids for source index and pivot preview having the full width. The advanced editors for source query and pivot configuration also cover a wider width. --- .../components/data_grid/data_grid.tsx | 269 ++-- .../public/app/common/request.test.ts | 2 +- .../transform/public/app/common/request.ts | 2 +- .../public/app/hooks/use_pivot_data.ts | 3 +- .../advanced_pivot_editor.tsx | 69 + .../components/advanced_pivot_editor/index.ts | 7 + .../advanced_pivot_editor_switch.tsx | 69 + .../advanced_pivot_editor_switch/index.ts | 7 + .../advanced_query_editor_switch.tsx | 76 ++ .../advanced_query_editor_switch/index.ts | 7 + .../advanced_source_editor.tsx | 59 + .../advanced_source_editor/index.ts | 7 + .../aggregation_dropdown/dropdown.tsx | 1 + .../components/pivot_configuration/index.ts | 7 + .../pivot_configuration.tsx | 89 ++ .../components/source_search_bar/index.ts | 7 + .../source_search_bar/source_search_bar.tsx | 68 + .../apply_transform_config_to_define_state.ts | 71 + .../step_define/{ => common}/common.test.ts | 4 +- .../step_define/common/constants.ts | 11 + .../get_agg_name_conflict_toast_messages.ts | 100 ++ .../common/get_default_aggregation_config.ts | 37 + .../common/get_default_group_by_config.ts | 44 + .../common/get_default_step_define_state.ts | 25 + .../get_pivot_dropdown_options.ts} | 79 +- .../components/step_define/common/index.ts | 19 + .../components/step_define/common/types.ts | 34 + .../hooks/use_advanced_pivot_editor.ts | 76 ++ .../hooks/use_advanced_source_editor.ts | 87 ++ .../step_define/hooks/use_pivot_config.ts | 144 ++ .../step_define/hooks/use_search_bar.ts | 90 ++ .../step_define/hooks/use_step_define_form.ts | 87 ++ .../components/step_define/index.ts | 7 +- .../step_define/step_define_form.test.tsx | 3 +- .../step_define/step_define_form.tsx | 1160 ++++------------- .../step_define/step_define_summary.test.tsx | 2 +- .../step_define/step_define_summary.tsx | 152 +-- .../components/switch_modal/index.ts | 7 + .../switch_modal.tsx | 0 .../components/wizard/wizard.tsx | 4 +- .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - x-pack/run_functional_tests.sh | 3 + .../apps/transform/creation_index_pattern.ts | 4 +- .../services/transform_ui/wizard.ts | 11 +- 45 files changed, 1823 insertions(+), 1197 deletions(-) create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts rename x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/{ => common}/common.test.ts (95%) create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/constants.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_agg_name_conflict_toast_messages.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_aggregation_config.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_group_by_config.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts rename x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/{common.ts => common/get_pivot_dropdown_options.ts} (64%) create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/index.ts rename x-pack/plugins/transform/public/app/sections/create_transform/components/{step_define => switch_modal}/switch_modal.tsx (100%) create mode 100755 x-pack/run_functional_tests.sh diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index a5b301902cc75..aeb774a224021 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, FC } from 'react'; +import { isEqual } from 'lodash'; +import React, { memo, useEffect, FC } from 'react'; import { i18n } from '@kbn/i18n'; @@ -50,132 +51,154 @@ function isWithHeader(arg: any): arg is PropsWithHeader { type Props = PropsWithHeader | PropsWithoutHeader; -export const DataGrid: FC = props => { - const { - columns, - dataTestSubj, - errorMessage, - invalidSortingColumnns, - noDataMessage, - onChangeItemsPerPage, - onChangePage, - onSort, - pagination, - setVisibleColumns, - renderCellValue, - rowCount, - sortingColumns, - status, - tableItems: data, - toastNotifications, - visibleColumns, - } = props; - - useEffect(() => { - if (invalidSortingColumnns.length > 0) { - invalidSortingColumnns.forEach(columnId => { - toastNotifications.addDanger( - i18n.translate('xpack.ml.dataGrid.invalidSortingColumnError', { - defaultMessage: `The column '{columnId}' cannot be used for sorting.`, - values: { columnId }, - }) - ); - }); - } - }, [invalidSortingColumnns, toastNotifications]); - - if (status === INDEX_STATUS.LOADED && data.length === 0) { - return ( -
- {isWithHeader(props) && } - -

- {i18n.translate('xpack.ml.dataGrid.IndexNoDataCalloutBody', { - defaultMessage: - 'The query for the index returned no results. Please make sure you have sufficient permissions, the index contains documents and your query is not too restrictive.', +export const DataGrid: FC = memo( + props => { + const { + columns, + dataTestSubj, + errorMessage, + invalidSortingColumnns, + noDataMessage, + onChangeItemsPerPage, + onChangePage, + onSort, + pagination, + setVisibleColumns, + renderCellValue, + rowCount, + sortingColumns, + status, + tableItems: data, + toastNotifications, + visibleColumns, + } = props; + + useEffect(() => { + if (invalidSortingColumnns.length > 0) { + invalidSortingColumnns.forEach(columnId => { + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataGrid.invalidSortingColumnError', { + defaultMessage: `The column '{columnId}' cannot be used for sorting.`, + values: { columnId }, + }) + ); + }); + } + }, [invalidSortingColumnns, toastNotifications]); + + if (status === INDEX_STATUS.LOADED && data.length === 0) { + return ( +

+ {isWithHeader(props) && } + - -
- ); - } + color="primary" + > +

+ {i18n.translate('xpack.ml.dataGrid.IndexNoDataCalloutBody', { + defaultMessage: + 'The query for the index returned no results. Please make sure you have sufficient permissions, the index contains documents and your query is not too restrictive.', + })} +

+
+
+ ); + } - if (noDataMessage !== '') { - return ( -
- {isWithHeader(props) && } - -

{noDataMessage}

-
-
- ); - } - - return ( -
- {isWithHeader(props) && ( - - - - - - - {(copy: () => void) => ( - - )} - - - - )} - {status === INDEX_STATUS.ERROR && ( -
+ if (noDataMessage !== '') { + return ( +
+ {isWithHeader(props) && } - - {errorMessage} - +

{noDataMessage}

-
- )} - -
- ); -}; + ); + } + + return ( +
+ {isWithHeader(props) && ( + + + + + + + {(copy: () => void) => ( + + )} + + + + )} + {status === INDEX_STATUS.ERROR && ( +
+ + + {errorMessage} + + + +
+ )} + +
+ ); + }, + (prevProps, nextProps) => isEqual(pickProps(prevProps), pickProps(nextProps)) +); + +function pickProps(props: Props) { + return [ + props.columns, + props.dataTestSubj, + props.errorMessage, + props.invalidSortingColumnns, + props.noDataMessage, + props.pagination, + props.rowCount, + props.sortingColumns, + props.status, + props.tableItems, + props.visibleColumns, + ...(isWithHeader(props) + ? [props.copyToClipboard, props.copyToClipboardDescription, props.title] + : []), + ]; +} diff --git a/x-pack/plugins/transform/public/app/common/request.test.ts b/x-pack/plugins/transform/public/app/common/request.test.ts index 4c3fba3bbf8dd..63f1f8b10ad44 100644 --- a/x-pack/plugins/transform/public/app/common/request.test.ts +++ b/x-pack/plugins/transform/public/app/common/request.test.ts @@ -6,7 +6,7 @@ import { PivotGroupByConfig } from '../common'; -import { StepDefineExposedState } from '../sections/create_transform/components/step_define/step_define_form'; +import { StepDefineExposedState } from '../sections/create_transform/components/step_define'; import { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form'; import { PIVOT_SUPPORTED_GROUP_BY_AGGS } from './pivot_group_by'; diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index 7e965dbe802c0..1a69e9f6476b9 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -9,7 +9,7 @@ import { DefaultOperator } from 'elasticsearch'; import { dictionaryToArray } from '../../../common/types/common'; import { SavedSearchQuery } from '../hooks/use_search_items'; -import { StepDefineExposedState } from '../sections/create_transform/components/step_define/step_define_form'; +import { StepDefineExposedState } from '../sections/create_transform/components/step_define'; import { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form'; import { IndexPattern } from '../../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index ff7ca5d42b5f7..853540e19ea6f 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -123,8 +123,6 @@ export const usePivotData = ( tableItems, } = dataGrid; - const previewRequest = getPreviewRequestBody(indexPatternTitle, query, groupByArr, aggsArr); - const getPreviewData = async () => { if (aggsArr.length === 0 || groupByArr.length === 0) { setTableItems([]); @@ -142,6 +140,7 @@ export const usePivotData = ( setStatus(INDEX_STATUS.LOADING); try { + const previewRequest = getPreviewRequestBody(indexPatternTitle, query, groupByArr, aggsArr); const resp = await api.getTransformsPreview(previewRequest); setTableItems(resp.preview); setRowCount(resp.preview.length); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx new file mode 100644 index 0000000000000..983d36a20e87f --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEqual } from 'lodash'; +import React, { memo, FC } from 'react'; + +import { EuiCodeEditor, EuiFormRow } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { StepDefineFormHook } from '../step_define'; + +export const AdvancedPivotEditor: FC = memo( + ({ + actions: { convertToJson, setAdvancedEditorConfig, setAdvancedPivotEditorApplyButtonEnabled }, + state: { advancedEditorConfigLastApplied, advancedEditorConfig, xJsonMode }, + }) => { + return ( + + { + setAdvancedEditorConfig(d); + + // Disable the "Apply"-Button if the config hasn't changed. + if (advancedEditorConfigLastApplied === d) { + setAdvancedPivotEditorApplyButtonEnabled(false); + return; + } + + // Try to parse the string passed on from the editor. + // If parsing fails, the "Apply"-Button will be disabled + try { + JSON.parse(convertToJson(d)); + setAdvancedPivotEditorApplyButtonEnabled(true); + } catch (e) { + setAdvancedPivotEditorApplyButtonEnabled(false); + } + }} + setOptions={{ + fontSize: '12px', + }} + theme="textmate" + aria-label={i18n.translate('xpack.transform.stepDefineForm.advancedEditorAriaLabel', { + defaultMessage: 'Advanced pivot editor', + })} + /> + + ); + }, + (prevProps, nextProps) => isEqual(pickProps(prevProps), pickProps(nextProps)) +); + +function pickProps(props: StepDefineFormHook['advancedPivotEditor']) { + return [props.state.advancedEditorConfigLastApplied, props.state.advancedEditorConfig]; +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/index.ts new file mode 100644 index 0000000000000..340f7d37ba93b --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedPivotEditor } from './advanced_pivot_editor'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx new file mode 100644 index 0000000000000..ce155c58bc37c --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/advanced_pivot_editor_switch.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSwitch } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { SwitchModal } from '../switch_modal'; + +import { StepDefineFormHook } from '../step_define'; + +export const AdvancedPivotEditorSwitch: FC = ({ + advancedPivotEditor: { + actions: { setAdvancedEditorSwitchModalVisible, toggleAdvancedEditor }, + state: { + advancedEditorConfig, + advancedEditorConfigLastApplied, + isAdvancedEditorSwitchModalVisible, + isAdvancedPivotEditorEnabled, + isAdvancedPivotEditorApplyButtonEnabled, + }, + }, + pivotConfig: { + actions: { setAggList, setGroupByList }, + }, +}) => { + return ( + + + + { + if ( + isAdvancedPivotEditorEnabled && + (isAdvancedPivotEditorApplyButtonEnabled || + advancedEditorConfig !== advancedEditorConfigLastApplied) + ) { + setAdvancedEditorSwitchModalVisible(true); + return; + } + + toggleAdvancedEditor(); + }} + data-test-subj="transformAdvancedPivotEditorSwitch" + /> + {isAdvancedEditorSwitchModalVisible && ( + setAdvancedEditorSwitchModalVisible(false)} + onConfirm={() => { + setAdvancedEditorSwitchModalVisible(false); + toggleAdvancedEditor(); + }} + type={'pivot'} + /> + )} + + + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/index.ts new file mode 100644 index 0000000000000..377f54e12c03b --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor_switch/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedPivotEditorSwitch } from './advanced_pivot_editor_switch'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx new file mode 100644 index 0000000000000..66234b8cc2007 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/advanced_query_editor_switch.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { EuiSwitch } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { SwitchModal } from '../switch_modal'; +import { defaultSearch } from '../step_define'; + +import { StepDefineFormHook } from '../step_define'; + +export const AdvancedQueryEditorSwitch: FC = ({ + advancedSourceEditor: { + actions: { + setAdvancedSourceEditorSwitchModalVisible, + setSourceConfigUpdated, + toggleAdvancedSourceEditor, + }, + state: { + isAdvancedSourceEditorEnabled, + isAdvancedSourceEditorSwitchModalVisible, + sourceConfigUpdated, + }, + }, + searchBar: { + actions: { setSearchQuery }, + }, +}) => { + // If switching to KQL after updating via editor - reset search + const toggleEditorHandler = (reset = false) => { + if (reset === true) { + setSearchQuery(defaultSearch); + setSourceConfigUpdated(false); + } + toggleAdvancedSourceEditor(reset); + }; + + return ( + <> + { + if (isAdvancedSourceEditorEnabled && sourceConfigUpdated) { + setAdvancedSourceEditorSwitchModalVisible(true); + return; + } + + toggleEditorHandler(); + }} + data-test-subj="transformAdvancedQueryEditorSwitch" + /> + {isAdvancedSourceEditorSwitchModalVisible && ( + setAdvancedSourceEditorSwitchModalVisible(false)} + onConfirm={() => { + setAdvancedSourceEditorSwitchModalVisible(false); + toggleEditorHandler(true); + }} + type={'source'} + /> + )} + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/index.ts new file mode 100644 index 0000000000000..36474e99c66ba --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_query_editor_switch/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedQueryEditorSwitch } from './advanced_query_editor_switch'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx new file mode 100644 index 0000000000000..fecf4972330e1 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/advanced_source_editor.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { EuiCodeEditor } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { StepDefineFormHook } from '../step_define'; + +export const AdvancedSourceEditor: FC = ({ + searchBar: { + actions: { setSearchString }, + }, + advancedSourceEditor: { + actions: { setAdvancedEditorSourceConfig, setAdvancedSourceEditorApplyButtonEnabled }, + state: { advancedEditorSourceConfig, advancedEditorSourceConfigLastApplied }, + }, +}) => { + return ( + { + setSearchString(undefined); + setAdvancedEditorSourceConfig(d); + + // Disable the "Apply"-Button if the config hasn't changed. + if (advancedEditorSourceConfigLastApplied === d) { + setAdvancedSourceEditorApplyButtonEnabled(false); + return; + } + + // Try to parse the string passed on from the editor. + // If parsing fails, the "Apply"-Button will be disabled + try { + JSON.parse(d); + setAdvancedSourceEditorApplyButtonEnabled(true); + } catch (e) { + setAdvancedSourceEditorApplyButtonEnabled(false); + } + }} + setOptions={{ + fontSize: '12px', + }} + theme="textmate" + aria-label={i18n.translate('xpack.transform.stepDefineForm.advancedSourceEditorAriaLabel', { + defaultMessage: 'Advanced query editor', + })} + /> + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/index.ts new file mode 100644 index 0000000000000..8f5c88c5b44eb --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_source_editor/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedSourceEditor } from './advanced_source_editor'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx index 157e0f76856c8..e5381f09713b5 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_dropdown/dropdown.tsx @@ -23,6 +23,7 @@ export const DropDown: React.FC = ({ }) => { return ( = memo( + ({ + actions: { + addAggregation, + addGroupBy, + deleteAggregation, + deleteGroupBy, + updateAggregation, + updateGroupBy, + }, + state: { aggList, aggOptions, aggOptionsData, groupByList, groupByOptions, groupByOptionsData }, + }) => { + return ( + <> + + <> + + + + + + + <> + + + + + + ); + }, + (prevProps, nextProps) => { + return isEqual(prevProps.state, nextProps.state); + } +); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/index.ts new file mode 100644 index 0000000000000..4e1cf81eef98e --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SourceSearchBar } from './source_search_bar'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx new file mode 100644 index 0000000000000..a8e1bf3552c80 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { EuiCode, EuiInputPopover } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { QueryStringInput } from '../../../../../../../../../src/plugins/data/public'; + +import { SearchItems } from '../../../../hooks/use_search_items'; + +import { StepDefineFormHook, QUERY_LANGUAGE_KUERY } from '../step_define'; + +interface SourceSearchBarProps { + indexPattern: SearchItems['indexPattern']; + searchBar: StepDefineFormHook['searchBar']; +} +export const SourceSearchBar: FC = ({ indexPattern, searchBar }) => { + const { + actions: { searchChangeHandler, searchSubmitHandler, setErrorMessage }, + state: { errorMessage, searchInput }, + } = searchBar; + + return ( + setErrorMessage(undefined)} + input={ + + } + isOpen={errorMessage?.query === searchInput.query && errorMessage?.message !== ''} + > + + {i18n.translate('xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar', { + defaultMessage: 'Invalid query: {errorMessage}', + values: { + errorMessage: errorMessage?.message.split('\n')[0], + }, + })} + + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts new file mode 100644 index 0000000000000..bda1efe97837f --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEqual } from 'lodash'; + +import { + matchAllQuery, + PivotAggsConfig, + PivotAggsConfigDict, + PivotGroupByConfig, + PivotGroupByConfigDict, + TransformPivotConfig, + PIVOT_SUPPORTED_AGGS, + PIVOT_SUPPORTED_GROUP_BY_AGGS, +} from '../../../../../common'; +import { Dictionary } from '../../../../../../../common/types/common'; + +import { StepDefineExposedState } from './types'; + +export function applyTransformConfigToDefineState( + state: StepDefineExposedState, + transformConfig?: TransformPivotConfig +): StepDefineExposedState { + // apply the transform configuration to wizard DEFINE state + if (transformConfig !== undefined) { + // transform aggregations config to wizard state + state.aggList = Object.keys(transformConfig.pivot.aggregations).reduce((aggList, aggName) => { + const aggConfig = transformConfig.pivot.aggregations[aggName] as Dictionary; + const agg = Object.keys(aggConfig)[0]; + aggList[aggName] = { + ...aggConfig[agg], + agg: agg as PIVOT_SUPPORTED_AGGS, + aggName, + dropDownName: aggName, + } as PivotAggsConfig; + return aggList; + }, {} as PivotAggsConfigDict); + + // transform group by config to wizard state + state.groupByList = Object.keys(transformConfig.pivot.group_by).reduce( + (groupByList, groupByName) => { + const groupByConfig = transformConfig.pivot.group_by[groupByName] as Dictionary; + const groupBy = Object.keys(groupByConfig)[0]; + groupByList[groupByName] = { + agg: groupBy as PIVOT_SUPPORTED_GROUP_BY_AGGS, + aggName: groupByName, + dropDownName: groupByName, + ...groupByConfig[groupBy], + } as PivotGroupByConfig; + return groupByList; + }, + {} as PivotGroupByConfigDict + ); + + // only apply the query from the transform config to wizard state if it's not the default query + const query = transformConfig.source.query; + if (query !== undefined && !isEqual(query, matchAllQuery)) { + state.isAdvancedSourceEditorEnabled = true; + state.searchQuery = query; + state.sourceConfigUpdated = true; + } + + // applying a transform config to wizard state will always result in a valid configuration + state.valid = true; + } + + return state; +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts similarity index 95% rename from x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common.test.ts rename to x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts index 58ab4a1b8ac33..4fac3dce3de44 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common.test.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getPivotDropdownOptions } from './common'; -import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; +import { getPivotDropdownOptions } from '../common'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; describe('Transform: Define Pivot Common', () => { test('getPivotDropdownOptions()', () => { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/constants.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/constants.ts new file mode 100644 index 0000000000000..4eefa7c94464d --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const defaultSearch = '*'; + +export const QUERY_LANGUAGE_KUERY = 'kuery'; +export const QUERY_LANGUAGE_LUCENE = 'lucene'; +export type QUERY_LANGUAGE = 'kuery' | 'lucene'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_agg_name_conflict_toast_messages.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_agg_name_conflict_toast_messages.ts new file mode 100644 index 0000000000000..cad3ab8c71a22 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_agg_name_conflict_toast_messages.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { AggName, PivotAggsConfigDict, PivotGroupByConfigDict } from '../../../../../common'; + +export function getAggNameConflictToastMessages( + aggName: AggName, + aggList: PivotAggsConfigDict, + groupByList: PivotGroupByConfigDict +): string[] { + if (aggList[aggName] !== undefined) { + return [ + i18n.translate('xpack.transform.stepDefineForm.aggExistsErrorMessage', { + defaultMessage: `An aggregation configuration with the name '{aggName}' already exists.`, + values: { aggName }, + }), + ]; + } + + if (groupByList[aggName] !== undefined) { + return [ + i18n.translate('xpack.transform.stepDefineForm.groupByExistsErrorMessage', { + defaultMessage: `A group by configuration with the name '{aggName}' already exists.`, + values: { aggName }, + }), + ]; + } + + const conflicts: string[] = []; + + // check the new aggName against existing aggs and groupbys + const aggNameSplit = aggName.split('.'); + let aggNameCheck: string; + aggNameSplit.forEach(aggNamePart => { + aggNameCheck = aggNameCheck === undefined ? aggNamePart : `${aggNameCheck}.${aggNamePart}`; + if (aggList[aggNameCheck] !== undefined || groupByList[aggNameCheck] !== undefined) { + conflicts.push( + i18n.translate('xpack.transform.stepDefineForm.nestedConflictErrorMessage', { + defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{aggNameCheck}'.`, + values: { aggName, aggNameCheck }, + }) + ); + } + }); + + if (conflicts.length > 0) { + return conflicts; + } + + // check all aggs against new aggName + aggListNameLoop: for (const aggListName of Object.keys(aggList)) { + const aggListNameSplit = aggListName.split('.'); + let aggListNameCheck: string | undefined; + for (const aggListNamePart of aggListNameSplit) { + aggListNameCheck = + aggListNameCheck === undefined ? aggListNamePart : `${aggListNameCheck}.${aggListNamePart}`; + if (aggListNameCheck === aggName) { + conflicts.push( + i18n.translate('xpack.transform.stepDefineForm.nestedAggListConflictErrorMessage', { + defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{aggListName}'.`, + values: { aggName, aggListName }, + }) + ); + break aggListNameLoop; + } + } + } + + if (conflicts.length > 0) { + return conflicts; + } + + // check all group-bys against new aggName + groupByListNameLoop: for (const groupByListName of Object.keys(groupByList)) { + const groupByListNameSplit = groupByListName.split('.'); + let groupByListNameCheck: string | undefined; + for (const groupByListNamePart of groupByListNameSplit) { + groupByListNameCheck = + groupByListNameCheck === undefined + ? groupByListNamePart + : `${groupByListNameCheck}.${groupByListNamePart}`; + if (groupByListNameCheck === aggName) { + conflicts.push( + i18n.translate('xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage', { + defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{groupByListName}'.`, + values: { aggName, groupByListName }, + }) + ); + break groupByListNameLoop; + } + } + } + + return conflicts; +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_aggregation_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_aggregation_config.ts new file mode 100644 index 0000000000000..263a8954c96eb --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_aggregation_config.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EsFieldName, + PERCENTILES_AGG_DEFAULT_PERCENTS, + PivotAggsConfigWithUiSupport, + PIVOT_SUPPORTED_AGGS, +} from '../../../../../common'; + +export function getDefaultAggregationConfig( + aggName: string, + dropDownName: string, + fieldName: EsFieldName, + agg: PIVOT_SUPPORTED_AGGS +): PivotAggsConfigWithUiSupport { + switch (agg) { + case PIVOT_SUPPORTED_AGGS.PERCENTILES: + return { + agg, + aggName, + dropDownName, + field: fieldName, + percents: PERCENTILES_AGG_DEFAULT_PERCENTS, + }; + default: + return { + agg, + aggName, + dropDownName, + field: fieldName, + }; + } +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_group_by_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_group_by_config.ts new file mode 100644 index 0000000000000..712a745ff6e77 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_group_by_config.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EsFieldName, + GroupByConfigWithUiSupport, + PIVOT_SUPPORTED_GROUP_BY_AGGS, +} from '../../../../../common'; + +export function getDefaultGroupByConfig( + aggName: string, + dropDownName: string, + fieldName: EsFieldName, + groupByAgg: PIVOT_SUPPORTED_GROUP_BY_AGGS +): GroupByConfigWithUiSupport { + switch (groupByAgg) { + case PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS: + return { + agg: groupByAgg, + aggName, + dropDownName, + field: fieldName, + }; + case PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM: + return { + agg: groupByAgg, + aggName, + dropDownName, + field: fieldName, + interval: '10', + }; + case PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM: + return { + agg: groupByAgg, + aggName, + dropDownName, + field: fieldName, + calendar_interval: '1m', + }; + } +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts new file mode 100644 index 0000000000000..30e1659e9e6ab --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PivotAggsConfigDict, PivotGroupByConfigDict } from '../../../../../common'; +import { SearchItems } from '../../../../../hooks/use_search_items'; + +import { defaultSearch, QUERY_LANGUAGE_KUERY } from './constants'; +import { StepDefineExposedState } from './types'; + +export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineExposedState { + return { + aggList: {} as PivotAggsConfigDict, + groupByList: {} as PivotGroupByConfigDict, + isAdvancedPivotEditorEnabled: false, + isAdvancedSourceEditorEnabled: false, + searchLanguage: QUERY_LANGUAGE_KUERY, + searchString: undefined, + searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, + sourceConfigUpdated: false, + valid: false, + }; +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts similarity index 64% rename from x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common.ts rename to x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts index 65cea40276da9..f916afa921c12 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts @@ -3,87 +3,26 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { get } from 'lodash'; import { EuiComboBoxOptionOption } from '@elastic/eui'; -import { IndexPattern, KBN_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/public'; +import { + IndexPattern, + KBN_FIELD_TYPES, +} from '../../../../../../../../../../src/plugins/data/public'; import { DropDownLabel, DropDownOption, - EsFieldName, - GroupByConfigWithUiSupport, - PERCENTILES_AGG_DEFAULT_PERCENTS, - PivotAggsConfigWithUiSupport, PivotAggsConfigWithUiSupportDict, pivotAggsFieldSupport, PivotGroupByConfigWithUiSupportDict, pivotGroupByFieldSupport, - PIVOT_SUPPORTED_AGGS, - PIVOT_SUPPORTED_GROUP_BY_AGGS, -} from '../../../../common'; - -export interface Field { - name: EsFieldName; - type: KBN_FIELD_TYPES; -} +} from '../../../../../common'; -function getDefaultGroupByConfig( - aggName: string, - dropDownName: string, - fieldName: EsFieldName, - groupByAgg: PIVOT_SUPPORTED_GROUP_BY_AGGS -): GroupByConfigWithUiSupport { - switch (groupByAgg) { - case PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS: - return { - agg: groupByAgg, - aggName, - dropDownName, - field: fieldName, - }; - case PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM: - return { - agg: groupByAgg, - aggName, - dropDownName, - field: fieldName, - interval: '10', - }; - case PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM: - return { - agg: groupByAgg, - aggName, - dropDownName, - field: fieldName, - calendar_interval: '1m', - }; - } -} - -function getDefaultAggregationConfig( - aggName: string, - dropDownName: string, - fieldName: EsFieldName, - agg: PIVOT_SUPPORTED_AGGS -): PivotAggsConfigWithUiSupport { - switch (agg) { - case PIVOT_SUPPORTED_AGGS.PERCENTILES: - return { - agg, - aggName, - dropDownName, - field: fieldName, - percents: PERCENTILES_AGG_DEFAULT_PERCENTS, - }; - default: - return { - agg, - aggName, - dropDownName, - field: fieldName, - }; - } -} +import { getDefaultAggregationConfig } from './get_default_aggregation_config'; +import { getDefaultGroupByConfig } from './get_default_group_by_config'; +import { Field } from './types'; const illegalEsAggNameChars = /[[\]>]/g; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts new file mode 100644 index 0000000000000..af351759c84d1 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + defaultSearch, + QUERY_LANGUAGE, + QUERY_LANGUAGE_KUERY, + QUERY_LANGUAGE_LUCENE, +} from './constants'; +export { applyTransformConfigToDefineState } from './apply_transform_config_to_define_state'; +export { getAggNameConflictToastMessages } from './get_agg_name_conflict_toast_messages'; +export { getDefaultAggregationConfig } from './get_default_aggregation_config'; +export { getDefaultGroupByConfig } from './get_default_group_by_config'; +export { getDefaultStepDefineState } from './get_default_step_define_state'; +export { getPivotDropdownOptions } from './get_pivot_dropdown_options'; +export { ErrorMessage, Field, StepDefineExposedState } from './types'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts new file mode 100644 index 0000000000000..56fde98cd4c71 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KBN_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; + +import { EsFieldName, PivotAggsConfigDict, PivotGroupByConfigDict } from '../../../../../common'; +import { SavedSearchQuery } from '../../../../../hooks/use_search_items'; + +import { QUERY_LANGUAGE } from './constants'; + +export interface ErrorMessage { + query: string; + message: string; +} + +export interface Field { + name: EsFieldName; + type: KBN_FIELD_TYPES; +} + +export interface StepDefineExposedState { + aggList: PivotAggsConfigDict; + groupByList: PivotGroupByConfigDict; + isAdvancedPivotEditorEnabled: boolean; + isAdvancedSourceEditorEnabled: boolean; + searchLanguage: QUERY_LANGUAGE; + searchString: string | undefined; + searchQuery: string | SavedSearchQuery; + sourceConfigUpdated: boolean; + valid: boolean; +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts new file mode 100644 index 0000000000000..2e92114286599 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState } from 'react'; + +import { useXJsonMode } from '../../../../../../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks'; + +import { PreviewRequestBody } from '../../../../../common'; + +import { StepDefineExposedState } from '../common'; + +export const useAdvancedPivotEditor = ( + defaults: StepDefineExposedState, + previewRequest: PreviewRequestBody +) => { + const stringifiedPivotConfig = JSON.stringify(previewRequest.pivot, null, 2); + + // Advanced editor for pivot config state + const [isAdvancedEditorSwitchModalVisible, setAdvancedEditorSwitchModalVisible] = useState(false); + + const [ + isAdvancedPivotEditorApplyButtonEnabled, + setAdvancedPivotEditorApplyButtonEnabled, + ] = useState(false); + + const [isAdvancedPivotEditorEnabled, setAdvancedPivotEditorEnabled] = useState( + defaults.isAdvancedPivotEditorEnabled + ); + + const [advancedEditorConfigLastApplied, setAdvancedEditorConfigLastApplied] = useState( + stringifiedPivotConfig + ); + + const { + convertToJson, + setXJson: setAdvancedEditorConfig, + xJson: advancedEditorConfig, + xJsonMode, + } = useXJsonMode(stringifiedPivotConfig); + + useEffect(() => { + setAdvancedEditorConfig(stringifiedPivotConfig); + }, [setAdvancedEditorConfig, stringifiedPivotConfig]); + + const toggleAdvancedEditor = () => { + setAdvancedEditorConfig(advancedEditorConfig); + setAdvancedPivotEditorEnabled(!isAdvancedPivotEditorEnabled); + setAdvancedPivotEditorApplyButtonEnabled(false); + if (isAdvancedPivotEditorEnabled === false) { + setAdvancedEditorConfigLastApplied(advancedEditorConfig); + } + }; + + return { + actions: { + convertToJson, + setAdvancedEditorConfig, + setAdvancedEditorConfigLastApplied, + setAdvancedEditorSwitchModalVisible, + setAdvancedPivotEditorApplyButtonEnabled, + setAdvancedPivotEditorEnabled, + toggleAdvancedEditor, + }, + state: { + advancedEditorConfig, + advancedEditorConfigLastApplied, + isAdvancedEditorSwitchModalVisible, + isAdvancedPivotEditorApplyButtonEnabled, + isAdvancedPivotEditorEnabled, + xJsonMode, + }, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts new file mode 100644 index 0000000000000..1ea8a45248fb9 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState } from 'react'; + +import { PreviewRequestBody } from '../../../../../common'; + +import { StepDefineExposedState } from '../common'; + +export const useAdvancedSourceEditor = ( + defaults: StepDefineExposedState, + previewRequest: PreviewRequestBody +) => { + const stringifiedSourceConfig = JSON.stringify(previewRequest.source.query, null, 2); + + // Advanced editor for source config state + const [sourceConfigUpdated, setSourceConfigUpdated] = useState(defaults.sourceConfigUpdated); + + const [ + isAdvancedSourceEditorSwitchModalVisible, + setAdvancedSourceEditorSwitchModalVisible, + ] = useState(false); + + const [isAdvancedSourceEditorEnabled, setAdvancedSourceEditorEnabled] = useState( + defaults.isAdvancedSourceEditorEnabled + ); + + const [ + isAdvancedSourceEditorApplyButtonEnabled, + setAdvancedSourceEditorApplyButtonEnabled, + ] = useState(false); + + const [ + advancedEditorSourceConfigLastApplied, + setAdvancedEditorSourceConfigLastApplied, + ] = useState(stringifiedSourceConfig); + + const [advancedEditorSourceConfig, setAdvancedEditorSourceConfig] = useState( + stringifiedSourceConfig + ); + + const applyAdvancedSourceEditorChanges = () => { + const sourceConfig = JSON.parse(advancedEditorSourceConfig); + const prettySourceConfig = JSON.stringify(sourceConfig, null, 2); + setSourceConfigUpdated(true); + setAdvancedEditorSourceConfig(prettySourceConfig); + setAdvancedEditorSourceConfigLastApplied(prettySourceConfig); + setAdvancedSourceEditorApplyButtonEnabled(false); + }; + + // If switching to KQL after updating via editor - reset search + const toggleAdvancedSourceEditor = (reset = false) => { + if (reset === true) { + setSourceConfigUpdated(false); + } + if (isAdvancedSourceEditorEnabled === false) { + setAdvancedEditorSourceConfigLastApplied(advancedEditorSourceConfig); + } + + setAdvancedSourceEditorEnabled(!isAdvancedSourceEditorEnabled); + setAdvancedSourceEditorApplyButtonEnabled(false); + }; + + return { + actions: { + applyAdvancedSourceEditorChanges, + setAdvancedSourceEditorApplyButtonEnabled, + setAdvancedSourceEditorEnabled, + setAdvancedEditorSourceConfig, + setAdvancedEditorSourceConfigLastApplied, + setAdvancedSourceEditorSwitchModalVisible, + setSourceConfigUpdated, + toggleAdvancedSourceEditor, + }, + state: { + advancedEditorSourceConfig, + advancedEditorSourceConfigLastApplied, + isAdvancedSourceEditorApplyButtonEnabled, + isAdvancedSourceEditorEnabled, + isAdvancedSourceEditorSwitchModalVisible, + sourceConfigUpdated, + }, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts new file mode 100644 index 0000000000000..70886e41fef4e --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState } from 'react'; + +import { dictionaryToArray } from '../../../../../../../common/types/common'; + +import { useToastNotifications } from '../../../../../app_dependencies'; +import { AggName, DropDownLabel, PivotAggsConfig, PivotGroupByConfig } from '../../../../../common'; + +import { + getAggNameConflictToastMessages, + getPivotDropdownOptions, + StepDefineExposedState, +} from '../common'; +import { StepDefineFormProps } from '../step_define_form'; + +export const usePivotConfig = ( + defaults: StepDefineExposedState, + indexPattern: StepDefineFormProps['searchItems']['indexPattern'] +) => { + const toastNotifications = useToastNotifications(); + + const { + aggOptions, + aggOptionsData, + groupByOptions, + groupByOptionsData, + } = getPivotDropdownOptions(indexPattern); + + // The list of selected group by fields + const [groupByList, setGroupByList] = useState(defaults.groupByList); + + const addGroupBy = (d: DropDownLabel[]) => { + const label: AggName = d[0].label; + const config: PivotGroupByConfig = groupByOptionsData[label]; + const aggName: AggName = config.aggName; + + const aggNameConflictMessages = getAggNameConflictToastMessages(aggName, aggList, groupByList); + if (aggNameConflictMessages.length > 0) { + aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); + return; + } + + groupByList[aggName] = config; + setGroupByList({ ...groupByList }); + }; + + const updateGroupBy = (previousAggName: AggName, item: PivotGroupByConfig) => { + const groupByListWithoutPrevious = { ...groupByList }; + delete groupByListWithoutPrevious[previousAggName]; + + const aggNameConflictMessages = getAggNameConflictToastMessages( + item.aggName, + aggList, + groupByListWithoutPrevious + ); + if (aggNameConflictMessages.length > 0) { + aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); + return; + } + + groupByListWithoutPrevious[item.aggName] = item; + setGroupByList(groupByListWithoutPrevious); + }; + + const deleteGroupBy = (aggName: AggName) => { + delete groupByList[aggName]; + setGroupByList({ ...groupByList }); + }; + + // The list of selected aggregations + const [aggList, setAggList] = useState(defaults.aggList); + + const addAggregation = (d: DropDownLabel[]) => { + const label: AggName = d[0].label; + const config: PivotAggsConfig = aggOptionsData[label]; + const aggName: AggName = config.aggName; + + const aggNameConflictMessages = getAggNameConflictToastMessages(aggName, aggList, groupByList); + if (aggNameConflictMessages.length > 0) { + aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); + return; + } + + aggList[aggName] = config; + setAggList({ ...aggList }); + }; + + const updateAggregation = (previousAggName: AggName, item: PivotAggsConfig) => { + const aggListWithoutPrevious = { ...aggList }; + delete aggListWithoutPrevious[previousAggName]; + + const aggNameConflictMessages = getAggNameConflictToastMessages( + item.aggName, + aggListWithoutPrevious, + groupByList + ); + if (aggNameConflictMessages.length > 0) { + aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); + return; + } + + aggListWithoutPrevious[item.aggName] = item; + setAggList(aggListWithoutPrevious); + }; + + const deleteAggregation = (aggName: AggName) => { + delete aggList[aggName]; + setAggList({ ...aggList }); + }; + + const pivotAggsArr = dictionaryToArray(aggList); + const pivotGroupByArr = dictionaryToArray(groupByList); + + const valid = pivotGroupByArr.length > 0 && pivotAggsArr.length > 0; + + return { + actions: { + addAggregation, + addGroupBy, + deleteAggregation, + deleteGroupBy, + setAggList, + setGroupByList, + updateAggregation, + updateGroupBy, + }, + state: { + aggList, + aggOptions, + aggOptionsData, + groupByList, + groupByOptions, + groupByOptionsData, + pivotAggsArr, + pivotGroupByArr, + valid, + }, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts new file mode 100644 index 0000000000000..9fff49d300575 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState } from 'react'; + +import { esKuery, esQuery, Query } from '../../../../../../../../../../src/plugins/data/public'; + +import { getPivotQuery } from '../../../../../common'; + +import { + ErrorMessage, + StepDefineExposedState, + QUERY_LANGUAGE_KUERY, + QUERY_LANGUAGE_LUCENE, + QUERY_LANGUAGE, +} from '../common'; + +import { StepDefineFormProps } from '../step_define_form'; + +export const useSearchBar = ( + defaults: StepDefineExposedState, + indexPattern: StepDefineFormProps['searchItems']['indexPattern'] +) => { + // The internal state of the input query bar updated on every key stroke. + const [searchInput, setSearchInput] = useState({ + query: defaults.searchString || '', + language: defaults.searchLanguage, + }); + + // The state of the input query bar updated on every submit and to be exposed. + const [searchLanguage, setSearchLanguage] = useState( + defaults.searchLanguage + ); + + const [searchString, setSearchString] = useState( + defaults.searchString + ); + + const [searchQuery, setSearchQuery] = useState(defaults.searchQuery); + + const [errorMessage, setErrorMessage] = useState(undefined); + + const searchChangeHandler = (query: Query) => setSearchInput(query); + const searchSubmitHandler = (query: Query) => { + setSearchLanguage(query.language as QUERY_LANGUAGE); + setSearchString(query.query !== '' ? (query.query as string) : undefined); + try { + switch (query.language) { + case QUERY_LANGUAGE_KUERY: + setSearchQuery( + esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(query.query as string), + indexPattern + ) + ); + return; + case QUERY_LANGUAGE_LUCENE: + setSearchQuery(esQuery.luceneStringToDsl(query.query as string)); + return; + } + } catch (e) { + setErrorMessage({ query: query.query as string, message: e.message }); + } + }; + + const pivotQuery = getPivotQuery(searchQuery); + + return { + actions: { + searchChangeHandler, + searchSubmitHandler, + setErrorMessage, + setSearchInput, + setSearchLanguage, + setSearchQuery, + setSearchString, + }, + state: { + errorMessage, + pivotQuery, + searchInput, + searchLanguage, + searchQuery, + searchString, + }, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts new file mode 100644 index 0000000000000..fc47a9e3d3477 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; + +import { getPreviewRequestBody } from '../../../../../common'; + +import { getDefaultStepDefineState } from '../common'; + +import { StepDefineFormProps } from '../step_define_form'; + +import { useAdvancedPivotEditor } from './use_advanced_pivot_editor'; +import { useAdvancedSourceEditor } from './use_advanced_source_editor'; +import { usePivotConfig } from './use_pivot_config'; +import { useSearchBar } from './use_search_bar'; + +export type StepDefineFormHook = ReturnType; + +export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefineFormProps) => { + const defaults = { ...getDefaultStepDefineState(searchItems), ...overrides }; + const { indexPattern } = searchItems; + + const searchBar = useSearchBar(defaults, indexPattern); + const pivotConfig = usePivotConfig(defaults, indexPattern); + + const previewRequest = getPreviewRequestBody( + indexPattern.title, + searchBar.state.pivotQuery, + pivotConfig.state.pivotGroupByArr, + pivotConfig.state.pivotAggsArr + ); + + // pivot config hook + const advancedPivotEditor = useAdvancedPivotEditor(defaults, previewRequest); + + // source config hook + const advancedSourceEditor = useAdvancedSourceEditor(defaults, previewRequest); + + useEffect(() => { + if (!advancedSourceEditor.state.isAdvancedSourceEditorEnabled) { + const previewRequestUpdate = getPreviewRequestBody( + indexPattern.title, + searchBar.state.pivotQuery, + pivotConfig.state.pivotGroupByArr, + pivotConfig.state.pivotAggsArr + ); + + const stringifiedSourceConfigUpdate = JSON.stringify( + previewRequestUpdate.source.query, + null, + 2 + ); + + advancedSourceEditor.actions.setAdvancedEditorSourceConfig(stringifiedSourceConfigUpdate); + } + + onChange({ + aggList: pivotConfig.state.aggList, + groupByList: pivotConfig.state.groupByList, + isAdvancedPivotEditorEnabled: advancedPivotEditor.state.isAdvancedPivotEditorEnabled, + isAdvancedSourceEditorEnabled: advancedSourceEditor.state.isAdvancedSourceEditorEnabled, + searchLanguage: searchBar.state.searchLanguage, + searchString: searchBar.state.searchString, + searchQuery: searchBar.state.searchQuery, + sourceConfigUpdated: advancedSourceEditor.state.sourceConfigUpdated, + valid: pivotConfig.state.valid, + }); + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ + }, [ + JSON.stringify(advancedPivotEditor.state), + JSON.stringify(advancedSourceEditor.state), + JSON.stringify(pivotConfig.state), + JSON.stringify(searchBar.state), + /* eslint-enable react-hooks/exhaustive-deps */ + ]); + + return { + advancedPivotEditor, + advancedSourceEditor, + pivotConfig, + searchBar, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts index 881e8c6b26658..b73e281b057e9 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts @@ -5,9 +5,12 @@ */ export { + defaultSearch, applyTransformConfigToDefineState, getDefaultStepDefineState, StepDefineExposedState, - StepDefineForm, -} from './step_define_form'; + QUERY_LANGUAGE_KUERY, +} from './common'; +export { StepDefineFormHook } from './hooks/use_step_define_form'; +export { StepDefineForm } from './step_define_form'; export { StepDefineSummary } from './step_define_summary'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index a15e958c16b73..bcd2900621a59 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -24,7 +24,8 @@ import { } from '../../../../common'; import { SearchItems } from '../../../../hooks/use_search_items'; -import { StepDefineForm, getAggNameConflictToastMessages } from './step_define_form'; +import { getAggNameConflictToastMessages } from './common'; +import { StepDefineForm } from './step_define_form'; jest.mock('../../../../../shared_imports'); jest.mock('../../../../../app/app_dependencies'); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 0e6e2c1a38d0e..33adc6781c158 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -4,37 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash'; -import React, { Fragment, FC, useEffect, useState } from 'react'; +import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, - EuiCodeEditor, - EuiCode, - EuiInputPopover, + EuiButtonIcon, + EuiCopy, EuiFlexGroup, EuiFlexItem, EuiForm, - EuiFormHelpText, EuiFormRow, EuiHorizontalRule, EuiLink, - EuiPanel, EuiSpacer, - EuiSwitch, + EuiText, } from '@elastic/eui'; -import { - esKuery, - esQuery, - Query, - QueryStringInput, -} from '../../../../../../../../../src/plugins/data/public'; - -import { useXJsonMode } from '../../../../../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks'; - import { DataGrid } from '../../../../../shared_imports'; import { @@ -42,385 +29,62 @@ import { getPivotPreviewDevConsoleStatement, } from '../../../../common/data_grid'; -import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; -import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items'; -import { useIndexData } from '../../../../hooks/use_index_data'; -import { usePivotData } from '../../../../hooks/use_pivot_data'; -import { useToastNotifications } from '../../../../app_dependencies'; -import { dictionaryToArray, Dictionary } from '../../../../../../common/types/common'; import { - getPivotQuery, getPreviewRequestBody, - matchAllQuery, - AggName, - DropDownLabel, PivotAggDict, - PivotAggsConfig, PivotAggsConfigDict, PivotGroupByDict, - PivotGroupByConfig, PivotGroupByConfigDict, PivotSupportedGroupByAggs, - TransformPivotConfig, PIVOT_SUPPORTED_AGGS, - PIVOT_SUPPORTED_GROUP_BY_AGGS, } from '../../../../common'; +import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; +import { useIndexData } from '../../../../hooks/use_index_data'; +import { usePivotData } from '../../../../hooks/use_pivot_data'; +import { useToastNotifications } from '../../../../app_dependencies'; +import { SearchItems } from '../../../../hooks/use_search_items'; -import { DropDown } from '../aggregation_dropdown'; -import { AggListForm } from '../aggregation_list'; -import { GroupByListForm } from '../group_by_list'; - -import { getPivotDropdownOptions } from './common'; -import { SwitchModal } from './switch_modal'; - -export interface StepDefineExposedState { - aggList: PivotAggsConfigDict; - groupByList: PivotGroupByConfigDict; - isAdvancedPivotEditorEnabled: boolean; - isAdvancedSourceEditorEnabled: boolean; - searchLanguage: QUERY_LANGUAGE; - searchString: string | undefined; - searchQuery: string | SavedSearchQuery; - sourceConfigUpdated: boolean; - valid: boolean; -} - -interface ErrorMessage { - query: string; - message: string; -} - -const defaultSearch = '*'; - -const QUERY_LANGUAGE_KUERY = 'kuery'; -const QUERY_LANGUAGE_LUCENE = 'lucene'; -type QUERY_LANGUAGE = 'kuery' | 'lucene'; - -export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineExposedState { - return { - aggList: {} as PivotAggsConfigDict, - groupByList: {} as PivotGroupByConfigDict, - isAdvancedPivotEditorEnabled: false, - isAdvancedSourceEditorEnabled: false, - searchLanguage: QUERY_LANGUAGE_KUERY, - searchString: undefined, - searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, - sourceConfigUpdated: false, - valid: false, - }; -} - -export function applyTransformConfigToDefineState( - state: StepDefineExposedState, - transformConfig?: TransformPivotConfig -): StepDefineExposedState { - // apply the transform configuration to wizard DEFINE state - if (transformConfig !== undefined) { - // transform aggregations config to wizard state - state.aggList = Object.keys(transformConfig.pivot.aggregations).reduce((aggList, aggName) => { - const aggConfig = transformConfig.pivot.aggregations[aggName] as Dictionary; - const agg = Object.keys(aggConfig)[0]; - aggList[aggName] = { - ...aggConfig[agg], - agg: agg as PIVOT_SUPPORTED_AGGS, - aggName, - dropDownName: aggName, - } as PivotAggsConfig; - return aggList; - }, {} as PivotAggsConfigDict); - - // transform group by config to wizard state - state.groupByList = Object.keys(transformConfig.pivot.group_by).reduce( - (groupByList, groupByName) => { - const groupByConfig = transformConfig.pivot.group_by[groupByName] as Dictionary; - const groupBy = Object.keys(groupByConfig)[0]; - groupByList[groupByName] = { - agg: groupBy as PIVOT_SUPPORTED_GROUP_BY_AGGS, - aggName: groupByName, - dropDownName: groupByName, - ...groupByConfig[groupBy], - } as PivotGroupByConfig; - return groupByList; - }, - {} as PivotGroupByConfigDict - ); - - // only apply the query from the transform config to wizard state if it's not the default query - const query = transformConfig.source.query; - if (query !== undefined && !isEqual(query, matchAllQuery)) { - state.isAdvancedSourceEditorEnabled = true; - state.searchQuery = query; - state.sourceConfigUpdated = true; - } - - // applying a transform config to wizard state will always result in a valid configuration - state.valid = true; - } - - return state; -} - -export function getAggNameConflictToastMessages( - aggName: AggName, - aggList: PivotAggsConfigDict, - groupByList: PivotGroupByConfigDict -): string[] { - if (aggList[aggName] !== undefined) { - return [ - i18n.translate('xpack.transform.stepDefineForm.aggExistsErrorMessage', { - defaultMessage: `An aggregation configuration with the name '{aggName}' already exists.`, - values: { aggName }, - }), - ]; - } - - if (groupByList[aggName] !== undefined) { - return [ - i18n.translate('xpack.transform.stepDefineForm.groupByExistsErrorMessage', { - defaultMessage: `A group by configuration with the name '{aggName}' already exists.`, - values: { aggName }, - }), - ]; - } - - const conflicts: string[] = []; - - // check the new aggName against existing aggs and groupbys - const aggNameSplit = aggName.split('.'); - let aggNameCheck: string; - aggNameSplit.forEach(aggNamePart => { - aggNameCheck = aggNameCheck === undefined ? aggNamePart : `${aggNameCheck}.${aggNamePart}`; - if (aggList[aggNameCheck] !== undefined || groupByList[aggNameCheck] !== undefined) { - conflicts.push( - i18n.translate('xpack.transform.stepDefineForm.nestedConflictErrorMessage', { - defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{aggNameCheck}'.`, - values: { aggName, aggNameCheck }, - }) - ); - } - }); - - if (conflicts.length > 0) { - return conflicts; - } - - // check all aggs against new aggName - Object.keys(aggList).some(aggListName => { - const aggListNameSplit = aggListName.split('.'); - let aggListNameCheck: string; - return aggListNameSplit.some(aggListNamePart => { - aggListNameCheck = - aggListNameCheck === undefined ? aggListNamePart : `${aggListNameCheck}.${aggListNamePart}`; - if (aggListNameCheck === aggName) { - conflicts.push( - i18n.translate('xpack.transform.stepDefineForm.nestedAggListConflictErrorMessage', { - defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{aggListName}'.`, - values: { aggName, aggListName }, - }) - ); - return true; - } - return false; - }); - }); - - if (conflicts.length > 0) { - return conflicts; - } - - // check all group-bys against new aggName - Object.keys(groupByList).some(groupByListName => { - const groupByListNameSplit = groupByListName.split('.'); - let groupByListNameCheck: string; - return groupByListNameSplit.some(groupByListNamePart => { - groupByListNameCheck = - groupByListNameCheck === undefined - ? groupByListNamePart - : `${groupByListNameCheck}.${groupByListNamePart}`; - if (groupByListNameCheck === aggName) { - conflicts.push( - i18n.translate('xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage', { - defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{groupByListName}'.`, - values: { aggName, groupByListName }, - }) - ); - return true; - } - return false; - }); - }); +import { AdvancedPivotEditor } from '../advanced_pivot_editor'; +import { AdvancedPivotEditorSwitch } from '../advanced_pivot_editor_switch'; +import { AdvancedQueryEditorSwitch } from '../advanced_query_editor_switch'; +import { AdvancedSourceEditor } from '../advanced_source_editor'; +import { PivotConfiguration } from '../pivot_configuration'; +import { SourceSearchBar } from '../source_search_bar'; - return conflicts; -} +import { StepDefineExposedState } from './common'; +import { useStepDefineForm } from './hooks/use_step_define_form'; -interface Props { +export interface StepDefineFormProps { overrides?: StepDefineExposedState; onChange(s: StepDefineExposedState): void; searchItems: SearchItems; } -export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, searchItems }) => { - const toastNotifications = useToastNotifications(); - const { esQueryDsl, esTransformPivot } = useDocumentationLinks(); - - const defaults = { ...getDefaultStepDefineState(searchItems), ...overrides }; - - // The internal state of the input query bar updated on every key stroke. - const [searchInput, setSearchInput] = useState({ - query: defaults.searchString || '', - language: defaults.searchLanguage, - }); - const [errorMessage, setErrorMessage] = useState(undefined); - - // The state of the input query bar updated on every submit and to be exposed. - const [searchLanguage, setSearchLanguage] = useState( - defaults.searchLanguage - ); - const [searchString, setSearchString] = useState( - defaults.searchString - ); - const [searchQuery, setSearchQuery] = useState(defaults.searchQuery); - +export const StepDefineForm: FC = React.memo(props => { + const { searchItems } = props; const { indexPattern } = searchItems; - const searchChangeHandler = (query: Query) => setSearchInput(query); - const searchSubmitHandler = (query: Query) => { - setSearchLanguage(query.language as QUERY_LANGUAGE); - setSearchString(query.query !== '' ? (query.query as string) : undefined); - try { - switch (query.language) { - case QUERY_LANGUAGE_KUERY: - setSearchQuery( - esKuery.toElasticsearchQuery( - esKuery.fromKueryExpression(query.query as string), - indexPattern - ) - ); - return; - case QUERY_LANGUAGE_LUCENE: - setSearchQuery(esQuery.luceneStringToDsl(query.query as string)); - return; - } - } catch (e) { - setErrorMessage({ query: query.query as string, message: e.message }); - } - }; - - // The list of selected group by fields - const [groupByList, setGroupByList] = useState(defaults.groupByList); + const toastNotifications = useToastNotifications(); + const stepDefineForm = useStepDefineForm(props); const { - groupByOptions, - groupByOptionsData, - aggOptions, - aggOptionsData, - } = getPivotDropdownOptions(indexPattern); - - const addGroupBy = (d: DropDownLabel[]) => { - const label: AggName = d[0].label; - const config: PivotGroupByConfig = groupByOptionsData[label]; - const aggName: AggName = config.aggName; - - const aggNameConflictMessages = getAggNameConflictToastMessages(aggName, aggList, groupByList); - if (aggNameConflictMessages.length > 0) { - aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); - return; - } - - groupByList[aggName] = config; - setGroupByList({ ...groupByList }); - }; - - const updateGroupBy = (previousAggName: AggName, item: PivotGroupByConfig) => { - const groupByListWithoutPrevious = { ...groupByList }; - delete groupByListWithoutPrevious[previousAggName]; - - const aggNameConflictMessages = getAggNameConflictToastMessages( - item.aggName, - aggList, - groupByListWithoutPrevious - ); - if (aggNameConflictMessages.length > 0) { - aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); - return; - } - - groupByListWithoutPrevious[item.aggName] = item; - setGroupByList({ ...groupByListWithoutPrevious }); - }; - - const deleteGroupBy = (aggName: AggName) => { - delete groupByList[aggName]; - setGroupByList({ ...groupByList }); - }; - - // The list of selected aggregations - const [aggList, setAggList] = useState(defaults.aggList); - - const addAggregation = (d: DropDownLabel[]) => { - const label: AggName = d[0].label; - const config: PivotAggsConfig = aggOptionsData[label]; - const aggName: AggName = config.aggName; - - const aggNameConflictMessages = getAggNameConflictToastMessages(aggName, aggList, groupByList); - if (aggNameConflictMessages.length > 0) { - aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); - return; - } - - aggList[aggName] = config; - setAggList({ ...aggList }); - }; - - const updateAggregation = (previousAggName: AggName, item: PivotAggsConfig) => { - const aggListWithoutPrevious = { ...aggList }; - delete aggListWithoutPrevious[previousAggName]; - - const aggNameConflictMessages = getAggNameConflictToastMessages( - item.aggName, - aggListWithoutPrevious, - groupByList - ); - if (aggNameConflictMessages.length > 0) { - aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m)); - return; - } - - aggListWithoutPrevious[item.aggName] = item; - setAggList({ ...aggListWithoutPrevious }); - }; - - const deleteAggregation = (aggName: AggName) => { - delete aggList[aggName]; - setAggList({ ...aggList }); - }; - - const pivotAggsArr = dictionaryToArray(aggList); - const pivotGroupByArr = dictionaryToArray(groupByList); - const pivotQuery = getPivotQuery(searchQuery); - - // Advanced editor for pivot config state - const [isAdvancedEditorSwitchModalVisible, setAdvancedEditorSwitchModalVisible] = useState(false); - const [ + advancedEditorConfig, + isAdvancedPivotEditorEnabled, isAdvancedPivotEditorApplyButtonEnabled, - setAdvancedPivotEditorApplyButtonEnabled, - ] = useState(false); - const [isAdvancedPivotEditorEnabled, setAdvancedPivotEditorEnabled] = useState( - defaults.isAdvancedPivotEditorEnabled - ); - // Advanced editor for source config state - const [sourceConfigUpdated, setSourceConfigUpdated] = useState(defaults.sourceConfigUpdated); - const [ - isAdvancedSourceEditorSwitchModalVisible, - setAdvancedSourceEditorSwitchModalVisible, - ] = useState(false); - const [isAdvancedSourceEditorEnabled, setAdvancedSourceEditorEnabled] = useState( - defaults.isAdvancedSourceEditorEnabled - ); - const [ + } = stepDefineForm.advancedPivotEditor.state; + const { + advancedEditorSourceConfig, + isAdvancedSourceEditorEnabled, isAdvancedSourceEditorApplyButtonEnabled, - setAdvancedSourceEditorApplyButtonEnabled, - ] = useState(false); + } = stepDefineForm.advancedSourceEditor.state; + const { aggList, groupByList, pivotGroupByArr, pivotAggsArr } = stepDefineForm.pivotConfig.state; + const pivotQuery = stepDefineForm.searchBar.state.pivotQuery; + + const indexPreviewProps = { + ...useIndexData(indexPattern, stepDefineForm.searchBar.state.pivotQuery), + dataTestSubj: 'transformIndexPreview', + toastNotifications, + }; const previewRequest = getPreviewRequestBody( indexPattern.title, @@ -428,49 +92,48 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, pivotGroupByArr, pivotAggsArr ); - // pivot config - const stringifiedPivotConfig = JSON.stringify(previewRequest.pivot, null, 2); - const [advancedEditorConfigLastApplied, setAdvancedEditorConfigLastApplied] = useState( - stringifiedPivotConfig - ); - const { - convertToJson, - setXJson: setAdvancedEditorConfig, - xJson: advancedEditorConfig, - xJsonMode, - } = useXJsonMode(stringifiedPivotConfig); + const pivotPreviewProps = { + ...usePivotData(indexPattern.title, pivotQuery, aggList, groupByList), + dataTestSubj: 'transformPivotPreview', + toastNotifications, + }; - useEffect(() => { - setAdvancedEditorConfig(stringifiedPivotConfig); - }, [setAdvancedEditorConfig, stringifiedPivotConfig]); + // TODO This should use the actual value of `indices.query.bool.max_clause_count` + const maxIndexFields = 1024; + const numIndexFields = indexPattern.fields.length; + const disabledQuery = numIndexFields > maxIndexFields; - // source config - const stringifiedSourceConfig = JSON.stringify(previewRequest.source.query, null, 2); - const [ - advancedEditorSourceConfigLastApplied, - setAdvancedEditorSourceConfigLastApplied, - ] = useState(stringifiedSourceConfig); - const [advancedEditorSourceConfig, setAdvancedEditorSourceConfig] = useState( - stringifiedSourceConfig + const copyToClipboardSource = getIndexDevConsoleStatement(pivotQuery, indexPattern.title); + const copyToClipboardSourceDescription = i18n.translate( + 'xpack.transform.indexPreview.copyClipboardTooltip', + { + defaultMessage: 'Copy Dev Console statement of the index preview to the clipboard.', + } ); - const applyAdvancedSourceEditorChanges = () => { + const copyToClipboardPivot = getPivotPreviewDevConsoleStatement(previewRequest); + const copyToClipboardPivotDescription = i18n.translate( + 'xpack.transform.pivotPreview.copyClipboardTooltip', + { + defaultMessage: 'Copy Dev Console statement of the pivot preview to the clipboard.', + } + ); + + const applySourceChangesHandler = () => { const sourceConfig = JSON.parse(advancedEditorSourceConfig); - const prettySourceConfig = JSON.stringify(sourceConfig, null, 2); - setSearchQuery(sourceConfig); - setSourceConfigUpdated(true); - setAdvancedEditorSourceConfig(prettySourceConfig); - setAdvancedEditorSourceConfigLastApplied(prettySourceConfig); - setAdvancedSourceEditorApplyButtonEnabled(false); + stepDefineForm.searchBar.actions.setSearchQuery(sourceConfig); + stepDefineForm.advancedSourceEditor.actions.applyAdvancedSourceEditorChanges(); }; - const applyAdvancedPivotEditorChanges = () => { - const pivotConfig = JSON.parse(convertToJson(advancedEditorConfig)); + const applyPivotChangesHandler = () => { + const pivot = JSON.parse( + stepDefineForm.advancedPivotEditor.actions.convertToJson(advancedEditorConfig) + ); const newGroupByList: PivotGroupByConfigDict = {}; - if (pivotConfig !== undefined && pivotConfig.group_by !== undefined) { - Object.entries(pivotConfig.group_by).forEach(d => { + if (pivot !== undefined && pivot.group_by !== undefined) { + Object.entries(pivot.group_by).forEach(d => { const aggName = d[0]; const aggConfig = d[1] as PivotGroupByDict; const aggConfigKeys = Object.keys(aggConfig); @@ -483,11 +146,11 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, }; }); } - setGroupByList(newGroupByList); + stepDefineForm.pivotConfig.actions.setGroupByList(newGroupByList); const newAggList: PivotAggsConfigDict = {}; - if (pivotConfig !== undefined && pivotConfig.aggregations !== undefined) { - Object.entries(pivotConfig.aggregations).forEach(d => { + if (pivot !== undefined && pivot.aggregations !== undefined) { + Object.entries(pivot.aggregations).forEach(d => { const aggName = d[0]; const aggConfig = d[1] as PivotAggDict; const aggConfigKeys = Object.keys(aggConfig); @@ -500,459 +163,207 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, }; }); } - setAggList(newAggList); - - setAdvancedEditorConfigLastApplied(advancedEditorConfig); - setAdvancedPivotEditorApplyButtonEnabled(false); - }; - - const toggleAdvancedEditor = () => { - setAdvancedEditorConfig(advancedEditorConfig); - setAdvancedPivotEditorEnabled(!isAdvancedPivotEditorEnabled); - setAdvancedPivotEditorApplyButtonEnabled(false); - if (isAdvancedPivotEditorEnabled === false) { - setAdvancedEditorConfigLastApplied(advancedEditorConfig); - } - }; - // If switching to KQL after updating via editor - reset search - const toggleAdvancedSourceEditor = (reset = false) => { - if (reset === true) { - setSearchQuery(defaultSearch); - setSourceConfigUpdated(false); - } - if (isAdvancedSourceEditorEnabled === false) { - setAdvancedEditorSourceConfigLastApplied(advancedEditorSourceConfig); - } - - setAdvancedSourceEditorEnabled(!isAdvancedSourceEditorEnabled); - setAdvancedSourceEditorApplyButtonEnabled(false); - }; - - const advancedEditorHelpText = ( - - {i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpText', { - defaultMessage: - 'The advanced editor allows you to edit the pivot configuration of the transform.', - })}{' '} - - {i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpTextLink', { - defaultMessage: 'Learn more about available options.', - })} - - - ); - - const advancedSourceEditorHelpText = ( - - {i18n.translate('xpack.transform.stepDefineForm.advancedSourceEditorHelpText', { - defaultMessage: - 'The advanced editor allows you to edit the source query clause of the transform.', - })}{' '} - - {i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpTextLink', { - defaultMessage: 'Learn more about available options.', - })} - - - ); + stepDefineForm.pivotConfig.actions.setAggList(newAggList); - const valid = pivotGroupByArr.length > 0 && pivotAggsArr.length > 0; - - useEffect(() => { - const previewRequestUpdate = getPreviewRequestBody( - indexPattern.title, - pivotQuery, - pivotGroupByArr, - pivotAggsArr - ); - - const stringifiedSourceConfigUpdate = JSON.stringify( - previewRequestUpdate.source.query, - null, - 2 + stepDefineForm.advancedPivotEditor.actions.setAdvancedEditorConfigLastApplied( + advancedEditorConfig ); - setAdvancedEditorSourceConfig(stringifiedSourceConfigUpdate); - - onChange({ - aggList, - groupByList, - isAdvancedPivotEditorEnabled, - isAdvancedSourceEditorEnabled, - searchLanguage, - searchString, - searchQuery, - sourceConfigUpdated, - valid, - }); - // custom comparison - /* eslint-disable react-hooks/exhaustive-deps */ - }, [ - JSON.stringify(pivotAggsArr), - JSON.stringify(pivotGroupByArr), - isAdvancedPivotEditorEnabled, - isAdvancedSourceEditorEnabled, - searchLanguage, - searchString, - searchQuery, - valid, - /* eslint-enable react-hooks/exhaustive-deps */ - ]); + stepDefineForm.advancedPivotEditor.actions.setAdvancedPivotEditorApplyButtonEnabled(false); + }; - const indexPreviewProps = useIndexData(indexPattern, pivotQuery); - const pivotPreviewProps = usePivotData(indexPattern.title, pivotQuery, aggList, groupByList); + const { esQueryDsl } = useDocumentationLinks(); + const { esTransformPivot } = useDocumentationLinks(); - // TODO This should use the actual value of `indices.query.bool.max_clause_count` - const maxIndexFields = 1024; - const numIndexFields = indexPattern.fields.length; - const disabledQuery = numIndexFields > maxIndexFields; + const advancedEditorsSidebarWidth = '220px'; return ( - - -
- - {searchItems.savedSearch === undefined && ( - - - {indexPattern.title} - - {!disabledQuery && ( - - {!isAdvancedSourceEditorEnabled && ( - - setErrorMessage(undefined)} - input={ - - } - isOpen={ - errorMessage?.query === searchInput.query && - errorMessage?.message !== '' - } - > - - {i18n.translate( - 'xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar', - { - defaultMessage: 'Invalid query', - } - )} - {': '} - {errorMessage?.message.split('\n')[0]} - - - - )} - - )} - - )} - - {isAdvancedSourceEditorEnabled && ( - - - - { - setSearchString(undefined); - setAdvancedEditorSourceConfig(d); - - // Disable the "Apply"-Button if the config hasn't changed. - if (advancedEditorSourceConfigLastApplied === d) { - setAdvancedSourceEditorApplyButtonEnabled(false); - return; - } - - // Try to parse the string passed on from the editor. - // If parsing fails, the "Apply"-Button will be disabled - try { - JSON.parse(d); - setAdvancedSourceEditorApplyButtonEnabled(true); - } catch (e) { - setAdvancedSourceEditorApplyButtonEnabled(false); - } - }} - setOptions={{ - fontSize: '12px', - }} - theme="textmate" - aria-label={i18n.translate( - 'xpack.transform.stepDefineForm.advancedSourceEditorAriaLabel', - { - defaultMessage: 'Advanced query editor', - } - )} - /> - - - - )} - {searchItems.savedSearch === undefined && ( - - - - { - if (isAdvancedSourceEditorEnabled && sourceConfigUpdated) { - setAdvancedSourceEditorSwitchModalVisible(true); - return; - } - - toggleAdvancedSourceEditor(); - }} - data-test-subj="transformAdvancedQueryEditorSwitch" - /> - {isAdvancedSourceEditorSwitchModalVisible && ( - setAdvancedSourceEditorSwitchModalVisible(false)} - onConfirm={() => { - setAdvancedSourceEditorSwitchModalVisible(false); - toggleAdvancedSourceEditor(true); - }} - type={'source'} +
+ + {searchItems.savedSearch === undefined && ( + + {indexPattern.title} + + )} + + <> + + + {/* Flex Column #1: Search Bar / Advanced Search Editor */} + {searchItems.savedSearch === undefined && ( + <> + {!disabledQuery && !isAdvancedSourceEditorEnabled && ( + )} + {isAdvancedSourceEditorEnabled && } + + )} + {searchItems?.savedSearch?.id !== undefined && ( + {searchItems.savedSearch.title} + )} + + + {/* Search options: Advanced Editor Switch / Copy to Clipboard / Advanced Editor Apply Button */} + + + + + + {searchItems.savedSearch === undefined && ( + + )} + + + + {(copy: () => void) => ( + + )} + + + {isAdvancedSourceEditorEnabled && ( - - {i18n.translate( - 'xpack.transform.stepDefineForm.advancedSourceEditorApplyButtonText', - { - defaultMessage: 'Apply changes', - } - )} - + + + + {i18n.translate( + 'xpack.transform.stepDefineForm.advancedSourceEditorHelpText', + { + defaultMessage: + 'The advanced editor allows you to edit the source query clause of the transform configuration.', + } + )}{' '} + + {i18n.translate( + 'xpack.transform.stepDefineForm.advancedEditorHelpTextLink', + { + defaultMessage: 'Learn more about available options.', + } + )} + + + + + {i18n.translate( + 'xpack.transform.stepDefineForm.advancedSourceEditorApplyButtonText', + { + defaultMessage: 'Apply changes', + } + )} + + )} - - )} - {searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && ( - - {searchItems.savedSearch.title} - - )} - + + + + + + + + + + + {/* Flex Column #1: Pivot Config Form / Advanced Pivot Config Editor */} + {!isAdvancedPivotEditorEnabled && ( - - - - - - - - - - - - - - - + )} - {isAdvancedPivotEditorEnabled && ( - - - - { - setAdvancedEditorConfig(d); - - // Disable the "Apply"-Button if the config hasn't changed. - if (advancedEditorConfigLastApplied === d) { - setAdvancedPivotEditorApplyButtonEnabled(false); - return; - } - - // Try to parse the string passed on from the editor. - // If parsing fails, the "Apply"-Button will be disabled - try { - JSON.parse(convertToJson(d)); - setAdvancedPivotEditorApplyButtonEnabled(true); - } catch (e) { - setAdvancedPivotEditorApplyButtonEnabled(false); - } - }} - setOptions={{ - fontSize: '12px', - }} - theme="textmate" - aria-label={i18n.translate( - 'xpack.transform.stepDefineForm.advancedEditorAriaLabel', - { - defaultMessage: 'Advanced pivot editor', - } - )} - /> - - - + )} - - - - { - if ( - isAdvancedPivotEditorEnabled && - (isAdvancedPivotEditorApplyButtonEnabled || - advancedEditorConfig !== advancedEditorConfigLastApplied) - ) { - setAdvancedEditorSwitchModalVisible(true); - return; - } - - toggleAdvancedEditor(); - }} - data-test-subj="transformAdvancedPivotEditorSwitch" - /> - {isAdvancedEditorSwitchModalVisible && ( - setAdvancedEditorSwitchModalVisible(false)} - onConfirm={() => { - setAdvancedEditorSwitchModalVisible(false); - toggleAdvancedEditor(); - }} - type={'pivot'} - /> - )} - - {isAdvancedPivotEditorEnabled && ( + + + + + + + + + + + + {(copy: () => void) => ( + + )} + + + + + + {isAdvancedPivotEditorEnabled && ( + + + + <> + {i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpText', { + defaultMessage: + 'The advanced editor allows you to edit the pivot configuration of the transform.', + })}{' '} + + {i18n.translate( + 'xpack.transform.stepDefineForm.advancedEditorHelpTextLink', + { + defaultMessage: 'Learn more about available options.', + } + )} + + + + {i18n.translate( @@ -962,58 +373,15 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, } )} - )} - - - {!valid && ( - - - - {i18n.translate('xpack.transform.stepDefineForm.formHelp', { - defaultMessage: - 'Transforms are scalable and automated processes for pivoting. Choose at least one group-by and aggregation to get started.', - })} - - - )} - -
-
- - - - - - -
+ + )} + + + +
+ + + +
); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx index f2e5d30b0601f..60bea6f20ae50 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx @@ -16,7 +16,7 @@ import { } from '../../../../common'; import { SearchItems } from '../../../../hooks/use_search_items'; -import { StepDefineExposedState } from './step_define_form'; +import { StepDefineExposedState } from './common'; import { StepDefineSummary } from './step_define_summary'; jest.mock('../../../../../shared_imports'); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx index b9021f4ee5b11..414f6e37504da 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx @@ -8,14 +8,7 @@ import React, { Fragment, FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiText, -} from '@elastic/eui'; +import { EuiCodeBlock, EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; import { dictionaryToArray } from '../../../../../../common/types/common'; @@ -35,7 +28,7 @@ import { SearchItems } from '../../../../hooks/use_search_items'; import { AggListSummary } from '../aggregation_list'; import { GroupByListSummary } from '../group_by_list'; -import { StepDefineExposedState } from './step_define_form'; +import { StepDefineExposedState } from './common'; interface Props { formState: StepDefineExposedState; @@ -65,85 +58,80 @@ export const StepDefineSummary: FC = ({ groupByList ); + const isModifiedQuery = + typeof searchString === 'undefined' && + !isDefaultQuery(pivotQuery) && + !isMatchAllQuery(pivotQuery); + return ( - - -
- - {searchItems.savedSearch === undefined && ( - - - {searchItems.indexPattern.title} - - {typeof searchString === 'string' && ( - - {searchString} - - )} - {typeof searchString === 'undefined' && - !isDefaultQuery(pivotQuery) && - !isMatchAllQuery(pivotQuery) && ( - - - {JSON.stringify(pivotQuery, null, 2)} - - - )} - +
+ + {searchItems.savedSearch === undefined && ( + + + {searchItems.indexPattern.title} + + {typeof searchString === 'string' && ( + + {searchString} + )} - - {searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && ( + {isModifiedQuery && ( - {searchItems.savedSearch.title} + + {JSON.stringify(pivotQuery, null, 2)} + )} + + )} - - - - - - - - -
- - - + {searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && ( + + {searchItems.savedSearch.title} + + )} + + + + + + + + + + = ({ toastNotifications={toastNotifications} /> - - +
+
); }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/index.ts new file mode 100644 index 0000000000000..1aa00177cfbc8 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SwitchModal } from './switch_modal'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/switch_modal.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx similarity index 100% rename from x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/switch_modal.tsx rename to x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index 0773ecbb1d8d3..3fcfd77ba54cd 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -155,8 +155,8 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) const stepsConfig = [ { - title: i18n.translate('xpack.transform.transformsWizard.stepDefineTitle', { - defaultMessage: 'Define pivot', + title: i18n.translate('xpack.transform.transformsWizard.stepConfigurationTitle', { + defaultMessage: 'Configuration', }), children: ( { await transform.wizard.enabledAdvancedPivotEditor(); await transform.wizard.assertAdvancedPivotEditorContent( - testData.expected.pivotAdvancedEditorValue + testData.expected.pivotAdvancedEditorValueArr ); }); diff --git a/x-pack/test/functional/services/transform_ui/wizard.ts b/x-pack/test/functional/services/transform_ui/wizard.ts index e63af493438d6..4b136746eb525 100644 --- a/x-pack/test/functional/services/transform_ui/wizard.ts +++ b/x-pack/test/functional/services/transform_ui/wizard.ts @@ -274,10 +274,15 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { await this.assertAggregationEntryExists(index, expectedLabel); }, - async assertAdvancedPivotEditorContent(expectedValue: Record) { + async assertAdvancedPivotEditorContent(expectedValue: string[]) { const advancedEditorString = await aceEditor.getValue('transformAdvancedPivotEditor'); - const advancedEditorValue = JSON.parse(advancedEditorString); - expect(advancedEditorValue).to.eql(expectedValue); + // Not all lines may be visible in the editor and thus aceEditor may not return all lines. + // This means we might not get back valid JSON so we only test against the first few lines + // and see if the string matches. + + // const advancedEditorValue = JSON.parse(advancedEditorString); + // expect(advancedEditorValue).to.eql(expectedValue); + expect(advancedEditorString.split('\n').splice(0, 3)).to.eql(expectedValue); }, async assertAdvancedPivotEditorSwitchExists() {