diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 33aa36a38293d..2ae4616283205 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -205,6 +205,18 @@ export class WebElementWrapper { }); } + /** + * Focuses this element. + * + * @return {Promise} + */ + public async focus() { + await this.retryCall(async function focus(wrapper) { + await wrapper.scrollIntoViewIfNecessary(); + await wrapper.driver.executeScript(`arguments[0].focus()`, wrapper._webElement); + }); + } + /** * Clear the value of this element. This command has no effect if the underlying DOM element * is neither a text INPUT element nor a TEXTAREA element. diff --git a/x-pack/legacy/common/eui_draggable/index.d.ts b/x-pack/legacy/common/eui_draggable/index.d.ts new file mode 100644 index 0000000000000..a85da7a69534c --- /dev/null +++ b/x-pack/legacy/common/eui_draggable/index.d.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiDraggable, EuiDragDropContext } from '@elastic/eui'; + +type PropsOf = T extends React.ComponentType ? ComponentProps : never; +type FirstArgumentOf = Func extends ((arg1: infer FirstArgument, ...rest: any[]) => any) + ? FirstArgument + : never; +export type DragHandleProps = FirstArgumentOf< + Exclude['children'], React.ReactElement> +>['dragHandleProps']; +export type DropResult = FirstArgumentOf['onDragEnd']>; diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_form_state.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_form_state.tsx index 7564fbb0233ce..c5398cf79ef43 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_form_state.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_form_state.tsx @@ -106,6 +106,20 @@ export const useLogColumnsConfigurationFormState = ({ [formState.logColumns] ); + const moveLogColumn = useCallback( + (sourceIndex, destinationIndex) => { + if (destinationIndex >= 0 && sourceIndex < formState.logColumns.length - 1) { + const newLogColumns = [...formState.logColumns]; + newLogColumns.splice(destinationIndex, 0, newLogColumns.splice(sourceIndex, 1)[0]); + setFormStateChanges(changes => ({ + ...changes, + logColumns: newLogColumns, + })); + } + }, + [formState.logColumns] + ); + const errors = useMemo( () => logColumnConfigurationProps.length <= 0 @@ -125,6 +139,7 @@ export const useLogColumnsConfigurationFormState = ({ return { addLogColumn, + moveLogColumn, errors, logColumnConfigurationProps, formState, diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx index 9dfd95a773351..bbc3cde41794c 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx @@ -14,9 +14,14 @@ import { EuiTitle, EuiFlexGroup, EuiFlexItem, + EuiDragDropContext, + EuiDraggable, + EuiDroppable, + EuiIcon, } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import React from 'react'; +import React, { useCallback } from 'react'; +import { DragHandleProps, DropResult } from '../../../../../common/eui_draggable'; import { AddLogColumnButtonAndPopover } from './add_log_column_popover'; import { @@ -30,70 +35,95 @@ interface LogColumnsConfigurationPanelProps { isLoading: boolean; logColumnConfiguration: LogColumnConfigurationProps[]; addLogColumn: (logColumn: LogColumnConfiguration) => void; + moveLogColumn: (sourceIndex: number, destinationIndex: number) => void; } export const LogColumnsConfigurationPanel: React.FunctionComponent< LogColumnsConfigurationPanelProps -> = ({ addLogColumn, availableFields, isLoading, logColumnConfiguration }) => ( - - - - -

- -

-
-
- - - -
- {logColumnConfiguration.length > 0 ? ( - logColumnConfiguration.map((column, index) => ( - - )) - ) : ( - - )} -
-); +> = ({ addLogColumn, moveLogColumn, availableFields, isLoading, logColumnConfiguration }) => { + const onDragEnd = useCallback( + ({ source, destination }: DropResult) => + destination && moveLogColumn(source.index, destination.index), + [moveLogColumn] + ); + + return ( + + + + +

+ +

+
+
+ + + +
+ {logColumnConfiguration.length > 0 ? ( + + + <> + {/* Fragment here necessary for typechecking */} + {logColumnConfiguration.map((column, index) => ( + + {provided => ( + + )} + + ))} + + + + ) : ( + + )} +
+ ); +}; interface LogColumnConfigurationPanelProps { logColumnConfigurationProps: LogColumnConfigurationProps; + dragHandleProps: DragHandleProps; } -const LogColumnConfigurationPanel: React.FunctionComponent = ({ - logColumnConfigurationProps, -}) => ( +const LogColumnConfigurationPanel: React.FunctionComponent< + LogColumnConfigurationPanelProps +> = props => ( <> - {logColumnConfigurationProps.type === 'timestamp' ? ( - - ) : logColumnConfigurationProps.type === 'message' ? ( - + {props.logColumnConfigurationProps.type === 'timestamp' ? ( + + ) : props.logColumnConfigurationProps.type === 'message' ? ( + ) : ( - + )} ); const TimestampLogColumnConfigurationPanel: React.FunctionComponent< LogColumnConfigurationPanelProps -> = ({ logColumnConfigurationProps }) => ( +> = ({ logColumnConfigurationProps, dragHandleProps }) => ( } removeColumn={logColumnConfigurationProps.remove} + dragHandleProps={dragHandleProps} /> ); const MessageLogColumnConfigurationPanel: React.FunctionComponent< LogColumnConfigurationPanelProps -> = ({ logColumnConfigurationProps }) => ( +> = ({ logColumnConfigurationProps, dragHandleProps }) => ( } removeColumn={logColumnConfigurationProps.remove} + dragHandleProps={dragHandleProps} /> ); const FieldLogColumnConfigurationPanel: React.FunctionComponent<{ logColumnConfigurationProps: FieldLogColumnConfigurationProps; + dragHandleProps: DragHandleProps; }> = ({ logColumnConfigurationProps: { logColumnConfiguration: { field }, remove, }, + dragHandleProps, }) => ( + +
+ +
+
void; -}> = ({ fieldName, helpText, removeColumn }) => ( + dragHandleProps: DragHandleProps; +}> = ({ fieldName, helpText, removeColumn, dragHandleProps }) => ( + +
+ +
+
{fieldName} diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx index 799c207b94bee..4fea7b76e4f1f 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx @@ -57,6 +57,7 @@ export const SourceConfigurationFlyout = injectI18n( const { addLogColumn, + moveLogColumn, indicesConfigurationProps, logColumnConfigurationProps, errors, @@ -137,6 +138,7 @@ export const SourceConfigurationFlyout = injectI18n( { const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels(); - expect(columnHeaderLabels).to.eql(['Timestamp', 'host.name', '']); + expect(columnHeaderLabels).to.eql(['host.name', 'Timestamp', '']); const logStreamEntries = await infraLogStream.getStreamEntries(); diff --git a/x-pack/test/functional/services/infra_source_configuration_flyout.ts b/x-pack/test/functional/services/infra_source_configuration_flyout.ts index e3b9d29352eae..4ba87c018c412 100644 --- a/x-pack/test/functional/services/infra_source_configuration_flyout.ts +++ b/x-pack/test/functional/services/infra_source_configuration_flyout.ts @@ -13,6 +13,7 @@ export function InfraSourceConfigurationFlyoutProvider({ const find = getService('find'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const browser = getService('browser'); return { /** @@ -81,6 +82,25 @@ export function InfraSourceConfigurationFlyoutProvider({ await this.removeLogColumn(0); } }, + async moveLogColumn(sourceIndex: number, destinationIndex: number) { + const logColumnPanel = (await this.getLogColumnPanels())[sourceIndex]; + const moveLogColumnHandle = await testSubjects.findDescendant( + 'moveLogColumnHandle', + logColumnPanel + ); + await moveLogColumnHandle.focus(); + const movementDifference = destinationIndex - sourceIndex; + await moveLogColumnHandle.pressKeys(browser.keys.SPACE); + for (let i = 0; i < Math.abs(movementDifference); i++) { + await new Promise(res => setTimeout(res, 100)); + if (movementDifference > 0) { + await moveLogColumnHandle.pressKeys(browser.keys.ARROW_DOWN); + } else { + await moveLogColumnHandle.pressKeys(browser.keys.ARROW_UP); + } + } + await moveLogColumnHandle.pressKeys(browser.keys.SPACE); + }, /** * Form and flyout