Skip to content

Commit b2b8dd2

Browse files
[ES|QL] Simplified search visor (elastic#242123)
## Summary Closes elastic#220050 <img width="1509" height="224" alt="image" src="https://github.com/user-attachments/assets/3634ceb8-c335-4be6-a8fc-5c480995229b" /> Adds a quick search functionality in the editor. The visor: - doesn't have a double biding functionality. It is an one off feature. You use it to update your query but it is not updated if the query changes. Only the indices - It closes if the user starts typing in the editor - It updates the query with the KQL function, it doesnt continue the current query, it overwrites it ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 5106829 commit b2b8dd2

16 files changed

Lines changed: 980 additions & 31 deletions

src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { cssFavoriteHoverWithinEuiTableRow } from '@kbn/content-management-favor
3232
import { FAVORITES_LIMIT as ESQL_STARRED_QUERIES_LIMIT } from '@kbn/content-management-favorites-common';
3333
import type { Interpolation, Theme } from '@emotion/react';
3434
import { css } from '@emotion/react';
35+
import { QuerySource } from '@kbn/esql-types/src/esql_telemetry_types';
3536
import {
3637
type QueryHistoryItem,
3738
getHistoryItems,
@@ -235,7 +236,7 @@ export function QueryList({
235236
listItems: QueryHistoryItem[];
236237
containerCSS: Interpolation<Theme>;
237238
containerWidth: number;
238-
onUpdateAndSubmit: (qs: string, isStarred: boolean) => void;
239+
onUpdateAndSubmit: (qs: string, querySource: QuerySource) => void;
239240
height: number;
240241
starredQueriesService?: EsqlStarredQueriesService;
241242
tableCaption?: string;
@@ -305,7 +306,12 @@ export function QueryList({
305306
data-test-subj="ESQLEditor-history-starred-queries-run-button"
306307
role="button"
307308
iconSize="m"
308-
onClick={() => onUpdateAndSubmit(item.queryString, isStarred)}
309+
onClick={() =>
310+
onUpdateAndSubmit(
311+
item.queryString,
312+
isStarred ? QuerySource.STARRED : QuerySource.HISTORY
313+
)
314+
}
309315
css={css`
310316
cursor: pointer;
311317
`}
@@ -493,7 +499,7 @@ export function HistoryAndStarredQueriesTabs({
493499
}: {
494500
containerCSS: Interpolation<Theme>;
495501
containerWidth: number;
496-
onUpdateAndSubmit: (qs: string, isStarred: boolean) => void;
502+
onUpdateAndSubmit: (qs: string, querySource: QuerySource) => void;
497503
isSpaceReduced?: boolean;
498504
height: number;
499505
}) {

src/platform/packages/private/kbn-esql-editor/src/editor_footer/index.tsx

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@ import {
2727
} from '@kbn/language-documentation';
2828
import React, { memo, useCallback, useMemo, useState } from 'react';
2929
import type { MonacoMessage } from '@kbn/monaco/src/languages/esql/language';
30-
import type { TelemetryQuerySubmittedProps } from '@kbn/esql-types/src/esql_telemetry_types';
31-
import { QuerySource } from '@kbn/esql-types/src/esql_telemetry_types';
30+
import type { QuerySource } from '@kbn/esql-types/src/esql_telemetry_types';
3231
import type { DataErrorsControl, ESQLEditorDeps } from '../types';
3332
import { ErrorsWarningsFooterPopover } from './errors_warnings_popover';
3433
import { HistoryAndStarredQueriesTabs, QueryHistoryAction } from './history_starred_queries';
3534
import { KeyboardShortcuts } from './keyboard_shortcuts';
3635
import { QueryWrapComponent } from './query_wrap_component';
37-
import type { ESQLEditorTelemetryService } from '../telemetry/telemetry_service';
36+
import { QuickSearchAction } from '../editor_visor/quick_search_action';
3837

3938
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
4039
const COMMAND_KEY = isMac ? '⌘' : '^';
@@ -50,7 +49,7 @@ interface EditorFooterProps {
5049
warnings?: MonacoMessage[];
5150
detectedTimestamp?: string;
5251
onErrorClick: (error: MonacoMessage) => void;
53-
runQuery: (source: TelemetryQuerySubmittedProps['source']) => void;
52+
onUpdateAndSubmitQuery: (newQuery: string, querySource: QuerySource) => void;
5453
updateQuery: (qs: string) => void;
5554
isHistoryOpen: boolean;
5655
setIsHistoryOpen: (status: boolean) => void;
@@ -64,9 +63,10 @@ interface EditorFooterProps {
6463
isSpaceReduced?: boolean;
6564
hideTimeFilterInfo?: boolean;
6665
hideQueryHistory?: boolean;
66+
hideQuickSearch?: boolean;
6767
displayDocumentationAsFlyout?: boolean;
6868
dataErrorsControl?: DataErrorsControl;
69-
telemetryService: ESQLEditorTelemetryService;
69+
toggleVisor: () => void;
7070
}
7171

7272
export const EditorFooter = memo(function EditorFooter({
@@ -76,7 +76,7 @@ export const EditorFooter = memo(function EditorFooter({
7676
warnings,
7777
detectedTimestamp,
7878
onErrorClick,
79-
runQuery,
79+
onUpdateAndSubmitQuery,
8080
updateQuery,
8181
hideRunQueryText,
8282
editorIsInline,
@@ -89,34 +89,18 @@ export const EditorFooter = memo(function EditorFooter({
8989
isLanguageComponentOpen,
9090
setIsLanguageComponentOpen,
9191
hideQueryHistory,
92+
hideQuickSearch,
9293
displayDocumentationAsFlyout,
9394
measuredContainerWidth,
9495
code,
9596
dataErrorsControl,
96-
telemetryService,
97+
toggleVisor,
9798
}: EditorFooterProps) {
9899
const kibana = useKibana<ESQLEditorDeps>();
99100
const { docLinks } = kibana.services;
100101
const [isErrorPopoverOpen, setIsErrorPopoverOpen] = useState(false);
101102
const [isWarningPopoverOpen, setIsWarningPopoverOpen] = useState(false);
102103

103-
const onUpdateAndSubmit = useCallback(
104-
(qs: string, isStarred: boolean) => {
105-
// notify telemetry that a query has been submitted from the history panel
106-
telemetryService.trackQueryHistoryClicked(isStarred);
107-
// update the query first
108-
updateQuery(qs);
109-
// submit the query with some latency
110-
// if I do it immediately there is some race condition until
111-
// the state is updated and it won't be sumbitted correctly
112-
const source = isStarred ? QuerySource.STARRED : QuerySource.HISTORY;
113-
setTimeout(() => {
114-
runQuery(source);
115-
}, 300);
116-
},
117-
[runQuery, updateQuery, telemetryService]
118-
);
119-
120104
const toggleHistoryComponent = useCallback(() => {
121105
setIsHistoryOpen(!isHistoryOpen);
122106
setIsLanguageComponentOpen(false);
@@ -243,6 +227,7 @@ export const EditorFooter = memo(function EditorFooter({
243227
<EuiFlexGroup gutterSize="xs" responsive={false} alignItems="center">
244228
{!Boolean(editorIsInline) && (
245229
<>
230+
{!hideQuickSearch && <QuickSearchAction toggleVisor={toggleVisor} />}
246231
{!hideQueryHistory && (
247232
<QueryHistoryAction
248233
toggleHistory={() => setIsHistoryOpen(!isHistoryOpen)}
@@ -304,6 +289,9 @@ export const EditorFooter = memo(function EditorFooter({
304289
<>
305290
<EuiFlexItem grow={false}>
306291
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
292+
{!hideQuickSearch && (
293+
<QuickSearchAction toggleVisor={toggleVisor} isSpaceReduced={true} />
294+
)}
307295
{!hideQueryHistory && (
308296
<QueryHistoryAction
309297
toggleHistory={toggleHistoryComponent}
@@ -338,7 +326,7 @@ export const EditorFooter = memo(function EditorFooter({
338326
<EuiFlexItem grow={false}>
339327
<HistoryAndStarredQueriesTabs
340328
containerCSS={styles.historyContainer}
341-
onUpdateAndSubmit={onUpdateAndSubmit}
329+
onUpdateAndSubmit={onUpdateAndSubmitQuery}
342330
containerWidth={measuredContainerWidth}
343331
height={resizableContainerHeight}
344332
isSpaceReduced={isSpaceReduced}

src/platform/packages/private/kbn-esql-editor/src/editor_footer/keyboard_shortcuts.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ const listItems = [
4848
defaultMessage: 'Comment/uncomment line',
4949
}),
5050
},
51+
{
52+
title: (
53+
<>
54+
<kbd>{COMMAND_KEY}</kbd> <kbd>K</kbd>
55+
</>
56+
),
57+
description: i18n.translate('esqlEditor.query.openVisorKeyboardShortcutsLabel', {
58+
defaultMessage: 'Open quick search',
59+
}),
60+
},
5161
];
5262

5363
export function KeyboardShortcuts() {
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import React, { useCallback, useMemo, useState } from 'react';
11+
import {
12+
EuiSelectable,
13+
EuiFlexGroup,
14+
EuiFlexItem,
15+
EuiPanel,
16+
EuiButtonIcon,
17+
type EuiSelectableListProps,
18+
EuiToolTip,
19+
} from '@elastic/eui';
20+
import { i18n } from '@kbn/i18n';
21+
import { css } from '@emotion/react';
22+
23+
interface DataSourcesListProps {
24+
// List of all available data sources
25+
sourcesList: string[];
26+
// Callback when the selected data sources change
27+
onChangeDatasources: (newId: string[]) => void;
28+
// Currently selected data sources
29+
currentSources: string[];
30+
}
31+
32+
interface DataSourceOption {
33+
key?: string;
34+
label: string;
35+
value?: string;
36+
checked?: 'on' | 'off' | undefined;
37+
}
38+
39+
export function DataSourcesList({
40+
sourcesList,
41+
onChangeDatasources,
42+
currentSources,
43+
}: DataSourcesListProps) {
44+
const [showOnlySelected, setShowOnlySelected] = useState(false);
45+
46+
const filterToggleLabel = useMemo(() => {
47+
return showOnlySelected
48+
? i18n.translate('esqlEditor.visor.showAllOptionsAriaLabel', {
49+
defaultMessage: 'Show all options',
50+
})
51+
: i18n.translate('esqlEditor.visor.showSelectedOptionsAriaLabel', {
52+
defaultMessage: 'Show selected options',
53+
});
54+
}, [showOnlySelected]);
55+
56+
const onChoicesChange = useCallback(
57+
(choices: EuiSelectableListProps<DataSourceOption>['options']) => {
58+
const selectedChoices = choices.filter(({ checked }) => checked) as unknown as {
59+
value: string;
60+
}[];
61+
const newSelectedValues = selectedChoices.map((choice) => choice.value);
62+
63+
// Prevent deselecting the last remaining source
64+
if (newSelectedValues.length === 0 && currentSources.length === 1) {
65+
return;
66+
}
67+
68+
// Preserve the order of existing selections and append new ones at the end
69+
const orderedSelections = [
70+
...currentSources.filter((source) => newSelectedValues.includes(source)),
71+
...newSelectedValues.filter((value) => !currentSources.includes(value)),
72+
];
73+
74+
onChangeDatasources(orderedSelections);
75+
},
76+
[currentSources, onChangeDatasources]
77+
);
78+
79+
const options: DataSourceOption[] = useMemo(() => {
80+
return sourcesList
81+
?.filter((source) => (showOnlySelected ? currentSources?.includes(source) : true))
82+
?.map((source) => ({
83+
key: source,
84+
label: source,
85+
value: source,
86+
checked: currentSources?.includes(source) ? ('on' as const) : undefined,
87+
}));
88+
}, [sourcesList, showOnlySelected, currentSources]);
89+
90+
const onFilterToggleClick = useCallback(() => {
91+
setShowOnlySelected((prev) => !prev);
92+
}, []);
93+
94+
return (
95+
<EuiSelectable<DataSourceOption>
96+
listProps={{
97+
truncationProps: {
98+
truncation: 'middle',
99+
},
100+
}}
101+
data-test-subj="esqlEditor-visor-datasourcesList-switcher"
102+
searchable
103+
options={options}
104+
onChange={onChoicesChange}
105+
searchProps={{
106+
id: 'visorSearchListId',
107+
compressed: true,
108+
placeholder: i18n.translate('esqlEditor.visor.searchSourcesPlaceholder', {
109+
defaultMessage: 'Search',
110+
}),
111+
autoFocus: false,
112+
inputRef: (ref) => {
113+
ref?.focus({ preventScroll: true });
114+
},
115+
}}
116+
>
117+
{(list, search) => (
118+
<>
119+
<EuiPanel
120+
css={css`
121+
padding-bottom: 0;
122+
`}
123+
color="transparent"
124+
paddingSize="s"
125+
>
126+
<EuiFlexGroup
127+
gutterSize="xs"
128+
direction="row"
129+
justifyContent="spaceBetween"
130+
alignItems="center"
131+
responsive={false}
132+
>
133+
<EuiFlexItem>{search}</EuiFlexItem>
134+
<EuiFlexItem grow={false}>
135+
<EuiToolTip position="top" content={filterToggleLabel} disableScreenReaderOutput>
136+
<EuiButtonIcon
137+
size="xs"
138+
iconType="list"
139+
aria-pressed={showOnlySelected}
140+
color={showOnlySelected ? 'primary' : 'text'}
141+
display={showOnlySelected ? 'base' : 'empty'}
142+
onClick={onFilterToggleClick}
143+
aria-label={filterToggleLabel}
144+
/>
145+
</EuiToolTip>
146+
</EuiFlexItem>
147+
</EuiFlexGroup>
148+
</EuiPanel>
149+
{list}
150+
</>
151+
)}
152+
</EuiSelectable>
153+
);
154+
}

0 commit comments

Comments
 (0)