Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { FunctionComponent } from 'react';
import React, { forwardRef } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonEmpty, EuiButton } from '@elastic/eui';
import { css } from '@emotion/react';
Expand All @@ -30,7 +30,7 @@ const styles = {
`,
};

export const AddProcessorButton: FunctionComponent<Props> = (props) => {
export const AddProcessorButton = forwardRef<HTMLButtonElement, Props>((props, ref) => {
const { onClick, renderButtonAsLink } = props;
const {
state: { editor },
Expand All @@ -44,6 +44,7 @@ export const AddProcessorButton: FunctionComponent<Props> = (props) => {
iconSide="left"
iconType="plusInCircle"
onClick={onClick}
buttonRef={ref}
>
{addProcessorButtonLabel}
</EuiButtonEmpty>
Expand All @@ -56,8 +57,9 @@ export const AddProcessorButton: FunctionComponent<Props> = (props) => {
css={styles.button}
disabled={editor.mode.id !== 'idle'}
onClick={onClick}
buttonRef={ref}
>
{addProcessorButtonLabel}
</EuiButton>
);
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* 2.0.
*/

import type { FunctionComponent } from 'react';
import React, { useState } from 'react';
import React, { useState, forwardRef } from 'react';
import { css } from '@emotion/react';

import { EuiContextMenuItem, EuiContextMenuPanel, EuiPopover, EuiButtonIcon } from '@elastic/eui';
Expand All @@ -31,7 +30,7 @@ const getStyles = ({ hidden }: { hidden?: boolean }) => ({
: undefined,
});

export const ContextMenu: FunctionComponent<Props> = (props) => {
export const ContextMenu = forwardRef<HTMLButtonElement, Props>((props, ref) => {
const { showAddOnFailure, onDuplicate, onAddOnFailure, onDelete, disabled, hidden } = props;
const styles = getStyles({ hidden });
const [isOpen, setIsOpen] = useState<boolean>(false);
Expand Down Expand Up @@ -85,6 +84,7 @@ export const ContextMenu: FunctionComponent<Props> = (props) => {
closePopover={() => setIsOpen(false)}
button={
<EuiButtonIcon
buttonRef={ref}
data-test-subj="button"
disabled={disabled}
onClick={() => setIsOpen((v) => !v)}
Expand All @@ -97,4 +97,4 @@ export const ContextMenu: FunctionComponent<Props> = (props) => {
</EuiPopover>
</div>
);
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import type { FunctionComponent } from 'react';
import React, { memo, useCallback } from 'react';
import React, { memo, useCallback, useRef } from 'react';
import {
EuiButtonIcon,
EuiFlexGroup,
Expand Down Expand Up @@ -116,6 +116,8 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
editor,
processorsDispatch,
}) {
const editButtonRef = useRef<HTMLAnchorElement>(null);
const contextMenuButtonRef = useRef<HTMLButtonElement>(null);
const isEditorNotInIdleMode = editor.mode.id !== 'idle';
const isInMoveMode = Boolean(movingProcessor);
const isMovingThisProcessor = processor.id === movingProcessor?.id;
Expand Down Expand Up @@ -240,12 +242,13 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
<EuiFlexItem grow={false}>
<EuiText css={styles.processorText} color={isDimmed ? 'subdued' : undefined}>
<EuiLink
ref={editButtonRef}
tabIndex={isEditorNotInIdleMode ? -1 : 0}
disabled={isEditorNotInIdleMode}
onClick={() => {
editor.setMode({
id: 'managingProcessor',
arg: { processor, selector },
arg: { processor, selector, buttonRef: editButtonRef },
});
}}
data-test-subj="manageItemButton"
Expand Down Expand Up @@ -275,12 +278,16 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ContextMenu
ref={contextMenuButtonRef}
data-test-subj="moreMenu"
disabled={isEditorNotInIdleMode}
hidden={isInMoveMode}
showAddOnFailure={!processor.onFailure?.length}
onAddOnFailure={() => {
editor.setMode({ id: 'creatingProcessor', arg: { selector } });
editor.setMode({
id: 'creatingProcessor',
arg: { selector, buttonRef: contextMenuButtonRef },
});
}}
onDelete={() => {
editor.setMode({ id: 'removingProcessor', arg: { selector } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface Props {
esDocsBasePath: string;
closeFlyout: () => void;
handleSubmit: (shouldCloseFlyout?: boolean) => Promise<void>;
buttonRef?: React.RefObject<HTMLButtonElement | HTMLAnchorElement>;
}

const addButtonLabel = i18n.translate(
Expand Down Expand Up @@ -68,6 +69,7 @@ export const AddProcessorForm: FunctionComponent<Props> = ({
esDocsBasePath,
closeFlyout,
handleSubmit,
buttonRef,
}) => {
useEffect(
() => {
Expand All @@ -87,6 +89,19 @@ export const AddProcessorForm: FunctionComponent<Props> = ({
onClose={closeFlyout}
outsideClickCloses={!isFormDirty}
aria-labelledby={pipelineTitleId}
focusTrapProps={{
returnFocus: (triggerElement) => {
if (buttonRef?.current) {
// Using setTimeout here to postpone focus until after the flyout has finished unmounting and cleaning up its focus traps.
// Without this, the focus gets applied too early and it's overridden by the browser's default focus behavior.
setTimeout(() => {
buttonRef.current?.focus();
}, 0);
return false;
}
return true;
},
}}
>
<EuiFlyoutHeader>
<EuiFlexGroup gutterSize="xs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface Props {
resetProcessors: () => void;
handleSubmit: (shouldCloseFlyout?: boolean) => Promise<void>;
getProcessor: () => ProcessorInternal;
buttonRef?: React.RefObject<HTMLButtonElement | HTMLAnchorElement>;
}

const updateButtonLabel = i18n.translate(
Expand Down Expand Up @@ -103,6 +104,7 @@ export const EditProcessorForm: FunctionComponent<Props> = ({
closeFlyout,
handleSubmit,
resetProcessors,
buttonRef,
}) => {
const { testPipelineData, testPipelineDataDispatch } = useTestPipelineContext();
const {
Expand Down Expand Up @@ -168,6 +170,19 @@ export const EditProcessorForm: FunctionComponent<Props> = ({
}}
outsideClickCloses={!isFormDirty}
aria-labelledby={flyoutTitleId}
focusTrapProps={{
returnFocus: (triggerElement) => {
if (buttonRef?.current) {
// Using setTimeout here to postpone focus until after the flyout has finished unmounting and cleaning up its focus traps.
// Without this, the focus gets applied too early and it's overridden by the browser's default focus behavior.
setTimeout(() => {
buttonRef.current?.focus();
}, 0);
return false;
}
return true;
},
}}
>
<EuiFlyoutHeader>
<EuiFlexGroup gutterSize="xs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface Props {
onOpen: () => void;
onClose: () => void;
processor?: ProcessorInternal;
buttonRef?: React.RefObject<HTMLButtonElement | HTMLAnchorElement>;
}

const formOptions: FormOptions = {
Expand All @@ -52,6 +53,7 @@ export const ProcessorFormContainer: FunctionComponent<Props> = ({
onFormUpdate,
onSubmit,
onClose,
buttonRef,
...rest
}) => {
const { services } = useKibana();
Expand Down Expand Up @@ -142,6 +144,7 @@ export const ProcessorFormContainer: FunctionComponent<Props> = ({
closeFlyout={onClose}
resetProcessors={resetProcessors}
handleSubmit={handleSubmit}
buttonRef={buttonRef}
/>
);
}
Expand All @@ -153,6 +156,7 @@ export const ProcessorFormContainer: FunctionComponent<Props> = ({
esDocsBasePath={services.documentation.getEsDocsBasePath()}
closeFlyout={onClose}
handleSubmit={handleSubmit}
buttonRef={buttonRef}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* 2.0.
*/

import React, { FunctionComponent } from 'react';
import type { FunctionComponent } from 'react';
import React, { useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiEmptyPrompt, EuiSpacer, EuiLink } from '@elastic/eui';
Expand All @@ -28,6 +29,8 @@ export const ProcessorsEmptyPrompt: FunctionComponent<Props> = ({ onLoadJson })
const { onTreeAction } = usePipelineProcessorsContext();
const { services } = useKibana();

const buttonRef = useRef<HTMLButtonElement>(null);

return (
<EuiEmptyPrompt
title={<h2>{i18nTexts.emptyPromptTitle}</h2>}
Expand Down Expand Up @@ -55,8 +58,12 @@ export const ProcessorsEmptyPrompt: FunctionComponent<Props> = ({ onLoadJson })
actions={
<>
<AddProcessorButton
ref={buttonRef}
onClick={() => {
onTreeAction({ type: 'addProcessor', payload: { target: ['processors'] } });
onTreeAction({
type: 'addProcessor',
payload: { target: ['processors'], buttonRef },
});
}}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* 2.0.
*/

import React, { FunctionComponent, useMemo, useCallback } from 'react';
import type { FunctionComponent } from 'react';
import React, { useMemo, useCallback, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { EuiText, useEuiTheme } from '@elastic/eui';
Expand Down Expand Up @@ -52,6 +53,8 @@ export const TreeNode: FunctionComponent<Props> = ({
}) => {
const stringSelector = useMemo(() => processorInfo.selector.join('.'), [processorInfo.selector]);
const styles = useStyles({ level });
const buttonRef = useRef<HTMLButtonElement>(null);

const handlers = useMemo((): Handlers => {
return {
onMove: () => {
Expand Down Expand Up @@ -83,12 +86,13 @@ export const TreeNode: FunctionComponent<Props> = ({
processors={processor.onFailure}
/>
<AddProcessorButton
ref={buttonRef}
data-test-subj={stringSelector}
renderButtonAsLink
onClick={() =>
onAction({
type: 'addProcessor',
payload: { target: processorInfo.selector.concat('onFailure') },
payload: { target: processorInfo.selector.concat('onFailure'), buttonRef },
})
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export type Action =
| { type: 'move'; payload: { source: ProcessorSelector; destination: ProcessorSelector } }
| { type: 'selectToMove'; payload: { info: ProcessorInfo } }
| { type: 'cancelMove' }
| { type: 'addProcessor'; payload: { target: ProcessorSelector } };
| {
type: 'addProcessor';
payload: { target: ProcessorSelector; buttonRef?: React.RefObject<HTMLButtonElement> };
};

export type OnActionHandler = (action: Action) => void;

Expand Down Expand Up @@ -58,6 +61,7 @@ const useStyles = () => {
export const ProcessorsTree: FunctionComponent<Props> = memo((props) => {
const { processors, baseSelector, onAction, movingProcessor } = props;
const styles = useStyles();
const buttonRef = useRef<HTMLButtonElement>(null);
// These refs are created here so they can be shared with all
// recursively rendered trees. Their values should come from react-virtualized
// List component and WindowScroller component.
Expand Down Expand Up @@ -140,8 +144,9 @@ export const ProcessorsTree: FunctionComponent<Props> = memo((props) => {
)}
<EuiFlexItem grow={false}>
<AddProcessorButton
ref={buttonRef}
onClick={() => {
onAction({ type: 'addProcessor', payload: { target: baseSelector } });
onAction({ type: 'addProcessor', payload: { target: baseSelector, buttonRef } });
}}
renderButtonAsLink
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
(action) => {
switch (action.type) {
case 'addProcessor':
setMode({ id: 'creatingProcessor', arg: { selector: action.payload.target } });
setMode({
id: 'creatingProcessor',
arg: { selector: action.payload.target, buttonRef: action.payload.buttonRef },
});
break;
case 'move':
setMode({ id: 'idle' });
Expand Down Expand Up @@ -263,6 +266,7 @@ export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
<ProcessorForm
isOnFailure={isOnFailureSelector(mode.arg.selector)}
processor={mode.id === 'managingProcessor' ? mode.arg.processor : undefined}
buttonRef={mode.arg.buttonRef}
onOpen={onFlyoutOpen}
onFormUpdate={onFormUpdate}
onSubmit={onSubmit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,22 @@ export type OnUpdateHandler = (arg: OnUpdateHandlerArg) => void;
* which will be used to update the in-memory processors data structure.
*/
export type EditorMode =
| { id: 'creatingProcessor'; arg: { selector: ProcessorSelector } }
| {
id: 'creatingProcessor';
arg: {
selector: ProcessorSelector;
buttonRef?: React.RefObject<HTMLButtonElement | HTMLAnchorElement>;
};
}
| { id: 'movingProcessor'; arg: ProcessorInfo }
| { id: 'managingProcessor'; arg: { processor: ProcessorInternal; selector: ProcessorSelector } }
| {
id: 'managingProcessor';
arg: {
processor: ProcessorInternal;
selector: ProcessorSelector;
buttonRef?: React.RefObject<HTMLButtonElement | HTMLAnchorElement>;
};
}
| { id: 'removingProcessor'; arg: { selector: ProcessorSelector } }
| { id: 'idle' };

Expand Down