From 6ebe65b0362ffbb779e542ccbcfbd9e9e9eae397 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 26 Jun 2020 16:28:04 -0700 Subject: [PATCH 01/13] Multiline kql bar --- .../ui/query_string_input/_query_bar.scss | 31 ++++ .../query_string_input/query_bar_top_row.tsx | 11 +- .../query_string_input/query_string_input.tsx | 144 ++++++++++++------ .../data/public/ui/typeahead/_suggestion.scss | 9 +- 4 files changed, 141 insertions(+), 54 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index f95fe748dfdae..888e897b2eba7 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -1,3 +1,34 @@ +.kbnQueryBar__wrap { + max-width: 100%; + z-index: $euiZContentMenu; +} + +.kbnQueryBar__textarea { + z-index: $euiZContentMenu; + resize: none !important; // When in the group, it will autosize + height: $euiSizeXXL; + padding-top: $euiSizeS + 1px !important; + transform: translateY(-2px); + padding: $euiSizeS - 1px; // Accounts for the border from the control group + + &: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 { diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index f65bf97e391e2..46387bb7e2f84 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -67,6 +67,7 @@ interface Props { export function QueryBarTopRow(props: Props) { const [isDateRangeInvalid, setIsDateRangeInvalid] = useState(false); + const [isQueryInputFocused, setIsQueryInputFocused] = useState(false); const kibana = useKibana(); const { uiSettings, notifications, storage, appName, docLinks } = kibana.services; @@ -105,6 +106,10 @@ export function QueryBarTopRow(props: Props) { }); } + function onChangeQueryInputFocus(isFocused: boolean) { + setIsQueryInputFocused(isFocused); + } + function onTimeChange({ start, end, @@ -180,6 +185,7 @@ export function QueryBarTopRow(props: Props) { query={props.query!} screenTitle={props.screenTitle} onChange={onQueryChange} + onChangeQueryInputFocus={onChangeQueryInputFocus} onSubmit={onInputSubmit} persistedLog={persistedLog} dataTestSubj={props.dataTestSubj} @@ -265,7 +271,10 @@ export function QueryBarTopRow(props: Props) { }); return ( - + ['prepend']; + prepend?: any; persistedLog?: PersistedLog; bubbleSubmitEvent?: boolean; placeholder?: string; languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; onChange?: (query: Query) => void; + onChangeQueryInputFocus?: (isFocused: boolean) => void; onSubmit?: (query: Query) => void; dataTestSubj?: string; } @@ -92,7 +93,7 @@ export class QueryStringInputUI extends Component { indexPatterns: [], }; - public inputRef: HTMLInputElement | null = null; + public inputRef: HTMLTextAreaElement | null = null; private persistedLog: PersistedLog | undefined; private abortController: AbortController | undefined; @@ -222,27 +223,32 @@ export class QueryStringInputUI extends Component { this.onChange({ query: value, language: this.props.query.language }); }; - private onInputChange = (event: React.ChangeEvent) => { + private onInputChange = (event: React.ChangeEvent) => { this.onQueryStringChange(event.target.value); + if (event.target.value === '') { + this.handleRemoveHeight(); + } else { + this.handleAutoHeight(); + } }; - private onClickInput = (event: React.MouseEvent) => { - if (event.target instanceof HTMLInputElement) { + private onClickInput = (event: React.MouseEvent) => { + if (event.target instanceof HTMLTextAreaElement) { this.onQueryStringChange(event.target.value); } }; - private onKeyUp = (event: React.KeyboardEvent) => { + private onKeyUp = (event: React.KeyboardEvent) => { 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) => { - if (event.target instanceof HTMLInputElement) { + private onKeyDown = (event: React.KeyboardEvent) => { + if (event.target instanceof HTMLTextAreaElement) { const { isSuggestionsVisible, index } = this.state; const preventDefault = event.preventDefault.bind(event); const { target, key, metaKey } = event; @@ -438,6 +444,10 @@ export class QueryStringInputUI extends Component { if (this.state.isSuggestionsVisible) { this.setState({ isSuggestionsVisible: false, index: null }); } + this.handleBlurHeight(); + if (this.props.onChangeQueryInputFocus) { + this.props.onChangeQueryInputFocus(false); + } }; private onClickSuggestion = (suggestion: QuerySuggestion) => { @@ -493,6 +503,11 @@ export class QueryStringInputUI extends Component { selectionStart: null, selectionEnd: null, }); + if (document.activeElement !== null && document.activeElement.id === 'kqlbar_id') { + this.handleAutoHeight(); + } else { + this.handleRemoveHeight(); + } } } @@ -501,6 +516,32 @@ export class QueryStringInputUI extends Component { this.componentIsUnmounting = true; } + handleAutoHeight = () => { + if (this.inputRef !== null) { + 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 = () => { + this.handleAutoHeight(); + if (this.props.onChangeQueryInputFocus) { + this.props.onChangeQueryInputFocus(true); + } + }; + public render() { const isSuggestionsVisible = this.state.isSuggestionsVisible && { 'aria-controls': 'kbnTypeahead__items', @@ -509,20 +550,24 @@ export class QueryStringInputUI extends Component { const ariaCombobox = { ...isSuggestionsVisible, role: 'combobox' }; return ( - -
-
-
- + {this.props.prepend} + +
+
+ { onKeyUp={this.onKeyUp} onChange={this.onInputChange} onClick={this.onClickInput} + onFocus={this.handleOnFocus} + className="kbnQueryBar__textarea" fullWidth - autoFocus={!this.props.disableAutoFocus} - inputRef={(node) => { + rows={1} + id="kqlbar_id" + autoFocus={ + this.props.onChangeQueryInputFocus ? false : !this.props.disableAutoFocus + } + inputRef={(node: any) => { if (node) { this.inputRef = node; } @@ -547,7 +598,6 @@ export class QueryStringInputUI extends Component { 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={ @@ -556,29 +606,29 @@ export class QueryStringInputUI extends Component { : undefined } role="textbox" - prepend={this.props.prepend} - append={ - - } data-test-subj={this.props.dataTestSubj || 'queryInput'} - /> + > + {this.getQueryString()} +
-
- -
- + +
+ + + +
); } } diff --git a/src/plugins/data/public/ui/typeahead/_suggestion.scss b/src/plugins/data/public/ui/typeahead/_suggestion.scss index 3a215ceddcd00..81c05f1a8a78c 100644 --- a/src/plugins/data/public/ui/typeahead/_suggestion.scss +++ b/src/plugins/data/public/ui/typeahead/_suggestion.scss @@ -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; } } From 5fc0bc408feddcea74dce2be9f110ede4f56d6ba Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 26 Jun 2020 16:59:36 -0700 Subject: [PATCH 02/13] fix id --- .../ui/query_string_input/query_string_input.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 9fc786fa5c1d2..52d052d44cbe0 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -29,6 +29,7 @@ import { EuiFlexItem, EuiButton, EuiLink, + htmlIdGenerator, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -469,6 +470,8 @@ export class QueryStringInputUI extends Component { this.setState({ index }); }; + textareaId = htmlIdGenerator()(); + public componentDidMount() { const parsedQuery = fromUser(toUser(this.props.query.query)); if (!isEqual(this.props.query.query, parsedQuery)) { @@ -503,7 +506,7 @@ export class QueryStringInputUI extends Component { selectionStart: null, selectionEnd: null, }); - if (document.activeElement !== null && document.activeElement.id === 'kqlbar_id') { + if (document.activeElement !== null && document.activeElement.id === this.textareaId) { this.handleAutoHeight(); } else { this.handleRemoveHeight(); @@ -536,10 +539,12 @@ export class QueryStringInputUI extends Component { }; handleOnFocus = () => { - this.handleAutoHeight(); if (this.props.onChangeQueryInputFocus) { this.props.onChangeQueryInputFocus(true); } + requestAnimationFrame(() => { + this.handleAutoHeight(); + }); }; public render() { @@ -583,7 +588,7 @@ export class QueryStringInputUI extends Component { className="kbnQueryBar__textarea" fullWidth rows={1} - id="kqlbar_id" + id={this.textareaId} autoFocus={ this.props.onChangeQueryInputFocus ? false : !this.props.disableAutoFocus } From 657f62451933c6f741aa48ba4e4ca378e4ff18ac Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Mon, 29 Jun 2020 16:40:56 -0700 Subject: [PATCH 03/13] use visibility rather than display to hide stuff, cross fingers for tests --- .../public/ui/query_string_input/_query_bar.scss | 12 ++++++++++-- .../ui/query_string_input/query_bar_top_row.tsx | 9 +++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index 888e897b2eba7..508cc7e30b081 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -7,9 +7,11 @@ z-index: $euiZContentMenu; resize: none !important; // When in the group, it will autosize height: $euiSizeXXL; - padding-top: $euiSizeS + 1px !important; + // 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; // Accounts for the border from the control group + padding: $euiSizeS - 1px; &:not(:focus) { @include euiYScrollWithShadows; @@ -47,5 +49,11 @@ // sass-lint:disable-block no-important flex-grow: 0 !important; flex-basis: auto !important; + + &.kbnQueryBar__datePickerWrapper-isHidden { + width: 0; + overflow: hidden; + visibility: hidden; + } } } diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index 46387bb7e2f84..caa8fbb3f6a13 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -270,11 +270,12 @@ export function QueryBarTopRow(props: Props) { }; }); + const wrapperClasses = classNames('kbnQueryBar__datePickerWrapper', { + 'kbnQueryBar__datePickerWrapper-isHidden': isQueryInputFocused, + }); + return ( - + Date: Mon, 29 Jun 2020 19:07:20 -0700 Subject: [PATCH 04/13] another vis trick for tests --- src/plugins/data/public/ui/query_string_input/_query_bar.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index 508cc7e30b081..8ea178a2b1289 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -53,7 +53,8 @@ &.kbnQueryBar__datePickerWrapper-isHidden { width: 0; overflow: hidden; - visibility: hidden; + margin-right: 0; + margin-left: 0; } } } From e94cc023a339e979435ca1ed67c624f5e842d29e Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Tue, 30 Jun 2020 10:19:21 -0700 Subject: [PATCH 05/13] quasi fix tests, still some failures --- .../public/common/components/query_bar/index.test.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index f079715baec1c..9fc3f8176b275 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -214,15 +214,17 @@ describe('QueryBar ', () => { /> ); - 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: 'host.name:*' } }); - expect(queryInput.html()).toContain('value="host.name:*"'); + wrapper.update(); + queryInput.update(); + expect(queryInput.props().children).toBe('"host.name:*"'); wrapper.setProps({ filterQueryDraft: null }); wrapper.update(); - expect(queryInput.html()).toContain('value=""'); + expect(queryInput.props().children).toBe('""'); }); }); @@ -258,7 +260,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(); From 9b8b57a6c57db9b3d4e13e87e7b9b925039709b4 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Tue, 30 Jun 2020 14:43:08 -0700 Subject: [PATCH 06/13] caroline feedback --- .../ui/query_string_input/_query_bar.scss | 8 ++++++-- .../query_string_input/language_switcher.tsx | 2 +- .../query_string_input/query_bar_top_row.tsx | 1 + .../query_string_input/query_string_input.tsx | 18 +++++++++++++++--- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index 8ea178a2b1289..007be9da63e49 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -3,6 +3,11 @@ 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 @@ -49,12 +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; - margin-right: 0; - margin-left: 0; } } } diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx index ded48d462722d..4a1fa036cbd79 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -60,7 +60,7 @@ export function QueryLanguageSwitcher(props: Props) { setIsPopoverOpen(!isPopoverOpen)} - className="euiFormControlLayout__append" + className="euiFormControlLayout__append kqlQueryBar__languageSwitcherButton" data-test-subj={'switchQueryLanguageButton'} > {props.language === 'lucene' ? luceneLabel : kqlLabel} diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index caa8fbb3f6a13..5b3defcd750a6 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -289,6 +289,7 @@ export function QueryBarTopRow(props: Props) { commonlyUsedRanges={commonlyUsedRanges} dateFormat={uiSettings!.get('dateFormat')} isAutoRefreshOnly={props.showAutoRefreshOnly} + className="kbnQueryBar__datePicker" /> ); diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 52d052d44cbe0..3135d7d4d6474 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -264,16 +264,17 @@ export class QueryStringInputUI extends Component { switch (event.keyCode) { case KEY_CODES.DOWN: - event.preventDefault(); if (isSuggestionsVisible && index !== null) { + event.preventDefault(); this.incrementIndex(index); - } else { + } else if (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; @@ -451,6 +452,16 @@ export class QueryStringInputUI extends Component { } }; + private onInputBlur = () => { + if (this.state.isSuggestionsVisible) { + this.setState({ isSuggestionsVisible: false, index: null }); + } + this.handleBlurHeight(); + if (this.props.onChangeQueryInputFocus) { + this.props.onChangeQueryInputFocus(false); + } + }; + private onClickSuggestion = (suggestion: QuerySuggestion) => { if (!this.inputRef) { return; @@ -584,6 +595,7 @@ export class QueryStringInputUI extends Component { onKeyUp={this.onKeyUp} onChange={this.onInputChange} onClick={this.onClickInput} + onBlur={this.onInputBlur} onFocus={this.handleOnFocus} className="kbnQueryBar__textarea" fullWidth From 5e4551a79bfa28c576d1fbcf25c5434544228837 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Tue, 30 Jun 2020 16:44:10 -0700 Subject: [PATCH 07/13] fun! --- .../data/public/ui/query_string_input/query_string_input.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 3135d7d4d6474..372bad9329eae 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -267,7 +267,9 @@ export class QueryStringInputUI extends Component { if (isSuggestionsVisible && index !== null) { event.preventDefault(); this.incrementIndex(index); - } else if (this.getQueryString() === '') { + // 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 }); } From a90b77bec886fadd2975c9fa7738c1cb1eb3be4c Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Wed, 1 Jul 2020 10:11:32 -0700 Subject: [PATCH 08/13] fix for mouse --- .../data/public/ui/query_string_input/query_string_input.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 372bad9329eae..fcb971964a667 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -455,9 +455,6 @@ export class QueryStringInputUI extends Component { }; private onInputBlur = () => { - if (this.state.isSuggestionsVisible) { - this.setState({ isSuggestionsVisible: false, index: null }); - } this.handleBlurHeight(); if (this.props.onChangeQueryInputFocus) { this.props.onChangeQueryInputFocus(false); @@ -512,7 +509,7 @@ export class QueryStringInputUI extends Component { 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 + // @ts-expect-error this.inputRef.setSelectionRange(this.state.selectionStart, this.state.selectionEnd); } this.setState({ From 7ce67a9518411a5659fdf804a29c73974dc753cc Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 1 Jul 2020 14:17:30 -0400 Subject: [PATCH 09/13] fix test --- .../public/ui/query_string_input/query_string_input.tsx | 4 +--- test/functional/apps/discover/_discover.js | 2 ++ .../public/common/components/query_bar/index.test.tsx | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index fcb971964a667..1be6f8fe32682 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -507,9 +507,7 @@ export class QueryStringInputUI extends Component { } 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-expect-error + if (this.inputRef != null) { this.inputRef.setSelectionRange(this.state.selectionStart, this.state.selectionEnd); } this.setState({ diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 906f0b83e99e7..949a01ff7873a 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -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( diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index 9fc3f8176b275..cc8a6d156aaa6 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -214,17 +214,18 @@ describe('QueryBar ', () => { /> ); - const queryInput = wrapper.find(QueryBar).find('textarea[data-test-subj="queryInput"]'); + let queryInput = wrapper.find(QueryBar).find('textarea[data-test-subj="queryInput"]'); queryInput.simulate('change', { target: { value: 'host.name:*' } }); wrapper.update(); - queryInput.update(); - expect(queryInput.props().children).toBe('"host.name:*"'); + 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.props().children).toBe('""'); + expect(queryInput.props().children).toBe(''); }); }); From 00f297b668417255b63f95c62b8a5ee93a7ab780 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Thu, 2 Jul 2020 11:23:49 -0400 Subject: [PATCH 10/13] check api --- .../kibana-plugin-plugins-data-public.querystringinput.md | 2 +- src/plugins/data/public/public.api.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index a25f4a0c373b2..617be9bf26294 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index f19611bc1d526..47f18d74d4d60 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -50,7 +50,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'; @@ -1536,7 +1535,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>; +export const QueryStringInput: React.FC>; // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; From 1b72ca4a94e15ad519647a7ac0f0751ec75dcbdf Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Thu, 2 Jul 2020 17:11:08 -0400 Subject: [PATCH 11/13] fix unit test on query_string_input --- .../query_string_input/query_string_input.test.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx index 755716aee8f48..0397c34d0c2b8 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx @@ -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'); From 1c75023358d8f5b0e0207a56aec048eaaac36b8c Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Thu, 2 Jul 2020 23:37:02 -0400 Subject: [PATCH 12/13] Fix cypress test --- .../cypress/integration/cases.spec.ts | 2 +- .../integration/ml_conditional_links.spec.ts | 73 +++++++++---------- .../cypress/integration/url_state.spec.ts | 7 +- .../cypress/tasks/create_new_rule.ts | 4 +- .../cypress/tasks/timeline.ts | 2 +- 5 files changed, 40 insertions(+), 48 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts index efd9ece8aec56..9438c28f05fef 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -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); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts index 0c3424576e4cf..6b3fc9e751ea4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts @@ -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', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index a3a927cbea7d4..81af9ece9ed45 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -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', () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index eca5885e7b3d9..88ae582b58891 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -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'); diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 9e17433090c2b..761fd2c1e6a0b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -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 = () => { From 35cb80217b50f5f911b922feb3e267f49ff1d44c Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 6 Jul 2020 12:05:10 -0400 Subject: [PATCH 13/13] handle the resize of the height of the textarea when the window have been resize --- .../data/public/ui/query_string_input/query_string_input.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index c0b817d529484..a436521a5a9df 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -490,6 +490,8 @@ export class QueryStringInputUI extends Component { this.initPersistedLog(); this.fetchIndexPatterns().then(this.updateSuggestions); + + window.addEventListener('resize', this.handleAutoHeight); } public componentDidUpdate(prevProps: Props) { @@ -526,10 +528,11 @@ export class QueryStringInputUI extends Component { if (this.abortController) this.abortController.abort(); this.updateSuggestions.cancel(); this.componentIsUnmounting = true; + window.removeEventListener('resize', this.handleAutoHeight); } handleAutoHeight = () => { - if (this.inputRef !== null) { + if (this.inputRef !== null && document.activeElement === this.inputRef) { this.inputRef.style.setProperty('height', `${this.inputRef.scrollHeight}px`, 'important'); } };