Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Endpoint] Response Console framework support for argument value selectors #148693

Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
49bd6b7
Type definitions for Command Argument Value selector component
paul-tavares Jan 4, 2023
b7a5fe7
Argument File Selector component
paul-tavares Jan 4, 2023
52db251
Add test command to console (`upload`)
paul-tavares Jan 4, 2023
a124c8f
Add `enteredCommand` structure to the console's input store state
paul-tavares Jan 4, 2023
4f20a11
Refactor input state properties of left/right of cursor text
paul-tavares Jan 4, 2023
4cce1cd
refactor the types for parsing user input into a `types.ts` file
paul-tavares Jan 4, 2023
61cc541
add `parsedInput` to the input store
paul-tavares Jan 4, 2023
5cdbe93
`EnteredInput` class and initial implementation for displaying value …
paul-tavares Jan 5, 2023
3cfbd6d
Add complete input state to the callback argument of `updateInputText…
paul-tavares Jan 5, 2023
56fae04
Add methods to EnteredInput to retrieve rendering values and use them…
paul-tavares Jan 7, 2023
7e9d23b
save argument value selector state to store
paul-tavares Jan 7, 2023
217a405
Change execute command action to include parsed input and entered com…
paul-tavares Jan 7, 2023
1f5a3ea
Adjust output of the upload execution dev component
paul-tavares Jan 9, 2023
a31b35a
Fix hints for known commands
paul-tavares Jan 9, 2023
db80987
Refactor `EnteredInput` to use an array of input items instead of man…
paul-tavares Jan 9, 2023
5f82e7b
`EnteredInput` support for keybaord movement around argument's with v…
paul-tavares Jan 10, 2023
5815f82
Ensure Entered Command is reset in store when command is entered
paul-tavares Jan 10, 2023
01c796e
Better handling of text selection replacement (but not done yet)
paul-tavares Jan 10, 2023
123f1b2
Fix EnteredInput to handle when cursor is between replacement value
paul-tavares Jan 11, 2023
f0f6009
Test file for `EnteredInput`
paul-tavares Jan 11, 2023
dd2b2eb
Fix `setArgSelectorValueToParsedArgs()` to ensure that any arg with s…
paul-tavares Jan 11, 2023
fd70ff9
add new argument definition property (`mustHaveValue`) and associated…
paul-tavares Jan 11, 2023
dfc997b
update dev command to use new argument value validations
paul-tavares Jan 11, 2023
9cd92a1
Fix some types
paul-tavares Jan 11, 2023
5fda48a
Fix text replacement for when argument value selectors are present
paul-tavares Jan 11, 2023
61d1e91
extracted i18n translations out of command execution module
paul-tavares Jan 11, 2023
843ddc2
Fix type
paul-tavares Jan 12, 2023
efecdd0
`EnteredInput` support for getting input text that includes argument …
paul-tavares Jan 12, 2023
d785b50
Added `display` property to Input History and added migration for ret…
paul-tavares Jan 12, 2023
288ca57
Add `inputDisplay` to `Command` and display it when command is entered
paul-tavares Jan 12, 2023
5637bda
Use `display` for showing input history in popup and `input` for when…
paul-tavares Jan 12, 2023
8fc08e3
Merge remote-tracking branch 'upstream/main' into task/olm-5711-conso…
paul-tavares Jan 12, 2023
9ae2bbd
Merge branch 'main' into task/olm-5711-console-arg-value-selector-sup…
paul-tavares Jan 12, 2023
6efc0a1
Merge remote-tracking branch 'upstream/main' into task/olm-5711-conso…
paul-tavares Jan 13, 2023
9674d32
Some minor adjustments to dev command
paul-tavares Jan 13, 2023
e5428f6
Styling of Argument Selector wrapper
paul-tavares Jan 13, 2023
8a3cc6d
Styles for the File Selector
paul-tavares Jan 14, 2023
97ef33e
Make area that displays file name clickable
paul-tavares Jan 14, 2023
fd5fc12
two different styles for displaying argument selectors (prep for UX r…
paul-tavares Jan 14, 2023
515b741
Styles for better displaying left/right of input areas content
paul-tavares Jan 17, 2023
c53b643
adjustments to styles for an argument selector
paul-tavares Jan 17, 2023
e66a23b
Ensure input state is reset when command is entered
paul-tavares Jan 17, 2023
bde47e0
Enable multiple instances of the same argument that has an argument s…
paul-tavares Jan 17, 2023
7562805
Ensure that argument value selection is stored against the correct in…
paul-tavares Jan 17, 2023
05378d4
Ensure that Argument Selector state is removed if user uses `DELETE` …
paul-tavares Jan 17, 2023
c037931
command input tests for command having argument with selector component
paul-tavares Jan 17, 2023
a1c164c
Merge remote-tracking branch 'upstream/main' into task/olm-5711-conso…
paul-tavares Jan 17, 2023
a42bdf0
fix types
paul-tavares Jan 17, 2023
1d3e58b
Merge remote-tracking branch 'upstream/main' into task/olm-5711-conso…
paul-tavares Jan 18, 2023
1236353
pass `argName` and `argInstance` to Arg. Selector components
paul-tavares Jan 18, 2023
3faae4a
adjustments to ArgumentSelector dev/mock component
paul-tavares Jan 18, 2023
a326ee7
Merge remote-tracking branch 'upstream/main' into task/olm-5711-conso…
paul-tavares Jan 18, 2023
9199c53
Fix tests
paul-tavares Jan 18, 2023
cc563c1
Merge branch 'main' into task/olm-5711-console-arg-value-selector-sup…
paul-tavares Jan 23, 2023
19772a7
Merge branch 'main' into task/olm-5711-console-arg-value-selector-sup…
paul-tavares Jan 23, 2023
26f78a9
Merge branch 'main' into task/olm-5711-console-arg-value-selector-sup…
paul-tavares Jan 23, 2023
3a2a7be
Merge branch 'main' into task/olm-5711-console-arg-value-selector-sup…
paul-tavares Jan 24, 2023
8cab164
Merge branch 'main' into task/olm-5711-console-arg-value-selector-sup…
paul-tavares Jan 24, 2023
13e957f
Merge remote-tracking branch 'upstream/main' into task/olm-5711-conso…
paul-tavares Jan 24, 2023
9eac198
Fixes from PR reviews
paul-tavares Jan 24, 2023
55816db
Merge remote-tracking branch 'origin/task/olm-5711-console-arg-value-…
paul-tavares Jan 24, 2023
304bea4
Add `store` data to ArgumentSelector components
paul-tavares Jan 24, 2023
e3e7413
rename `argInstance` to `argIndex`
paul-tavares Jan 25, 2023
c3c1606
Merge remote-tracking branch 'upstream/main' into task/olm-5711-conso…
paul-tavares Jan 25, 2023
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 @@ -92,7 +92,7 @@ export const CommandExecutionOutput = memo<CommandExecutionOutputProps>(
return (
<CommandOutputContainer>
<div>
<UserCommandInput input={command.input} isValid={isValid} />
<UserCommandInput input={command.inputDisplay} isValid={isValid} />
</div>
<div>
{/* UX desire for 12px (current theme): achieved with EuiSpace sizes - s (8px) + xs (4px) */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiResizeObserver } from '@el
import styled from 'styled-components';
import classNames from 'classnames';
import type { EuiResizeObserverProps } from '@elastic/eui/src/components/observer/resize_observer/resize_observer';
import type { ExecuteCommandPayload, ConsoleDataState } from '../console_state/types';
import { useWithInputShowPopover } from '../../hooks/state_selectors/use_with_input_show_popover';
import { EnteredInput } from './lib/entered_input';
import type { InputCaptureProps } from './components/input_capture';
Expand Down Expand Up @@ -40,6 +41,13 @@ const CommandInputContainer = styled.div`
border-bottom-color: ${({ theme: { eui } }) => eui.euiColorDanger};
}

.inputDisplay {
& > * {
flex-direction: row;
align-items: center;
}
}

.textEntered {
white-space: break-spaces;
}
Expand Down Expand Up @@ -88,13 +96,17 @@ export interface CommandInputProps extends CommonProps {

export const CommandInput = memo<CommandInputProps>(({ prompt = '', focusRef, ...commonProps }) => {
useInputHints();
const getTestId = useTestIdGenerator(useDataTestSubj());
const dispatch = useConsoleStateDispatch();
const { rightOfCursor, textEntered, fullTextEntered } = useWithInputTextEntered();
const { rightOfCursorText, leftOfCursorText, fullTextEntered, enteredCommand, parsedInput } =
useWithInputTextEntered();
const visibleState = useWithInputVisibleState();
const [isKeyInputBeingCaptured, setIsKeyInputBeingCaptured] = useState(false);
const getTestId = useTestIdGenerator(useDataTestSubj());
const isPopoverOpen = !!useWithInputShowPopover();
const [commandToExecute, setCommandToExecute] = useState('');

const [isKeyInputBeingCaptured, setIsKeyInputBeingCaptured] = useState(false);
const [commandToExecute, setCommandToExecute] = useState<ExecuteCommandPayload | undefined>(
undefined
);
const [popoverWidth, setPopoverWidth] = useState('94vw');

const _focusRef: InputCaptureProps['focusRef'] = useRef(null);
Expand All @@ -111,22 +123,23 @@ export const CommandInput = memo<CommandInputProps>(({ prompt = '', focusRef, ..

const disableArrowButton = useMemo(() => fullTextEntered.trim().length === 0, [fullTextEntered]);

const userInput = useMemo(() => {
return new EnteredInput(leftOfCursorText, rightOfCursorText, parsedInput, enteredCommand);
}, [enteredCommand, leftOfCursorText, parsedInput, rightOfCursorText]);

const handleOnResize = useCallback<EuiResizeObserverProps['onResize']>(({ width }) => {
if (width > 0) {
setPopoverWidth(`${width}px`);
}
}, []);

const handleSubmitButton = useCallback<MouseEventHandler>(() => {
setCommandToExecute(textEntered + rightOfCursor.text);
dispatch({
type: 'updateInputTextEnteredState',
payload: {
textEntered: '',
rightOfCursor: undefined,
},
setCommandToExecute({
input: userInput.getFullText(true),
enteredCommand,
parsedInput,
});
}, [dispatch, textEntered, rightOfCursor.text]);
}, [enteredCommand, parsedInput, userInput]);

const handleOnChangeFocus = useCallback<NonNullable<InputCaptureProps['onChangeFocus']>>(
(hasFocus) => {
Expand Down Expand Up @@ -163,8 +176,18 @@ export const CommandInput = memo<CommandInputProps>(({ prompt = '', focusRef, ..
// Update the store with the updated text that was entered
dispatch({
type: 'updateInputTextEnteredState',
payload: ({ textEntered: prevLeftOfCursor, rightOfCursor: prevRightOfCursor }) => {
let inputText = new EnteredInput(prevLeftOfCursor, prevRightOfCursor.text);
payload: ({
leftOfCursorText: prevLeftOfCursor,
rightOfCursorText: prevRightOfCursor,
enteredCommand: prevEnteredCommand,
parsedInput: prevParsedInput,
}) => {
const inputText = new EnteredInput(
prevLeftOfCursor,
prevRightOfCursor,
prevParsedInput,
prevEnteredCommand
);

inputText.addValue(value ?? '', selection);

Expand All @@ -181,8 +204,12 @@ export const CommandInput = memo<CommandInputProps>(({ prompt = '', focusRef, ..

// ENTER = Execute command and blank out the input area
case 13:
setCommandToExecute(inputText.getFullText());
inputText = new EnteredInput('', '');
setCommandToExecute({
input: inputText.getFullText(true),
enteredCommand: prevEnteredCommand as ConsoleDataState['input']['enteredCommand'],
parsedInput: prevParsedInput as ConsoleDataState['input']['parsedInput'],
});
inputText.clear();
break;

// ARROW LEFT
Expand All @@ -207,8 +234,9 @@ export const CommandInput = memo<CommandInputProps>(({ prompt = '', focusRef, ..
}

return {
textEntered: inputText.getLeftOfCursorText(),
rightOfCursor: { text: inputText.getRightOfCursorText() },
leftOfCursorText: inputText.getLeftOfCursorText(),
rightOfCursorText: inputText.getRightOfCursorText(),
argState: inputText.getArgState(),
};
},
});
Expand All @@ -219,8 +247,17 @@ export const CommandInput = memo<CommandInputProps>(({ prompt = '', focusRef, ..
// Execute the command if one was ENTER'd.
useEffect(() => {
if (commandToExecute) {
dispatch({ type: 'executeCommand', payload: { input: commandToExecute } });
setCommandToExecute('');
dispatch({ type: 'executeCommand', payload: commandToExecute });
setCommandToExecute(undefined);

// reset input
dispatch({
type: 'updateInputTextEnteredState',
payload: {
leftOfCursorText: '',
rightOfCursorText: '',
},
});
}
}, [commandToExecute, dispatch]);

Expand Down Expand Up @@ -248,17 +285,20 @@ export const CommandInput = memo<CommandInputProps>(({ prompt = '', focusRef, ..
onChangeFocus={handleOnChangeFocus}
focusRef={focusRef}
>
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="none">
<EuiFlexItem grow={false}>
<div data-test-subj={getTestId('cmdInput-leftOfCursor')}>{textEntered}</div>
<EuiFlexGroup
responsive={false}
alignItems="center"
gutterSize="none"
className="inputDisplay"
>
<EuiFlexItem grow={false} data-test-subj={getTestId('cmdInput-leftOfCursor')}>
{userInput.getLeftOfCursorRenderingContent()}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span className="cursor essentialAnimation" />
</EuiFlexItem>
<EuiFlexItem>
<div data-test-subj={getTestId('cmdInput-rightOfCursor')}>
{rightOfCursor.text}
</div>
<EuiFlexItem data-test-subj={getTestId('cmdInput-rightOfCursor')}>
{userInput.getRightOfCursorRenderingContent()}
</EuiFlexItem>
</EuiFlexGroup>
</InputCapture>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { memo, useCallback } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import styled, { createGlobalStyle } from 'styled-components';
import type { EuiTheme } from '@kbn/kibana-react-plugin/common';
import { useConsoleStateDispatch } from '../../../hooks/state_selectors/use_console_state_dispatch';
import { useWithCommandArgumentState } from '../../../hooks/state_selectors/use_with_command_argument_state';
import type { CommandArgDefinition, CommandArgumentValueSelectorProps } from '../../../types';

const ArgumentSelectorWrapperContainer = styled.span`
user-select: none;

.selectorContainer {
max-width: 25vw;
display: flex;
align-items: center;
height: 100%;
}
`;

// FIXME:PT Delete below. Only here for DEV purposes
const DevUxStyles = createGlobalStyle<{ theme: EuiTheme }>`

body {

&.style1 .argSelectorWrapper {
.style1-hide {
display: none;
}

.selectorContainer {
border: ${({ theme: { eui } }) => eui.euiBorderThin};
border-radius: ${({ theme: { eui } }) => eui.euiBorderRadiusSmall};
padding: 0 ${({ theme: { eui } }) => eui.euiSizeXS};
}
}

&.style2 {
.argSelectorWrapper {
border: ${({ theme: { eui } }) => eui.euiBorderThin};
border-radius: ${({ theme: { eui } }) => eui.euiBorderRadiusSmall};
overflow: hidden;

& > .euiFlexGroup {
align-items: stretch;
}

.style2-hide {
display: none;
}

.argNameContainer {
background-color: ${({ theme: { eui } }) => eui.euiFormInputGroupLabelBackground};
}

.argName {
padding-left: ${({ theme: { eui } }) => eui.euiSizeXS};
height: 100%;
display: flex;
align-items: center;
}
.selectorContainer {
padding: 0 ${({ theme: { eui } }) => eui.euiSizeXS};
}
}
}
}
`;

// Type to ensure that `SelectorComponent` is defined
type ArgDefinitionWithRequiredSelector = Omit<CommandArgDefinition, 'SelectorComponent'> &
Pick<Required<CommandArgDefinition>, 'SelectorComponent'>;

export interface ArgumentSelectorWrapperProps {
argName: string;
argInstance: number;
argDefinition: ArgDefinitionWithRequiredSelector;
}

/**
* handles displaying a custom argument value selector and manages its state
*/
export const ArgumentSelectorWrapper = memo<ArgumentSelectorWrapperProps>(
({ argName, argInstance, argDefinition: { SelectorComponent } }) => {
const dispatch = useConsoleStateDispatch();
const { valueText, value } = useWithCommandArgumentState(argName, argInstance);

const handleSelectorComponentOnChange = useCallback<
CommandArgumentValueSelectorProps['onChange']
>(
(updates) => {
dispatch({
type: 'updateInputCommandArgState',
payload: {
name: argName,
instance: argInstance,
state: updates,
},
});
},
[argInstance, argName, dispatch]
);

return (
<ArgumentSelectorWrapperContainer className="eui-displayInlineBlock argSelectorWrapper">
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="none">
<EuiFlexItem grow={false} className="argNameContainer">
<div className="argName">
<span>{`--${argName}=`}</span>
<span className="style1-hide style2-hide">{'"'}</span>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{/* `div` below ensures that the `SelectorComponent` does NOT inherit the styles of a `flex` container */}
<div className="selectorContainer eui-textTruncate">
<SelectorComponent
value={value}
valueText={valueText ?? ''}
argName={argName}
argInstance={argInstance}
onChange={handleSelectorComponentOnChange}
/>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false} className="style1-hide style2-hide">
{'"'}
</EuiFlexItem>
</EuiFlexGroup>

<DevUxStyles />
</ArgumentSelectorWrapperContainer>
);
}
);
ArgumentSelectorWrapper.displayName = 'ArgumentSelectorWrapper';
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const CommandInputHistory = memo(() => {
const selectableHistoryOptions = useMemo(() => {
return inputHistory.map<EuiSelectableProps['options'][number]>((inputItem, index) => {
return {
label: inputItem.input,
label: inputItem.display,
key: inputItem.id,
data: inputItem,
};
Expand Down Expand Up @@ -94,7 +94,13 @@ export const CommandInputHistory = memo(() => {
dispatch({ type: 'updateInputPlaceholderState', payload: { placeholder: '' } });

if (selected) {
dispatch({ type: 'updateInputTextEnteredState', payload: { textEntered: selected.label } });
dispatch({
type: 'updateInputTextEnteredState',
payload: {
leftOfCursorText: (selected.data as InputHistoryItem).input,
rightOfCursorText: '',
},
});
}

dispatch({ type: 'addFocusToKeyCapture' });
Expand Down Expand Up @@ -124,15 +130,18 @@ export const CommandInputHistory = memo(() => {
// unloads, if no option from the history was selected, then set the prior text
// entered back
useEffect(() => {
dispatch({ type: 'updateInputTextEnteredState', payload: { textEntered: '' } });
dispatch({
type: 'updateInputTextEnteredState',
payload: { leftOfCursorText: '', rightOfCursorText: '' },
});

return () => {
if (!optionWasSelected.current) {
dispatch({
type: 'updateInputTextEnteredState',
payload: {
textEntered: priorInputState.textEntered,
rightOfCursor: priorInputState.rightOfCursor,
leftOfCursorText: priorInputState.leftOfCursorText,
rightOfCursorText: priorInputState.rightOfCursorText,
},
});
dispatch({ type: 'updateInputPlaceholderState', payload: { placeholder: '' } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const InputPlaceholderContainer = styled(EuiText)`
padding-left: 0.5em;
width: 96%;
color: ${({ theme: { eui } }) => eui.euiFormControlPlaceholderText};
user-select: none;
line-height: ${({ theme: { eui } }) => {
return `calc(${eui.euiLineHeight}em + 0.5em)`;
}};
`;

export const InputPlaceholder = memo(() => {
Expand Down
Loading