Skip to content

Commit

Permalink
[Logs UI] Make column configurations reorderable (#41035) (#41697)
Browse files Browse the repository at this point in the history
* [Logs UI] Make column configurations reorderable

* Improve typing aand memoize callback

* Guard against index bounds and rename reorderLogColumns

* Fix useCallback memoization

* Add functional test for reordering log columns

* Use browser.keys instead of Key in functional test
  • Loading branch information
Zacqary committed Jul 23, 2019
1 parent 8148b2d commit 2f31e58
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ export class WebElementWrapper {
});
}

/**
* Focuses this element.
*
* @return {Promise<void>}
*/
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.
Expand Down
17 changes: 17 additions & 0 deletions x-pack/legacy/common/eui_draggable/index.d.ts
Original file line number Diff line number Diff line change
@@ -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> = T extends React.ComponentType<infer ComponentProps> ? ComponentProps : never;
type FirstArgumentOf<Func> = Func extends ((arg1: infer FirstArgument, ...rest: any[]) => any)
? FirstArgument
: never;
export type DragHandleProps = FirstArgumentOf<
Exclude<PropsOf<typeof EuiDraggable>['children'], React.ReactElement>
>['dragHandleProps'];
export type DropResult = FirstArgumentOf<FirstArgumentOf<typeof EuiDragDropContext>['onDragEnd']>;
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -125,6 +139,7 @@ export const useLogColumnsConfigurationFormState = ({

return {
addLogColumn,
moveLogColumn,
errors,
logColumnConfigurationProps,
formState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 }) => (
<EuiForm>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="s" data-test-subj="sourceConfigurationLogColumnsSectionTitle">
<h3>
<FormattedMessage
id="xpack.infra.sourceConfiguration.logColumnsSectionTitle"
defaultMessage="Columns"
/>
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AddLogColumnButtonAndPopover
addLogColumn={addLogColumn}
availableFields={availableFields}
isDisabled={isLoading}
/>
</EuiFlexItem>
</EuiFlexGroup>
{logColumnConfiguration.length > 0 ? (
logColumnConfiguration.map((column, index) => (
<LogColumnConfigurationPanel
logColumnConfigurationProps={column}
key={`logColumnConfigurationPanel-${index}`}
/>
))
) : (
<LogColumnConfigurationEmptyPrompt />
)}
</EuiForm>
);
> = ({ addLogColumn, moveLogColumn, availableFields, isLoading, logColumnConfiguration }) => {
const onDragEnd = useCallback(
({ source, destination }: DropResult) =>
destination && moveLogColumn(source.index, destination.index),
[moveLogColumn]
);

return (
<EuiForm>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="s" data-test-subj="sourceConfigurationLogColumnsSectionTitle">
<h3>
<FormattedMessage
id="xpack.infra.sourceConfiguration.logColumnsSectionTitle"
defaultMessage="Columns"
/>
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AddLogColumnButtonAndPopover
addLogColumn={addLogColumn}
availableFields={availableFields}
isDisabled={isLoading}
/>
</EuiFlexItem>
</EuiFlexGroup>
{logColumnConfiguration.length > 0 ? (
<EuiDragDropContext onDragEnd={onDragEnd}>
<EuiDroppable droppableId="COLUMN_CONFIG_DROPPABLE_AREA">
<>
{/* Fragment here necessary for typechecking */}
{logColumnConfiguration.map((column, index) => (
<EuiDraggable
key={`logColumnConfigurationPanel-${column.logColumnConfiguration.id}`}
index={index}
draggableId={column.logColumnConfiguration.id}
customDragHandle
>
{provided => (
<LogColumnConfigurationPanel
dragHandleProps={provided.dragHandleProps}
logColumnConfigurationProps={column}
/>
)}
</EuiDraggable>
))}
</>
</EuiDroppable>
</EuiDragDropContext>
) : (
<LogColumnConfigurationEmptyPrompt />
)}
</EuiForm>
);
};

interface LogColumnConfigurationPanelProps {
logColumnConfigurationProps: LogColumnConfigurationProps;
dragHandleProps: DragHandleProps;
}

const LogColumnConfigurationPanel: React.FunctionComponent<LogColumnConfigurationPanelProps> = ({
logColumnConfigurationProps,
}) => (
const LogColumnConfigurationPanel: React.FunctionComponent<
LogColumnConfigurationPanelProps
> = props => (
<>
<EuiSpacer size="m" />
{logColumnConfigurationProps.type === 'timestamp' ? (
<TimestampLogColumnConfigurationPanel
logColumnConfigurationProps={logColumnConfigurationProps}
/>
) : logColumnConfigurationProps.type === 'message' ? (
<MessageLogColumnConfigurationPanel
logColumnConfigurationProps={logColumnConfigurationProps}
/>
{props.logColumnConfigurationProps.type === 'timestamp' ? (
<TimestampLogColumnConfigurationPanel {...props} />
) : props.logColumnConfigurationProps.type === 'message' ? (
<MessageLogColumnConfigurationPanel {...props} />
) : (
<FieldLogColumnConfigurationPanel logColumnConfigurationProps={logColumnConfigurationProps} />
<FieldLogColumnConfigurationPanel
logColumnConfigurationProps={props.logColumnConfigurationProps}
dragHandleProps={props.dragHandleProps}
/>
)}
</>
);

const TimestampLogColumnConfigurationPanel: React.FunctionComponent<
LogColumnConfigurationPanelProps
> = ({ logColumnConfigurationProps }) => (
> = ({ logColumnConfigurationProps, dragHandleProps }) => (
<ExplainedLogColumnConfigurationPanel
fieldName="Timestamp"
helpText={
Expand All @@ -107,12 +137,13 @@ const TimestampLogColumnConfigurationPanel: React.FunctionComponent<
/>
}
removeColumn={logColumnConfigurationProps.remove}
dragHandleProps={dragHandleProps}
/>
);

const MessageLogColumnConfigurationPanel: React.FunctionComponent<
LogColumnConfigurationPanelProps
> = ({ logColumnConfigurationProps }) => (
> = ({ logColumnConfigurationProps, dragHandleProps }) => (
<ExplainedLogColumnConfigurationPanel
fieldName="Message"
helpText={
Expand All @@ -123,19 +154,27 @@ const MessageLogColumnConfigurationPanel: React.FunctionComponent<
/>
}
removeColumn={logColumnConfigurationProps.remove}
dragHandleProps={dragHandleProps}
/>
);

const FieldLogColumnConfigurationPanel: React.FunctionComponent<{
logColumnConfigurationProps: FieldLogColumnConfigurationProps;
dragHandleProps: DragHandleProps;
}> = ({
logColumnConfigurationProps: {
logColumnConfiguration: { field },
remove,
},
dragHandleProps,
}) => (
<EuiPanel data-test-subj={`logColumnPanel fieldLogColumnPanel fieldLogColumnPanel:${field}`}>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<div data-test-subj="moveLogColumnHandle" {...dragHandleProps}>
<EuiIcon type="grab" />
</div>
</EuiFlexItem>
<EuiFlexItem grow={1}>
<FormattedMessage
id="xpack.infra.sourceConfiguration.fieldLogColumnTitle"
Expand All @@ -156,11 +195,17 @@ const ExplainedLogColumnConfigurationPanel: React.FunctionComponent<{
fieldName: React.ReactNode;
helpText: React.ReactNode;
removeColumn: () => void;
}> = ({ fieldName, helpText, removeColumn }) => (
dragHandleProps: DragHandleProps;
}> = ({ fieldName, helpText, removeColumn, dragHandleProps }) => (
<EuiPanel
data-test-subj={`logColumnPanel systemLogColumnPanel systemLogColumnPanel:${fieldName}`}
>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<div data-test-subj="moveLogColumnHandle" {...dragHandleProps}>
<EuiIcon type="grab" />
</div>
</EuiFlexItem>
<EuiFlexItem grow={1}>{fieldName}</EuiFlexItem>
<EuiFlexItem grow={3}>
<EuiText size="s" color="subdued">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const SourceConfigurationFlyout = injectI18n(

const {
addLogColumn,
moveLogColumn,
indicesConfigurationProps,
logColumnConfigurationProps,
errors,
Expand Down Expand Up @@ -137,6 +138,7 @@ export const SourceConfigurationFlyout = injectI18n(
<EuiSpacer />
<LogColumnsConfigurationPanel
addLogColumn={addLogColumn}
moveLogColumn={moveLogColumn}
availableFields={availableFields}
isLoading={isLoading}
logColumnConfiguration={logColumnConfigurationProps}
Expand All @@ -148,6 +150,7 @@ export const SourceConfigurationFlyout = injectI18n(
: [],
[
addLogColumn,
moveLogColumn,
availableFields,
indicesConfigurationProps,
intl.formatMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const useSourceConfigurationFormState = (configuration?: SourceConfigurat

return {
addLogColumn: logColumnsConfigurationFormState.addLogColumn,
moveLogColumn: logColumnsConfigurationFormState.moveLogColumn,
errors,
formState,
formStateChanges,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,16 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi
await infraSourceConfigurationFlyout.addTimestampLogColumn();
await infraSourceConfigurationFlyout.addFieldLogColumn('host.name');

await infraSourceConfigurationFlyout.moveLogColumn(0, 1);

await infraSourceConfigurationFlyout.saveConfiguration();
await infraSourceConfigurationFlyout.closeFlyout();
});

it('renders the changed log columns with their headers', async () => {
const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels();

expect(columnHeaderLabels).to.eql(['Timestamp', 'host.name', '']);
expect(columnHeaderLabels).to.eql(['host.name', 'Timestamp', '']);

const logStreamEntries = await infraLogStream.getStreamEntries();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function InfraSourceConfigurationFlyoutProvider({
const find = getService('find');
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const browser = getService('browser');

return {
/**
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 2f31e58

Please sign in to comment.