Multi-line kql bar (#70140)

* Multiline kql bar

* fix id

* use visibility rather than display to hide stuff, cross fingers for tests

* another vis trick for tests

* quasi fix tests, still some failures

* caroline feedback

* fun!

* fix for mouse

* fix test

* check api

* fix unit test on query_string_input

* Fix cypress test

* handle the resize of the height of the textarea when the window have been resize

Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Liza K <liza.katz@elastic.co>
This commit is contained in:
Dave Snider 2020-07-08 10:01:00 -07:00 committed by GitHub
parent 93ac059cac
commit c815c96937
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 237 additions and 123 deletions

View file

@ -7,5 +7,5 @@
<b>Signature:</b>
```typescript
QueryStringInput: React.FC<Pick<Props, "query" | "prepend" | "placeholder" | "onChange" | "onBlur" | "onSubmit" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "disableAutoFocus" | "persistedLog" | "bubbleSubmitEvent" | "languageSwitcherPopoverAnchorPosition">>
QueryStringInput: React.FC<Pick<Props, "query" | "prepend" | "placeholder" | "onChange" | "onBlur" | "onSubmit" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "disableAutoFocus" | "persistedLog" | "bubbleSubmitEvent" | "languageSwitcherPopoverAnchorPosition" | "onChangeQueryInputFocus">>
```

View file

@ -51,7 +51,6 @@ import { ErrorToastOptions } from 'src/core/public/notifications';
import { EuiButtonEmptyProps } from '@elastic/eui';
import { EuiComboBoxProps } from '@elastic/eui';
import { EuiConfirmModalProps } from '@elastic/eui';
import { EuiFieldText } from '@elastic/eui';
import { EuiGlobalToastListToast } from '@elastic/eui';
import { ExclusiveUnion } from '@elastic/eui';
import { ExistsParams } from 'elasticsearch';
@ -1482,7 +1481,7 @@ export interface QueryState {
// Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const QueryStringInput: React.FC<Pick<Props_3, "query" | "prepend" | "placeholder" | "onChange" | "onBlur" | "onSubmit" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "disableAutoFocus" | "persistedLog" | "bubbleSubmitEvent" | "languageSwitcherPopoverAnchorPosition">>;
export const QueryStringInput: React.FC<Pick<Props_3, "query" | "prepend" | "placeholder" | "onChange" | "onBlur" | "onSubmit" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "disableAutoFocus" | "persistedLog" | "bubbleSubmitEvent" | "languageSwitcherPopoverAnchorPosition" | "onChangeQueryInputFocus">>;
// @public (undocumented)
export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField;

View file

@ -1,3 +1,41 @@
.kbnQueryBar__wrap {
max-width: 100%;
z-index: $euiZContentMenu;
}
// Uses the append style, but no bordering
.kqlQueryBar__languageSwitcherButton {
border-right: none !important;
}
.kbnQueryBar__textarea {
z-index: $euiZContentMenu;
resize: none !important; // When in the group, it will autosize
height: $euiSizeXXL;
// Unlike most inputs within layout control groups, the text area still needs a border.
// These adjusts help it sit above the control groups shadow to line up correctly.
padding-top: $euiSizeS + 3px !important;
transform: translateY(-2px);
padding: $euiSizeS - 1px;
&:not(:focus) {
@include euiYScrollWithShadows;
white-space: nowrap;
overflow-y: hidden;
overflow-x: hidden;
border: none;
box-shadow: none;
}
// When focused, let it scroll
&:focus {
overflow-x: auto;
overflow-y: auto;
width: calc(100% + 1px); // To overtake the group's fake border
white-space: normal;
}
}
@include euiBreakpoint('xs', 's') {
.kbnQueryBar--withDatePicker {
> :first-child {
@ -16,5 +54,11 @@
// sass-lint:disable-block no-important
flex-grow: 0 !important;
flex-basis: auto !important;
margin-right: -$euiSizeXS !important;
&.kbnQueryBar__datePickerWrapper-isHidden {
width: 0;
overflow: hidden;
}
}
}

View file

@ -60,7 +60,7 @@ export function QueryLanguageSwitcher(props: Props) {
<EuiButtonEmpty
size="xs"
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
className="euiFormControlLayout__append"
className="euiFormControlLayout__append kqlQueryBar__languageSwitcherButton"
data-test-subj={'switchQueryLanguageButton'}
>
{props.language === 'lucene' ? luceneLabel : kqlLabel}

View file

@ -69,6 +69,7 @@ interface Props {
export function QueryBarTopRow(props: Props) {
const [isDateRangeInvalid, setIsDateRangeInvalid] = useState(false);
const [isQueryInputFocused, setIsQueryInputFocused] = useState(false);
const kibana = useKibana<IDataPluginServices>();
const { uiSettings, notifications, storage, appName, docLinks } = kibana.services;
@ -107,6 +108,10 @@ export function QueryBarTopRow(props: Props) {
});
}
function onChangeQueryInputFocus(isFocused: boolean) {
setIsQueryInputFocused(isFocused);
}
function onTimeChange({
start,
end,
@ -182,6 +187,7 @@ export function QueryBarTopRow(props: Props) {
query={props.query!}
screenTitle={props.screenTitle}
onChange={onQueryChange}
onChangeQueryInputFocus={onChangeQueryInputFocus}
onSubmit={onInputSubmit}
persistedLog={persistedLog}
dataTestSubj={props.dataTestSubj}
@ -268,8 +274,12 @@ export function QueryBarTopRow(props: Props) {
};
});
const wrapperClasses = classNames('kbnQueryBar__datePickerWrapper', {
'kbnQueryBar__datePickerWrapper-isHidden': isQueryInputFocused,
});
return (
<EuiFlexItem className="kbnQueryBar__datePickerWrapper">
<EuiFlexItem className={wrapperClasses}>
<EuiSuperDatePicker
start={props.dateRangeFrom}
end={props.dateRangeTo}
@ -283,6 +293,7 @@ export function QueryBarTopRow(props: Props) {
commonlyUsedRanges={commonlyUsedRanges}
dateFormat={uiSettings!.get('dateFormat')}
isAutoRefreshOnly={props.showAutoRefreshOnly}
className="kbnQueryBar__datePicker"
/>
</EuiFlexItem>
);

View file

@ -23,7 +23,7 @@ import {
mockPersistedLogFactory,
} from './query_string_input.test.mocks';
import { EuiFieldText } from '@elastic/eui';
import { EuiTextArea } from '@elastic/eui';
import React from 'react';
import { QueryLanguageSwitcher } from './language_switcher';
import { QueryStringInput, QueryStringInputUI } from './query_string_input';
@ -102,7 +102,7 @@ describe('QueryStringInput', () => {
indexPatterns: [stubIndexPatternWithFields],
})
);
expect(component.find(EuiFieldText).props().value).toBe(kqlQuery.query);
expect(component.find(EuiTextArea).props().value).toBe(kqlQuery.query);
expect(component.find(QueryLanguageSwitcher).prop('language')).toBe(kqlQuery.language);
});
@ -117,7 +117,7 @@ describe('QueryStringInput', () => {
expect(component.find(QueryLanguageSwitcher).prop('language')).toBe(luceneQuery.language);
});
it('Should disable autoFocus on EuiFieldText when disableAutoFocus prop is true', () => {
it('Should disable autoFocus on EuiTextArea when disableAutoFocus prop is true', () => {
const component = mount(
wrapQueryStringInputInContext({
query: kqlQuery,
@ -126,7 +126,7 @@ describe('QueryStringInput', () => {
disableAutoFocus: true,
})
);
expect(component.find(EuiFieldText).prop('autoFocus')).toBeFalsy();
expect(component.find(EuiTextArea).prop('autoFocus')).toBeFalsy();
});
it('Should create a unique PersistedLog based on the appName and query language', () => {
@ -179,7 +179,7 @@ describe('QueryStringInput', () => {
const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI;
const input = instance.inputRef;
const inputWrapper = component.find(EuiFieldText).find('input');
const inputWrapper = component.find(EuiTextArea).find('textarea');
inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true });
expect(mockCallback).toHaveBeenCalledTimes(1);
@ -199,7 +199,7 @@ describe('QueryStringInput', () => {
const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI;
const input = instance.inputRef;
const inputWrapper = component.find(EuiFieldText).find('input');
const inputWrapper = component.find(EuiTextArea).find('textarea');
inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true });
expect(mockPersistedLog.add).toHaveBeenCalledWith('response:200');

View file

@ -22,13 +22,14 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiFieldText,
EuiTextArea,
EuiOutsideClickDetector,
PopoverAnchorPosition,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiLink,
htmlIdGenerator,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@ -49,13 +50,14 @@ interface Props {
query: Query;
disableAutoFocus?: boolean;
screenTitle?: string;
prepend?: React.ComponentProps<typeof EuiFieldText>['prepend'];
prepend?: any;
persistedLog?: PersistedLog;
bubbleSubmitEvent?: boolean;
placeholder?: string;
languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition;
onBlur?: () => void;
onChange?: (query: Query) => void;
onChangeQueryInputFocus?: (isFocused: boolean) => void;
onSubmit?: (query: Query) => void;
dataTestSubj?: string;
}
@ -93,7 +95,7 @@ export class QueryStringInputUI extends Component<Props, State> {
indexPatterns: [],
};
public inputRef: HTMLInputElement | null = null;
public inputRef: HTMLTextAreaElement | null = null;
private persistedLog: PersistedLog | undefined;
private abortController?: AbortController;
@ -223,27 +225,32 @@ export class QueryStringInputUI extends Component<Props, State> {
this.onChange({ query: value, language: this.props.query.language });
};
private onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
private onInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
this.onQueryStringChange(event.target.value);
if (event.target.value === '') {
this.handleRemoveHeight();
} else {
this.handleAutoHeight();
}
};
private onClickInput = (event: React.MouseEvent<HTMLInputElement>) => {
if (event.target instanceof HTMLInputElement) {
private onClickInput = (event: React.MouseEvent<HTMLTextAreaElement>) => {
if (event.target instanceof HTMLTextAreaElement) {
this.onQueryStringChange(event.target.value);
}
};
private onKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
private onKeyUp = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) {
this.setState({ isSuggestionsVisible: true });
if (event.target instanceof HTMLInputElement) {
if (event.target instanceof HTMLTextAreaElement) {
this.onQueryStringChange(event.target.value);
}
}
};
private onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.target instanceof HTMLInputElement) {
private onKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.target instanceof HTMLTextAreaElement) {
const { isSuggestionsVisible, index } = this.state;
const preventDefault = event.preventDefault.bind(event);
const { target, key, metaKey } = event;
@ -258,16 +265,19 @@ export class QueryStringInputUI extends Component<Props, State> {
switch (event.keyCode) {
case KEY_CODES.DOWN:
event.preventDefault();
if (isSuggestionsVisible && index !== null) {
event.preventDefault();
this.incrementIndex(index);
} else {
// Note to engineers. `isSuggestionVisible` does not mean the suggestions are visible.
// This should likely be fixed, it's more that suggestions can be shown.
} else if ((isSuggestionsVisible && index == null) || this.getQueryString() === '') {
event.preventDefault();
this.setState({ isSuggestionsVisible: true, index: 0 });
}
break;
case KEY_CODES.UP:
event.preventDefault();
if (isSuggestionsVisible && index !== null) {
event.preventDefault();
this.decrementIndex(index);
}
break;
@ -439,6 +449,17 @@ export class QueryStringInputUI extends Component<Props, State> {
if (this.state.isSuggestionsVisible) {
this.setState({ isSuggestionsVisible: false, index: null });
}
this.handleBlurHeight();
if (this.props.onChangeQueryInputFocus) {
this.props.onChangeQueryInputFocus(false);
}
};
private onInputBlur = () => {
this.handleBlurHeight();
if (this.props.onChangeQueryInputFocus) {
this.props.onChangeQueryInputFocus(false);
}
};
private onClickSuggestion = (suggestion: QuerySuggestion) => {
@ -460,6 +481,8 @@ export class QueryStringInputUI extends Component<Props, State> {
this.setState({ index });
};
textareaId = htmlIdGenerator()();
public componentDidMount() {
const parsedQuery = fromUser(toUser(this.props.query.query));
if (!isEqual(this.props.query.query, parsedQuery)) {
@ -468,6 +491,8 @@ export class QueryStringInputUI extends Component<Props, State> {
this.initPersistedLog();
this.fetchIndexPatterns().then(this.updateSuggestions);
window.addEventListener('resize', this.handleAutoHeight);
}
public componentDidUpdate(prevProps: Props) {
@ -485,15 +510,18 @@ export class QueryStringInputUI extends Component<Props, State> {
}
if (this.state.selectionStart !== null && this.state.selectionEnd !== null) {
if (this.inputRef) {
// For some reason the type guard above does not make the compiler happy
// @ts-ignore
if (this.inputRef != null) {
this.inputRef.setSelectionRange(this.state.selectionStart, this.state.selectionEnd);
}
this.setState({
selectionStart: null,
selectionEnd: null,
});
if (document.activeElement !== null && document.activeElement.id === this.textareaId) {
this.handleAutoHeight();
} else {
this.handleRemoveHeight();
}
}
}
@ -501,8 +529,37 @@ export class QueryStringInputUI extends Component<Props, State> {
if (this.abortController) this.abortController.abort();
this.updateSuggestions.cancel();
this.componentIsUnmounting = true;
window.removeEventListener('resize', this.handleAutoHeight);
}
handleAutoHeight = () => {
if (this.inputRef !== null && document.activeElement === this.inputRef) {
this.inputRef.style.setProperty('height', `${this.inputRef.scrollHeight}px`, 'important');
}
};
handleRemoveHeight = () => {
if (this.inputRef !== null) {
this.inputRef.style.removeProperty('height');
}
};
handleBlurHeight = () => {
if (this.inputRef !== null) {
this.handleRemoveHeight();
this.inputRef.scrollTop = 0;
}
};
handleOnFocus = () => {
if (this.props.onChangeQueryInputFocus) {
this.props.onChangeQueryInputFocus(true);
}
requestAnimationFrame(() => {
this.handleAutoHeight();
});
};
public render() {
const isSuggestionsVisible = this.state.isSuggestionsVisible && {
'aria-controls': 'kbnTypeahead__items',
@ -511,20 +568,24 @@ export class QueryStringInputUI extends Component<Props, State> {
const ariaCombobox = { ...isSuggestionsVisible, role: 'combobox' };
return (
<EuiOutsideClickDetector onOutsideClick={this.onOutsideClick}>
<div
{...ariaCombobox}
style={{ position: 'relative' }}
aria-label={i18n.translate('data.query.queryBar.comboboxAriaLabel', {
defaultMessage: 'Search and filter the {pageType} page',
values: { pageType: this.services.appName },
})}
aria-haspopup="true"
aria-expanded={this.state.isSuggestionsVisible}
>
<div role="search">
<div className="kuiLocalSearchAssistedInput">
<EuiFieldText
<div className="euiFormControlLayout euiFormControlLayout--group kbnQueryBar__wrap">
{this.props.prepend}
<EuiOutsideClickDetector onOutsideClick={this.onOutsideClick}>
<div
{...ariaCombobox}
style={{ position: 'relative', width: '100%' }}
aria-label={i18n.translate('data.query.queryBar.comboboxAriaLabel', {
defaultMessage: 'Search and filter the {pageType} page',
values: { pageType: this.services.appName },
})}
aria-haspopup="true"
aria-expanded={this.state.isSuggestionsVisible}
>
<div
role="search"
className="euiFormControlLayout__childrenWrapper kuiLocalSearchAssistedInput"
>
<EuiTextArea
placeholder={
this.props.placeholder ||
i18n.translate('data.query.queryBar.searchInputPlaceholder', {
@ -536,10 +597,16 @@ export class QueryStringInputUI extends Component<Props, State> {
onKeyUp={this.onKeyUp}
onChange={this.onInputChange}
onClick={this.onClickInput}
onBlur={this.props.onBlur}
onBlur={this.onInputBlur}
onFocus={this.handleOnFocus}
className="kbnQueryBar__textarea"
fullWidth
autoFocus={!this.props.disableAutoFocus}
inputRef={(node) => {
rows={1}
id={this.textareaId}
autoFocus={
this.props.onChangeQueryInputFocus ? false : !this.props.disableAutoFocus
}
inputRef={(node: any) => {
if (node) {
this.inputRef = node;
}
@ -550,7 +617,6 @@ export class QueryStringInputUI extends Component<Props, State> {
defaultMessage: 'Start typing to search and filter the {pageType} page',
values: { pageType: this.services.appName },
})}
type="text"
aria-autocomplete="list"
aria-controls={this.state.isSuggestionsVisible ? 'kbnTypeahead__items' : undefined}
aria-activedescendant={
@ -559,29 +625,29 @@ export class QueryStringInputUI extends Component<Props, State> {
: undefined
}
role="textbox"
prepend={this.props.prepend}
append={
<QueryLanguageSwitcher
language={this.props.query.language}
anchorPosition={this.props.languageSwitcherPopoverAnchorPosition}
onSelectLanguage={this.onSelectLanguage}
/>
}
data-test-subj={this.props.dataTestSubj || 'queryInput'}
/>
>
{this.getQueryString()}
</EuiTextArea>
</div>
</div>
<SuggestionsComponent
show={this.state.isSuggestionsVisible}
suggestions={this.state.suggestions.slice(0, this.state.suggestionLimit)}
index={this.state.index}
onClick={this.onClickSuggestion}
onMouseEnter={this.onMouseEnterSuggestion}
loadMore={this.increaseLimit}
/>
</div>
</EuiOutsideClickDetector>
<SuggestionsComponent
show={this.state.isSuggestionsVisible}
suggestions={this.state.suggestions.slice(0, this.state.suggestionLimit)}
index={this.state.index}
onClick={this.onClickSuggestion}
onMouseEnter={this.onMouseEnterSuggestion}
loadMore={this.increaseLimit}
/>
</div>
</EuiOutsideClickDetector>
<QueryLanguageSwitcher
language={this.props.query.language}
anchorPosition={this.props.languageSwitcherPopoverAnchorPosition}
onSelectLanguage={this.onSelectLanguage}
/>
</div>
);
}
}

View file

@ -16,7 +16,7 @@ $kbnTypeaheadTypes: (
color: $euiTextColor;
background-color: $euiColorEmptyShade;
position: absolute;
top: -1px;
top: -2px;
z-index: $euiZContentMenu;
width: 100%;
border-bottom-left-radius: $euiBorderRadius;
@ -56,7 +56,6 @@ $kbnTypeaheadTypes: (
.kbnTypeahead__item.active {
background-color: $euiColorLightestShade;
.kbnSuggestionItem__callout {
background: $euiColorEmptyShade;
}
@ -130,7 +129,6 @@ $kbnTypeaheadTypes: (
align-items: center;
}
.kbnSuggestionItem__text {
flex-grow: 0; /* 2 */
flex-basis: auto; /* 2 */
@ -142,16 +140,15 @@ $kbnTypeaheadTypes: (
color: $euiTextColor;
}
.kbnSuggestionItem__description {
color: $euiColorDarkShade;
overflow: hidden;
text-overflow: ellipsis;
margin-left: $euiSizeXL;
&:empty {
flex-grow: 0;
margin-left:0;
margin-left: 0;
}
}

View file

@ -218,6 +218,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.common.navigateToApp('discover');
await PageObjects.header.awaitKibanaChrome();
await queryBar.setQuery('');
// To remove focus of the of the search bar so date/time picker can show
await PageObjects.discover.selectIndexPattern(defaultSettings.defaultIndex);
await PageObjects.timePicker.setDefaultAbsoluteRange();
log.debug(

View file

@ -99,6 +99,6 @@ describe('Cases', () => {
cy.get(TIMELINE_TITLE).should('have.attr', 'value', case1.timeline.title);
cy.get(TIMELINE_DESCRIPTION).should('have.attr', 'value', case1.timeline.description);
cy.get(TIMELINE_QUERY).should('have.attr', 'value', case1.timeline.query);
cy.get(TIMELINE_QUERY).invoke('text').should('eq', case1.timeline.query);
});
});

View file

@ -27,74 +27,67 @@ import {
describe('ml conditional links', () => {
it('sets the KQL from a single IP with a value for the query', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'(process.name: "conhost.exe" or process.name: "sc.exe")'
);
cy.get(KQL_INPUT)
.invoke('text')
.should('eq', '(process.name: "conhost.exe" or process.name: "sc.exe")');
});
it('sets the KQL from a multiple IPs with a null for the query', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2"))'
);
cy.get(KQL_INPUT)
.invoke('text')
.should(
'eq',
'((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2"))'
);
});
it('sets the KQL from a multiple IPs with a value for the query', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2")) and ((process.name: "conhost.exe" or process.name: "sc.exe"))'
);
cy.get(KQL_INPUT)
.invoke('text')
.should(
'eq',
'((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2")) and ((process.name: "conhost.exe" or process.name: "sc.exe"))'
);
});
it('sets the KQL from a $ip$ with a value for the query', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'(process.name: "conhost.exe" or process.name: "sc.exe")'
);
cy.get(KQL_INPUT)
.invoke('text')
.should('eq', '(process.name: "conhost.exe" or process.name: "sc.exe")');
});
it('sets the KQL from a single host name with a value for query', () => {
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'(process.name: "conhost.exe" or process.name: "sc.exe")'
);
cy.get(KQL_INPUT)
.invoke('text')
.should('eq', '(process.name: "conhost.exe" or process.name: "sc.exe")');
});
it('sets the KQL from a multiple host names with null for query', () => {
loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'(host.name: "siem-windows" or host.name: "siem-suricata")'
);
cy.get(KQL_INPUT)
.invoke('text')
.should('eq', '(host.name: "siem-windows" or host.name: "siem-suricata")');
});
it('sets the KQL from a multiple host names with a value for query', () => {
loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'(host.name: "siem-windows" or host.name: "siem-suricata") and ((process.name: "conhost.exe" or process.name: "sc.exe"))'
);
cy.get(KQL_INPUT)
.invoke('text')
.should(
'eq',
'(host.name: "siem-windows" or host.name: "siem-suricata") and ((process.name: "conhost.exe" or process.name: "sc.exe"))'
);
});
it('sets the KQL from a undefined/null host name but with a value for query', () => {
loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery);
cy.get(KQL_INPUT).should(
'have.attr',
'value',
'(process.name: "conhost.exe" or process.name: "sc.exe")'
);
cy.get(KQL_INPUT)
.invoke('text')
.should('eq', '(process.name: "conhost.exe" or process.name: "sc.exe")');
});
it('redirects from a single IP with a null for the query', () => {

View file

@ -154,12 +154,12 @@ describe('url state', () => {
it('sets kql on network page', () => {
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlNetworkNetwork);
cy.get(KQL_INPUT).should('have.attr', 'value', 'source.ip: "10.142.0.9"');
cy.get(KQL_INPUT).invoke('text').should('eq', 'source.ip: "10.142.0.9"');
});
it('sets kql on hosts page', () => {
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts);
cy.get(KQL_INPUT).should('have.attr', 'value', 'source.ip: "10.142.0.9"');
cy.get(KQL_INPUT).invoke('text').should('eq', 'source.ip: "10.142.0.9"');
});
it('sets the url state when kql is set', () => {
@ -230,8 +230,7 @@ describe('url state', () => {
it('Do not clears kql when navigating to a new page', () => {
loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts);
navigateFromHeaderTo(NETWORK);
cy.get(KQL_INPUT).should('have.attr', 'value', 'source.ip: "10.142.0.9"');
cy.get(KQL_INPUT).invoke('text').should('eq', 'source.ip: "10.142.0.9"');
});
it.skip('sets and reads the url state for timeline by id', () => {

View file

@ -82,7 +82,7 @@ export const fillAboutRuleAndContinue = (rule: CustomRule | MachineLearningRule)
export const fillDefineCustomRuleAndContinue = (rule: CustomRule) => {
cy.get(CUSTOM_QUERY_INPUT).type(rule.customQuery);
cy.get(CUSTOM_QUERY_INPUT).should('have.attr', 'value', rule.customQuery);
cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', rule.customQuery);
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
cy.get(CUSTOM_QUERY_INPUT).should('not.exist');
@ -91,7 +91,7 @@ export const fillDefineCustomRuleAndContinue = (rule: CustomRule) => {
export const fillDefineCustomRuleWithImportedQueryAndContinue = (rule: CustomRule) => {
cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click();
cy.get(TIMELINE(rule.timelineId)).click();
cy.get(CUSTOM_QUERY_INPUT).should('have.attr', 'value', rule.customQuery);
cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', rule.customQuery);
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
cy.get(CUSTOM_QUERY_INPUT).should('not.exist');

View file

@ -58,7 +58,7 @@ export const createNewTimeline = () => {
};
export const executeTimelineKQL = (query: string) => {
cy.get(`${SEARCH_OR_FILTER_CONTAINER} input`).type(`${query} {enter}`);
cy.get(`${SEARCH_OR_FILTER_CONTAINER} textarea`).type(`${query} {enter}`);
};
export const expandFirstTimelineEventDetails = () => {

View file

@ -214,15 +214,18 @@ describe('QueryBar ', () => {
/>
);
const queryInput = wrapper.find(QueryBar).find('input[data-test-subj="queryInput"]');
let queryInput = wrapper.find(QueryBar).find('textarea[data-test-subj="queryInput"]');
queryInput.simulate('change', { target: { value: 'host.name:*' } });
expect(queryInput.html()).toContain('value="host.name:*"');
wrapper.update();
queryInput = wrapper.find(QueryBar).find('textarea[data-test-subj="queryInput"]');
expect(queryInput.props().children).toBe('host.name:*');
wrapper.setProps({ filterQueryDraft: null });
wrapper.update();
queryInput = wrapper.find(QueryBar).find('textarea[data-test-subj="queryInput"]');
expect(queryInput.html()).toContain('value=""');
expect(queryInput.props().children).toBe('');
});
});
@ -258,7 +261,7 @@ describe('QueryBar ', () => {
const onSubmitQueryRef = searchBarProps.onQuerySubmit;
const onSavedQueryRef = searchBarProps.onSavedQueryUpdated;
const queryInput = wrapper.find(QueryBar).find('input[data-test-subj="queryInput"]');
const queryInput = wrapper.find(QueryBar).find('textarea[data-test-subj="queryInput"]');
queryInput.simulate('change', { target: { value: 'hello: world' } });
wrapper.update();