[Ingest Pipelines] Processor Editor Move Tooltip (#72239)

* first implementation of tooltip

* Add processor tooltip component files

* remove init position from code for now

* colocate on change handler and make code a bit cleaner

* removed document.body.appendChild logic because EuiPortal does that for us

* use correct toggle button api

* added test to check button disabled while editing

* remove cursor not allowed

* simplify logic

* assert if against positive

* remove unused variable

* Remove unused actions const

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Jean-Louis Leysens 2020-07-20 17:02:54 +02:00 committed by GitHub
parent 11182c8ef7
commit e5c7e9a474
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 151 additions and 21 deletions

View file

@ -178,5 +178,13 @@ describe('Pipeline Editor', () => {
expect(data2.processors).toEqual(testProcessors.processors);
expect(data2.on_failure).toEqual([{ test: { if: '1 == 5' } }]);
});
it('prevents moving a processor while in edit mode', () => {
const { find, exists } = testBed;
find('processors>0.editItemButton').simulate('click');
expect(exists('processorSettingsForm')).toBe(true);
expect(find('processors>0.moveItemButton').props().disabled).toBe(true);
expect(find('processors>1.moveItemButton').props().disabled).toBe(true);
});
});
});

View file

@ -1,2 +1,3 @@
$dropZoneZIndex: $euiZLevel1; /* Prevent the next item down from obscuring the button */
$cancelButtonZIndex: $euiZLevel2;
$processorItemMouseTooltipZIndex: $euiZLevel3;

View file

@ -19,3 +19,5 @@ export { PipelineProcessorsEditorItem } from './pipeline_processors_editor_item'
export { ProcessorRemoveModal } from './processor_remove_modal';
export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json';
export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip';

View file

@ -51,7 +51,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
editor,
processorsDispatch,
}) {
const isDisabled = editor.mode.id !== 'idle';
const isEditorNotInIdleMode = editor.mode.id !== 'idle';
const isInMoveMode = Boolean(movingProcessor);
const isMovingThisProcessor = processor.id === movingProcessor?.id;
const isEditingThisProcessor =
@ -83,6 +83,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
'pipelineProcessorsEditor__item__moveButton--cancel': isMovingThisProcessor,
});
const icon = isMovingThisProcessor ? 'cross' : 'sortable';
const disabled = isEditorNotInIdleMode && !isMovingThisProcessor;
const moveButton = (
<EuiButtonToggle
isEmpty={!isMovingThisProcessor}
@ -91,10 +92,16 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
iconType={icon}
data-test-subj={dataTestSubj}
size="s"
disabled={isDisabled && !isMovingThisProcessor}
isDisabled={disabled}
label={label}
aria-label={label}
onChange={() => (!isMovingThisProcessor ? onMove() : onCancelMove())}
onChange={() => {
if (isMovingThisProcessor) {
onCancelMove();
} else {
onMove();
}
}}
/>
);
// Remove the tooltip from the DOM to prevent it from lingering if the mouse leave event
@ -132,7 +139,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
</EuiFlexItem>
<EuiFlexItem className={inlineTextInputContainerClasses} grow={false}>
<InlineTextInput
disabled={isDisabled}
disabled={isEditorNotInIdleMode}
onChange={(nextDescription) => {
let nextOptions: Record<string, any>;
if (!nextDescription) {
@ -164,7 +171,8 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
{!isInMoveMode && (
<EuiToolTip content={i18nTexts.editButtonLabel}>
<EuiButtonIcon
disabled={isDisabled}
data-test-subj="editItemButton"
disabled={isEditorNotInIdleMode}
aria-label={i18nTexts.editButtonLabel}
iconType="pencil"
size="s"
@ -183,7 +191,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
<EuiFlexItem grow={false}>
<ContextMenu
data-test-subj="moreMenu"
disabled={isDisabled}
disabled={isEditorNotInIdleMode}
hidden={isInMoveMode}
showAddOnFailure={!processor.onFailure?.length}
onAddOnFailure={() => {

View file

@ -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 { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip';

View file

@ -0,0 +1,7 @@
@import '../shared';
.pipelineProcessorsEditor__itemTooltip {
position: fixed;
pointer-events: none;
z-index: $processorItemMouseTooltipZIndex;
}

View file

@ -0,0 +1,61 @@
/*
* 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, { FunctionComponent, useEffect, useState } from 'react';
import { EuiPortal } from '@elastic/eui';
import { ProcessorInternal } from '../../types';
import './pipeline_processors_editor_item_toolip.scss';
import { ProcessorInformation } from './processor_information';
export interface Position {
x: number;
y: number;
}
interface Props {
processor: ProcessorInternal;
}
const MOUSE_PADDING_RIGHT = 20;
const MOUSE_PADDING_BOTTOM = 20;
export const PipelineProcessorsItemTooltip: FunctionComponent<Props> = ({ processor }) => {
const [position, setPosition] = useState<Position | undefined>();
useEffect(() => {
const mouseMoveListener = (event: MouseEvent) => {
setPosition({ x: event.pageX, y: event.pageY - window.scrollY });
};
document.addEventListener('mousemove', mouseMoveListener);
return () => {
document.removeEventListener('mousemove', mouseMoveListener);
};
}, []);
if (!position) {
return null;
}
return (
/**
* To get around issues with parent elements potentially being position: relative or
* overflow: hidden we use a portal to render this tooltip in the document body so
* that we can render it anywhere the cursor can go.
*/
<EuiPortal>
<div
className="pipelineProcessorsEditor__itemTooltip"
style={{
left: position.x + MOUSE_PADDING_RIGHT,
top: position.y + MOUSE_PADDING_BOTTOM,
}}
>
<ProcessorInformation processor={processor} />
</div>
</EuiPortal>
);
};

View file

@ -0,0 +1,32 @@
/*
* 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, { FunctionComponent, memo } from 'react';
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { ProcessorInternal } from '../../types';
interface Props {
processor: ProcessorInternal;
}
export const ProcessorInformation: FunctionComponent<Props> = memo(({ processor }) => {
return (
<EuiPanel>
<EuiFlexGroup justifyContent="center" alignItems="center" responsive={false} gutterSize="s">
<EuiFlexItem grow={false}>
<b>{processor.type}</b>
</EuiFlexItem>
{processor.options.description ? (
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="m">
{processor.options.description}
</EuiText>
</EuiFlexItem>
) : undefined}
</EuiFlexGroup>
</EuiPanel>
);
});

View file

@ -37,7 +37,6 @@ export const DropZoneButton: FunctionComponent<Props> = (props) => {
});
const buttonClasses = classNames({
'pipelineProcessorsEditor__tree__dropZoneButton--visible': isVisible,
'pipelineProcessorsEditor__tree__dropZoneButton--unavailable': isUnavailable,
});
const content = (

View file

@ -50,10 +50,6 @@
transform: none !important;
}
}
&--unavailable {
cursor: not-allowed !important;
}
}
&__onFailureHandlerContainer {

View file

@ -4,12 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ON_FAILURE_STATE_SCOPE, PROCESSOR_STATE_SCOPE } from './processors_reducer';
export enum DropSpecialLocations {
top = 'TOP',
bottom = 'BOTTOM',
}
export const PROCESSORS_BASE_SELECTOR = [PROCESSOR_STATE_SCOPE];
export const ON_FAILURE_BASE_SELECTOR = [ON_FAILURE_STATE_SCOPE];

View file

@ -25,6 +25,7 @@ import {
ContextValue,
ContextValueState,
Links,
ProcessorInternal,
} from './types';
import { useProcessorsState, isOnFailureSelector } from './processors_reducer';
@ -33,11 +34,14 @@ import { deserialize } from './deserialize';
import { serialize } from './serialize';
import { OnSubmitHandler, ProcessorSettingsForm } from './components/processor_settings_form';
import { OnActionHandler } from './components/processors_tree';
import { ProcessorRemoveModal } from './components';
import {
ProcessorRemoveModal,
PipelineProcessorsItemTooltip,
ProcessorSettingsForm,
OnSubmitHandler,
} from './components';
import { getValue } from './utils';
@ -64,6 +68,7 @@ export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
children,
}) => {
const initRef = useRef(false);
const [mode, setMode] = useState<EditorMode>(() => ({
id: 'idle',
}));
@ -206,6 +211,15 @@ export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
>
{children}
{mode.id === 'movingProcessor' && (
<PipelineProcessorsItemTooltip
processor={getValue<ProcessorInternal>(mode.arg.selector, {
processors,
onFailure: onFailureProcessors,
})}
/>
)}
{mode.id === 'editingProcessor' || mode.id === 'creatingProcessor' ? (
<ProcessorSettingsForm
isOnFailure={isOnFailureSelector(mode.arg.selector)}
@ -219,7 +233,7 @@ export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
{mode.id === 'removingProcessor' && (
<ProcessorRemoveModal
selector={mode.arg.selector}
processor={getValue(mode.arg.selector, {
processor={getValue<ProcessorInternal>(mode.arg.selector, {
processors,
onFailure: onFailureProcessors,
})}

View file

@ -7,7 +7,7 @@
import { Dispatch } from 'react';
import { OnFormUpdateArg } from '../../../shared_imports';
import { SerializeResult } from './serialize';
import { OnActionHandler, ProcessorInfo } from './components/processors_tree';
import { OnActionHandler, ProcessorInfo } from './components';
import { ProcessorsDispatch, State as ProcessorsReducerState } from './processors_reducer';
export interface Links {