mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ES|QL] Increase discoverability (#188898)
## Summary Closes https://github.com/elastic/kibana/issues/184691 Closes https://github.com/elastic/kibana/issues/189029 Closes https://github.com/elastic/kibana/issues/166085 This PR is mostly a redesign of the unified search with the dataview picker and the ES|QL editor ### Unified search - We removed the ES|QL switch from the dataview picker - A lot of cleanup <img width="2502" alt="image" src="https://github.com/user-attachments/assets/2afca7ce-c7d5-4300-93c9-2c2b77434fd8"> ### ES|QL Editor - The biggest change is the elimination of the compact mode <img width="1256" alt="image" src="https://github.com/user-attachments/assets/a0b96796-e086-4397-b8c9-c270e445a034"> ### Discover - Moved the transition modal to Discover - Added a new menu item (Try ES|QL, Switch to classic) - A small redesign of the transition modal <img width="858" alt="image" src="https://github.com/user-attachments/assets/7fdba235-e0ed-46c2-9e29-e3ae586c019c"> ### Checklist - [ ] 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/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
22287545aa
commit
f2d7a28134
84 changed files with 1049 additions and 1816 deletions
|
@ -6,3 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
export const ENABLE_ESQL = 'enableESQL';
|
||||
export const FEEDBACK_LINK = 'https://ela.st/esql-feedback';
|
||||
|
|
|
@ -27,4 +27,4 @@ export {
|
|||
TextBasedLanguages,
|
||||
} from './src';
|
||||
|
||||
export { ENABLE_ESQL } from './constants';
|
||||
export { ENABLE_ESQL, FEEDBACK_LINK } from './constants';
|
||||
|
|
|
@ -7,7 +7,7 @@ It can be used in every application that would like to add an in-app documentati
|
|||
- A details page
|
||||
|
||||
```
|
||||
<LanguageDocumentationPopover language={language} sections={documentationSections} />
|
||||
<LanguageDocumentationPopover language={language} sections={documentationSections} onHelpMenuVisibilityChange={onHelpMenuVisibilityChange} isHelpMenuOpen={isHelpMenuOpen} />
|
||||
```
|
||||
|
||||
The properties are typed as:
|
||||
|
|
|
@ -66,5 +66,7 @@ storiesOf('Language documentation popover', module).add('default', () => (
|
|||
language="Test"
|
||||
sections={sections}
|
||||
buttonProps={{ color: 'text' }}
|
||||
isHelpMenuOpen={true}
|
||||
onHelpMenuVisibilityChange={() => {}}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiPopover,
|
||||
|
@ -21,6 +21,8 @@ import {
|
|||
|
||||
interface DocumentationPopoverProps {
|
||||
language: string;
|
||||
isHelpMenuOpen: boolean;
|
||||
onHelpMenuVisibilityChange: (status: boolean) => void;
|
||||
sections?: LanguageDocumentationSections;
|
||||
buttonProps?: Omit<EuiButtonIconProps, 'iconType'>;
|
||||
searchInDescription?: boolean;
|
||||
|
@ -33,24 +35,28 @@ function DocumentationPopover({
|
|||
buttonProps,
|
||||
searchInDescription,
|
||||
linkToDocumentation,
|
||||
isHelpMenuOpen,
|
||||
onHelpMenuVisibilityChange,
|
||||
}: DocumentationPopoverProps) {
|
||||
const [isHelpOpen, setIsHelpOpen] = useState<boolean>(false);
|
||||
|
||||
const toggleDocumentationPopover = useCallback(() => {
|
||||
setIsHelpOpen(!isHelpOpen);
|
||||
}, [isHelpOpen]);
|
||||
onHelpMenuVisibilityChange?.(!isHelpMenuOpen);
|
||||
}, [isHelpMenuOpen, onHelpMenuVisibilityChange]);
|
||||
|
||||
useEffect(() => {
|
||||
onHelpMenuVisibilityChange(isHelpMenuOpen ?? false);
|
||||
}, [isHelpMenuOpen, onHelpMenuVisibilityChange]);
|
||||
|
||||
return (
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={() => {
|
||||
setIsHelpOpen(false);
|
||||
onHelpMenuVisibilityChange?.(false);
|
||||
}}
|
||||
>
|
||||
<EuiPopover
|
||||
panelClassName="documentation__docs--overlay"
|
||||
panelPaddingSize="none"
|
||||
isOpen={isHelpOpen}
|
||||
closePopover={() => setIsHelpOpen(false)}
|
||||
isOpen={isHelpMenuOpen}
|
||||
closePopover={() => onHelpMenuVisibilityChange(false)}
|
||||
button={
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
|
@ -62,7 +68,7 @@ function DocumentationPopover({
|
|||
})}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="iInCircle"
|
||||
iconType="documentation"
|
||||
onClick={toggleDocumentationPopover}
|
||||
{...buttonProps}
|
||||
/>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type { TextBasedLanguagesEditorProps } from './src/text_based_languages_editor';
|
||||
export type { TextBasedLanguagesEditorProps } from './src/types';
|
||||
export { fetchFieldsFromESQL } from './src/fetch_fields_from_esql';
|
||||
import { TextBasedLanguagesEditor } from './src/text_based_languages_editor';
|
||||
|
||||
|
|
|
@ -28,16 +28,15 @@ The TextBasedLanguagesEditor component is a reusable component and can be used t
|
|||
|
||||
<Canvas>
|
||||
<Story
|
||||
name='compact mode'
|
||||
name='expanded mode'
|
||||
args={
|
||||
{
|
||||
query: { esql: 'from dataview | keep field1, field2' },
|
||||
isCodeEditorExpanded:false,
|
||||
'data-test-subj':'test-id'
|
||||
}
|
||||
}
|
||||
argTypes={
|
||||
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }}
|
||||
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }}
|
||||
}
|
||||
>
|
||||
{Template.bind({})}
|
||||
|
@ -52,7 +51,6 @@ When there are errors to the query the UI displays the errors to the editor:
|
|||
args={
|
||||
{
|
||||
query: { esql: 'from dataview | keep field1, field2' },
|
||||
isCodeEditorExpanded:false,
|
||||
'data-test-subj':'test-id',
|
||||
errors: [
|
||||
new Error(
|
||||
|
@ -62,69 +60,7 @@ When there are errors to the query the UI displays the errors to the editor:
|
|||
}
|
||||
}
|
||||
argTypes={
|
||||
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }}
|
||||
}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
When there the query is long and the editor is on the compact view:
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name='with long query'
|
||||
args={
|
||||
{
|
||||
query: { esql: 'from dataview | keep field1, field2, field 3, field 4, field 5 | where field5 > 5 | stats var = avg(field3)' },
|
||||
isCodeEditorExpanded:false,
|
||||
'data-test-subj':'test-id',
|
||||
}
|
||||
}
|
||||
argTypes={
|
||||
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }}
|
||||
}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
|
||||
The editor also works on the expanded mode:
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name='on expanded mode'
|
||||
args={
|
||||
{
|
||||
query: { esql: 'from dataview | keep field1, field2' },
|
||||
isCodeEditorExpanded:true,
|
||||
'data-test-subj':'test-id',
|
||||
}
|
||||
}
|
||||
argTypes={
|
||||
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }}
|
||||
}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
The editor also works on the expanded mode with the minimize button hidden:
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name='on expanded mode with hidden the minimize button'
|
||||
args={
|
||||
{
|
||||
query: { esql: 'from dataview | keep field1, field2' },
|
||||
isCodeEditorExpanded:true,
|
||||
hideMinimizeButton: true,
|
||||
'data-test-subj':'test-id',
|
||||
}
|
||||
}
|
||||
argTypes={
|
||||
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }}
|
||||
{ onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }}
|
||||
}
|
||||
>
|
||||
{Template.bind({})}
|
||||
|
@ -135,4 +71,4 @@ The editor also works on the expanded mode with the minimize button hidden:
|
|||
|
||||
The component exposes the following properties:
|
||||
|
||||
<ArgsTable story="compact mode"/>
|
||||
<ArgsTable story="expanded mode"/>
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiText,
|
||||
|
@ -17,13 +17,12 @@ import {
|
|||
EuiPopoverTitle,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListDescription,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
import { css, Interpolation, Theme } from '@emotion/react';
|
||||
import { css } from '@emotion/react';
|
||||
import { css as classNameCss } from '@emotion/css';
|
||||
import type { MonacoMessage } from './helpers';
|
||||
import type { MonacoMessage } from '../helpers';
|
||||
|
||||
export const getConstsByType = (type: 'error' | 'warning', count: number) => {
|
||||
const getConstsByType = (type: 'error' | 'warning', count: number) => {
|
||||
if (type === 'error') {
|
||||
return {
|
||||
color: 'danger',
|
||||
|
@ -49,7 +48,7 @@ export const getConstsByType = (type: 'error' | 'warning', count: number) => {
|
|||
}
|
||||
};
|
||||
|
||||
export function ErrorsWarningsContent({
|
||||
function ErrorsWarningsContent({
|
||||
items,
|
||||
type,
|
||||
onErrorClick,
|
||||
|
@ -100,48 +99,6 @@ export function ErrorsWarningsContent({
|
|||
);
|
||||
}
|
||||
|
||||
export function ErrorsWarningsCompactViewPopover({
|
||||
items,
|
||||
type,
|
||||
onErrorClick,
|
||||
popoverCSS,
|
||||
}: {
|
||||
items: MonacoMessage[];
|
||||
type: 'error' | 'warning';
|
||||
onErrorClick: (error: MonacoMessage) => void;
|
||||
popoverCSS: Interpolation<Theme>;
|
||||
}) {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const { color, message } = getConstsByType(type, items.length);
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiBadge
|
||||
color={color}
|
||||
onClick={() => setIsPopoverOpen(true)}
|
||||
onClickAriaLabel={message}
|
||||
iconType={type}
|
||||
iconSide="left"
|
||||
data-test-subj={`TextBasedLangEditor-inline-${type}-badge`}
|
||||
title={message}
|
||||
css={css`
|
||||
cursor: pointer;
|
||||
`}
|
||||
>
|
||||
{items.length}
|
||||
</EuiBadge>
|
||||
}
|
||||
css={popoverCSS}
|
||||
ownFocus={false}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
data-test-subj={`TextBasedLangEditor-inline-${type}-popover`}
|
||||
>
|
||||
<ErrorsWarningsContent items={items} type={type} onErrorClick={onErrorClick} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorsWarningsFooterPopover({
|
||||
isPopoverOpen,
|
||||
items,
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexItem, EuiIcon, useEuiTheme, EuiLink, EuiToolTip } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { FEEDBACK_LINK } from '@kbn/esql-utils';
|
||||
|
||||
export function SubmitFeedbackComponent({ isSpaceReduced }: { isSpaceReduced?: boolean }) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return (
|
||||
<>
|
||||
{isSpaceReduced && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
href={FEEDBACK_LINK}
|
||||
external={false}
|
||||
target="_blank"
|
||||
data-test-subj="TextBasedLangEditor-feedback-link"
|
||||
>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.feedback', {
|
||||
defaultMessage: 'Feedback',
|
||||
})}
|
||||
>
|
||||
<EuiIcon
|
||||
type="editorComment"
|
||||
color="primary"
|
||||
size="m"
|
||||
css={css`
|
||||
margin-right: ${euiTheme.size.s};
|
||||
`}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{!isSpaceReduced && (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="editorComment" color="primary" size="s" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
href={FEEDBACK_LINK}
|
||||
external={false}
|
||||
target="_blank"
|
||||
css={css`
|
||||
font-size: 12px;
|
||||
margin-right: ${euiTheme.size.m};
|
||||
`}
|
||||
data-test-subj="TextBasedLangEditor-feedback-link"
|
||||
>
|
||||
{i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.submitFeedback', {
|
||||
defaultMessage: 'Submit feedback',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -6,92 +6,25 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useCallback } from 'react';
|
||||
import React, { memo, useState, useCallback, useEffect } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
useEuiTheme,
|
||||
EuiLink,
|
||||
EuiCode,
|
||||
EuiButtonIcon,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { EuiText, EuiFlexGroup, EuiFlexItem, EuiCode } from '@elastic/eui';
|
||||
import { Interpolation, Theme, css } from '@emotion/react';
|
||||
import type { MonacoMessage } from './helpers';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import {
|
||||
LanguageDocumentationPopover,
|
||||
type LanguageDocumentationSections,
|
||||
} from '@kbn/language-documentation-popover';
|
||||
import { type MonacoMessage, getDocumentationSections } from '../helpers';
|
||||
import { ErrorsWarningsFooterPopover } from './errors_warnings_popover';
|
||||
import { QueryHistoryAction, QueryHistory } from './query_history';
|
||||
import { SubmitFeedbackComponent } from './feedback_component';
|
||||
import { QueryWrapComponent } from './query_wrap_component';
|
||||
import type { TextBasedEditorDeps } from '../types';
|
||||
|
||||
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
|
||||
const COMMAND_KEY = isMac ? '⌘' : '^';
|
||||
const FEEDBACK_LINK = 'https://ela.st/esql-feedback';
|
||||
|
||||
export function SubmitFeedbackComponent({ isSpaceReduced }: { isSpaceReduced?: boolean }) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return (
|
||||
<>
|
||||
{isSpaceReduced && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
href={FEEDBACK_LINK}
|
||||
external={false}
|
||||
target="_blank"
|
||||
data-test-subj="TextBasedLangEditor-feedback-link"
|
||||
>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.submitFeedback',
|
||||
{
|
||||
defaultMessage: 'Submit feedback',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiIcon
|
||||
type="editorComment"
|
||||
color="primary"
|
||||
size="m"
|
||||
css={css`
|
||||
margin-right: ${euiTheme.size.s};
|
||||
`}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{!isSpaceReduced && (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="editorComment" color="primary" size="s" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
href={FEEDBACK_LINK}
|
||||
external={false}
|
||||
target="_blank"
|
||||
css={css`
|
||||
font-size: 12px;
|
||||
margin-right: ${euiTheme.size.m};
|
||||
`}
|
||||
data-test-subj="TextBasedLangEditor-feedback-link"
|
||||
>
|
||||
{isSpaceReduced
|
||||
? i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.feedback', {
|
||||
defaultMessage: 'Feedback',
|
||||
})
|
||||
: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.submitFeedback', {
|
||||
defaultMessage: 'Submit feedback',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface EditorFooterProps {
|
||||
lines: number;
|
||||
|
@ -99,6 +32,7 @@ interface EditorFooterProps {
|
|||
bottomContainer: Interpolation<Theme>;
|
||||
historyContainer: Interpolation<Theme>;
|
||||
};
|
||||
code: string;
|
||||
errors?: MonacoMessage[];
|
||||
warnings?: MonacoMessage[];
|
||||
detectedTimestamp?: string;
|
||||
|
@ -107,18 +41,16 @@ interface EditorFooterProps {
|
|||
updateQuery: (qs: string) => void;
|
||||
isHistoryOpen: boolean;
|
||||
setIsHistoryOpen: (status: boolean) => void;
|
||||
isHelpMenuOpen: boolean;
|
||||
setIsHelpMenuOpen: (status: boolean) => void;
|
||||
measuredContainerWidth: number;
|
||||
hideRunQueryText?: boolean;
|
||||
disableSubmitAction?: boolean;
|
||||
editorIsInline?: boolean;
|
||||
isSpaceReduced?: boolean;
|
||||
isLoading?: boolean;
|
||||
allowQueryCancellation?: boolean;
|
||||
hideTimeFilterInfo?: boolean;
|
||||
hideQueryHistory?: boolean;
|
||||
refetchHistoryItems?: boolean;
|
||||
isInCompactMode?: boolean;
|
||||
queryHasChanged?: boolean;
|
||||
}
|
||||
|
||||
export const EditorFooter = memo(function EditorFooter({
|
||||
|
@ -131,22 +63,27 @@ export const EditorFooter = memo(function EditorFooter({
|
|||
runQuery,
|
||||
updateQuery,
|
||||
hideRunQueryText,
|
||||
disableSubmitAction,
|
||||
editorIsInline,
|
||||
isSpaceReduced,
|
||||
isLoading,
|
||||
allowQueryCancellation,
|
||||
hideTimeFilterInfo,
|
||||
isHistoryOpen,
|
||||
setIsHistoryOpen,
|
||||
hideQueryHistory,
|
||||
refetchHistoryItems,
|
||||
isInCompactMode,
|
||||
queryHasChanged,
|
||||
measuredContainerWidth,
|
||||
code,
|
||||
isHelpMenuOpen,
|
||||
setIsHelpMenuOpen,
|
||||
}: EditorFooterProps) {
|
||||
const kibana = useKibana<TextBasedEditorDeps>();
|
||||
const { docLinks } = kibana.services;
|
||||
|
||||
const [isErrorPopoverOpen, setIsErrorPopoverOpen] = useState(false);
|
||||
const [isWarningPopoverOpen, setIsWarningPopoverOpen] = useState(false);
|
||||
const [documentationSections, setDocumentationSections] =
|
||||
useState<LanguageDocumentationSections>();
|
||||
|
||||
const onUpdateAndSubmit = useCallback(
|
||||
(qs: string) => {
|
||||
// update the query first
|
||||
|
@ -161,6 +98,16 @@ export const EditorFooter = memo(function EditorFooter({
|
|||
[runQuery, updateQuery]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function getDocumentation() {
|
||||
const sections = await getDocumentationSections('esql');
|
||||
setDocumentationSections(sections);
|
||||
}
|
||||
if (!documentationSections) {
|
||||
getDocumentation();
|
||||
}
|
||||
}, [documentationSections]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
|
@ -180,6 +127,7 @@ export const EditorFooter = memo(function EditorFooter({
|
|||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s" responsive={false} alignItems="center">
|
||||
<QueryWrapComponent code={code} updateQuery={updateQuery} />
|
||||
<EuiFlexItem grow={false} style={{ marginRight: '8px' }}>
|
||||
<EuiText
|
||||
size="xs"
|
||||
|
@ -300,6 +248,29 @@ export const EditorFooter = memo(function EditorFooter({
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{documentationSections && !editorIsInline && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<LanguageDocumentationPopover
|
||||
language="ES|QL"
|
||||
sections={documentationSections}
|
||||
searchInDescription
|
||||
linkToDocumentation={docLinks?.links?.query?.queryESQL ?? ''}
|
||||
buttonProps={{
|
||||
color: 'text',
|
||||
size: 'xs',
|
||||
'data-test-subj': 'TextBasedLangEditor-documentation',
|
||||
'aria-label': i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel',
|
||||
{
|
||||
defaultMessage: 'Documentation',
|
||||
}
|
||||
),
|
||||
}}
|
||||
isHelpMenuOpen={isHelpMenuOpen}
|
||||
onHelpMenuVisibilityChange={setIsHelpMenuOpen}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{Boolean(editorIsInline) && (
|
||||
|
@ -314,49 +285,29 @@ export const EditorFooter = memo(function EditorFooter({
|
|||
isSpaceReduced={true}
|
||||
/>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.runQuery',
|
||||
{
|
||||
defaultMessage: 'Run query',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
display="base"
|
||||
color={queryHasChanged ? 'success' : 'primary'}
|
||||
onClick={runQuery}
|
||||
iconType={
|
||||
allowQueryCancellation && isLoading
|
||||
? 'cross'
|
||||
: queryHasChanged
|
||||
? 'play'
|
||||
: 'refresh'
|
||||
}
|
||||
size="s"
|
||||
isLoading={isLoading && !allowQueryCancellation}
|
||||
isDisabled={Boolean(disableSubmitAction && !allowQueryCancellation)}
|
||||
data-test-subj="TextBasedLangEditor-run-query-button"
|
||||
aria-label={
|
||||
allowQueryCancellation && isLoading
|
||||
? i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.cancel',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.runQuery',
|
||||
{
|
||||
defaultMessage: 'Run query',
|
||||
}
|
||||
)
|
||||
}
|
||||
{documentationSections && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<LanguageDocumentationPopover
|
||||
language="ES|QL"
|
||||
sections={documentationSections}
|
||||
searchInDescription
|
||||
linkToDocumentation={docLinks?.links?.query?.queryESQL ?? ''}
|
||||
buttonProps={{
|
||||
color: 'text',
|
||||
size: 'xs',
|
||||
'data-test-subj': 'TextBasedLangEditor-documentation',
|
||||
'aria-label': i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel',
|
||||
{
|
||||
defaultMessage: 'Documentation',
|
||||
}
|
||||
),
|
||||
}}
|
||||
isHelpMenuOpen={isHelpMenuOpen}
|
||||
onHelpMenuVisibilityChange={setIsHelpMenuOpen}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</>
|
|
@ -9,8 +9,8 @@ import React from 'react';
|
|||
import { QueryHistoryAction, getTableColumns, QueryHistory, QueryColumn } from './query_history';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
jest.mock('./history_local_storage', () => {
|
||||
const module = jest.requireActual('./history_local_storage');
|
||||
jest.mock('../history_local_storage', () => {
|
||||
const module = jest.requireActual('../history_local_storage');
|
||||
return {
|
||||
...module,
|
||||
getHistoryItems: () => [
|
|
@ -24,7 +24,7 @@ import {
|
|||
euiScrollBarStyles,
|
||||
} from '@elastic/eui';
|
||||
import { css, Interpolation, Theme } from '@emotion/react';
|
||||
import { type QueryHistoryItem, getHistoryItems } from './history_local_storage';
|
||||
import { type QueryHistoryItem, getHistoryItems } from '../history_local_storage';
|
||||
import { getReducedSpaceStyling, swapArrayElements } from './query_history_helpers';
|
||||
|
||||
const CONTAINER_MAX_HEIGHT_EXPANDED = 190;
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import type { QueryHistoryItem } from './history_local_storage';
|
||||
import type { QueryHistoryItem } from '../history_local_storage';
|
||||
|
||||
export const getReducedSpaceStyling = () => {
|
||||
return `
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexItem, EuiToolTip, EuiButtonIcon } from '@elastic/eui';
|
||||
import { getWrappedInPipesCode } from '../helpers';
|
||||
|
||||
export function QueryWrapComponent({
|
||||
code,
|
||||
updateQuery,
|
||||
}: {
|
||||
code: string;
|
||||
updateQuery: (qs: string) => void;
|
||||
}) {
|
||||
const isWrappedInPipes = useMemo(() => {
|
||||
const pipes = code.split('|');
|
||||
const pipesWithNewLine = code?.split('\n|');
|
||||
return pipes?.length === pipesWithNewLine?.length;
|
||||
}, [code]);
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
isWrappedInPipes
|
||||
? i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel',
|
||||
{
|
||||
defaultMessage: 'Remove line breaks on pipes',
|
||||
}
|
||||
)
|
||||
: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel', {
|
||||
defaultMessage: 'Add line breaks on pipes',
|
||||
})
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType={isWrappedInPipes ? 'pipeNoBreaks' : 'pipeBreaks'}
|
||||
color="text"
|
||||
size="xs"
|
||||
data-test-subj="TextBasedLangEditor-toggleWordWrap"
|
||||
aria-label={
|
||||
isWrappedInPipes
|
||||
? i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel',
|
||||
{
|
||||
defaultMessage: 'Remove line breaks on pipes',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel',
|
||||
{
|
||||
defaultMessage: 'Add line breaks on pipes',
|
||||
}
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
const updatedCode = getWrappedInPipesCode(code, isWrappedInPipes);
|
||||
if (code !== updatedCode) {
|
||||
updateQuery(updatedCode);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
|
@ -9,7 +9,6 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
|||
import {
|
||||
parseErrors,
|
||||
parseWarning,
|
||||
getInlineEditorText,
|
||||
getWrappedInPipesCode,
|
||||
getIndicesList,
|
||||
getRemoteIndicesList,
|
||||
|
@ -209,33 +208,6 @@ describe('helpers', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getInlineEditorText', function () {
|
||||
it('should return the entire query if it is one liner', function () {
|
||||
const text = getInlineEditorText('FROM index1 | keep field1, field2 | order field1', false);
|
||||
expect(text).toEqual(text);
|
||||
});
|
||||
|
||||
it('should return the query on one line with extra space if is multiliner', function () {
|
||||
const text = getInlineEditorText(
|
||||
'FROM index1 | keep field1, field2\n| keep field1, field2 | order field1',
|
||||
true
|
||||
);
|
||||
expect(text).toEqual(
|
||||
'FROM index1 | keep field1, field2 | keep field1, field2 | order field1'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the query on one line with extra spaces removed if is multiliner', function () {
|
||||
const text = getInlineEditorText(
|
||||
'FROM index1 | keep field1, field2\n| keep field1, field2 \n | order field1',
|
||||
true
|
||||
);
|
||||
expect(text).toEqual(
|
||||
'FROM index1 | keep field1, field2 | keep field1, field2 | order field1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWrappedInPipesCode', function () {
|
||||
it('should return the code wrapped', function () {
|
||||
const code = getWrappedInPipesCode('FROM index1 | keep field1, field2 | order field1', false);
|
||||
|
|
|
@ -195,10 +195,6 @@ export const getDocumentationSections = async (language: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getInlineEditorText = (queryString: string, isMultiLine: boolean) => {
|
||||
return isMultiLine ? queryString.replace(/\r?\n|\r/g, ' ').replace(/ +/g, ' ') : queryString;
|
||||
};
|
||||
|
||||
export const getWrappedInPipesCode = (code: string, isWrapped: boolean): string => {
|
||||
const pipes = code?.split('|');
|
||||
const codeNoLines = pipes?.map((pipe) => {
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
/* Editor styles for any layout mode */
|
||||
/* NOTE: Much of this is overriding Monaco styles so the specificity is intentional */
|
||||
|
||||
// Radius for both the main container and the margin (container for line numbers)
|
||||
.TextBasedLangEditor .monaco-editor, .TextBasedLangEditor .monaco-editor .margin, .TextBasedLangEditor .monaco-editor .overflow-guard {
|
||||
border-top-left-radius: $euiBorderRadius;
|
||||
border-bottom-left-radius: $euiBorderRadius;
|
||||
}
|
||||
|
||||
.TextBasedLangEditor .monaco-editor .monaco-hover {
|
||||
display: none !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.TextBasedLangEditor .monaco-editor .margin-view-overlays .line-numbers {
|
||||
|
@ -35,29 +29,15 @@
|
|||
@include euiTextBreakWord;
|
||||
}
|
||||
|
||||
/* For compact mode */
|
||||
|
||||
// All scrollable containers (e.g. main container and suggest menu)
|
||||
.TextBasedLangEditor--compact .monaco-editor .monaco-scrollable-element {
|
||||
.TextBasedLangEditor .monaco-editor .monaco-scrollable-element {
|
||||
margin-left: $euiSizeS;
|
||||
}
|
||||
|
||||
// Suggest menu in compact mode
|
||||
.TextBasedLangEditor--compact .monaco-editor .monaco-list .monaco-scrollable-element {
|
||||
.TextBasedLangEditor .monaco-editor .monaco-list .monaco-scrollable-element {
|
||||
margin-left: 0;
|
||||
|
||||
.monaco-list-row.focused {
|
||||
border-radius: $euiBorderRadius;
|
||||
}
|
||||
}
|
||||
|
||||
/* For expanded mode */
|
||||
|
||||
.TextBasedLangEditor--expanded .monaco-editor .monaco-hover {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.TextBasedLangEditor--expanded .monaco-editor, .TextBasedLangEditor--expanded .monaco-editor .margin, .TextBasedLangEditor--expanded .monaco-editor .overflow-guard {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
|
@ -7,61 +7,37 @@
|
|||
*/
|
||||
import type { EuiThemeComputed } from '@elastic/eui';
|
||||
|
||||
export const EDITOR_INITIAL_HEIGHT = 38;
|
||||
export const EDITOR_INITIAL_HEIGHT_EXPANDED = 140;
|
||||
export const EDITOR_INITIAL_HEIGHT = 80;
|
||||
export const EDITOR_INITIAL_HEIGHT_INLINE_EDITING = 140;
|
||||
export const EDITOR_MIN_HEIGHT = 40;
|
||||
export const EDITOR_MAX_HEIGHT = 400;
|
||||
|
||||
export const textBasedLanguageEditorStyles = (
|
||||
euiTheme: EuiThemeComputed,
|
||||
isCompactFocused: boolean,
|
||||
editorHeight: number,
|
||||
isCodeEditorExpanded: boolean,
|
||||
hasErrors: boolean,
|
||||
hasWarning: boolean,
|
||||
isCodeEditorExpandedFocused: boolean,
|
||||
hasReference: boolean,
|
||||
editorIsInline: boolean,
|
||||
historyIsOpen: boolean,
|
||||
hideHeaderWhenExpanded: boolean
|
||||
hasOutline: boolean
|
||||
) => {
|
||||
const bottomContainerBorderColor = hasErrors ? euiTheme.colors.danger : euiTheme.colors.primary;
|
||||
|
||||
const showHeader = hideHeaderWhenExpanded === true && isCodeEditorExpanded;
|
||||
|
||||
let position = isCompactFocused ? ('absolute' as const) : ('relative' as const);
|
||||
if (isCodeEditorExpanded) {
|
||||
position = 'relative';
|
||||
}
|
||||
|
||||
return {
|
||||
editorContainer: {
|
||||
position,
|
||||
position: 'relative' as const,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: isCompactFocused ? 4 : 0,
|
||||
zIndex: 4,
|
||||
height: `${editorHeight}px`,
|
||||
border: isCompactFocused ? euiTheme.border.thin : 'none',
|
||||
borderLeft: editorIsInline || !isCompactFocused ? 'none' : euiTheme.border.thin,
|
||||
borderRight: editorIsInline || !isCompactFocused ? 'none' : euiTheme.border.thin,
|
||||
borderTopLeftRadius: isCodeEditorExpanded ? 0 : euiTheme.border.radius.medium,
|
||||
borderBottom: isCodeEditorExpanded
|
||||
? 'none'
|
||||
: isCompactFocused
|
||||
? euiTheme.border.thin
|
||||
: 'none',
|
||||
},
|
||||
resizableContainer: {
|
||||
display: 'flex',
|
||||
width: isCodeEditorExpanded ? '100%' : `calc(100% - ${hasReference ? 80 : 40}px)`,
|
||||
alignItems: isCompactFocused ? 'flex-start' : 'center',
|
||||
border: !isCompactFocused ? euiTheme.border.thin : 'none',
|
||||
borderTopLeftRadius: isCodeEditorExpanded ? 0 : euiTheme.border.radius.medium,
|
||||
borderBottomLeftRadius: isCodeEditorExpanded ? 0 : euiTheme.border.radius.medium,
|
||||
borderBottomWidth: hasErrors ? '2px' : '1px',
|
||||
borderBottomColor: hasErrors ? euiTheme.colors.danger : euiTheme.colors.lightShade,
|
||||
borderRight: isCodeEditorExpanded ? euiTheme.border.thin : 'none',
|
||||
...(isCodeEditorExpanded && { overflow: 'hidden' }),
|
||||
width: '100%',
|
||||
alignItems: 'flex-start',
|
||||
border: hasOutline ? euiTheme.border.thin : 'none',
|
||||
borderBottom: 'none',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
linesBadge: {
|
||||
position: 'absolute' as const,
|
||||
|
@ -78,51 +54,44 @@ export const textBasedLanguageEditorStyles = (
|
|||
transform: 'translate(0, -50%)',
|
||||
},
|
||||
bottomContainer: {
|
||||
borderLeft: editorIsInline ? 'none' : euiTheme.border.thin,
|
||||
borderRight: editorIsInline ? 'none' : euiTheme.border.thin,
|
||||
borderTop:
|
||||
isCodeEditorExpanded && !isCodeEditorExpandedFocused
|
||||
? hasErrors
|
||||
? `2px solid ${euiTheme.colors.danger}`
|
||||
: euiTheme.border.thin
|
||||
: `2px solid ${bottomContainerBorderColor}`,
|
||||
borderBottom: editorIsInline ? 'none' : euiTheme.border.thin,
|
||||
borderTop: !isCodeEditorExpandedFocused
|
||||
? hasErrors
|
||||
? `2px solid ${euiTheme.colors.danger}`
|
||||
: `2px solid ${euiTheme.colors.lightestShade}`
|
||||
: `2px solid ${bottomContainerBorderColor}`,
|
||||
backgroundColor: euiTheme.colors.lightestShade,
|
||||
paddingLeft: euiTheme.size.base,
|
||||
paddingRight: euiTheme.size.base,
|
||||
paddingLeft: euiTheme.size.xs,
|
||||
paddingRight: euiTheme.size.xs,
|
||||
paddingTop: editorIsInline ? euiTheme.size.s : euiTheme.size.xs,
|
||||
paddingBottom: editorIsInline ? euiTheme.size.s : euiTheme.size.xs,
|
||||
width: isCodeEditorExpanded ? '100%' : 'calc(100% + 2px)',
|
||||
width: '100%',
|
||||
position: 'relative' as const,
|
||||
marginTop: 0,
|
||||
marginLeft: isCodeEditorExpanded ? 0 : -1,
|
||||
marginLeft: 0,
|
||||
marginBottom: 0,
|
||||
borderBottomLeftRadius: editorIsInline || historyIsOpen ? 0 : euiTheme.border.radius.medium,
|
||||
borderBottomRightRadius: editorIsInline || historyIsOpen ? 0 : euiTheme.border.radius.medium,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
historyContainer: {
|
||||
border: euiTheme.border.thin,
|
||||
borderTop: 'none',
|
||||
borderLeft: editorIsInline ? 'none' : euiTheme.border.thin,
|
||||
borderRight: editorIsInline ? 'none' : euiTheme.border.thin,
|
||||
border: 'none',
|
||||
backgroundColor: euiTheme.colors.lightestShade,
|
||||
width: '100%',
|
||||
position: 'relative' as const,
|
||||
marginTop: 0,
|
||||
marginLeft: 0,
|
||||
marginBottom: 0,
|
||||
borderBottomLeftRadius: editorIsInline ? 0 : euiTheme.border.radius.medium,
|
||||
borderBottomRightRadius: editorIsInline ? 0 : euiTheme.border.radius.medium,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
topContainer: {
|
||||
border: editorIsInline ? 'none' : euiTheme.border.thin,
|
||||
borderTopLeftRadius: editorIsInline ? 0 : euiTheme.border.radius.medium,
|
||||
borderTopRightRadius: editorIsInline ? 0 : euiTheme.border.radius.medium,
|
||||
border: 'none',
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
backgroundColor: euiTheme.colors.lightestShade,
|
||||
paddingLeft: euiTheme.size.s,
|
||||
paddingRight: euiTheme.size.s,
|
||||
paddingTop: showHeader ? euiTheme.size.s : euiTheme.size.xs,
|
||||
paddingBottom: showHeader ? euiTheme.size.s : euiTheme.size.xs,
|
||||
paddingTop: euiTheme.size.s,
|
||||
paddingBottom: euiTheme.size.s,
|
||||
width: '100%',
|
||||
position: 'relative' as const,
|
||||
marginLeft: 0,
|
||||
|
|
|
@ -12,10 +12,8 @@ import { IUiSettingsClient } from '@kbn/core/public';
|
|||
import { mountWithIntl as mount } from '@kbn/test-jest-helpers';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import {
|
||||
TextBasedLanguagesEditor,
|
||||
TextBasedLanguagesEditorProps,
|
||||
} from './text_based_languages_editor';
|
||||
import { TextBasedLanguagesEditor } from './text_based_languages_editor';
|
||||
import type { TextBasedLanguagesEditorProps } from './types';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
|
||||
jest.mock('./helpers', () => {
|
||||
|
@ -63,10 +61,8 @@ describe('TextBasedLanguagesEditor', () => {
|
|||
beforeEach(() => {
|
||||
props = {
|
||||
query: { esql: 'from test' },
|
||||
isCodeEditorExpanded: false,
|
||||
onTextLangQueryChange: jest.fn(),
|
||||
onTextLangQuerySubmit: jest.fn(),
|
||||
expandCodeEditor: jest.fn(),
|
||||
};
|
||||
});
|
||||
it('should render the editor component', async () => {
|
||||
|
@ -74,13 +70,6 @@ describe('TextBasedLanguagesEditor', () => {
|
|||
expect(component.find('[data-test-subj="TextBasedLangEditor"]').length).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should render the lines badge for the inline mode by default', async () => {
|
||||
const component = mount(renderTextBasedLanguagesEditorComponent({ ...props }));
|
||||
expect(
|
||||
component.find('[data-test-subj="TextBasedLangEditor-inline-lines-badge"]').length
|
||||
).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should render the date info with no @timestamp found', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
|
@ -163,60 +152,6 @@ describe('TextBasedLanguagesEditor', () => {
|
|||
).toBe(0);
|
||||
});
|
||||
|
||||
it('should render the errors badge for the inline mode by default if errors are provided', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
errors: [new Error('error1')],
|
||||
};
|
||||
const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps }));
|
||||
const errorBadge = component.find('[data-test-subj="TextBasedLangEditor-inline-error-badge"]');
|
||||
expect(errorBadge.length).not.toBe(0);
|
||||
errorBadge.at(0).simulate('click');
|
||||
expect(
|
||||
component.find('[data-test-subj="TextBasedLangEditor-inline-error-popover"]').length
|
||||
).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should render the warnings badge for the inline mode by default if warning are provided', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
warning: 'Line 1: 20: Warning',
|
||||
};
|
||||
const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps }));
|
||||
const warningBadge = component.find(
|
||||
'[data-test-subj="TextBasedLangEditor-inline-warning-badge"]'
|
||||
);
|
||||
expect(warningBadge.length).not.toBe(0);
|
||||
warningBadge.at(0).simulate('click');
|
||||
expect(
|
||||
component.find('[data-test-subj="TextBasedLangEditor-inline-warning-popover"]').length
|
||||
).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should render the correct buttons for the inline code editor mode', async () => {
|
||||
let component: ReactWrapper;
|
||||
|
||||
await act(async () => {
|
||||
component = mount(renderTextBasedLanguagesEditorComponent({ ...props }));
|
||||
});
|
||||
component!.update();
|
||||
expect(component!.find('[data-test-subj="TextBasedLangEditor-expand"]').length).not.toBe(0);
|
||||
expect(
|
||||
component!.find('[data-test-subj="TextBasedLangEditor-inline-documentation"]').length
|
||||
).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should call the expand editor function when expand button is clicked', async () => {
|
||||
const expandCodeEditorSpy = jest.fn();
|
||||
const newProps = {
|
||||
...props,
|
||||
expandCodeEditor: expandCodeEditorSpy,
|
||||
};
|
||||
const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps }));
|
||||
findTestSubject(component, 'TextBasedLangEditor-expand').simulate('click');
|
||||
expect(expandCodeEditorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render the correct buttons for the expanded code editor mode', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
|
@ -230,46 +165,11 @@ describe('TextBasedLanguagesEditor', () => {
|
|||
expect(
|
||||
component!.find('[data-test-subj="TextBasedLangEditor-toggleWordWrap"]').length
|
||||
).not.toBe(0);
|
||||
expect(component!.find('[data-test-subj="TextBasedLangEditor-minimize"]').length).not.toBe(0);
|
||||
expect(component!.find('[data-test-subj="TextBasedLangEditor-documentation"]').length).not.toBe(
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render the minimize button for the expanded code editor mode if the prop is set to true', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
isCodeEditorExpanded: true,
|
||||
hideMinimizeButton: true,
|
||||
};
|
||||
let component: ReactWrapper;
|
||||
await act(async () => {
|
||||
component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps }));
|
||||
});
|
||||
component!.update();
|
||||
await act(async () => {
|
||||
expect(
|
||||
component.find('[data-test-subj="TextBasedLangEditor-toggleWordWrap"]').length
|
||||
).not.toBe(0);
|
||||
expect(component.find('[data-test-subj="TextBasedLangEditor-minimize"]').length).toBe(0);
|
||||
expect(
|
||||
component.find('[data-test-subj="TextBasedLangEditor-documentation"]').length
|
||||
).not.toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the expand editor function when minimize button is clicked', async () => {
|
||||
const expandCodeEditorSpy = jest.fn();
|
||||
const newProps = {
|
||||
...props,
|
||||
isCodeEditorExpanded: true,
|
||||
expandCodeEditor: expandCodeEditorSpy,
|
||||
};
|
||||
const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps }));
|
||||
findTestSubject(component, 'TextBasedLangEditor-minimize').simulate('click');
|
||||
expect(expandCodeEditorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render the resize for the expanded code editor mode', async () => {
|
||||
const newProps = {
|
||||
...props,
|
||||
|
|
|
@ -7,45 +7,33 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiOutsideClickDetector,
|
||||
EuiToolTip,
|
||||
useEuiTheme,
|
||||
EuiDatePicker,
|
||||
EuiToolTip,
|
||||
EuiButton,
|
||||
type EuiButtonColor,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { CodeEditor, CodeEditorProps } from '@kbn/code-editor';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
import { getAggregateQueryMode, getLanguageDisplayName } from '@kbn/es-query';
|
||||
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { IndexManagementPluginSetup } from '@kbn/index-management';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import {
|
||||
LanguageDocumentationPopover,
|
||||
type LanguageDocumentationSections,
|
||||
} from '@kbn/language-documentation-popover';
|
||||
import { ESQLLang, ESQL_LANG_ID, ESQL_THEME_ID, monaco, type ESQLCallbacks } from '@kbn/monaco';
|
||||
import classNames from 'classnames';
|
||||
import memoize from 'lodash/memoize';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { css } from '@emotion/react';
|
||||
import { EditorFooter } from './editor_footer';
|
||||
import { ErrorsWarningsCompactViewPopover } from './errors_warnings_popover';
|
||||
import { fetchFieldsFromESQL } from './fetch_fields_from_esql';
|
||||
import {
|
||||
clearCacheWhenOld,
|
||||
getDocumentationSections,
|
||||
getESQLSources,
|
||||
getInlineEditorText,
|
||||
getWrappedInPipesCode,
|
||||
parseErrors,
|
||||
parseWarning,
|
||||
useDebounceWithOptions,
|
||||
|
@ -55,112 +43,31 @@ import { addQueriesToCache, updateCachedQueries } from './history_local_storage'
|
|||
import { ResizableButton } from './resizable_button';
|
||||
import {
|
||||
EDITOR_INITIAL_HEIGHT,
|
||||
EDITOR_INITIAL_HEIGHT_EXPANDED,
|
||||
EDITOR_INITIAL_HEIGHT_INLINE_EDITING,
|
||||
EDITOR_MAX_HEIGHT,
|
||||
EDITOR_MIN_HEIGHT,
|
||||
textBasedLanguageEditorStyles,
|
||||
} from './text_based_languages_editor.styles';
|
||||
import { getRateLimitedColumnsWithMetadata } from './ecs_metadata_helper';
|
||||
import type { TextBasedLanguagesEditorProps, TextBasedEditorDeps } from './types';
|
||||
|
||||
import './overwrite.scss';
|
||||
|
||||
export interface TextBasedLanguagesEditorProps {
|
||||
/** The aggregate type query */
|
||||
query: AggregateQuery;
|
||||
/** Callback running everytime the query changes */
|
||||
onTextLangQueryChange: (query: AggregateQuery) => void;
|
||||
/** Callback running when the user submits the query */
|
||||
onTextLangQuerySubmit: (
|
||||
query?: AggregateQuery,
|
||||
abortController?: AbortController
|
||||
) => Promise<void>;
|
||||
/** Can be used to expand/minimize the editor */
|
||||
expandCodeEditor: (status: boolean) => void;
|
||||
/** If it is true, the editor initializes with height EDITOR_INITIAL_HEIGHT_EXPANDED */
|
||||
isCodeEditorExpanded: boolean;
|
||||
/** If it is true, the editor displays the message @timestamp found
|
||||
* The text based queries are relying on adhoc dataviews which
|
||||
* can have an @timestamp timefield or nothing
|
||||
*/
|
||||
detectedTimestamp?: string;
|
||||
/** Array of errors */
|
||||
errors?: Error[];
|
||||
/** Warning string as it comes from ES */
|
||||
warning?: string;
|
||||
/** Disables the editor and displays loading icon in run button
|
||||
* It is also used for hiding the history component if it is not defined
|
||||
*/
|
||||
isLoading?: boolean;
|
||||
/** Disables the editor */
|
||||
isDisabled?: boolean;
|
||||
/** Indicator if the editor is on dark mode */
|
||||
isDarkMode?: boolean;
|
||||
dataTestSubj?: string;
|
||||
/** If true it hides the minimize button and the user can't return to the minimized version
|
||||
* Useful when the application doesn't want to give this capability
|
||||
*/
|
||||
hideMinimizeButton?: boolean;
|
||||
/** Hide the Run query information which appears on the footer*/
|
||||
hideRunQueryText?: boolean;
|
||||
/** This is used for applications (such as the inline editing flyout in dashboards)
|
||||
* which want to add the editor without being part of the Unified search component
|
||||
* It renders a submit query button inside the editor
|
||||
*/
|
||||
editorIsInline?: boolean;
|
||||
/** Disables the submit query action*/
|
||||
disableSubmitAction?: boolean;
|
||||
|
||||
/** when set to true enables query cancellation **/
|
||||
allowQueryCancellation?: boolean;
|
||||
|
||||
/** hide @timestamp info **/
|
||||
hideTimeFilterInfo?: boolean;
|
||||
|
||||
/** hide query history **/
|
||||
hideQueryHistory?: boolean;
|
||||
|
||||
/** hide header buttons when editor is expanded */
|
||||
hideHeaderWhenExpanded?: boolean;
|
||||
}
|
||||
|
||||
interface TextBasedEditorDeps {
|
||||
core: CoreStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
expressions: ExpressionsStart;
|
||||
indexManagementApiService?: IndexManagementPluginSetup['apiService'];
|
||||
fieldsMetadata?: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
const MAX_COMPACT_VIEW_LENGTH = 250;
|
||||
const FONT_WIDTH = 8;
|
||||
const EDITOR_ONE_LINER_UNUSED_SPACE = 180;
|
||||
const EDITOR_ONE_LINER_UNUSED_SPACE_WITH_ERRORS = 220;
|
||||
|
||||
const KEYCODE_ARROW_UP = 38;
|
||||
const KEYCODE_ARROW_DOWN = 40;
|
||||
|
||||
// for editor width smaller than this value we want to start hiding some text
|
||||
const BREAKPOINT_WIDTH = 540;
|
||||
|
||||
let clickedOutside = false;
|
||||
let initialRender = true;
|
||||
let updateLinesFromModel = false;
|
||||
let lines = 1;
|
||||
let isDatePickerOpen = false;
|
||||
|
||||
export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
||||
query,
|
||||
onTextLangQueryChange,
|
||||
onTextLangQuerySubmit,
|
||||
expandCodeEditor,
|
||||
isCodeEditorExpanded,
|
||||
detectedTimestamp,
|
||||
errors: serverErrors,
|
||||
warning: serverWarning,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
isDarkMode,
|
||||
hideMinimizeButton,
|
||||
hideRunQueryText,
|
||||
editorIsInline,
|
||||
disableSubmitAction,
|
||||
|
@ -168,41 +75,30 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
allowQueryCancellation,
|
||||
hideTimeFilterInfo,
|
||||
hideQueryHistory,
|
||||
hideHeaderWhenExpanded,
|
||||
hasOutline,
|
||||
}: TextBasedLanguagesEditorProps) {
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const datePickerOpenStatusRef = useRef<boolean>(false);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const language = getAggregateQueryMode(query);
|
||||
const queryString: string = query[language] ?? '';
|
||||
const kibana = useKibana<TextBasedEditorDeps>();
|
||||
const {
|
||||
dataViews,
|
||||
expressions,
|
||||
indexManagementApiService,
|
||||
application,
|
||||
docLinks,
|
||||
core,
|
||||
fieldsMetadata,
|
||||
} = kibana.services;
|
||||
const { dataViews, expressions, indexManagementApiService, application, core, fieldsMetadata } =
|
||||
kibana.services;
|
||||
const timeZone = core?.uiSettings?.get('dateFormat:tz');
|
||||
const [code, setCode] = useState<string>(queryString ?? '');
|
||||
const [codeOneLiner, setCodeOneLiner] = useState<string | null>(null);
|
||||
const [code, setCode] = useState<string>(query.esql ?? '');
|
||||
// To make server side errors less "sticky", register the state of the code when submitting
|
||||
const [codeWhenSubmitted, setCodeStateOnSubmission] = useState(code);
|
||||
const [editorHeight, setEditorHeight] = useState(
|
||||
isCodeEditorExpanded ? EDITOR_INITIAL_HEIGHT_EXPANDED : EDITOR_INITIAL_HEIGHT
|
||||
editorIsInline ? EDITOR_INITIAL_HEIGHT_INLINE_EDITING : EDITOR_INITIAL_HEIGHT
|
||||
);
|
||||
const [popoverPosition, setPopoverPosition] = useState<{ top?: number; left?: number }>({});
|
||||
const [timePickerDate, setTimePickerDate] = useState(moment());
|
||||
const [measuredEditorWidth, setMeasuredEditorWidth] = useState(0);
|
||||
const [measuredContentWidth, setMeasuredContentWidth] = useState(0);
|
||||
|
||||
const isSpaceReduced = Boolean(editorIsInline) && measuredEditorWidth < BREAKPOINT_WIDTH;
|
||||
|
||||
const [isHistoryOpen, setIsHistoryOpen] = useState(false);
|
||||
const [showLineNumbers, setShowLineNumbers] = useState(isCodeEditorExpanded);
|
||||
const [isCompactFocused, setIsCompactFocused] = useState(isCodeEditorExpanded);
|
||||
const [isCodeEditorExpandedFocused, setIsCodeEditorExpandedFocused] = useState(false);
|
||||
const [isLanguagePopoverOpen, setIsLanguagePopoverOpen] = useState(false);
|
||||
const [isQueryLoading, setIsQueryLoading] = useState(true);
|
||||
const [abortController, setAbortController] = useState(new AbortController());
|
||||
// contains both client side validation and server messages
|
||||
|
@ -230,10 +126,9 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
|
||||
const onQueryUpdate = useCallback(
|
||||
(value: string) => {
|
||||
setCode(value);
|
||||
onTextLangQueryChange({ [language]: value } as AggregateQuery);
|
||||
onTextLangQueryChange({ esql: value } as AggregateQuery);
|
||||
},
|
||||
[language, onTextLangQueryChange]
|
||||
[onTextLangQueryChange]
|
||||
);
|
||||
|
||||
const onQuerySubmit = useCallback(() => {
|
||||
|
@ -249,16 +144,9 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
if (currentValue != null) {
|
||||
setCodeStateOnSubmission(currentValue);
|
||||
}
|
||||
onTextLangQuerySubmit({ [language]: currentValue } as AggregateQuery, abc);
|
||||
onTextLangQuerySubmit({ esql: currentValue } as AggregateQuery, abc);
|
||||
}
|
||||
}, [
|
||||
isQueryLoading,
|
||||
isLoading,
|
||||
allowQueryCancellation,
|
||||
abortController,
|
||||
onTextLangQuerySubmit,
|
||||
language,
|
||||
]);
|
||||
}, [isQueryLoading, isLoading, allowQueryCancellation, abortController, onTextLangQuerySubmit]);
|
||||
|
||||
const onCommentLine = useCallback(() => {
|
||||
const currentSelection = editor1?.current?.getSelection();
|
||||
|
@ -290,8 +178,13 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
if (!isLoading) setIsQueryLoading(false);
|
||||
}, [isLoading]);
|
||||
|
||||
const [documentationSections, setDocumentationSections] =
|
||||
useState<LanguageDocumentationSections>();
|
||||
useEffect(() => {
|
||||
if (editor1.current) {
|
||||
if (code !== query.esql) {
|
||||
setCode(query.esql);
|
||||
}
|
||||
}
|
||||
}, [code, query.esql]);
|
||||
|
||||
const toggleHistory = useCallback((status: boolean) => {
|
||||
setIsHistoryOpen(status);
|
||||
|
@ -310,7 +203,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
const absoluteLeft = editorLeft + (editorPosition?.left ?? 0);
|
||||
|
||||
setPopoverPosition({ top: absoluteTop, left: absoluteLeft });
|
||||
isDatePickerOpen = true;
|
||||
datePickerOpenStatusRef.current = true;
|
||||
popoverRef.current?.focus();
|
||||
}
|
||||
}, []);
|
||||
|
@ -330,28 +223,17 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
|
||||
const styles = textBasedLanguageEditorStyles(
|
||||
euiTheme,
|
||||
isCompactFocused,
|
||||
editorHeight,
|
||||
isCodeEditorExpanded,
|
||||
Boolean(editorMessages.errors.length),
|
||||
Boolean(editorMessages.warnings.length),
|
||||
isCodeEditorExpandedFocused,
|
||||
Boolean(documentationSections),
|
||||
Boolean(editorIsInline),
|
||||
isHistoryOpen,
|
||||
!!hideHeaderWhenExpanded
|
||||
Boolean(hasOutline)
|
||||
);
|
||||
const isDark = isDarkMode;
|
||||
const editorModel = useRef<monaco.editor.ITextModel>();
|
||||
const editor1 = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||
const containerRef = useRef<HTMLElement>(null);
|
||||
|
||||
const editorClassName = classNames('TextBasedLangEditor', {
|
||||
'TextBasedLangEditor--expanded': isCodeEditorExpanded,
|
||||
'TextBasedLangEditor--compact': isCompactFocused,
|
||||
'TextBasedLangEditor--initial': !isCompactFocused,
|
||||
});
|
||||
|
||||
// When the editor is on full size mode, the user can resize the height of the editor.
|
||||
const onMouseDownResizeHandler = useCallback(
|
||||
(mouseDownEvent) => {
|
||||
|
@ -389,36 +271,8 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
[editorHeight]
|
||||
);
|
||||
|
||||
const restoreInitialMode = () => {
|
||||
setIsCodeEditorExpandedFocused(false);
|
||||
if (isCodeEditorExpanded) return;
|
||||
setEditorHeight(EDITOR_INITIAL_HEIGHT);
|
||||
setIsCompactFocused(false);
|
||||
setShowLineNumbers(false);
|
||||
updateLinesFromModel = false;
|
||||
clickedOutside = true;
|
||||
if (editor1.current) {
|
||||
const contentWidth = editor1.current.getLayoutInfo().width;
|
||||
calculateVisibleCode(contentWidth, true);
|
||||
editor1.current.layout({ width: contentWidth, height: EDITOR_INITIAL_HEIGHT });
|
||||
}
|
||||
};
|
||||
|
||||
const updateHeight = useCallback((editor: monaco.editor.IStandaloneCodeEditor) => {
|
||||
if (clickedOutside || initialRender) return;
|
||||
const contentHeight = Math.min(MAX_COMPACT_VIEW_LENGTH, editor.getContentHeight());
|
||||
setEditorHeight(contentHeight);
|
||||
editor.layout({ width: editor.getLayoutInfo().width, height: contentHeight });
|
||||
}, []);
|
||||
|
||||
const onEditorFocus = useCallback(() => {
|
||||
setIsCompactFocused(true);
|
||||
setIsCodeEditorExpandedFocused(true);
|
||||
setShowLineNumbers(true);
|
||||
setCodeOneLiner(null);
|
||||
clickedOutside = false;
|
||||
initialRender = false;
|
||||
updateLinesFromModel = true;
|
||||
}, []);
|
||||
|
||||
const { cache: esqlFieldsCache, memoizedFieldsFromESQL } = useMemo(() => {
|
||||
|
@ -449,7 +303,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
const esqlCallbacks: ESQLCallbacks = useMemo(() => {
|
||||
const callbacks: ESQLCallbacks = {
|
||||
getSources: async () => {
|
||||
clearCacheWhenOld(dataSourcesCache, queryString);
|
||||
clearCacheWhenOld(dataSourcesCache, query.esql);
|
||||
const sources = await memoizedSources(dataViews, core).result;
|
||||
return sources;
|
||||
},
|
||||
|
@ -494,7 +348,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
};
|
||||
return callbacks;
|
||||
}, [
|
||||
queryString,
|
||||
query.esql,
|
||||
memoizedSources,
|
||||
dataSourcesCache,
|
||||
dataViews,
|
||||
|
@ -507,15 +361,43 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
fieldsMetadata,
|
||||
]);
|
||||
|
||||
const queryRunButtonProperties = useMemo(() => {
|
||||
if (allowQueryCancellation && isLoading) {
|
||||
return {
|
||||
label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.cancel', {
|
||||
defaultMessage: 'Cancel',
|
||||
}),
|
||||
iconType: 'cross',
|
||||
color: 'text',
|
||||
};
|
||||
}
|
||||
if (code !== codeWhenSubmitted) {
|
||||
return {
|
||||
label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.runQuery', {
|
||||
defaultMessage: 'Run query',
|
||||
}),
|
||||
iconType: 'play',
|
||||
color: 'success',
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.refreshLabel', {
|
||||
defaultMessage: 'Refresh',
|
||||
}),
|
||||
iconType: 'refresh',
|
||||
color: 'primary',
|
||||
};
|
||||
}, [allowQueryCancellation, code, codeWhenSubmitted, isLoading]);
|
||||
|
||||
const parseMessages = useCallback(async () => {
|
||||
if (editorModel.current) {
|
||||
return await ESQLLang.validate(editorModel.current, queryString, esqlCallbacks);
|
||||
return await ESQLLang.validate(editorModel.current, code, esqlCallbacks);
|
||||
}
|
||||
return {
|
||||
errors: [],
|
||||
warnings: [],
|
||||
};
|
||||
}, [esqlCallbacks, queryString]);
|
||||
}, [esqlCallbacks, code]);
|
||||
|
||||
const clientParserStatus = clientParserMessages.errors?.length
|
||||
? 'error'
|
||||
|
@ -535,24 +417,24 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
};
|
||||
if (isQueryLoading || isLoading) {
|
||||
addQueriesToCache({
|
||||
queryString,
|
||||
queryString: code,
|
||||
timeZone,
|
||||
});
|
||||
validateQuery();
|
||||
setRefetchHistoryItems(false);
|
||||
} else {
|
||||
updateCachedQueries({
|
||||
queryString,
|
||||
queryString: code,
|
||||
status: clientParserStatus,
|
||||
});
|
||||
|
||||
setRefetchHistoryItems(true);
|
||||
}
|
||||
}, [clientParserStatus, isLoading, isQueryLoading, parseMessages, queryString, timeZone]);
|
||||
}, [clientParserStatus, isLoading, isQueryLoading, parseMessages, code, timeZone]);
|
||||
|
||||
const queryValidation = useCallback(
|
||||
async ({ active }: { active: boolean }) => {
|
||||
if (!editorModel.current || language !== 'esql' || editorModel.current.isDisposed()) return;
|
||||
if (!editorModel.current || editorModel.current.isDisposed()) return;
|
||||
monaco.editor.setModelMarkers(editorModel.current, 'Unified search', []);
|
||||
const { warnings: parserWarnings, errors: parserErrors } = await parseMessages();
|
||||
const markers = [];
|
||||
|
@ -566,7 +448,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
return;
|
||||
}
|
||||
},
|
||||
[language, parseMessages]
|
||||
[parseMessages]
|
||||
);
|
||||
|
||||
useDebounceWithOptions(
|
||||
|
@ -602,18 +484,15 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
);
|
||||
|
||||
const suggestionProvider = useMemo(
|
||||
() => (language === 'esql' ? ESQLLang.getSuggestionProvider?.(esqlCallbacks) : undefined),
|
||||
[language, esqlCallbacks]
|
||||
() => ESQLLang.getSuggestionProvider?.(esqlCallbacks),
|
||||
[esqlCallbacks]
|
||||
);
|
||||
|
||||
const hoverProvider = useMemo(
|
||||
() => (language === 'esql' ? ESQLLang.getHoverProvider?.(esqlCallbacks) : undefined),
|
||||
[language, esqlCallbacks]
|
||||
);
|
||||
const hoverProvider = useMemo(() => ESQLLang.getHoverProvider?.(esqlCallbacks), [esqlCallbacks]);
|
||||
|
||||
const codeActionProvider = useMemo(
|
||||
() => (language === 'esql' ? ESQLLang.getCodeActionProvider?.(esqlCallbacks) : undefined),
|
||||
[language, esqlCallbacks]
|
||||
() => ESQLLang.getCodeActionProvider?.(esqlCallbacks),
|
||||
[esqlCallbacks]
|
||||
);
|
||||
|
||||
const onErrorClick = useCallback(({ startLineNumber, startColumn }: MonacoMessage) => {
|
||||
|
@ -639,46 +518,11 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
};
|
||||
}, []);
|
||||
|
||||
const calculateVisibleCode = useCallback(
|
||||
(width: number, force?: boolean) => {
|
||||
const containerWidth = containerRef.current?.offsetWidth;
|
||||
if (containerWidth && (!isCompactFocused || force)) {
|
||||
const hasLines = /\r|\n/.exec(queryString);
|
||||
if (hasLines && !updateLinesFromModel) {
|
||||
lines = queryString.split(/\r|\n/).length;
|
||||
}
|
||||
const text = getInlineEditorText(queryString, Boolean(hasLines));
|
||||
const queryLength = text.length;
|
||||
const unusedSpace =
|
||||
editorMessages.errors.length || editorMessages.warnings.length
|
||||
? EDITOR_ONE_LINER_UNUSED_SPACE_WITH_ERRORS
|
||||
: EDITOR_ONE_LINER_UNUSED_SPACE;
|
||||
const charactersAlowed = Math.floor((width - unusedSpace) / FONT_WIDTH);
|
||||
if (queryLength > charactersAlowed) {
|
||||
const shortedCode = text.substring(0, charactersAlowed) + '...';
|
||||
setCodeOneLiner(shortedCode);
|
||||
} else {
|
||||
const shortedCode = text;
|
||||
setCodeOneLiner(shortedCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
[isCompactFocused, queryString, editorMessages]
|
||||
);
|
||||
|
||||
// When the layout changes, and the editor is not focused, we want to
|
||||
// recalculate the visible code so it fills up the available space. We
|
||||
// use a ref because editorDidMount is only called once, and the reference
|
||||
// to the state becomes stale after re-renders.
|
||||
const onLayoutChange = (layoutInfoEvent: monaco.editor.EditorLayoutInfo) => {
|
||||
if (layoutInfoEvent.contentWidth !== measuredContentWidth) {
|
||||
const nextMeasuredWidth = layoutInfoEvent.contentWidth;
|
||||
setMeasuredContentWidth(nextMeasuredWidth);
|
||||
if (!isCodeEditorExpandedFocused && !isCompactFocused) {
|
||||
calculateVisibleCode(nextMeasuredWidth, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (layoutInfoEvent.width !== measuredEditorWidth) {
|
||||
setMeasuredEditorWidth(layoutInfoEvent.width);
|
||||
}
|
||||
|
@ -688,38 +532,6 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
|
||||
onLayoutChangeRef.current = onLayoutChange;
|
||||
|
||||
useEffect(() => {
|
||||
if (editor1.current && !isCompactFocused) {
|
||||
if (code !== queryString) {
|
||||
setCode(queryString);
|
||||
calculateVisibleCode(editor1.current.getLayoutInfo().width);
|
||||
}
|
||||
}
|
||||
}, [calculateVisibleCode, code, isCompactFocused, queryString]);
|
||||
|
||||
useEffect(() => {
|
||||
// make sure to always update the code in expanded editor when query prop changes
|
||||
if (isCodeEditorExpanded && editor1.current?.getValue() !== queryString) {
|
||||
setCode(queryString);
|
||||
}
|
||||
}, [isCodeEditorExpanded, queryString]);
|
||||
|
||||
const isWrappedInPipes = useMemo(() => {
|
||||
const pipes = code?.split('|');
|
||||
const pipesWithNewLine = code?.split('\n|');
|
||||
return pipes?.length === pipesWithNewLine?.length;
|
||||
}, [code]);
|
||||
|
||||
useEffect(() => {
|
||||
async function getDocumentation() {
|
||||
const sections = await getDocumentationSections(language);
|
||||
setDocumentationSections(sections);
|
||||
}
|
||||
if (!documentationSections) {
|
||||
getDocumentation();
|
||||
}
|
||||
}, [language, documentationSections]);
|
||||
|
||||
const codeEditorOptions: CodeEditorProps['options'] = {
|
||||
accessibilitySupport: 'off',
|
||||
autoIndent: 'none',
|
||||
|
@ -734,7 +546,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
enabled: false,
|
||||
},
|
||||
lineDecorationsWidth: 12,
|
||||
lineNumbers: showLineNumbers ? 'on' : 'off',
|
||||
lineNumbers: 'on',
|
||||
lineNumbersMinChars: 3,
|
||||
minimap: { enabled: false },
|
||||
overviewRulerLanes: 0,
|
||||
|
@ -744,204 +556,78 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
bottom: 8,
|
||||
},
|
||||
quickSuggestions: true,
|
||||
readOnly:
|
||||
isDisabled || Boolean(!isCompactFocused && codeOneLiner && codeOneLiner.includes('...')),
|
||||
renderLineHighlight: !isCodeEditorExpanded ? 'none' : 'line',
|
||||
readOnly: isDisabled,
|
||||
renderLineHighlight: 'line',
|
||||
renderLineHighlightOnlyWhenFocus: true,
|
||||
scrollbar: {
|
||||
horizontal: 'hidden',
|
||||
vertical: 'auto',
|
||||
},
|
||||
scrollBeyondLastLine: false,
|
||||
theme: language === 'esql' ? ESQL_THEME_ID : isDark ? 'vs-dark' : 'vs',
|
||||
theme: ESQL_THEME_ID,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'none',
|
||||
};
|
||||
|
||||
if (isCompactFocused) {
|
||||
codeEditorOptions.overviewRulerLanes = 4;
|
||||
codeEditorOptions.hideCursorInOverviewRuler = false;
|
||||
codeEditorOptions.overviewRulerBorder = true;
|
||||
}
|
||||
|
||||
const editorPanel = (
|
||||
<>
|
||||
{isCodeEditorExpanded && !hideHeaderWhenExpanded && (
|
||||
{Boolean(editorIsInline) && (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
justifyContent="spaceBetween"
|
||||
css={styles.topContainer}
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
justifyContent="flexEnd"
|
||||
css={css`
|
||||
padding: ${euiTheme.size.s};
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="none" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
isWrappedInPipes
|
||||
? i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel',
|
||||
{
|
||||
defaultMessage: 'Remove line breaks on pipes',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel',
|
||||
{
|
||||
defaultMessage: 'Add line breaks on pipes',
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType={isWrappedInPipes ? 'pipeNoBreaks' : 'pipeBreaks'}
|
||||
color="text"
|
||||
size="xs"
|
||||
data-test-subj="TextBasedLangEditor-toggleWordWrap"
|
||||
aria-label={
|
||||
isWrappedInPipes
|
||||
? i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel',
|
||||
{
|
||||
defaultMessage: 'Remove line breaks on pipes',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel',
|
||||
{
|
||||
defaultMessage: 'Add line breaks on pipes',
|
||||
}
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
const updatedCode = getWrappedInPipesCode(code, isWrappedInPipes);
|
||||
if (code !== updatedCode) {
|
||||
setCode(updatedCode);
|
||||
onTextLangQueryChange({ [language]: updatedCode } as AggregateQuery);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="none" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{documentationSections && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<LanguageDocumentationPopover
|
||||
language={getLanguageDisplayName(String(language))}
|
||||
sections={documentationSections}
|
||||
searchInDescription
|
||||
linkToDocumentation={
|
||||
language === 'esql' ? docLinks?.links?.query?.queryESQL : ''
|
||||
}
|
||||
buttonProps={{
|
||||
color: 'text',
|
||||
size: 'xs',
|
||||
'data-test-subj': 'TextBasedLangEditor-documentation',
|
||||
'aria-label': i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel',
|
||||
{
|
||||
defaultMessage: 'Documentation',
|
||||
}
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{!Boolean(hideMinimizeButton) && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip',
|
||||
{
|
||||
defaultMessage: 'Compact query editor',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="minimize"
|
||||
color="text"
|
||||
aria-label={i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor',
|
||||
{
|
||||
defaultMessage: 'Minimize editor',
|
||||
}
|
||||
)}
|
||||
data-test-subj="TextBasedLangEditor-minimize"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
expandCodeEditor(false);
|
||||
updateLinesFromModel = false;
|
||||
}}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.runQuery', {
|
||||
defaultMessage: 'Run query',
|
||||
})}
|
||||
>
|
||||
<EuiButton
|
||||
color={queryRunButtonProperties.color as EuiButtonColor}
|
||||
onClick={onQuerySubmit}
|
||||
iconType={queryRunButtonProperties.iconType}
|
||||
size="s"
|
||||
isLoading={isLoading && !allowQueryCancellation}
|
||||
isDisabled={Boolean(disableSubmitAction && !allowQueryCancellation)}
|
||||
data-test-subj="TextBasedLangEditor-run-query-button"
|
||||
aria-label={queryRunButtonProperties.label}
|
||||
>
|
||||
{queryRunButtonProperties.label}
|
||||
</EuiButton>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiFlexGroup gutterSize="none" responsive={false} ref={containerRef}>
|
||||
<EuiOutsideClickDetector
|
||||
onOutsideClick={() => {
|
||||
restoreInitialMode();
|
||||
setIsCodeEditorExpandedFocused(false);
|
||||
}}
|
||||
>
|
||||
<div css={styles.resizableContainer}>
|
||||
<EuiFlexItem
|
||||
data-test-subj={dataTestSubj ?? 'TextBasedLangEditor'}
|
||||
className={editorClassName}
|
||||
className="TextBasedLangEditor"
|
||||
css={css`
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
`}
|
||||
>
|
||||
<div css={styles.editorContainer}>
|
||||
{!isCompactFocused && (
|
||||
<EuiBadge
|
||||
color={euiTheme.colors.lightShade}
|
||||
css={styles.linesBadge}
|
||||
data-test-subj="TextBasedLangEditor-inline-lines-badge"
|
||||
>
|
||||
{i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.lineCount', {
|
||||
defaultMessage: '{count} {count, plural, one {line} other {lines}}',
|
||||
values: { count: lines },
|
||||
})}
|
||||
</EuiBadge>
|
||||
)}
|
||||
{!isCompactFocused && editorMessages.errors.length > 0 && (
|
||||
<ErrorsWarningsCompactViewPopover
|
||||
items={editorMessages.errors}
|
||||
type="error"
|
||||
onErrorClick={onErrorClick}
|
||||
popoverCSS={styles.errorsBadge}
|
||||
/>
|
||||
)}
|
||||
{!isCompactFocused &&
|
||||
editorMessages.warnings.length > 0 &&
|
||||
editorMessages.errors.length === 0 && (
|
||||
<ErrorsWarningsCompactViewPopover
|
||||
items={editorMessages.warnings}
|
||||
type="warning"
|
||||
onErrorClick={onErrorClick}
|
||||
popoverCSS={styles.errorsBadge}
|
||||
/>
|
||||
)}
|
||||
<CodeEditor
|
||||
languageId={ESQL_LANG_ID}
|
||||
value={codeOneLiner || code}
|
||||
value={code}
|
||||
options={codeEditorOptions}
|
||||
width="100%"
|
||||
suggestionProvider={suggestionProvider}
|
||||
hoverProvider={{
|
||||
provideHover: (model, position, token) => {
|
||||
if (isCompactFocused || !hoverProvider?.provideHover) {
|
||||
if (!hoverProvider?.provideHover) {
|
||||
return { contents: [] };
|
||||
}
|
||||
return hoverProvider?.provideHover(model, position, token);
|
||||
|
@ -955,10 +641,6 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
if (model) {
|
||||
editorModel.current = model;
|
||||
}
|
||||
if (isCodeEditorExpanded) {
|
||||
lines = model?.getLineCount() || 1;
|
||||
}
|
||||
|
||||
// this is fixing a bug between the EUIPopover and the monaco editor
|
||||
// when the user clicks the editor, we force it to focus and the onDidFocusEditorText
|
||||
// to fire, the timeout is needed because otherwise it refocuses on the popover icon
|
||||
|
@ -968,7 +650,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
setTimeout(() => {
|
||||
editor.focus();
|
||||
}, 100);
|
||||
if (isDatePickerOpen) {
|
||||
if (datePickerOpenStatusRef.current) {
|
||||
setPopoverPosition({});
|
||||
}
|
||||
});
|
||||
|
@ -996,156 +678,45 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
);
|
||||
|
||||
setMeasuredEditorWidth(editor.getLayoutInfo().width);
|
||||
setMeasuredContentWidth(editor.getContentWidth());
|
||||
|
||||
editor.onDidLayoutChange((layoutInfoEvent) => {
|
||||
onLayoutChangeRef.current(layoutInfoEvent);
|
||||
});
|
||||
|
||||
if (!isCodeEditorExpanded) {
|
||||
editor.onDidContentSizeChange((e) => {
|
||||
if (e.contentHeightChanged) {
|
||||
updateHeight(editor);
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isCompactFocused && !isCodeEditorExpanded && (
|
||||
<EditorFooter
|
||||
lines={lines}
|
||||
styles={{
|
||||
bottomContainer: styles.bottomContainer,
|
||||
historyContainer: styles.historyContainer,
|
||||
}}
|
||||
{...editorMessages}
|
||||
onErrorClick={onErrorClick}
|
||||
runQuery={onQuerySubmit}
|
||||
updateQuery={onQueryUpdate}
|
||||
detectedTimestamp={detectedTimestamp}
|
||||
editorIsInline={editorIsInline}
|
||||
disableSubmitAction={disableSubmitAction}
|
||||
hideRunQueryText={hideRunQueryText}
|
||||
isSpaceReduced={isSpaceReduced}
|
||||
isLoading={isQueryLoading}
|
||||
allowQueryCancellation={allowQueryCancellation}
|
||||
hideTimeFilterInfo={hideTimeFilterInfo}
|
||||
isHistoryOpen={isHistoryOpen}
|
||||
setIsHistoryOpen={toggleHistory}
|
||||
measuredContainerWidth={measuredEditorWidth}
|
||||
hideQueryHistory={hideHistoryComponent}
|
||||
refetchHistoryItems={refetchHistoryItems}
|
||||
isInCompactMode={true}
|
||||
queryHasChanged={code !== codeWhenSubmitted}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
</EuiOutsideClickDetector>
|
||||
{!isCodeEditorExpanded && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="none" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{documentationSections && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<LanguageDocumentationPopover
|
||||
language={
|
||||
String(language) === 'esql' ? 'ES|QL' : String(language).toUpperCase()
|
||||
}
|
||||
linkToDocumentation={
|
||||
language === 'esql' ? docLinks?.links?.query?.queryESQL : ''
|
||||
}
|
||||
searchInDescription
|
||||
sections={documentationSections}
|
||||
buttonProps={{
|
||||
display: 'empty',
|
||||
'data-test-subj': 'TextBasedLangEditor-inline-documentation',
|
||||
'aria-label': i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel',
|
||||
{
|
||||
defaultMessage: 'Documentation',
|
||||
}
|
||||
),
|
||||
size: 'm',
|
||||
css: {
|
||||
borderRadius: 0,
|
||||
backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3',
|
||||
border: '1px solid rgb(17 43 134 / 10%) !important',
|
||||
transform: 'none !important',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'textBasedEditor.query.textBasedLanguagesEditor.expandTooltip',
|
||||
{
|
||||
defaultMessage: 'Expand query editor',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
display="empty"
|
||||
iconType="expand"
|
||||
size="m"
|
||||
aria-label="Expand"
|
||||
onClick={() => expandCodeEditor(true)}
|
||||
data-test-subj="TextBasedLangEditor-expand"
|
||||
css={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3',
|
||||
border: '1px solid rgb(17 43 134 / 10%) !important',
|
||||
borderLeft: 'transparent !important',
|
||||
transform: 'none !important',
|
||||
}}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
{isCodeEditorExpanded && (
|
||||
<EditorFooter
|
||||
lines={lines}
|
||||
styles={{
|
||||
bottomContainer: styles.bottomContainer,
|
||||
historyContainer: styles.historyContainer,
|
||||
}}
|
||||
onErrorClick={onErrorClick}
|
||||
runQuery={onQuerySubmit}
|
||||
updateQuery={onQueryUpdate}
|
||||
detectedTimestamp={detectedTimestamp}
|
||||
hideRunQueryText={hideRunQueryText}
|
||||
editorIsInline={editorIsInline}
|
||||
disableSubmitAction={disableSubmitAction}
|
||||
isSpaceReduced={isSpaceReduced}
|
||||
isLoading={isQueryLoading}
|
||||
allowQueryCancellation={allowQueryCancellation}
|
||||
hideTimeFilterInfo={hideTimeFilterInfo}
|
||||
{...editorMessages}
|
||||
isHistoryOpen={isHistoryOpen}
|
||||
setIsHistoryOpen={toggleHistory}
|
||||
measuredContainerWidth={measuredEditorWidth}
|
||||
hideQueryHistory={hideHistoryComponent}
|
||||
refetchHistoryItems={refetchHistoryItems}
|
||||
queryHasChanged={code !== codeWhenSubmitted}
|
||||
/>
|
||||
)}
|
||||
{isCodeEditorExpanded && (
|
||||
<ResizableButton
|
||||
onMouseDownResizeHandler={onMouseDownResizeHandler}
|
||||
onKeyDownResizeHandler={onKeyDownResizeHandler}
|
||||
editorIsInline={editorIsInline}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EditorFooter
|
||||
lines={editorModel.current?.getLineCount() || 1}
|
||||
styles={{
|
||||
bottomContainer: styles.bottomContainer,
|
||||
historyContainer: styles.historyContainer,
|
||||
}}
|
||||
code={code}
|
||||
onErrorClick={onErrorClick}
|
||||
runQuery={onQuerySubmit}
|
||||
updateQuery={onQueryUpdate}
|
||||
detectedTimestamp={detectedTimestamp}
|
||||
hideRunQueryText={hideRunQueryText}
|
||||
editorIsInline={editorIsInline}
|
||||
isSpaceReduced={isSpaceReduced}
|
||||
hideTimeFilterInfo={hideTimeFilterInfo}
|
||||
{...editorMessages}
|
||||
isHistoryOpen={isHistoryOpen}
|
||||
setIsHistoryOpen={toggleHistory}
|
||||
measuredContainerWidth={measuredEditorWidth}
|
||||
hideQueryHistory={hideHistoryComponent}
|
||||
refetchHistoryItems={refetchHistoryItems}
|
||||
isHelpMenuOpen={isLanguagePopoverOpen}
|
||||
setIsHelpMenuOpen={setIsLanguagePopoverOpen}
|
||||
/>
|
||||
<ResizableButton
|
||||
onMouseDownResizeHandler={onMouseDownResizeHandler}
|
||||
onKeyDownResizeHandler={onKeyDownResizeHandler}
|
||||
editorIsInline={editorIsInline}
|
||||
/>
|
||||
{createPortal(
|
||||
Object.keys(popoverPosition).length !== 0 && popoverPosition.constructor === Object && (
|
||||
<div
|
||||
|
@ -1193,7 +764,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({
|
|||
},
|
||||
]);
|
||||
setPopoverPosition({});
|
||||
isDatePickerOpen = false;
|
||||
datePickerOpenStatusRef.current = false;
|
||||
}
|
||||
}}
|
||||
inline
|
||||
|
|
70
packages/kbn-text-based-editor/src/types.ts
Normal file
70
packages/kbn-text-based-editor/src/types.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import type { IndexManagementPluginSetup } from '@kbn/index-management';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
export interface TextBasedLanguagesEditorProps {
|
||||
/** The aggregate type query */
|
||||
query: AggregateQuery;
|
||||
/** Callback running everytime the query changes */
|
||||
onTextLangQueryChange: (query: AggregateQuery) => void;
|
||||
/** Callback running when the user submits the query */
|
||||
onTextLangQuerySubmit: (
|
||||
query?: AggregateQuery,
|
||||
abortController?: AbortController
|
||||
) => Promise<void>;
|
||||
/** If it is true, the editor displays the message @timestamp found
|
||||
* The text based queries are relying on adhoc dataviews which
|
||||
* can have an @timestamp timefield or nothing
|
||||
*/
|
||||
detectedTimestamp?: string;
|
||||
/** Array of errors */
|
||||
errors?: Error[];
|
||||
/** Warning string as it comes from ES */
|
||||
warning?: string;
|
||||
/** Disables the editor and displays loading icon in run button
|
||||
* It is also used for hiding the history component if it is not defined
|
||||
*/
|
||||
isLoading?: boolean;
|
||||
/** Disables the editor */
|
||||
isDisabled?: boolean;
|
||||
dataTestSubj?: string;
|
||||
/** Hide the Run query information which appears on the footer*/
|
||||
hideRunQueryText?: boolean;
|
||||
/** This is used for applications (such as the inline editing flyout in dashboards)
|
||||
* which want to add the editor without being part of the Unified search component
|
||||
* It renders a submit query button inside the editor
|
||||
*/
|
||||
editorIsInline?: boolean;
|
||||
/** Disables the submit query action*/
|
||||
disableSubmitAction?: boolean;
|
||||
|
||||
/** when set to true enables query cancellation **/
|
||||
allowQueryCancellation?: boolean;
|
||||
|
||||
/** hide @timestamp info **/
|
||||
hideTimeFilterInfo?: boolean;
|
||||
|
||||
/** hide query history **/
|
||||
hideQueryHistory?: boolean;
|
||||
|
||||
/** adds border in the editor **/
|
||||
hasOutline?: boolean;
|
||||
}
|
||||
|
||||
export interface TextBasedEditorDeps {
|
||||
core: CoreStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
expressions: ExpressionsStart;
|
||||
indexManagementApiService?: IndexManagementPluginSetup['apiService'];
|
||||
fieldsMetadata?: FieldsMetadataPublicStart;
|
||||
}
|
|
@ -28,6 +28,7 @@
|
|||
"@kbn/shared-ux-markdown",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
"@kbn/esql-validation-autocomplete",
|
||||
"@kbn/esql-utils",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -21,3 +21,6 @@ export enum VIEW_MODE {
|
|||
export const getDefaultRowsPerPage = (uiSettings: IUiSettingsClient): number => {
|
||||
return parseInt(uiSettings.get(SAMPLE_ROWS_PER_PAGE_SETTING), 10) || DEFAULT_ROWS_PER_PAGE;
|
||||
};
|
||||
|
||||
// local storage key for the ES|QL to Dataviews transition modal
|
||||
export const ESQL_TRANSITION_MODAL_KEY = 'data.textLangTransitionModal';
|
||||
|
|
|
@ -11,11 +11,8 @@ import { type DataView, DataViewType } from '@kbn/data-views-plugin/public';
|
|||
import { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
|
||||
import { ENABLE_ESQL } from '@kbn/esql-utils';
|
||||
import { TextBasedLanguages } from '@kbn/esql-utils';
|
||||
import {
|
||||
useSavedSearch,
|
||||
useSavedSearchHasChanged,
|
||||
useSavedSearchInitial,
|
||||
} from '../../state_management/discover_state_provider';
|
||||
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
||||
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
|
||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
|
@ -25,6 +22,7 @@ import { addLog } from '../../../../utils/add_log';
|
|||
import { useAppStateSelector } from '../../state_management/discover_app_state_container';
|
||||
import { useDiscoverTopNav } from './use_discover_topnav';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import { ESQLToDataViewTransitionModal } from './esql_dataview_transition';
|
||||
|
||||
export interface DiscoverTopNavProps {
|
||||
savedQuery?: string;
|
||||
|
@ -59,6 +57,9 @@ export const DiscoverTopNav = ({
|
|||
const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews);
|
||||
const dataView = useInternalStateSelector((state) => state.dataView!);
|
||||
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
|
||||
const isESQLToDataViewTransitionModalVisible = useInternalStateSelector(
|
||||
(state) => state.isESQLToDataViewTransitionModalVisible
|
||||
);
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
const isEsqlMode = useIsEsqlMode();
|
||||
const showDatePicker = useMemo(() => {
|
||||
|
@ -150,17 +151,34 @@ export const DiscoverTopNav = ({
|
|||
}
|
||||
};
|
||||
|
||||
const onEsqlSavedAndExit = useCallback(
|
||||
({ onSave, onCancel }) => {
|
||||
onSaveSearch({
|
||||
savedSearch: stateContainer.savedSearchState.getState(),
|
||||
services,
|
||||
state: stateContainer,
|
||||
onClose: onCancel,
|
||||
onSaveCb: onSave,
|
||||
});
|
||||
const onESQLToDataViewTransitionModalClose = useCallback(
|
||||
(shouldDismissModal?: boolean, needsSave?: boolean) => {
|
||||
if (shouldDismissModal) {
|
||||
services.storage.set(ESQL_TRANSITION_MODAL_KEY, true);
|
||||
}
|
||||
stateContainer.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(false);
|
||||
// the user dismissed the modal, we don't need to save the search or switch to the data view mode
|
||||
if (needsSave == null) {
|
||||
return;
|
||||
}
|
||||
if (needsSave) {
|
||||
onSaveSearch({
|
||||
savedSearch: stateContainer.savedSearchState.getState(),
|
||||
services,
|
||||
state: stateContainer,
|
||||
onClose: () =>
|
||||
stateContainer.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(
|
||||
false
|
||||
),
|
||||
onSaveCb: () => {
|
||||
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||
}
|
||||
},
|
||||
[services, stateContainer]
|
||||
[dataView.id, services, stateContainer]
|
||||
);
|
||||
|
||||
const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer });
|
||||
|
@ -181,8 +199,6 @@ export const DiscoverTopNav = ({
|
|||
topNavMenu,
|
||||
]);
|
||||
|
||||
const savedSearchId = useSavedSearch().id;
|
||||
const savedSearchHasChanged = useSavedSearchHasChanged();
|
||||
const dataViewPickerProps: DataViewPickerProps = useMemo(() => {
|
||||
const isESQLModeEnabled = uiSettings.get(ENABLE_ESQL);
|
||||
const supportedTextBasedLanguages: DataViewPickerProps['textBasedLanguages'] = isESQLModeEnabled
|
||||
|
@ -201,7 +217,6 @@ export const DiscoverTopNav = ({
|
|||
onCreateDefaultAdHocDataView: stateContainer.actions.createAndAppendAdHocDataView,
|
||||
onChangeDataView: stateContainer.actions.onChangeDataView,
|
||||
textBasedLanguages: supportedTextBasedLanguages,
|
||||
shouldShowTextBasedLanguageTransitionModal: !savedSearchId || savedSearchHasChanged,
|
||||
adHocDataViews,
|
||||
savedDataViews,
|
||||
onEditDataView,
|
||||
|
@ -213,8 +228,6 @@ export const DiscoverTopNav = ({
|
|||
dataView,
|
||||
onEditDataView,
|
||||
savedDataViews,
|
||||
savedSearchHasChanged,
|
||||
savedSearchId,
|
||||
stateContainer,
|
||||
uiSettings,
|
||||
]);
|
||||
|
@ -230,40 +243,44 @@ export const DiscoverTopNav = ({
|
|||
!!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker;
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
{...topNavProps}
|
||||
appName="discover"
|
||||
indexPatterns={[dataView]}
|
||||
onQuerySubmit={stateContainer.actions.onUpdateQuery}
|
||||
onCancel={onCancelClick}
|
||||
isLoading={isLoading}
|
||||
onSavedQueryIdChange={updateSavedQueryId}
|
||||
query={query}
|
||||
savedQueryId={savedQuery}
|
||||
screenTitle={savedSearch.title}
|
||||
showDatePicker={showDatePicker}
|
||||
saveQueryMenuVisibility={
|
||||
services.capabilities.discover.saveQuery ? 'allowed_by_app_privilege' : 'globally_managed'
|
||||
}
|
||||
showSearchBar={true}
|
||||
useDefaultBehaviors={true}
|
||||
dataViewPickerOverride={
|
||||
searchBarCustomization?.CustomDataViewPicker ? (
|
||||
<searchBarCustomization.CustomDataViewPicker />
|
||||
) : undefined
|
||||
}
|
||||
dataViewPickerComponentProps={
|
||||
shouldHideDefaultDataviewPicker ? undefined : dataViewPickerProps
|
||||
}
|
||||
displayStyle="detached"
|
||||
textBasedLanguageModeErrors={esqlModeErrors ? [esqlModeErrors] : undefined}
|
||||
textBasedLanguageModeWarning={esqlModeWarning}
|
||||
onTextBasedSavedAndExit={onEsqlSavedAndExit}
|
||||
prependFilterBar={
|
||||
searchBarCustomization?.PrependFilterBar ? (
|
||||
<searchBarCustomization.PrependFilterBar />
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
<>
|
||||
<SearchBar
|
||||
{...topNavProps}
|
||||
appName="discover"
|
||||
indexPatterns={[dataView]}
|
||||
onQuerySubmit={stateContainer.actions.onUpdateQuery}
|
||||
onCancel={onCancelClick}
|
||||
isLoading={isLoading}
|
||||
onSavedQueryIdChange={updateSavedQueryId}
|
||||
query={query}
|
||||
savedQueryId={savedQuery}
|
||||
screenTitle={savedSearch.title}
|
||||
showDatePicker={showDatePicker}
|
||||
saveQueryMenuVisibility={
|
||||
services.capabilities.discover.saveQuery ? 'allowed_by_app_privilege' : 'globally_managed'
|
||||
}
|
||||
showSearchBar={true}
|
||||
useDefaultBehaviors={true}
|
||||
dataViewPickerOverride={
|
||||
searchBarCustomization?.CustomDataViewPicker ? (
|
||||
<searchBarCustomization.CustomDataViewPicker />
|
||||
) : undefined
|
||||
}
|
||||
dataViewPickerComponentProps={
|
||||
shouldHideDefaultDataviewPicker ? undefined : dataViewPickerProps
|
||||
}
|
||||
displayStyle="detached"
|
||||
textBasedLanguageModeErrors={esqlModeErrors ? [esqlModeErrors] : undefined}
|
||||
textBasedLanguageModeWarning={esqlModeWarning}
|
||||
prependFilterBar={
|
||||
searchBarCustomization?.PrependFilterBar ? (
|
||||
<searchBarCustomization.PrependFilterBar />
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
{isESQLToDataViewTransitionModalVisible && (
|
||||
<ESQLToDataViewTransitionModal onClose={onESQLToDataViewTransitionModalClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FEEDBACK_LINK } from '@kbn/esql-utils';
|
||||
import {
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiText,
|
||||
EuiCheckbox,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiLink,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export interface ESQLToDataViewTransitionModalProps {
|
||||
onClose: (dismissFlag?: boolean, needsSave?: boolean) => void;
|
||||
}
|
||||
// Needed for React.lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function ESQLToDataViewTransitionModal({
|
||||
onClose,
|
||||
}: ESQLToDataViewTransitionModalProps) {
|
||||
const [dismissModalChecked, setDismissModalChecked] = useState(false);
|
||||
const onTransitionModalDismiss = useCallback((e) => {
|
||||
setDismissModalChecked(e.target.checked);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiModal
|
||||
onClose={() => onClose()}
|
||||
style={{ width: 700 }}
|
||||
data-test-subj="discover-esql-to-dataview-modal"
|
||||
>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
{i18n.translate('discover.esqlToDataViewTransitionModal.title', {
|
||||
defaultMessage: 'Unsaved changes',
|
||||
})}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<EuiText size="m">
|
||||
{i18n.translate('discover.esqlToDataviewTransitionModalBody', {
|
||||
defaultMessage:
|
||||
'Switching data views removes the current ES|QL query. Save this search to avoid losing work.',
|
||||
})}
|
||||
</EuiText>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink external href={FEEDBACK_LINK} target="_blank">
|
||||
{i18n.translate('discover.esqlToDataViewTransitionModal.feedbackLink', {
|
||||
defaultMessage: 'Submit ES|QL feedback',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter css={{ paddingBlockStart: 0 }}>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCheckbox
|
||||
id="dismiss-text-based-languages-transition-modal"
|
||||
label={i18n.translate('discover.esqlToDataViewTransitionModal.dismissButtonLabel', {
|
||||
defaultMessage: "Don't ask me again",
|
||||
})}
|
||||
checked={dismissModalChecked}
|
||||
onChange={onTransitionModalDismiss}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={() => onClose(dismissModalChecked, false)}
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
data-test-subj="discover-esql-to-dataview-no-save-btn"
|
||||
>
|
||||
{i18n.translate('discover.esqlToDataViewTransitionModal.closeButtonLabel', {
|
||||
defaultMessage: 'Discard and switch',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => onClose(dismissModalChecked, true)}
|
||||
fill
|
||||
color="primary"
|
||||
iconType="save"
|
||||
data-test-subj="discover-esql-to-dataview-save-btn"
|
||||
>
|
||||
{i18n.translate('discover.esqlToDataViewTransitionModal.saveButtonLabel', {
|
||||
defaultMessage: 'Save and switch',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { ESQLToDataViewTransitionModalProps } from './esql_dataview_transition_modal';
|
||||
|
||||
const Fallback = () => <div />;
|
||||
|
||||
const LazyESQLToDataViewTransitionModal = React.lazy(
|
||||
() => import('./esql_dataview_transition_modal')
|
||||
);
|
||||
export const ESQLToDataViewTransitionModal = (props: ESQLToDataViewTransitionModalProps) => (
|
||||
<React.Suspense fallback={<Fallback />}>
|
||||
<LazyESQLToDataViewTransitionModal {...props} />
|
||||
</React.Suspense>
|
||||
);
|
|
@ -17,6 +17,9 @@ const services = {
|
|||
save: true,
|
||||
},
|
||||
},
|
||||
uiSettings: {
|
||||
get: jest.fn(() => true),
|
||||
},
|
||||
} as unknown as DiscoverServices;
|
||||
|
||||
const state = {} as unknown as DiscoverStateContainer;
|
||||
|
@ -30,9 +33,20 @@ test('getTopNavLinks result', () => {
|
|||
isEsqlMode: false,
|
||||
adHocDataViews: [],
|
||||
topNavCustomization: undefined,
|
||||
shouldShowESQLToDataViewTransitionModal: false,
|
||||
});
|
||||
expect(topNavLinks).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"color": "text",
|
||||
"emphasize": true,
|
||||
"fill": false,
|
||||
"iconType": "editorRedo",
|
||||
"id": "esql",
|
||||
"label": "Try ES|QL",
|
||||
"run": [Function],
|
||||
"testId": "select-text-based-language-btn",
|
||||
},
|
||||
Object {
|
||||
"description": "New Search",
|
||||
"id": "new",
|
||||
|
@ -83,9 +97,20 @@ test('getTopNavLinks result for ES|QL mode', () => {
|
|||
isEsqlMode: true,
|
||||
adHocDataViews: [],
|
||||
topNavCustomization: undefined,
|
||||
shouldShowESQLToDataViewTransitionModal: false,
|
||||
});
|
||||
expect(topNavLinks).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"color": "text",
|
||||
"emphasize": true,
|
||||
"fill": false,
|
||||
"iconType": "editorRedo",
|
||||
"id": "esql",
|
||||
"label": "Switch to classic",
|
||||
"run": [Function],
|
||||
"testId": "switch-to-dataviews",
|
||||
},
|
||||
Object {
|
||||
"description": "New Search",
|
||||
"id": "new",
|
||||
|
|
|
@ -11,7 +11,10 @@ import type { DataView } from '@kbn/data-views-plugin/public';
|
|||
import type { TopNavMenuData } from '@kbn/navigation-plugin/public';
|
||||
import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
import { omit } from 'lodash';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { ENABLE_ESQL } from '@kbn/esql-utils';
|
||||
import type { DiscoverAppLocatorParams } from '../../../../../common';
|
||||
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
|
||||
import { showOpenSearchPanel } from './show_open_search_panel';
|
||||
import { getSharingData, showPublicUrlSwitch } from '../../../../utils/get_sharing_data';
|
||||
import { DiscoverServices } from '../../../../build_services';
|
||||
|
@ -31,6 +34,7 @@ export const getTopNavLinks = ({
|
|||
isEsqlMode,
|
||||
adHocDataViews,
|
||||
topNavCustomization,
|
||||
shouldShowESQLToDataViewTransitionModal,
|
||||
}: {
|
||||
dataView: DataView | undefined;
|
||||
services: DiscoverServices;
|
||||
|
@ -39,6 +43,7 @@ export const getTopNavLinks = ({
|
|||
isEsqlMode: boolean;
|
||||
adHocDataViews: DataView[];
|
||||
topNavCustomization: TopNavCustomization | undefined;
|
||||
shouldShowESQLToDataViewTransitionModal: boolean;
|
||||
}): TopNavMenuData[] => {
|
||||
const alerts = {
|
||||
id: 'alerts',
|
||||
|
@ -60,6 +65,48 @@ export const getTopNavLinks = ({
|
|||
testId: 'discoverAlertsButton',
|
||||
};
|
||||
|
||||
/**
|
||||
* Switches from ES|QL to classic mode and vice versa
|
||||
*/
|
||||
const esqLDataViewTransitionToggle = {
|
||||
id: 'esql',
|
||||
label: isEsqlMode
|
||||
? i18n.translate('discover.localMenu.switchToClassicTitle', {
|
||||
defaultMessage: 'Switch to classic',
|
||||
})
|
||||
: i18n.translate('discover.localMenu.tryESQLTitle', {
|
||||
defaultMessage: 'Try ES|QL',
|
||||
}),
|
||||
emphasize: true,
|
||||
iconType: 'editorRedo',
|
||||
fill: false,
|
||||
color: 'text',
|
||||
run: () => {
|
||||
if (dataView) {
|
||||
if (isEsqlMode) {
|
||||
services.trackUiMetric?.(METRIC_TYPE.CLICK, `esql:back_to_classic_clicked`);
|
||||
/**
|
||||
* Display the transition modal if:
|
||||
* - the user has not dismissed the modal
|
||||
* - the user has opened and applied changes to the saved search
|
||||
*/
|
||||
if (
|
||||
shouldShowESQLToDataViewTransitionModal &&
|
||||
!services.storage.get(ESQL_TRANSITION_MODAL_KEY)
|
||||
) {
|
||||
state.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(true);
|
||||
} else {
|
||||
state.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||
}
|
||||
} else {
|
||||
state.actions.transitionFromDataViewToESQL(dataView);
|
||||
services.trackUiMetric?.(METRIC_TYPE.CLICK, `esql:try_btn_clicked`);
|
||||
}
|
||||
}
|
||||
},
|
||||
testId: isEsqlMode ? 'switch-to-dataviews' : 'select-text-based-language-btn',
|
||||
};
|
||||
|
||||
const newSearch = {
|
||||
id: 'new',
|
||||
label: i18n.translate('discover.localMenu.localMenu.newSearchTitle', {
|
||||
|
@ -226,6 +273,10 @@ export const getTopNavLinks = ({
|
|||
const defaultMenu = topNavCustomization?.defaultMenu;
|
||||
const entries = [...(topNavCustomization?.getMenuItems?.() ?? [])];
|
||||
|
||||
if (services.uiSettings.get(ENABLE_ESQL)) {
|
||||
entries.push({ data: esqLDataViewTransitionToggle, order: 0 });
|
||||
}
|
||||
|
||||
if (!defaultMenu?.newItem?.disabled) {
|
||||
entries.push({ data: newSearch, order: defaultMenu?.newItem?.order ?? 100 });
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
|||
import { useInspector } from '../../hooks/use_inspector';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
||||
import {
|
||||
useSavedSearch,
|
||||
useSavedSearchHasChanged,
|
||||
} from '../../state_management/discover_state_provider';
|
||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import { getTopNavBadges } from './get_top_nav_badges';
|
||||
import { getTopNavLinks } from './get_top_nav_links';
|
||||
|
@ -39,7 +43,9 @@ export const useDiscoverTopNav = ({
|
|||
}),
|
||||
[stateContainer, services, hasUnsavedChanges, topNavCustomization]
|
||||
);
|
||||
|
||||
const savedSearchId = useSavedSearch().id;
|
||||
const savedSearchHasChanged = useSavedSearchHasChanged();
|
||||
const shouldShowESQLToDataViewTransitionModal = !savedSearchId || savedSearchHasChanged;
|
||||
const dataView = useInternalStateSelector((state) => state.dataView);
|
||||
const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews);
|
||||
const isEsqlMode = useIsEsqlMode();
|
||||
|
@ -58,6 +64,7 @@ export const useDiscoverTopNav = ({
|
|||
isEsqlMode,
|
||||
adHocDataViews,
|
||||
topNavCustomization,
|
||||
shouldShowESQLToDataViewTransitionModal,
|
||||
}),
|
||||
[
|
||||
adHocDataViews,
|
||||
|
@ -67,6 +74,7 @@ export const useDiscoverTopNav = ({
|
|||
services,
|
||||
stateContainer,
|
||||
topNavCustomization,
|
||||
shouldShowESQLToDataViewTransitionModal,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ export interface InternalState {
|
|||
expandedDoc: DataTableRecord | undefined;
|
||||
customFilters: Filter[];
|
||||
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined; // it will be used during saved search saving
|
||||
isESQLToDataViewTransitionModalVisible?: boolean;
|
||||
resetDefaultProfileState: { columns: boolean; rowHeight: boolean };
|
||||
}
|
||||
|
||||
|
@ -49,6 +50,9 @@ export interface InternalStateTransitions {
|
|||
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined
|
||||
) => InternalState;
|
||||
resetOnSavedSearchChange: (state: InternalState) => () => InternalState;
|
||||
setIsESQLToDataViewTransitionModalVisible: (
|
||||
state: InternalState
|
||||
) => (isVisible: boolean) => InternalState;
|
||||
setResetDefaultProfileState: (
|
||||
state: InternalState
|
||||
) => (resetDefaultProfileState: InternalState['resetDefaultProfileState']) => InternalState;
|
||||
|
@ -85,6 +89,11 @@ export function getInternalStateContainer() {
|
|||
...prevState,
|
||||
isDataViewLoading: loading,
|
||||
}),
|
||||
setIsESQLToDataViewTransitionModalVisible:
|
||||
(prevState: InternalState) => (isVisible: boolean) => ({
|
||||
...prevState,
|
||||
isESQLToDataViewTransitionModalVisible: isVisible,
|
||||
}),
|
||||
setSavedDataViews: (prevState: InternalState) => (nextDataViewList: DataViewListItem[]) => ({
|
||||
...prevState,
|
||||
savedDataViews: nextDataViewList,
|
||||
|
|
|
@ -753,6 +753,28 @@ describe('Test discover state actions', () => {
|
|||
expect(persistedDataViewId).toBe(nextSavedSearch?.searchSource.getField('index')!.id);
|
||||
});
|
||||
|
||||
test('transitionFromDataViewToESQL', async () => {
|
||||
const savedSearchWithQuery = copySavedSearch(savedSearchMock);
|
||||
const query = { query: "foo: 'bar'", language: 'kuery' };
|
||||
savedSearchWithQuery.searchSource.setField('query', query);
|
||||
const { state } = await getState('/', { savedSearch: savedSearchWithQuery });
|
||||
await state.actions.transitionFromDataViewToESQL(dataViewMock);
|
||||
expect(state.appState.getState().query).toStrictEqual({
|
||||
esql: 'FROM the-data-view-title | LIMIT 10',
|
||||
});
|
||||
});
|
||||
|
||||
test('transitionFromESQLToDataView', async () => {
|
||||
const savedSearchWithQuery = copySavedSearch(savedSearchMock);
|
||||
const query = {
|
||||
esql: 'FROM the-data-view-title | LIMIT 10',
|
||||
};
|
||||
savedSearchWithQuery.searchSource.setField('query', query);
|
||||
const { state } = await getState('/', { savedSearch: savedSearchWithQuery });
|
||||
await state.actions.transitionFromESQLToDataView('the-data-view-id');
|
||||
expect(state.appState.getState().query).toStrictEqual({ query: '', language: 'kuery' });
|
||||
});
|
||||
|
||||
test('onChangeDataView', async () => {
|
||||
const { state, getCurrentUrl } = await getState('/', { savedSearch: savedSearchMock });
|
||||
const { actions, savedSearchState, dataState } = state;
|
||||
|
|
|
@ -23,6 +23,7 @@ import { DataView, DataViewSpec, DataViewType } from '@kbn/data-views-plugin/pub
|
|||
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { merge } from 'rxjs';
|
||||
import { getInitialESQLQuery } from '@kbn/esql-utils';
|
||||
import { AggregateQuery, Query, TimeRange } from '@kbn/es-query';
|
||||
import { loadSavedSearch as loadSavedSearchFn } from './utils/load_saved_search';
|
||||
import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search';
|
||||
|
@ -174,6 +175,16 @@ export interface DiscoverStateContainer {
|
|||
* @param dataView
|
||||
*/
|
||||
onDataViewEdited: (dataView: DataView) => Promise<void>;
|
||||
/**
|
||||
* Triggered when transitioning from ESQL to Dataview
|
||||
* Clean ups the ES|QL query and moves to the dataview mode
|
||||
*/
|
||||
transitionFromESQLToDataView: (dataViewId: string) => void;
|
||||
/**
|
||||
* Triggered when transitioning from ESQL to Dataview
|
||||
* Clean ups the ES|QL query and moves to the dataview mode
|
||||
*/
|
||||
transitionFromDataViewToESQL: (dataView: DataView) => void;
|
||||
/**
|
||||
* Triggered when a saved search is opened in the savedObject finder
|
||||
* @param savedSearchId
|
||||
|
@ -354,6 +365,31 @@ export function getDiscoverStateContainer({
|
|||
}
|
||||
};
|
||||
|
||||
const transitionFromESQLToDataView = (dataViewId: string) => {
|
||||
appStateContainer.update({
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: '',
|
||||
},
|
||||
columns: [],
|
||||
dataSource: {
|
||||
type: DataSourceType.DataView,
|
||||
dataViewId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const transitionFromDataViewToESQL = (dataView: DataView) => {
|
||||
const queryString = getInitialESQLQuery(dataView);
|
||||
appStateContainer.update({
|
||||
query: { esql: queryString },
|
||||
dataSource: {
|
||||
type: DataSourceType.Esql,
|
||||
},
|
||||
columns: [],
|
||||
});
|
||||
};
|
||||
|
||||
const onDataViewCreated = async (nextDataView: DataView) => {
|
||||
if (!nextDataView.isPersisted()) {
|
||||
internalStateContainer.transitions.appendAdHocDataViews(nextDataView);
|
||||
|
@ -549,6 +585,8 @@ export function getDiscoverStateContainer({
|
|||
onDataViewCreated,
|
||||
onDataViewEdited,
|
||||
onOpenSavedSearch,
|
||||
transitionFromESQLToDataView,
|
||||
transitionFromDataViewToESQL,
|
||||
onUpdateQuery,
|
||||
setDataView,
|
||||
undoSavedSearchChanges,
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
The editor accepts the following properties:
|
||||
- query: This is the **AggregateQuery** query. i.e. (`{esql: from index1 | limit 10}`)
|
||||
- onTextLangQueryChange: callback that is called every time the query is updated
|
||||
- expandCodeEditor: flag that opens the editor on the expanded mode
|
||||
- errors: array of `Error`.
|
||||
- warning: A string for visualizing warnings
|
||||
- onTextLangQuerySubmit: callback that is called when the user submits the query
|
||||
|
@ -17,8 +16,6 @@ import { TextBasedLangEditor } from '@kbn/esql/public';
|
|||
<TextBasedLangEditor
|
||||
query={query}
|
||||
onTextLangQueryChange={onTextLangQueryChange}
|
||||
expandCodeEditor={(status: boolean) => setCodeEditorIsExpanded(status)}
|
||||
isCodeEditorExpanded={codeEditorIsExpandedFlag}
|
||||
errors={props.textBasedLanguageModeErrors}
|
||||
isDisabled={false}
|
||||
onTextLangQuerySubmit={onTextLangQuerySubmit}
|
||||
|
|
|
@ -30,7 +30,7 @@ export const TextBasedLangEditor = (props: TextBasedLanguagesEditorProps) => {
|
|||
...deps,
|
||||
}}
|
||||
>
|
||||
<TextBasedLanguagesEditor {...props} isDarkMode={deps.darkMode} />
|
||||
<TextBasedLanguagesEditor {...props} />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,6 @@ export let core: CoreStart;
|
|||
|
||||
interface ServiceDeps {
|
||||
core: CoreStart;
|
||||
darkMode: boolean;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
expressions: ExpressionsStart;
|
||||
indexManagementApiService?: IndexManagementPluginSetup['apiService'];
|
||||
|
@ -45,14 +44,11 @@ export const setKibanaServices = (
|
|||
fieldsMetadata?: FieldsMetadataPublicStart
|
||||
) => {
|
||||
core = kibanaCore;
|
||||
core.theme.theme$.subscribe(({ darkMode }) => {
|
||||
servicesReady$.next({
|
||||
core,
|
||||
darkMode,
|
||||
dataViews,
|
||||
expressions,
|
||||
indexManagementApiService: indexManagement?.apiService,
|
||||
fieldsMetadata,
|
||||
});
|
||||
servicesReady$.next({
|
||||
core,
|
||||
dataViews,
|
||||
expressions,
|
||||
indexManagementApiService: indexManagement?.apiService,
|
||||
fieldsMetadata,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
exports[`TopNavMenu Should render emphasized item which should be clickable 1`] = `
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={true}
|
||||
iconSide="right"
|
||||
iconType="beaker"
|
||||
|
|
|
@ -22,6 +22,8 @@ export interface TopNavMenuData {
|
|||
tooltip?: string | (() => string | undefined);
|
||||
badge?: EuiBetaBadgeProps;
|
||||
emphasize?: boolean;
|
||||
fill?: boolean;
|
||||
color?: string;
|
||||
isLoading?: boolean;
|
||||
iconType?: string;
|
||||
iconSide?: EuiButtonProps['iconSide'];
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { upperFirst, isFunction } from 'lodash';
|
||||
import React, { MouseEvent } from 'react';
|
||||
import { EuiToolTip, EuiButton, EuiHeaderLink, EuiBetaBadge } from '@elastic/eui';
|
||||
import { EuiToolTip, EuiButton, EuiHeaderLink, EuiBetaBadge, EuiButtonColor } from '@elastic/eui';
|
||||
import { TopNavMenuData } from './top_nav_menu_data';
|
||||
|
||||
export function TopNavMenuItem(props: TopNavMenuData) {
|
||||
|
@ -48,6 +48,8 @@ export function TopNavMenuItem(props: TopNavMenuData) {
|
|||
iconSide: props.iconSide,
|
||||
'data-test-subj': props.testId,
|
||||
className: props.className,
|
||||
color: (props.color ?? 'primary') as EuiButtonColor,
|
||||
fill: props.fill ?? true,
|
||||
};
|
||||
|
||||
// If the item specified a href, then override the suppress the onClick
|
||||
|
@ -58,11 +60,11 @@ export function TopNavMenuItem(props: TopNavMenuData) {
|
|||
: {};
|
||||
|
||||
const btn = props.emphasize ? (
|
||||
<EuiButton size="s" {...commonButtonProps} fill>
|
||||
<EuiButton size="s" {...commonButtonProps}>
|
||||
{getButtonContainer()}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiHeaderLink size="s" color="primary" {...commonButtonProps} {...overrideProps}>
|
||||
<EuiHeaderLink size="s" {...commonButtonProps} {...overrideProps}>
|
||||
{getButtonContainer()}
|
||||
</EuiHeaderLink>
|
||||
);
|
||||
|
|
|
@ -566,7 +566,7 @@ storiesOf('SearchBar', module)
|
|||
],
|
||||
} as unknown as SearchBarProps)
|
||||
)
|
||||
.add('with dataviewPicker with ESQL', () =>
|
||||
.add('with dataviewPicker with ES|QL', () =>
|
||||
wrapSearchBarInContext({
|
||||
dataViewPickerComponentProps: {
|
||||
currentDataViewId: '1234',
|
||||
|
@ -582,13 +582,13 @@ storiesOf('SearchBar', module)
|
|||
},
|
||||
} as SearchBarProps)
|
||||
)
|
||||
.add('with dataviewPicker with ESQL and ESQL query', () =>
|
||||
.add('with dataviewPicker with ES|QL and ES|QL query', () =>
|
||||
wrapSearchBarInContext({
|
||||
dataViewPickerComponentProps: {
|
||||
currentDataViewId: '1234',
|
||||
trigger: {
|
||||
'data-test-subj': 'dataView-switch-link',
|
||||
label: 'ESQL',
|
||||
label: 'ES|QL',
|
||||
title: 'ESQL',
|
||||
},
|
||||
onChangeDataView: action('onChangeDataView'),
|
||||
|
@ -599,13 +599,13 @@ storiesOf('SearchBar', module)
|
|||
query: { esql: 'from dataview | project field1, field2' },
|
||||
} as unknown as SearchBarProps<Query>)
|
||||
)
|
||||
.add('with dataviewPicker with ESQL and large ESQL query', () =>
|
||||
.add('with dataviewPicker with ES|QL and large ES|QL query', () =>
|
||||
wrapSearchBarInContext({
|
||||
dataViewPickerComponentProps: {
|
||||
currentDataViewId: '1234',
|
||||
trigger: {
|
||||
'data-test-subj': 'dataView-switch-link',
|
||||
label: 'ESQL',
|
||||
label: 'ES|QL',
|
||||
title: 'ESQL',
|
||||
},
|
||||
onChangeDataView: action('onChangeDataView'),
|
||||
|
@ -618,13 +618,13 @@ storiesOf('SearchBar', module)
|
|||
},
|
||||
} as unknown as SearchBarProps<Query>)
|
||||
)
|
||||
.add('with dataviewPicker with ESQL and errors in ESQL query', () =>
|
||||
.add('with dataviewPicker with ES|QL and errors in ES|QL query', () =>
|
||||
wrapSearchBarInContext({
|
||||
dataViewPickerComponentProps: {
|
||||
currentDataViewId: '1234',
|
||||
trigger: {
|
||||
'data-test-subj': 'dataView-switch-link',
|
||||
label: 'ESQL',
|
||||
label: 'ES|QL',
|
||||
title: 'ESQL',
|
||||
},
|
||||
onChangeDataView: action('onChangeDataView'),
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { EuiThemeComputed } from '@elastic/eui';
|
||||
import { calculateWidthFromEntries } from '@kbn/calculate-width-from-char-count';
|
||||
import { DataViewListItemEnhanced } from './dataview_list';
|
||||
|
||||
|
@ -14,13 +14,18 @@ const MIN_WIDTH = 300;
|
|||
export const changeDataViewStyles = ({
|
||||
fullWidth,
|
||||
dataViewsList,
|
||||
theme,
|
||||
}: {
|
||||
fullWidth?: boolean;
|
||||
dataViewsList: DataViewListItemEnhanced[];
|
||||
theme: EuiThemeComputed;
|
||||
}) => {
|
||||
return {
|
||||
trigger: {
|
||||
maxWidth: fullWidth ? undefined : MIN_WIDTH,
|
||||
border: theme.border.thin,
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
},
|
||||
popoverContent: {
|
||||
width: calculateWidthFromEntries(dataViewsList, ['name', 'id'], { minWidth: MIN_WIDTH }),
|
||||
|
|
|
@ -14,13 +14,10 @@ import { findTestSubject } from '@elastic/eui/lib/test';
|
|||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
|
||||
import { TextBasedLanguages } from '@kbn/esql-utils';
|
||||
import { ChangeDataView } from './change_dataview';
|
||||
import { DataViewSelector } from './data_view_selector';
|
||||
import { dataViewMock, dataViewMockEsql } from './mocks/dataview';
|
||||
import { DataViewPickerPropsExtended } from './data_view_picker';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('DataView component', () => {
|
||||
const createMockWebStorage = () => ({
|
||||
|
@ -89,7 +86,6 @@ describe('DataView component', () => {
|
|||
'data-test-subj': 'dataview-trigger',
|
||||
},
|
||||
onChangeDataView: jest.fn(),
|
||||
onTextLangQuerySubmit: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -135,39 +131,6 @@ describe('DataView component', () => {
|
|||
expect(addDataViewSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render the text based languages panels if languages are given', async () => {
|
||||
const component = mount(
|
||||
wrapDataViewComponentInContext(
|
||||
{
|
||||
...props,
|
||||
textBasedLanguages: [TextBasedLanguages.ESQL],
|
||||
textBasedLanguage: TextBasedLanguages.ESQL,
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
findTestSubject(component, 'dataview-trigger').simulate('click');
|
||||
const text = component.find('[data-test-subj="select-text-based-language-panel"]');
|
||||
expect(text.length).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should cleanup the query is on text based mode and add new dataview', async () => {
|
||||
const component = mount(
|
||||
wrapDataViewComponentInContext(
|
||||
{
|
||||
...props,
|
||||
onDataViewCreated: jest.fn(),
|
||||
textBasedLanguages: [TextBasedLanguages.ESQL],
|
||||
textBasedLanguage: TextBasedLanguages.ESQL,
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
findTestSubject(component, 'dataview-trigger').simulate('click');
|
||||
component.find('[data-test-subj="dataview-create-new"]').first().simulate('click');
|
||||
expect(props.onTextLangQuerySubmit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should properly handle ad hoc data views', async () => {
|
||||
const component = mount(
|
||||
wrapDataViewComponentInContext(
|
||||
|
@ -233,44 +196,4 @@ describe('DataView component', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('test based language switch warning icon', () => {
|
||||
beforeAll(() => {
|
||||
// Enzyme doesn't clean the DOM between tests, so we need to do it manually
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
it('should show text based language switch warning icon', () => {
|
||||
render(
|
||||
wrapDataViewComponentInContext(
|
||||
{
|
||||
...props,
|
||||
onDataViewCreated: jest.fn(),
|
||||
textBasedLanguages: [TextBasedLanguages.ESQL],
|
||||
textBasedLanguage: TextBasedLanguages.ESQL,
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
userEvent.click(screen.getByTestId('dataview-trigger'));
|
||||
expect(screen.queryByTestId('textBasedLang-warning')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show text based language switch warning icon when shouldShowTextBasedLanguageTransitionModal is false', () => {
|
||||
render(
|
||||
wrapDataViewComponentInContext(
|
||||
{
|
||||
...props,
|
||||
onDataViewCreated: jest.fn(),
|
||||
textBasedLanguages: [TextBasedLanguages.ESQL],
|
||||
textBasedLanguage: TextBasedLanguages.ESQL,
|
||||
shouldShowTextBasedLanguageTransitionModal: false,
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
userEvent.click(screen.getByTestId('dataview-trigger'));
|
||||
expect(screen.queryByTestId('textBasedLang-warning')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,13 +7,11 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
EuiPopover,
|
||||
EuiPanel,
|
||||
EuiHorizontalRule,
|
||||
EuiButton,
|
||||
EuiContextMenuPanel,
|
||||
EuiContextMenuItem,
|
||||
useEuiTheme,
|
||||
|
@ -24,37 +22,17 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { AggregateQuery, getLanguageDisplayName } from '@kbn/es-query';
|
||||
import { getInitialESQLQuery } from '@kbn/esql-utils';
|
||||
import { getLanguageDisplayName } from '@kbn/es-query';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { IUnifiedSearchPluginServices } from '../types';
|
||||
import { type DataViewPickerPropsExtended } from './data_view_picker';
|
||||
import type { DataViewListItemEnhanced } from './dataview_list';
|
||||
import type { TextBasedLanguagesTransitionModalProps } from './text_languages_transition_modal';
|
||||
import adhoc from './assets/adhoc.svg';
|
||||
import { changeDataViewStyles } from './change_dataview.styles';
|
||||
import { DataViewSelector } from './data_view_selector';
|
||||
|
||||
// local storage key for the text based languages transition modal
|
||||
const TEXT_LANG_TRANSITION_MODAL_KEY = 'data.textLangTransitionModal';
|
||||
|
||||
const Fallback = () => <div />;
|
||||
|
||||
const LazyTextBasedLanguagesTransitionModal = React.lazy(
|
||||
() => import('./text_languages_transition_modal')
|
||||
);
|
||||
export const TextBasedLanguagesTransitionModal = (
|
||||
props: TextBasedLanguagesTransitionModalProps
|
||||
) => (
|
||||
<React.Suspense fallback={<Fallback />}>
|
||||
<LazyTextBasedLanguagesTransitionModal {...props} />
|
||||
</React.Suspense>
|
||||
);
|
||||
|
||||
const mapAdHocDataView = (adHocDataView: DataView): DataViewListItemEnhanced => {
|
||||
return {
|
||||
title: adHocDataView.title,
|
||||
|
@ -75,11 +53,7 @@ export function ChangeDataView({
|
|||
onDataViewCreated,
|
||||
trigger,
|
||||
selectableProps,
|
||||
textBasedLanguages,
|
||||
onSaveTextLanguageQuery,
|
||||
onTextLangQuerySubmit,
|
||||
textBasedLanguage,
|
||||
shouldShowTextBasedLanguageTransitionModal = true,
|
||||
isDisabled,
|
||||
onEditDataView,
|
||||
onCreateDefaultAdHocDataView,
|
||||
|
@ -91,19 +65,15 @@ export function ChangeDataView({
|
|||
const [isTextBasedLangSelected, setIsTextBasedLangSelected] = useState(
|
||||
Boolean(textBasedLanguage)
|
||||
);
|
||||
const [isTextLangTransitionModalVisible, setIsTextLangTransitionModalVisible] = useState(false);
|
||||
const [selectedDataView, setSelectedDataView] = useState<DataView | undefined>(undefined);
|
||||
|
||||
const kibana = useKibana<IUnifiedSearchPluginServices>();
|
||||
const { application, data, storage, dataViews, dataViewEditor, appName, usageCollection } =
|
||||
kibana.services;
|
||||
const reportUiCounter = usageCollection?.reportUiCounter.bind(usageCollection, appName);
|
||||
const { application, data, dataViews, dataViewEditor } = kibana.services;
|
||||
|
||||
const styles = changeDataViewStyles({ fullWidth: trigger.fullWidth, dataViewsList });
|
||||
|
||||
const [isTextLangTransitionModalDismissed, setIsTextLangTransitionModalDismissed] = useState(() =>
|
||||
Boolean(storage.get(TEXT_LANG_TRANSITION_MODAL_KEY))
|
||||
);
|
||||
const styles = changeDataViewStyles({
|
||||
fullWidth: trigger.fullWidth,
|
||||
dataViewsList,
|
||||
theme: euiTheme,
|
||||
});
|
||||
|
||||
// Create a reusable id to ensure search input is the first focused item in the popover even though it's not the first item
|
||||
const searchListInputId = useGeneratedHtmlId({ prefix: 'dataviewPickerListSearchInput' });
|
||||
|
@ -112,18 +82,14 @@ export function ChangeDataView({
|
|||
const fetchDataViews = async () => {
|
||||
const savedDataViewRefs: DataViewListItemEnhanced[] = savedDataViews
|
||||
? savedDataViews
|
||||
: await data.dataViews.getIdsWithTitle();
|
||||
: (await data.dataViews.getIdsWithTitle()) ?? [];
|
||||
const adHocDataViewRefs: DataViewListItemEnhanced[] =
|
||||
adHocDataViews?.map(mapAdHocDataView) ?? [];
|
||||
|
||||
setDataViewsList(savedDataViewRefs.concat(adHocDataViewRefs));
|
||||
if (currentDataViewId) {
|
||||
const currentDataview = await data.dataViews.get(currentDataViewId, false);
|
||||
setSelectedDataView(currentDataview);
|
||||
}
|
||||
};
|
||||
fetchDataViews();
|
||||
}, [data, currentDataViewId, adHocDataViews, savedDataViews, isTextBasedLangSelected]);
|
||||
}, [data, currentDataViewId, adHocDataViews, savedDataViews]);
|
||||
|
||||
useEffect(() => {
|
||||
if (textBasedLanguage) {
|
||||
|
@ -146,17 +112,16 @@ export function ChangeDataView({
|
|||
const createTrigger = function () {
|
||||
const { label, title, 'data-test-subj': dataTestSubj, fullWidth, ...rest } = trigger;
|
||||
return (
|
||||
<EuiButton
|
||||
<EuiButtonEmpty
|
||||
css={styles.trigger}
|
||||
data-test-subj={dataTestSubj}
|
||||
onClick={() => {
|
||||
setPopoverIsOpen(!isPopoverOpen);
|
||||
}}
|
||||
color={isMissingCurrent ? 'danger' : 'primary'}
|
||||
color={isMissingCurrent ? 'danger' : 'text'}
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
title={triggerLabel}
|
||||
fullWidth={fullWidth}
|
||||
disabled={isDisabled}
|
||||
textProps={{ className: 'eui-textTruncate' }}
|
||||
{...rest}
|
||||
|
@ -174,13 +139,13 @@ export function ChangeDataView({
|
|||
)}
|
||||
{triggerLabel}
|
||||
</>
|
||||
</EuiButton>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
};
|
||||
|
||||
const getPanelItems = () => {
|
||||
const panelItems: EuiContextMenuPanelProps['items'] = [];
|
||||
if (onAddField && !isTextBasedLangSelected) {
|
||||
if (onAddField) {
|
||||
panelItems.push(
|
||||
<EuiContextMenuItem
|
||||
key="add"
|
||||
|
@ -242,29 +207,6 @@ export function ChangeDataView({
|
|||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{isTextBasedLangSelected && shouldShowTextBasedLanguageTransitionModal ? (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning',
|
||||
{
|
||||
defaultMessage:
|
||||
"Switching data views removes the current {textBasedLanguage} query. Save this search to ensure you don't lose work.",
|
||||
values: {
|
||||
textBasedLanguage: getLanguageDisplayName(textBasedLanguage),
|
||||
},
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiIcon
|
||||
type="warning"
|
||||
color="warning"
|
||||
data-test-subj="textBasedLang-warning"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<h5>
|
||||
|
@ -281,16 +223,6 @@ export function ChangeDataView({
|
|||
onClick={() => {
|
||||
setPopoverIsOpen(false);
|
||||
onDataViewCreated();
|
||||
// go to dataview mode
|
||||
if (isTextBasedLangSelected) {
|
||||
setIsTextBasedLangSelected(false);
|
||||
// clean up the Text based language query
|
||||
onTextLangQuerySubmit?.({
|
||||
language: 'kuery',
|
||||
query: '',
|
||||
});
|
||||
setTriggerLabel(trigger.label);
|
||||
}
|
||||
}}
|
||||
size="xs"
|
||||
iconType="plusInCircleFilled"
|
||||
|
@ -309,167 +241,60 @@ export function ChangeDataView({
|
|||
searchListInputId={searchListInputId}
|
||||
dataViewsList={dataViewsList}
|
||||
selectableProps={selectableProps}
|
||||
isTextBasedLangSelected={isTextBasedLangSelected}
|
||||
setPopoverIsOpen={setPopoverIsOpen}
|
||||
onChangeDataView={async (newId) => {
|
||||
const currentDataview = await data.dataViews.get(newId, false);
|
||||
setSelectedDataView(currentDataview);
|
||||
setPopoverIsOpen(false);
|
||||
|
||||
if (isTextBasedLangSelected) {
|
||||
const showTransitionModal =
|
||||
!isTextLangTransitionModalDismissed && shouldShowTextBasedLanguageTransitionModal;
|
||||
|
||||
if (showTransitionModal) {
|
||||
setIsTextLangTransitionModalVisible(true);
|
||||
} else {
|
||||
setIsTextBasedLangSelected(false);
|
||||
// clean up the Text based language query
|
||||
onTextLangQuerySubmit?.({
|
||||
language: 'kuery',
|
||||
query: '',
|
||||
});
|
||||
onChangeDataView(newId);
|
||||
setTriggerLabel(trigger.label);
|
||||
}
|
||||
} else {
|
||||
onChangeDataView(newId);
|
||||
}
|
||||
onChangeDataView(newId);
|
||||
}}
|
||||
onCreateDefaultAdHocDataView={onCreateDefaultAdHocDataView}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
if (textBasedLanguages?.length) {
|
||||
panelItems.push(
|
||||
<EuiHorizontalRule margin="none" key="textbasedLanguages-divider" />,
|
||||
<EuiPanel color="transparent" paddingSize="none" key="try-esql">
|
||||
<EuiButton
|
||||
css={css`
|
||||
border-top-right-radius: unset;
|
||||
border-top-left-radius: unset;
|
||||
`}
|
||||
color="success"
|
||||
size="s"
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
if (selectedDataView) {
|
||||
onTextBasedSubmit({
|
||||
esql: getInitialESQLQuery(selectedDataView),
|
||||
});
|
||||
}
|
||||
}}
|
||||
data-test-subj="select-text-based-language-panel"
|
||||
contentProps={{
|
||||
css: {
|
||||
justifyContent: 'flex-start',
|
||||
paddingLeft: '26px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTryLabel', {
|
||||
defaultMessage: 'Language: ES|QL',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
return panelItems;
|
||||
};
|
||||
|
||||
let modal;
|
||||
|
||||
const onTransitionModalDismiss = useCallback(() => {
|
||||
storage.set(TEXT_LANG_TRANSITION_MODAL_KEY, true);
|
||||
setIsTextLangTransitionModalDismissed(true);
|
||||
}, [storage]);
|
||||
|
||||
const onTextBasedSubmit = useCallback(
|
||||
(q: AggregateQuery) => {
|
||||
onTextLangQuerySubmit?.(q);
|
||||
setPopoverIsOpen(false);
|
||||
reportUiCounter?.(METRIC_TYPE.CLICK, `esql:unified_search_clicked`);
|
||||
},
|
||||
[onTextLangQuerySubmit, reportUiCounter]
|
||||
);
|
||||
|
||||
const cleanup = useCallback(
|
||||
(shouldDismissModal: boolean) => {
|
||||
setIsTextLangTransitionModalVisible(false);
|
||||
setIsTextBasedLangSelected(false);
|
||||
// clean up the Text based language query
|
||||
onTextLangQuerySubmit?.({
|
||||
language: 'kuery',
|
||||
query: '',
|
||||
});
|
||||
if (selectedDataView?.id) {
|
||||
onChangeDataView(selectedDataView?.id);
|
||||
}
|
||||
setTriggerLabel(trigger.label);
|
||||
if (shouldDismissModal) {
|
||||
onTransitionModalDismiss();
|
||||
}
|
||||
},
|
||||
[
|
||||
onChangeDataView,
|
||||
onTextLangQuerySubmit,
|
||||
onTransitionModalDismiss,
|
||||
selectedDataView?.id,
|
||||
trigger.label,
|
||||
]
|
||||
);
|
||||
|
||||
const onModalClose = useCallback(
|
||||
(shouldDismissModal: boolean, needsSave?: boolean) => {
|
||||
if (Boolean(needsSave)) {
|
||||
setIsTextLangTransitionModalVisible(false);
|
||||
onSaveTextLanguageQuery?.({
|
||||
onSave: () => {
|
||||
cleanup(shouldDismissModal);
|
||||
},
|
||||
onCancel: () => {
|
||||
setIsTextLangTransitionModalVisible(false);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
cleanup(shouldDismissModal);
|
||||
}
|
||||
},
|
||||
[cleanup, onSaveTextLanguageQuery]
|
||||
);
|
||||
|
||||
if (isTextLangTransitionModalVisible && !isTextLangTransitionModalDismissed) {
|
||||
modal = (
|
||||
<TextBasedLanguagesTransitionModal
|
||||
closeModal={onModalClose}
|
||||
setIsTextLangTransitionModalVisible={setIsTextLangTransitionModalVisible}
|
||||
textBasedLanguage={textBasedLanguage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
panelClassName="changeDataViewPopover"
|
||||
button={createTrigger()}
|
||||
panelProps={{
|
||||
['data-test-subj']: 'changeDataViewPopover',
|
||||
}}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setPopoverIsOpen(false)}
|
||||
panelPaddingSize="none"
|
||||
initialFocus={!isTextBasedLangSelected ? `#${searchListInputId}` : undefined}
|
||||
display="block"
|
||||
buffer={8}
|
||||
>
|
||||
<div css={styles.popoverContent}>
|
||||
<EuiContextMenuPanel size="s" items={getPanelItems()} />
|
||||
</div>
|
||||
</EuiPopover>
|
||||
{modal}
|
||||
</>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
{!isTextBasedLangSelected && (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
padding: 11px;
|
||||
border-radius: ${euiTheme.border.radius.small} 0 0 ${euiTheme.border.radius.small};
|
||||
background-color: ${euiTheme.colors.lightestShade};
|
||||
border: ${euiTheme.border.thin};
|
||||
border-right: 0;
|
||||
`}
|
||||
>
|
||||
{i18n.translate('unifiedSearch.query.queryBar.esqlMenu.switcherLabelTitle', {
|
||||
defaultMessage: 'Data view',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
<EuiPopover
|
||||
panelClassName="changeDataViewPopover"
|
||||
button={createTrigger()}
|
||||
panelProps={{
|
||||
['data-test-subj']: 'changeDataViewPopover',
|
||||
}}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setPopoverIsOpen(false)}
|
||||
panelPaddingSize="none"
|
||||
initialFocus={`#${searchListInputId}`}
|
||||
display="block"
|
||||
buffer={8}
|
||||
>
|
||||
<div css={styles.popoverContent}>
|
||||
<EuiContextMenuPanel size="s" items={getPanelItems()} />
|
||||
</div>
|
||||
</EuiPopover>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import React from 'react';
|
||||
import type { EuiButtonProps, EuiSelectableProps } from '@elastic/eui';
|
||||
import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import type { AggregateQuery, Query } from '@kbn/es-query';
|
||||
import { TextBasedLanguages } from '@kbn/esql-utils';
|
||||
import { ChangeDataView } from './change_dataview';
|
||||
|
||||
|
@ -18,11 +17,6 @@ export type ChangeDataViewTriggerProps = EuiButtonProps & {
|
|||
title?: string;
|
||||
};
|
||||
|
||||
export interface OnSaveTextLanguageQueryProps {
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface DataViewPickerProps {
|
||||
/**
|
||||
|
@ -76,16 +70,6 @@ export interface DataViewPickerProps {
|
|||
* will be available.
|
||||
*/
|
||||
textBasedLanguages?: TextBasedLanguages[];
|
||||
/**
|
||||
* Callback that is called when the user clicks the Save and switch transition modal button
|
||||
*/
|
||||
onSaveTextLanguageQuery?: ({ onSave, onCancel }: OnSaveTextLanguageQueryProps) => void;
|
||||
/**
|
||||
* Determines if the text based language transition
|
||||
* modal should be shown when switching data views
|
||||
*/
|
||||
shouldShowTextBasedLanguageTransitionModal?: boolean;
|
||||
|
||||
/**
|
||||
* Makes the picker disabled by disabling the popover trigger
|
||||
*/
|
||||
|
@ -93,10 +77,6 @@ export interface DataViewPickerProps {
|
|||
}
|
||||
|
||||
export interface DataViewPickerPropsExtended extends DataViewPickerProps {
|
||||
/**
|
||||
* Callback that is called when the user clicks the submit button
|
||||
*/
|
||||
onTextLangQuerySubmit?: (query?: Query | AggregateQuery) => void;
|
||||
/**
|
||||
* Text based language that is currently selected; depends on the query
|
||||
*/
|
||||
|
@ -115,10 +95,7 @@ export const DataViewPicker = ({
|
|||
trigger,
|
||||
selectableProps,
|
||||
textBasedLanguages,
|
||||
onSaveTextLanguageQuery,
|
||||
onTextLangQuerySubmit,
|
||||
textBasedLanguage,
|
||||
shouldShowTextBasedLanguageTransitionModal,
|
||||
onCreateDefaultAdHocDataView,
|
||||
isDisabled,
|
||||
}: DataViewPickerPropsExtended) => {
|
||||
|
@ -136,10 +113,7 @@ export const DataViewPicker = ({
|
|||
savedDataViews={savedDataViews}
|
||||
selectableProps={selectableProps}
|
||||
textBasedLanguages={textBasedLanguages}
|
||||
onSaveTextLanguageQuery={onSaveTextLanguageQuery}
|
||||
onTextLangQuerySubmit={onTextLangQuerySubmit}
|
||||
textBasedLanguage={textBasedLanguage}
|
||||
shouldShowTextBasedLanguageTransitionModal={shouldShowTextBasedLanguageTransitionModal}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -19,7 +19,6 @@ export interface DataViewSelectorProps {
|
|||
searchListInputId?: string;
|
||||
dataViewsList: DataViewListItem[];
|
||||
selectableProps?: EuiSelectableProps;
|
||||
isTextBasedLangSelected: boolean;
|
||||
setPopoverIsOpen: (isOpen: boolean) => void;
|
||||
onChangeDataView: (dataViewId: string) => void;
|
||||
onCreateDefaultAdHocDataView?: (dataViewSpec: DataViewSpec) => void;
|
||||
|
@ -30,7 +29,6 @@ export const DataViewSelector = ({
|
|||
searchListInputId,
|
||||
dataViewsList,
|
||||
selectableProps,
|
||||
isTextBasedLangSelected,
|
||||
setPopoverIsOpen,
|
||||
onChangeDataView,
|
||||
onCreateDefaultAdHocDataView,
|
||||
|
@ -83,7 +81,6 @@ export const DataViewSelector = ({
|
|||
},
|
||||
}}
|
||||
searchListInputId={searchListInputId}
|
||||
isTextBasedLangSelected={isTextBasedLangSelected}
|
||||
/>
|
||||
<ExploreMatchingButton
|
||||
noDataViewMatches={noDataViewMatches}
|
||||
|
|
|
@ -54,7 +54,6 @@ describe('DataView list component', () => {
|
|||
currentDataViewId: 'dataview-1',
|
||||
onChangeDataView: changeDataViewSpy,
|
||||
dataViewsList: list,
|
||||
isTextBasedLangSelected: false,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -75,17 +74,8 @@ describe('DataView list component', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should render a warning icon if a text based language is selected', () => {
|
||||
const component = shallow(<DataViewsList {...props} isTextBasedLangSelected />);
|
||||
|
||||
expect(getDataViewPickerOptions(component)!.map((option: any) => option.append)).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('ad hoc data views', () => {
|
||||
const runAdHocDataViewTest = (
|
||||
esqlMode: boolean,
|
||||
esqlDataViews: DataViewListItemEnhanced[] = []
|
||||
) => {
|
||||
const runAdHocDataViewTest = (esqlDataViews: DataViewListItemEnhanced[] = []) => {
|
||||
const dataViewList = [
|
||||
...list,
|
||||
{
|
||||
|
@ -95,9 +85,7 @@ describe('DataView list component', () => {
|
|||
},
|
||||
...esqlDataViews,
|
||||
];
|
||||
const component = shallow(
|
||||
<DataViewsList {...props} dataViewsList={dataViewList} isTextBasedLangSelected={esqlMode} />
|
||||
);
|
||||
const component = shallow(<DataViewsList {...props} dataViewsList={dataViewList} />);
|
||||
expect(getDataViewPickerOptions(component)!.map((option: any) => option.label)).toEqual([
|
||||
'dataview-1',
|
||||
'dataview-2',
|
||||
|
@ -114,20 +102,12 @@ describe('DataView list component', () => {
|
|||
},
|
||||
];
|
||||
|
||||
it('should show ad hoc data views for text based mode', () => {
|
||||
runAdHocDataViewTest(true);
|
||||
});
|
||||
|
||||
it('should show ad hoc data views for data view mode', () => {
|
||||
runAdHocDataViewTest(false);
|
||||
});
|
||||
|
||||
it('should not show ES|QL ad hoc data views for text based mode', () => {
|
||||
runAdHocDataViewTest(true, esqlDataViews);
|
||||
runAdHocDataViewTest();
|
||||
});
|
||||
|
||||
it('should not show ES|QL ad hoc data views for data view mode', () => {
|
||||
runAdHocDataViewTest(false, esqlDataViews);
|
||||
runAdHocDataViewTest(esqlDataViews);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -66,7 +66,6 @@ export interface DataViewListItemEnhanced extends DataViewListItem {
|
|||
export interface DataViewsListProps {
|
||||
dataViewsList: DataViewListItemEnhanced[];
|
||||
onChangeDataView: (newId: string) => void;
|
||||
isTextBasedLangSelected?: boolean;
|
||||
currentDataViewId?: string;
|
||||
selectableProps?: EuiSelectableProps;
|
||||
searchListInputId?: string;
|
||||
|
@ -75,7 +74,6 @@ export interface DataViewsListProps {
|
|||
export function DataViewsList({
|
||||
dataViewsList,
|
||||
onChangeDataView,
|
||||
isTextBasedLangSelected,
|
||||
currentDataViewId,
|
||||
selectableProps,
|
||||
searchListInputId,
|
||||
|
@ -135,7 +133,7 @@ export function DataViewsList({
|
|||
key: id,
|
||||
label: name ? name : title,
|
||||
value: id,
|
||||
checked: id === currentDataViewId && !Boolean(isTextBasedLangSelected) ? 'on' : undefined,
|
||||
checked: id === currentDataViewId ? 'on' : undefined,
|
||||
append: isAdhoc ? (
|
||||
<EuiBadge color="hollow" data-test-subj={`dataViewItemTempBadge-${name}`}>
|
||||
{strings.editorAndPopover.adhoc.getTemporaryDataviewLabel()}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import React from 'react';
|
||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
|
||||
export type { DataViewPickerProps, OnSaveTextLanguageQueryProps } from './data_view_picker';
|
||||
export type { DataViewPickerProps } from './data_view_picker';
|
||||
|
||||
/**
|
||||
* The Lazily-loaded `DataViewsList` component. Consumers should use `React.Suspense` or
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getLanguageDisplayName } from '@kbn/es-query';
|
||||
import {
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiButton,
|
||||
EuiText,
|
||||
EuiCheckbox,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export interface TextBasedLanguagesTransitionModalProps {
|
||||
closeModal: (dismissFlag: boolean, needsSave?: boolean) => void;
|
||||
setIsTextLangTransitionModalVisible: (flag: boolean) => void;
|
||||
textBasedLanguage?: string;
|
||||
}
|
||||
// Needed for React.lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function TextBasedLanguagesTransitionModal({
|
||||
closeModal,
|
||||
setIsTextLangTransitionModalVisible,
|
||||
textBasedLanguage,
|
||||
}: TextBasedLanguagesTransitionModalProps) {
|
||||
const [dismissModalChecked, setDismissModalChecked] = useState(false);
|
||||
const onTransitionModalDismiss = useCallback((e) => {
|
||||
setDismissModalChecked(e.target.checked);
|
||||
}, []);
|
||||
|
||||
const language = getLanguageDisplayName(textBasedLanguage);
|
||||
return (
|
||||
<EuiModal
|
||||
onClose={() => setIsTextLangTransitionModalVisible(false)}
|
||||
style={{ width: 700 }}
|
||||
data-test-subj="unifiedSearch_switch_modal"
|
||||
>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
{i18n.translate(
|
||||
'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle',
|
||||
{
|
||||
defaultMessage: 'Your query will be removed',
|
||||
}
|
||||
)}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<EuiText size="m">
|
||||
{i18n.translate(
|
||||
'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody',
|
||||
{
|
||||
defaultMessage:
|
||||
"Switching data views removes the current {language} query. Save this search to ensure you don't lose work.",
|
||||
values: { language },
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCheckbox
|
||||
id="dismiss-text-based-languages-transition-modal"
|
||||
label={i18n.translate(
|
||||
'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton',
|
||||
{
|
||||
defaultMessage: "Don't show this warning again",
|
||||
}
|
||||
)}
|
||||
checked={dismissModalChecked}
|
||||
onChange={onTransitionModalDismiss}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => closeModal(dismissModalChecked)}
|
||||
color="warning"
|
||||
iconType="merge"
|
||||
data-test-subj="unifiedSearch_switch_noSave"
|
||||
>
|
||||
{i18n.translate(
|
||||
'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton',
|
||||
{
|
||||
defaultMessage: 'Switch without saving',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => closeModal(dismissModalChecked, true)}
|
||||
fill
|
||||
color="success"
|
||||
iconType="save"
|
||||
data-test-subj="unifiedSearch_switch_andSave"
|
||||
>
|
||||
{i18n.translate(
|
||||
'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton',
|
||||
{
|
||||
defaultMessage: 'Save and switch',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
}
|
|
@ -22,7 +22,7 @@ function createEditor() {
|
|||
uiSettings: { get: () => {} },
|
||||
}}
|
||||
>
|
||||
<TextBasedLanguagesEditor {...props} isDarkMode={false} />
|
||||
<TextBasedLanguagesEditor {...props} />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import dateMath from '@kbn/datemath';
|
||||
import classNames from 'classnames';
|
||||
import { css } from '@emotion/react';
|
||||
import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
|
@ -35,6 +36,7 @@ import {
|
|||
EuiToolTip,
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimeHistoryContract, getQueryLog } from '@kbn/data-plugin/public';
|
||||
|
@ -47,11 +49,7 @@ import QueryStringInputUI from './query_string_input';
|
|||
import { NoDataPopover } from './no_data_popover';
|
||||
import { shallowEqual } from '../utils/shallow_equal';
|
||||
import { AddFilterPopover } from './add_filter_popover';
|
||||
import {
|
||||
DataViewPicker,
|
||||
DataViewPickerProps,
|
||||
OnSaveTextLanguageQueryProps,
|
||||
} from '../dataview_picker';
|
||||
import { DataViewPicker, DataViewPickerProps } from '../dataview_picker';
|
||||
|
||||
import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group';
|
||||
import type {
|
||||
|
@ -167,7 +165,6 @@ export interface QueryBarTopRowProps<QT extends Query | AggregateQuery = Query>
|
|||
dataViewPickerComponentProps?: DataViewPickerProps;
|
||||
textBasedLanguageModeErrors?: Error[];
|
||||
textBasedLanguageModeWarning?: string;
|
||||
onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void;
|
||||
filterBar?: React.ReactNode;
|
||||
showDatePickerAsBadge?: boolean;
|
||||
showSubmitButton?: boolean;
|
||||
|
@ -185,6 +182,7 @@ export interface QueryBarTopRowProps<QT extends Query | AggregateQuery = Query>
|
|||
onTextLangQueryChange: (query: AggregateQuery) => void;
|
||||
submitOnBlur?: boolean;
|
||||
renderQueryInputAppend?: () => React.ReactNode;
|
||||
disableExternalPadding?: boolean;
|
||||
}
|
||||
|
||||
export const SharingMetaFields = React.memo(function SharingMetaFields({
|
||||
|
@ -232,7 +230,6 @@ export const QueryBarTopRow = React.memo(
|
|||
) {
|
||||
const isMobile = useIsWithinBreakpoints(['xs', 's']);
|
||||
const [isXXLarge, setIsXXLarge] = useState<boolean>(false);
|
||||
const [codeEditorIsExpanded, setCodeEditorIsExpanded] = useState<boolean>(false);
|
||||
const submitButtonStyle: QueryBarTopRowProps['submitButtonStyle'] =
|
||||
props.submitButtonStyle ?? 'auto';
|
||||
const submitButtonIconOnly =
|
||||
|
@ -466,7 +463,10 @@ export const QueryBarTopRow = React.memo(
|
|||
}
|
||||
|
||||
function shouldShowDatePickerAsBadge(): boolean {
|
||||
return Boolean(props.showDatePickerAsBadge) && !shouldRenderQueryInput();
|
||||
return (
|
||||
(Boolean(props.showDatePickerAsBadge) && !shouldRenderQueryInput()) ||
|
||||
Boolean(isQueryLangSelected && props.query && isOfAggregateQueryType(props.query))
|
||||
);
|
||||
}
|
||||
|
||||
function renderDatePicker() {
|
||||
|
@ -632,9 +632,7 @@ export const QueryBarTopRow = React.memo(
|
|||
<DataViewPicker
|
||||
{...props.dataViewPickerComponentProps}
|
||||
trigger={{ fullWidth: isMobile, ...props.dataViewPickerComponentProps.trigger }}
|
||||
onTextLangQuerySubmit={props.onTextLangQuerySubmit}
|
||||
textBasedLanguage={textBasedLanguage}
|
||||
onSaveTextLanguageQuery={props.onTextBasedSavedAndExit}
|
||||
isDisabled={props.isDisabled}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -734,8 +732,6 @@ export const QueryBarTopRow = React.memo(
|
|||
<TextBasedLangEditor
|
||||
query={props.query}
|
||||
onTextLangQueryChange={props.onTextLangQueryChange}
|
||||
expandCodeEditor={(status: boolean) => setCodeEditorIsExpanded(status)}
|
||||
isCodeEditorExpanded={codeEditorIsExpanded}
|
||||
errors={props.textBasedLanguageModeErrors}
|
||||
warning={props.textBasedLanguageModeWarning}
|
||||
detectedTimestamp={detectedTimestamp}
|
||||
|
@ -753,7 +749,7 @@ export const QueryBarTopRow = React.memo(
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const isScreenshotMode = props.isScreenshotMode === true;
|
||||
|
||||
return (
|
||||
|
@ -770,6 +766,11 @@ export const QueryBarTopRow = React.memo(
|
|||
direction={isMobile && !shouldShowDatePickerAsBadge() ? 'column' : 'row'}
|
||||
responsive={false}
|
||||
gutterSize="s"
|
||||
css={css`
|
||||
padding: ${isQueryLangSelected && !props.disableExternalPadding
|
||||
? euiTheme.size.s
|
||||
: 0};
|
||||
`}
|
||||
justifyContent={shouldShowDatePickerAsBadge() ? 'flexStart' : 'flexEnd'}
|
||||
wrap
|
||||
>
|
||||
|
@ -778,18 +779,14 @@ export const QueryBarTopRow = React.memo(
|
|||
grow={!shouldShowDatePickerAsBadge()}
|
||||
style={{ minWidth: shouldShowDatePickerAsBadge() ? 'auto' : 320, maxWidth: '100%' }}
|
||||
>
|
||||
{!isQueryLangSelected
|
||||
? renderQueryInput()
|
||||
: !codeEditorIsExpanded
|
||||
? renderTextLangEditor()
|
||||
: null}
|
||||
{!isQueryLangSelected ? renderQueryInput() : null}
|
||||
</EuiFlexItem>
|
||||
{props.renderQueryInputAppend?.()}
|
||||
{shouldShowDatePickerAsBadge() && props.filterBar}
|
||||
{renderUpdateButton()}
|
||||
</EuiFlexGroup>
|
||||
{!shouldShowDatePickerAsBadge() && props.filterBar}
|
||||
{codeEditorIsExpanded && renderTextLangEditor()}
|
||||
{renderTextLangEditor()}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -261,7 +261,6 @@ export function createSearchBar({
|
|||
dataViewPickerComponentProps={props.dataViewPickerComponentProps}
|
||||
textBasedLanguageModeErrors={props.textBasedLanguageModeErrors}
|
||||
textBasedLanguageModeWarning={props.textBasedLanguageModeWarning}
|
||||
onTextBasedSavedAndExit={props.onTextBasedSavedAndExit}
|
||||
displayStyle={props.displayStyle}
|
||||
isScreenshotMode={isScreenshotMode}
|
||||
dataTestSubj={props.dataTestSubj}
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
import { UseEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const searchBarStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
export const searchBarStyles = ({ euiTheme }: UseEuiTheme, isESQLQuery: boolean) => {
|
||||
return {
|
||||
uniSearchBar: css`
|
||||
padding: ${euiTheme.size.s};
|
||||
padding: ${isESQLQuery ? 0 : euiTheme.size.s};
|
||||
position: relative;
|
||||
`,
|
||||
detached: css`
|
||||
|
@ -21,6 +21,10 @@ export const searchBarStyles = ({ euiTheme }: UseEuiTheme) => {
|
|||
inPage: css`
|
||||
padding: 0;
|
||||
`,
|
||||
withBorders: css`
|
||||
border: ${euiTheme.border.thin};
|
||||
border-bottom: none;
|
||||
`,
|
||||
hidden: css`
|
||||
display: none;
|
||||
`,
|
||||
|
|
|
@ -16,7 +16,14 @@ import { get, isEqual } from 'lodash';
|
|||
import memoizeOne from 'memoize-one';
|
||||
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { Query, Filter, TimeRange, AggregateQuery, isOfQueryType } from '@kbn/es-query';
|
||||
import {
|
||||
type Query,
|
||||
type Filter,
|
||||
type TimeRange,
|
||||
type AggregateQuery,
|
||||
isOfQueryType,
|
||||
isOfAggregateQueryType,
|
||||
} from '@kbn/es-query';
|
||||
import { withKibana, KibanaReactContextValue } from '@kbn/kibana-react-plugin/public';
|
||||
import type {
|
||||
TimeHistoryContract,
|
||||
|
@ -32,7 +39,7 @@ import type { IUnifiedSearchPluginServices } from '../types';
|
|||
import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form';
|
||||
import { SavedQueryManagementList } from '../saved_query_management';
|
||||
import { QueryBarMenu, QueryBarMenuProps } from '../query_string_input/query_bar_menu';
|
||||
import type { DataViewPickerProps, OnSaveTextLanguageQueryProps } from '../dataview_picker';
|
||||
import type { DataViewPickerProps } from '../dataview_picker';
|
||||
import QueryBarTopRow, { QueryBarTopRowProps } from '../query_string_input/query_bar_top_row';
|
||||
import { FilterBar, FilterItems } from '../filter_bar';
|
||||
import type {
|
||||
|
@ -108,13 +115,12 @@ export interface SearchBarOwnProps<QT extends AggregateQuery | Query = Query> {
|
|||
disableQueryLanguageSwitcher?: boolean;
|
||||
// defines padding and border; use 'inPage' to avoid any padding or border;
|
||||
// use 'detached' if the searchBar appears at the very top of the view, without any wrapper
|
||||
displayStyle?: 'inPage' | 'detached';
|
||||
displayStyle?: 'inPage' | 'detached' | 'withBorders';
|
||||
// super update button background fill control
|
||||
fillSubmitButton?: boolean;
|
||||
dataViewPickerComponentProps?: DataViewPickerProps;
|
||||
textBasedLanguageModeErrors?: Error[];
|
||||
textBasedLanguageModeWarning?: string;
|
||||
onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void;
|
||||
showSubmitButton?: boolean;
|
||||
submitButtonStyle?: QueryBarTopRowProps['submitButtonStyle'];
|
||||
// defines size of suggestions query popover
|
||||
|
@ -480,9 +486,10 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { theme } = this.props;
|
||||
const { theme, query } = this.props;
|
||||
const isESQLQuery = isOfAggregateQueryType(query);
|
||||
const isScreenshotMode = this.props.isScreenshotMode === true;
|
||||
const styles = searchBarStyles(theme);
|
||||
const styles = searchBarStyles(theme, isESQLQuery);
|
||||
const cssStyles = [
|
||||
styles.uniSearchBar,
|
||||
this.props.displayStyle && styles[this.props.displayStyle],
|
||||
|
@ -640,7 +647,6 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C
|
|||
dataViewPickerComponentProps={this.props.dataViewPickerComponentProps}
|
||||
textBasedLanguageModeErrors={this.props.textBasedLanguageModeErrors}
|
||||
textBasedLanguageModeWarning={this.props.textBasedLanguageModeWarning}
|
||||
onTextBasedSavedAndExit={this.props.onTextBasedSavedAndExit}
|
||||
showDatePickerAsBadge={this.shouldShowDatePickerAsBadge()}
|
||||
filterBar={filterBar}
|
||||
suggestionsSize={this.props.suggestionsSize}
|
||||
|
@ -650,6 +656,7 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C
|
|||
submitOnBlur={this.props.submitOnBlur}
|
||||
suggestionsAbstraction={this.props.suggestionsAbstraction}
|
||||
renderQueryInputAppend={this.props.renderQueryInputAppend}
|
||||
disableExternalPadding={this.props.displayStyle === 'withBorders'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -253,9 +253,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.selectTextBaseLang();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.existOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -266,19 +266,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.existOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
await find.clickByCssSelector(
|
||||
'[data-test-subj="unifiedSearch_switch_modal"] .euiModal__closeIcon'
|
||||
'[data-test-subj="discover-esql-to-dataview-modal"] .euiModal__closeIcon'
|
||||
);
|
||||
await retry.try(async () => {
|
||||
await testSubjects.missingOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.missingOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
await PageObjects.discover.saveSearch('esql_test');
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*');
|
||||
await testSubjects.missingOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await testSubjects.missingOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
|
||||
it('should show switch modal when switching to a data view while a saved search with unsaved changes is open', async () => {
|
||||
|
@ -291,9 +291,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.existOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -339,7 +339,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
const historyItems = await esql.getHistoryItems();
|
||||
log.debug(historyItems);
|
||||
|
@ -362,7 +361,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
const historyItems = await esql.getHistoryItems();
|
||||
log.debug(historyItems);
|
||||
|
@ -379,7 +377,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
// click a history item
|
||||
await esql.clickHistoryItem(1);
|
||||
|
@ -405,7 +402,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
const historyItem = await esql.getHistoryItem(0);
|
||||
await historyItem.findByTestSubject('TextBasedLangEditor-queryHistory-error');
|
||||
|
@ -597,7 +593,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await dataGrid.clickCellFilterForButtonExcludingControlColumns(0, 1);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
@ -630,7 +625,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await dataGrid.clickCellFilterForButtonExcludingControlColumns(0, 1);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
|
|
@ -66,13 +66,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('should modify the time range when the histogram is brushed', async function () {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
// this is the number of renderings of the histogram needed when new data is fetched
|
||||
let renderingCountInc = 1;
|
||||
const prevRenderingCount = await elasticChart.getVisualizationRenderingCount();
|
||||
await queryBar.submitQuery();
|
||||
await retry.waitFor('chart rendering complete', async () => {
|
||||
const actualCount = await elasticChart.getVisualizationRenderingCount();
|
||||
const expectedCount = prevRenderingCount + renderingCountInc;
|
||||
const expectedCount = prevRenderingCount;
|
||||
log.debug(`renderings before brushing - actual: ${actualCount} expected: ${expectedCount}`);
|
||||
return actualCount === expectedCount;
|
||||
});
|
||||
|
@ -85,7 +82,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
await PageObjects.discover.brushHistogram();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
renderingCountInc = 2;
|
||||
const renderingCountInc = 2;
|
||||
await retry.waitFor('chart rendering complete after being brushed', async () => {
|
||||
const actualCount = await elasticChart.getVisualizationRenderingCount();
|
||||
const expectedCount = prevRenderingCount + renderingCountInc * 2;
|
||||
|
|
|
@ -489,10 +489,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
(await PageObjects.unifiedFieldList.getSidebarSectionFieldNames('selected')).join(', ')
|
||||
).to.be('countB, geo.dest');
|
||||
|
||||
await PageObjects.unifiedSearch.switchToDataViewMode();
|
||||
|
||||
await PageObjects.unifiedSearch.switchDataView(
|
||||
'discover-dataView-switch-link',
|
||||
'logstash-*',
|
||||
true
|
||||
'logstash-*'
|
||||
);
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
|
|
@ -169,7 +169,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await PageObjects.unifiedFieldList.clickFieldListPlusFilter('bytes', '0');
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
const editorValue = await monacoEditor.getCodeEditorValue();
|
||||
expect(editorValue).to.eql(
|
||||
`from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`bytes\`==0`
|
||||
|
@ -190,7 +189,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await PageObjects.unifiedFieldList.clickFieldListPlusFilter('extension.raw', 'css');
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
const editorValue = await monacoEditor.getCodeEditorValue();
|
||||
expect(editorValue).to.eql(
|
||||
`from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension.raw\`=="css"`
|
||||
|
@ -212,7 +210,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await PageObjects.unifiedFieldList.clickFieldListPlusFilter('clientip', '216.126.255.31');
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
const editorValue = await monacoEditor.getCodeEditorValue();
|
||||
expect(editorValue).to.eql(
|
||||
`from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`clientip\`::string=="216.126.255.31"`
|
||||
|
@ -238,7 +235,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('should not have stats for a date field yet but create an is not null filter', async () => {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItem('@timestamp');
|
||||
await PageObjects.unifiedFieldList.clickFieldListExistsFilter('@timestamp');
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
const editorValue = await monacoEditor.getCodeEditorValue();
|
||||
expect(editorValue).to.eql(
|
||||
`from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`@timestamp\` is not null`
|
||||
|
@ -274,7 +270,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await PageObjects.unifiedFieldList.clickFieldListPlusFilter('extension', 'css');
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
const editorValue = await monacoEditor.getCodeEditorValue();
|
||||
expect(editorValue).to.eql(
|
||||
`from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension\`=="css"`
|
||||
|
@ -314,7 +309,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await PageObjects.unifiedFieldList.clickFieldListPlusFilter('avg(bytes)', '5453');
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
const editorValue = await monacoEditor.getCodeEditorValue();
|
||||
expect(editorValue).to.eql(
|
||||
`from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3\n| WHERE \`avg(bytes)\`==5453`
|
||||
|
@ -343,7 +337,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await PageObjects.unifiedFieldList.clickFieldListMinusFilter('enabled', 'true');
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
const editorValue = await monacoEditor.getCodeEditorValue();
|
||||
expect(editorValue).to.eql(`row enabled = true\n| WHERE \`enabled\`!=true`);
|
||||
await PageObjects.unifiedFieldList.closeFieldPopover();
|
||||
|
|
|
@ -571,9 +571,10 @@ export class DiscoverPageObject extends FtrService {
|
|||
}
|
||||
|
||||
public async selectTextBaseLang() {
|
||||
await this.testSubjects.click('discover-dataView-switch-link');
|
||||
await this.testSubjects.click('select-text-based-language-panel');
|
||||
await this.header.waitUntilLoadingHasFinished();
|
||||
if (await this.testSubjects.exists('select-text-based-language-btn')) {
|
||||
await this.testSubjects.click('select-text-based-language-btn');
|
||||
await this.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public async removeHeaderColumn(name: string) {
|
||||
|
|
|
@ -13,21 +13,13 @@ export class UnifiedSearchPageObject extends FtrService {
|
|||
private readonly testSubjects = this.ctx.getService('testSubjects');
|
||||
private readonly find = this.ctx.getService('find');
|
||||
|
||||
public async switchDataView(
|
||||
switchButtonSelector: string,
|
||||
dataViewTitle: string,
|
||||
transitionFromTextBasedLanguages?: boolean
|
||||
) {
|
||||
public async switchDataView(switchButtonSelector: string, dataViewTitle: string) {
|
||||
await this.testSubjects.click(switchButtonSelector);
|
||||
|
||||
const indexPatternSwitcher = await this.testSubjects.find('indexPattern-switcher', 500);
|
||||
await this.testSubjects.setValue('indexPattern-switcher--input', dataViewTitle);
|
||||
await (await indexPatternSwitcher.findByCssSelector(`[title="${dataViewTitle}"]`)).click();
|
||||
|
||||
if (Boolean(transitionFromTextBasedLanguages)) {
|
||||
await this.testSubjects.click('unifiedSearch_switch_noSave');
|
||||
}
|
||||
|
||||
await this.retry.waitFor(
|
||||
'wait for updating switcher',
|
||||
async () => (await this.getSelectedDataView(switchButtonSelector)) === dataViewTitle
|
||||
|
@ -50,4 +42,9 @@ export class UnifiedSearchPageObject extends FtrService {
|
|||
`[data-test-subj="text-based-languages-switcher"] [title="${language}"]`
|
||||
);
|
||||
}
|
||||
|
||||
public async switchToDataViewMode() {
|
||||
await this.testSubjects.click('switch-to-dataviews');
|
||||
await this.testSubjects.click('discover-esql-to-dataview-no-save-btn');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ export interface IndexDataVisualizerESQLProps {
|
|||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}
|
||||
const DEFAULT_ESQL_QUERY = { esql: '' };
|
||||
const expandCodeEditor = () => true;
|
||||
export const IndexDataVisualizerESQL: FC<IndexDataVisualizerESQLProps> = (dataVisualizerProps) => {
|
||||
const { services } = useDataVisualizerKibana();
|
||||
const { data } = services;
|
||||
|
@ -264,10 +263,7 @@ export const IndexDataVisualizerESQL: FC<IndexDataVisualizerESQLProps> = (dataVi
|
|||
query={localQuery}
|
||||
onTextLangQueryChange={onTextLangQueryChange}
|
||||
onTextLangQuerySubmit={onTextLangQuerySubmit}
|
||||
expandCodeEditor={expandCodeEditor}
|
||||
isCodeEditorExpanded={true}
|
||||
detectedTimestamp={currentDataView?.timeFieldName}
|
||||
hideMinimizeButton={true}
|
||||
hideRunQueryText={false}
|
||||
isLoading={queryHistoryStatus ?? false}
|
||||
/>
|
||||
|
|
|
@ -9,8 +9,6 @@ import { TextBasedLangEditor } from '@kbn/esql/public';
|
|||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
|
||||
const expandCodeEditor = (status: boolean) => {};
|
||||
|
||||
interface FieldStatsESQLEditorProps {
|
||||
canEditTextBasedQuery?: boolean;
|
||||
query: AggregateQuery;
|
||||
|
@ -47,9 +45,6 @@ export const FieldStatsESQLEditor = ({
|
|||
setQuery(q);
|
||||
prevQuery.current = q;
|
||||
}}
|
||||
expandCodeEditor={expandCodeEditor}
|
||||
isCodeEditorExpanded
|
||||
hideMinimizeButton
|
||||
editorIsInline
|
||||
hideRunQueryText
|
||||
onTextLangQuerySubmit={onTextLangQuerySubmit}
|
||||
|
|
|
@ -471,14 +471,6 @@ export function App({
|
|||
[dataViews, uiActions, http, notifications, uiSettings, initialContext, dispatch]
|
||||
);
|
||||
|
||||
const onTextBasedSavedAndExit = useCallback(async ({ onSave, onCancel: _onCancel }) => {
|
||||
setIsSaveModalVisible(true);
|
||||
setShouldCloseAndSaveTextBasedQuery(true);
|
||||
saveAndExit.current = () => {
|
||||
onSave();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// remember latest URL based on the configuration
|
||||
// url_panel_content has a similar logic
|
||||
const shareURLCache = useRef({ params: '', url: '' });
|
||||
|
@ -571,7 +563,6 @@ export function App({
|
|||
topNavMenuEntryGenerators={topNavMenuEntryGenerators}
|
||||
initialContext={initialContext}
|
||||
indexPatternService={indexPatternService}
|
||||
onTextBasedSavedAndExit={onTextBasedSavedAndExit}
|
||||
getUserMessages={getUserMessages}
|
||||
shortUrlService={shortUrlService}
|
||||
startServices={coreStart}
|
||||
|
|
|
@ -281,7 +281,6 @@ export const LensTopNavMenu = ({
|
|||
initialContext,
|
||||
indexPatternService,
|
||||
currentDoc,
|
||||
onTextBasedSavedAndExit,
|
||||
getUserMessages,
|
||||
shortUrlService,
|
||||
isCurrentStateDirty,
|
||||
|
@ -1112,7 +1111,6 @@ export const LensTopNavMenu = ({
|
|||
)
|
||||
}
|
||||
textBasedLanguageModeErrors={textBasedLanguageModeErrors}
|
||||
onTextBasedSavedAndExit={onTextBasedSavedAndExit}
|
||||
showFilterBar={true}
|
||||
data-test-subj="lnsApp_topNav"
|
||||
screenTitle={'lens'}
|
||||
|
|
|
@ -479,8 +479,6 @@ export function LensEditConfigurationFlyout({
|
|||
setQuery(q);
|
||||
prevQuery.current = q;
|
||||
}}
|
||||
expandCodeEditor={(status: boolean) => {}}
|
||||
isCodeEditorExpanded
|
||||
detectedTimestamp={adHocDataViews?.[0]?.timeFieldName}
|
||||
hideTimeFilterInfo={hideTimeFilterInfo}
|
||||
errors={errors}
|
||||
|
@ -492,7 +490,6 @@ export function LensEditConfigurationFlyout({
|
|||
})
|
||||
: undefined
|
||||
}
|
||||
hideMinimizeButton
|
||||
editorIsInline
|
||||
hideRunQueryText
|
||||
onTextLangQuerySubmit={async (q, a) => {
|
||||
|
|
|
@ -126,7 +126,6 @@ export interface LensTopNavMenuProps {
|
|||
initialContext?: VisualizeFieldContext | VisualizeEditorContext;
|
||||
currentDoc: Document | undefined;
|
||||
indexPatternService: IndexPatternServiceAPI;
|
||||
onTextBasedSavedAndExit: ({ onSave }: { onSave: () => void }) => Promise<void>;
|
||||
getUserMessages: UserMessagesGetter;
|
||||
shortUrlService: (params: LensAppLocatorParams) => Promise<string>;
|
||||
isCurrentStateDirty: boolean;
|
||||
|
|
|
@ -857,6 +857,8 @@ export function FormulaEditor({
|
|||
}
|
||||
),
|
||||
}}
|
||||
isHelpMenuOpen={isHelpOpen}
|
||||
onHelpMenuVisibilityChange={setIsHelpOpen}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -89,11 +89,6 @@ export function ESQLEditor(props: Props) {
|
|||
}}
|
||||
errors={error ? [error] : undefined}
|
||||
warning={warning}
|
||||
expandCodeEditor={(status: boolean) => {
|
||||
// never called because hideMinimizeButton hides UI
|
||||
}}
|
||||
isCodeEditorExpanded
|
||||
hideMinimizeButton
|
||||
editorIsInline
|
||||
hideRunQueryText
|
||||
isLoading={isLoading}
|
||||
|
|
|
@ -24,13 +24,11 @@ const emptyPreview = css`
|
|||
export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const [query, setQuery] = React.useState({ esql: '' });
|
||||
const [submittedQuery, setSubmittedQuery] = React.useState({ esql: '' });
|
||||
const [isPreviewOpen, setIsPreviewOpen] = React.useState(false);
|
||||
|
||||
const resetState = () => {
|
||||
setIsExpanded(false);
|
||||
setIsPreviewOpen(false);
|
||||
setQuery({ esql: '' });
|
||||
setSubmittedQuery({ esql: '' });
|
||||
|
@ -83,11 +81,6 @@ export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) {
|
|||
}}
|
||||
errors={undefined}
|
||||
warning={undefined}
|
||||
expandCodeEditor={(expanded: boolean) => {
|
||||
setIsExpanded(() => expanded);
|
||||
}}
|
||||
isCodeEditorExpanded={isExpanded}
|
||||
hideMinimizeButton={false}
|
||||
editorIsInline={false}
|
||||
hideRunQueryText
|
||||
isLoading={false}
|
||||
|
|
|
@ -180,7 +180,7 @@ export const QueryBar = memo<QueryBarComponentProps>(
|
|||
timeHistory={timeHistory}
|
||||
dataTestSubj={dataTestSubj}
|
||||
savedQuery={savedQuery}
|
||||
displayStyle={displayStyle}
|
||||
displayStyle={isEsql ? 'withBorders' : displayStyle}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -207,7 +207,6 @@ export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopove
|
|||
setPopoverIsOpen={setDataViewPopoverOpen}
|
||||
onChangeDataView={onChangeDataView}
|
||||
onCreateDefaultAdHocDataView={onCreateDefaultAdHocDataView}
|
||||
isTextBasedLangSelected={false}
|
||||
/>
|
||||
{createDataView ? (
|
||||
<EuiPopoverFooter paddingSize="none">
|
||||
|
|
|
@ -194,13 +194,11 @@ export const EsqlQueryExpression: React.FC<
|
|||
setParam('esqlQuery', q);
|
||||
refreshTimeFields(q);
|
||||
}}
|
||||
expandCodeEditor={() => true}
|
||||
isCodeEditorExpanded={true}
|
||||
onTextLangQuerySubmit={async () => {}}
|
||||
detectedTimestamp={detectedTimestamp}
|
||||
hideMinimizeButton={true}
|
||||
hideRunQueryText={true}
|
||||
isLoading={isLoading}
|
||||
hasOutline
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer />
|
||||
|
|
|
@ -6633,14 +6633,11 @@
|
|||
"textBasedEditor.query.textBasedLanguagesEditor.errorCount": "{count} {count, plural, one {erreur} other {erreurs}}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.errorsTitle": "Erreurs",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.esql": "ES|QL",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.expandTooltip": "Développer l’éditeur de requête",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.feedback": "Commentaires",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.functions": "Fonctions",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.functionsDocumentationESQLDescription": "Les fonctions sont compatibles avec \"ROW\" (Ligne), \"EVAL\" (Évaluation) et \"WHERE\" (Où).",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.lineCount": "{count} {count, plural, one {ligne} other {lignes}}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.lineNumber": "Ligne {lineNumber}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor": "Réduire l'éditeur",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip": "Réduire l’éditeur de requête",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.operators": "Opérateurs",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.operatorsDocumentationESQLDescription": "ES|QL est compatible avec les opérateurs suivants :",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.processingCommands": "Traitement des commandes",
|
||||
|
@ -7222,12 +7219,11 @@
|
|||
"unifiedSearch.query.queryBar.indexPattern.findFilterSet": "Trouver une requête",
|
||||
"unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "Gérer cette vue de données",
|
||||
"unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "Temporaire",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "Modifier la vue de données supprime la requête {textBasedLanguage} en cours. Sauvegardez cette recherche pour ne pas perdre de travail.",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "Modifier la vue de données supprime la requête {language} en cours. Sauvegardez cette recherche pour ne pas perdre de travail.",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "Basculer sans sauvegarder",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "Ne plus afficher cet avertissement",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "Sauvegarder et basculer",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle": "Votre requête sera supprimée",
|
||||
"discover.esqlToDataviewTransitionModalBody": "Modifier la vue de données supprime la requête ES|QL en cours. Sauvegardez cette recherche pour ne pas perdre de travail.",
|
||||
"discover.esqlToDataViewTransitionModal.closeButtonLabel": "Basculer sans sauvegarder",
|
||||
"discover.esqlToDataViewTransitionModal.dismissButtonLabel": "Ne plus afficher cet avertissement",
|
||||
"discover.esqlToDataViewTransitionModal.saveButtonLabel": "Sauvegarder et basculer",
|
||||
"discover.esqlToDataViewTransitionModal.title": "Votre requête sera supprimée",
|
||||
"unifiedSearch.query.queryBar.kqlLanguageName": "KQL",
|
||||
"unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "documents",
|
||||
"unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "Ne plus afficher",
|
||||
|
@ -7238,7 +7234,6 @@
|
|||
"unifiedSearch.query.queryBar.searchInputPlaceholder": "Filtrer vos données à l'aide de la syntaxe {language}",
|
||||
"unifiedSearch.query.queryBar.searchInputPlaceholderForText": "Filtrer vos données",
|
||||
"unifiedSearch.query.queryBar.syntaxOptionsTitle": "Options de syntaxe",
|
||||
"unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "Essayer ES|QL",
|
||||
"unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "La sélection de plage de données pour les requêtes en {language} requiert la présence d'un champ @timestamp dans l'ensemble de données.",
|
||||
"unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "Tout le temps",
|
||||
"unifiedSearch.queryBarTopRow.submitButton.cancel": "Annuler",
|
||||
|
|
|
@ -6609,14 +6609,11 @@
|
|||
"textBasedEditor.query.textBasedLanguagesEditor.errorCount": "{count} {count, plural, other {# 件のエラー}}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.errorsTitle": "エラー",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.esql": "ES|QL",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.expandTooltip": "クエリエディターを展開",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.feedback": "フィードバック",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.functions": "関数",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.functionsDocumentationESQLDescription": "関数はROW、EVAL、WHEREでサポートされています。",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.lineCount": "{count} {count, plural, other {行}}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.lineNumber": "行{lineNumber}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor": "エディターを最小化",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip": "クエリエディターを縮小",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.operators": "演算子",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.operatorsDocumentationESQLDescription": "ES|QLは以下の演算子をサポートしています。",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.processingCommands": "処理コマンド",
|
||||
|
@ -7198,12 +7195,11 @@
|
|||
"unifiedSearch.query.queryBar.indexPattern.findFilterSet": "クエリを検索",
|
||||
"unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "このデータビューを管理",
|
||||
"unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "一時",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "データビューを切り替えると、現在の{textBasedLanguage}クエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "データビューを切り替えると、現在の{language}クエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "保存せずに切り替え",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "次回以降この警告を表示しない",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "保存して切り替え",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle": "クエリは削除されます",
|
||||
"discover.esqlToDataviewTransitionModalBody": "データビューを切り替えると、現在のES|QLクエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。",
|
||||
"discover.esqlToDataViewTransitionModal.closeButtonLabel": "保存せずに切り替え",
|
||||
"discover.esqlToDataViewTransitionModal.dismissButtonLabel": "次回以降この警告を表示しない",
|
||||
"discover.esqlToDataViewTransitionModal.saveButtonLabel": "保存して切り替え",
|
||||
"discover.esqlToDataViewTransitionModal.title": "クエリは削除されます",
|
||||
"unifiedSearch.query.queryBar.kqlLanguageName": "KQL",
|
||||
"unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "ドキュメント",
|
||||
"unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "今後表示しない",
|
||||
|
@ -7214,7 +7210,6 @@
|
|||
"unifiedSearch.query.queryBar.searchInputPlaceholder": "{language}構文を使用してデータをフィルタリング",
|
||||
"unifiedSearch.query.queryBar.searchInputPlaceholderForText": "データのフィルタリング",
|
||||
"unifiedSearch.query.queryBar.syntaxOptionsTitle": "構文オプション",
|
||||
"unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "ES|QLを試す",
|
||||
"unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "{language}クエリの日付範囲選択では、データセットに@timestampフィールドが存在している必要があります。",
|
||||
"unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "常時",
|
||||
"unifiedSearch.queryBarTopRow.submitButton.cancel": "キャンセル",
|
||||
|
|
|
@ -6642,14 +6642,12 @@
|
|||
"textBasedEditor.query.textBasedLanguagesEditor.errorCount": "{count} 个{count, plural, other {错误}}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.errorsTitle": "错误",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.esql": "ES|QL",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.expandTooltip": "展开查询编辑器",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.feedback": "反馈",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.functions": "函数",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.functionsDocumentationESQLDescription": "ROW、EVAL 和 WHERE 支持的函数。",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.lineCount": "{count} {count, plural, other {行}}",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.lineNumber": "第 {lineNumber} 行",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor": "最小化编辑器",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip": "压缩查询编辑器",
|
||||
"": "压缩查询编辑器",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.operators": "运算符",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.operatorsDocumentationESQLDescription": "ES|QL 支持以下运算符:",
|
||||
"textBasedEditor.query.textBasedLanguagesEditor.processingCommands": "处理命令",
|
||||
|
@ -7234,12 +7232,11 @@
|
|||
"unifiedSearch.query.queryBar.indexPattern.findFilterSet": "查找查询",
|
||||
"unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "管理此数据视图",
|
||||
"unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "临时",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "切换数据视图会移除当前的 {textBasedLanguage} 查询。保存此搜索以确保不会丢失工作。",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "切换数据视图会移除当前的 {language} 查询。保存此搜索以确保不会丢失工作。",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "切换而不保存",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "不再显示此警告",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "保存并切换",
|
||||
"unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle": "将移除您的查询",
|
||||
"discover.esqlToDataviewTransitionModalBody": "切换数据视图会移除当前的 ES|QL 查询。保存此搜索以确保不会丢失工作。",
|
||||
"discover.esqlToDataViewTransitionModal.closeButtonLabel": "切换而不保存",
|
||||
"discover.esqlToDataViewTransitionModal.dismissButtonLabel": "不再显示此警告",
|
||||
"discover.esqlToDataViewTransitionModal.saveButtonLabel": "保存并切换",
|
||||
"discover.esqlToDataViewTransitionModal.title": "将移除您的查询",
|
||||
"unifiedSearch.query.queryBar.kqlLanguageName": "KQL",
|
||||
"unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "文档",
|
||||
"unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "不再显示",
|
||||
|
@ -7250,7 +7247,6 @@
|
|||
"unifiedSearch.query.queryBar.searchInputPlaceholder": "使用 {language} 语法筛选数据",
|
||||
"unifiedSearch.query.queryBar.searchInputPlaceholderForText": "筛选您的数据",
|
||||
"unifiedSearch.query.queryBar.syntaxOptionsTitle": "语法选项",
|
||||
"unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "尝试 ES|QL",
|
||||
"unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "{language} 查询的日期范围选择要求数据集中存在 @timestamp 字段。",
|
||||
"unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "所有时间",
|
||||
"unifiedSearch.queryBarTopRow.submitButton.cancel": "取消",
|
||||
|
|
|
@ -167,7 +167,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramEditFlyoutVisualization');
|
||||
expect(await testSubjects.exists('xyVisChart')).to.be(true);
|
||||
expect(await PageObjects.lens.canRemoveDimension('lnsXY_xDimensionPanel')).to.equal(true);
|
||||
|
@ -190,7 +189,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramEditFlyoutVisualization');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
@ -209,7 +207,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramEditFlyoutVisualization');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
@ -228,7 +225,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramSaveVisualization');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
|
@ -257,7 +253,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await monacoEditor.setCodeEditorValue('from logstash-* | limit 10');
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
// save the visualization
|
||||
await testSubjects.click('unifiedHistogramSaveVisualization');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
@ -308,7 +303,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramSaveVisualization');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
let title = await testSubjects.getAttribute('savedObjectTitle', 'value');
|
||||
|
|
|
@ -1035,15 +1035,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
/**
|
||||
* Changes the index pattern in the data panel
|
||||
*/
|
||||
async switchDataPanelIndexPattern(
|
||||
dataViewTitle: string,
|
||||
transitionFromTextBasedLanguages?: boolean
|
||||
) {
|
||||
await PageObjects.unifiedSearch.switchDataView(
|
||||
'lns-dataView-switch-link',
|
||||
dataViewTitle,
|
||||
transitionFromTextBasedLanguages
|
||||
);
|
||||
async switchDataPanelIndexPattern(dataViewTitle: string) {
|
||||
await PageObjects.unifiedSearch.switchDataView('lns-dataView-switch-link', dataViewTitle);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
},
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ import { getDetails, goBackToRulesTable } from '../../../../tasks/rule_details';
|
|||
import { expectNumberOfRules } from '../../../../tasks/alerts_detection_rules';
|
||||
import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common';
|
||||
import {
|
||||
expandEsqlQueryBar,
|
||||
fillAboutRuleAndContinue,
|
||||
fillDefineEsqlRuleAndContinue,
|
||||
fillScheduleRuleAndContinue,
|
||||
|
@ -87,7 +86,6 @@ describe(
|
|||
|
||||
it('creates an ES|QL rule', function () {
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
|
||||
fillDefineEsqlRuleAndContinue(rule);
|
||||
fillAboutRuleAndContinue(rule);
|
||||
|
@ -109,7 +107,6 @@ describe(
|
|||
// this test case is important, since field shown in rule override component are coming from ES|QL query, not data view fields API
|
||||
it('creates an ES|QL rule and overrides its name', function () {
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
|
||||
fillDefineEsqlRuleAndContinue(rule);
|
||||
fillAboutSpecificEsqlRuleAndContinue({ ...rule, rule_name_override: 'test_id' });
|
||||
|
@ -130,7 +127,6 @@ describe(
|
|||
});
|
||||
it('shows error when ES|QL query is empty', function () {
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
getDefineContinueButton().click();
|
||||
|
||||
cy.get(ESQL_QUERY_BAR).contains('ES|QL query is required');
|
||||
|
@ -138,7 +134,6 @@ describe(
|
|||
|
||||
it('proceeds further once invalid query is fixed', function () {
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
getDefineContinueButton().click();
|
||||
|
||||
cy.get(ESQL_QUERY_BAR).contains('required');
|
||||
|
@ -153,7 +148,6 @@ describe(
|
|||
it('shows error when non-aggregating ES|QL query does not have metadata operator', function () {
|
||||
const invalidNonAggregatingQuery = 'from auditbeat* | limit 5';
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
fillEsqlQueryBar(invalidNonAggregatingQuery);
|
||||
getDefineContinueButton().click();
|
||||
|
||||
|
@ -167,7 +161,6 @@ describe(
|
|||
'from auditbeat* metadata _id, _version, _index | keep agent.* | limit 5';
|
||||
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
fillEsqlQueryBar(invalidNonAggregatingQuery);
|
||||
getDefineContinueButton().click();
|
||||
|
||||
|
@ -182,7 +175,6 @@ describe(
|
|||
visit(CREATE_RULE_URL);
|
||||
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
fillEsqlQueryBar(invalidEsqlQuery);
|
||||
getDefineContinueButton().click();
|
||||
|
||||
|
@ -207,7 +199,6 @@ describe(
|
|||
workaroundForResizeObserver();
|
||||
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
fillEsqlQueryBar(queryWithCustomFields);
|
||||
getDefineContinueButton().click();
|
||||
|
||||
|
@ -242,7 +233,6 @@ describe(
|
|||
workaroundForResizeObserver();
|
||||
|
||||
selectEsqlRuleType();
|
||||
expandEsqlQueryBar();
|
||||
|
||||
interceptEsqlQueryFieldsRequest(queryWithCustomFields, 'esqlSuppressionFieldsRequest');
|
||||
fillEsqlQueryBar(queryWithCustomFields);
|
||||
|
|
|
@ -30,7 +30,6 @@ import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management';
|
|||
import { getDetails } from '../../../../tasks/rule_details';
|
||||
import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common';
|
||||
import {
|
||||
expandEsqlQueryBar,
|
||||
fillEsqlQueryBar,
|
||||
fillOverrideEsqlRuleName,
|
||||
goToAboutStepTab,
|
||||
|
@ -68,7 +67,6 @@ describe(
|
|||
});
|
||||
|
||||
it('edits ES|QL rule and checks details page', () => {
|
||||
expandEsqlQueryBar();
|
||||
// ensure once edit form opened, correct query is displayed in ES|QL input
|
||||
cy.get(ESQL_QUERY_BAR).contains(rule.query);
|
||||
|
||||
|
@ -94,7 +92,6 @@ describe(
|
|||
});
|
||||
|
||||
it('adds ES|QL override rule name on edit', () => {
|
||||
expandEsqlQueryBar();
|
||||
// ensure once edit form opened, correct query is displayed in ES|QL input
|
||||
cy.get(ESQL_QUERY_BAR).contains(rule.query);
|
||||
|
||||
|
|
|
@ -261,9 +261,6 @@ export const ESQL_QUERY_BAR_INPUT_AREA =
|
|||
|
||||
export const ESQL_QUERY_BAR = '[data-test-subj="detectionEngineStepDefineRuleEsqlQueryBar"]';
|
||||
|
||||
export const ESQL_QUERY_BAR_EXPAND_BTN =
|
||||
'[data-test-subj="detectionEngineStepDefineRuleEsqlQueryBar"] [data-test-subj="TextBasedLangEditor-expand"]';
|
||||
|
||||
export const NEW_TERMS_INPUT_AREA = '[data-test-subj="newTermsInput"]';
|
||||
|
||||
export const NEW_TERMS_HISTORY_SIZE =
|
||||
|
|
|
@ -15,7 +15,7 @@ export const DISCOVER_DATA_VIEW_SWITCHER = {
|
|||
INPUT: getDataTestSubjectSelector('indexPattern-switcher--input'),
|
||||
GET_DATA_VIEW: (title: string) => `.euiSelectableListItem[role=option][title^="${title}"]`,
|
||||
CREATE_NEW: getDataTestSubjectSelector('dataview-create-new'),
|
||||
TEXT_BASE_LANG_SWICTHER: getDataTestSubjectSelector('select-text-based-language-panel'),
|
||||
TEXT_BASE_LANG_SWITCHER: getDataTestSubjectSelector('select-text-based-language-btn'),
|
||||
};
|
||||
|
||||
export const DISCOVER_DATA_VIEW_EDITOR_FLYOUT = {
|
||||
|
@ -32,7 +32,6 @@ export const DISCOVER_ESQL_INPUT = `${DISCOVER_CONTAINER} ${getDataTestSubjectSe
|
|||
|
||||
export const DISCOVER_ESQL_INPUT_TEXT_CONTAINER = `${DISCOVER_ESQL_INPUT} .view-lines`;
|
||||
|
||||
export const DISCOVER_ESQL_INPUT_EXPAND = getDataTestSubjectSelector('TextBasedLangEditor-expand');
|
||||
export const DISCOVER_ESQL_EDITABLE_INPUT = `${DISCOVER_ESQL_INPUT} textarea`;
|
||||
|
||||
export const DISCOVER_ADD_FILTER = `${DISCOVER_CONTAINER} ${getDataTestSubjectSelector(
|
||||
|
|
|
@ -59,7 +59,6 @@ import {
|
|||
EQL_TYPE,
|
||||
ESQL_TYPE,
|
||||
ESQL_QUERY_BAR,
|
||||
ESQL_QUERY_BAR_EXPAND_BTN,
|
||||
ESQL_QUERY_BAR_INPUT_AREA,
|
||||
FALSE_POSITIVES_INPUT,
|
||||
IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK,
|
||||
|
@ -632,14 +631,6 @@ export const fillEsqlQueryBar = (query: string) => {
|
|||
typeEsqlQueryBar(query);
|
||||
};
|
||||
|
||||
/**
|
||||
* expands query bar, so query is not obscured on narrow screens
|
||||
* and validation message is not covered by input menu tooltip
|
||||
*/
|
||||
export const expandEsqlQueryBar = () => {
|
||||
cy.get(ESQL_QUERY_BAR_EXPAND_BTN).click();
|
||||
};
|
||||
|
||||
export const fillDefineEsqlRuleAndContinue = (rule: EsqlRuleCreateProps) => {
|
||||
cy.get(ESQL_QUERY_BAR).contains('ES|QL query');
|
||||
fillEsqlQueryBar(rule.query);
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
DISCOVER_DATA_VIEW_EDITOR_FLYOUT,
|
||||
DISCOVER_FIELD_LIST_LOADING,
|
||||
DISCOVER_ESQL_EDITABLE_INPUT,
|
||||
DISCOVER_ESQL_INPUT_EXPAND,
|
||||
} from '../screens/discover';
|
||||
import { GET_LOCAL_SEARCH_BAR_SUBMIT_BUTTON } from '../screens/search_bar';
|
||||
import { goToEsqlTab } from './timeline';
|
||||
|
@ -28,8 +27,7 @@ export const switchDataViewTo = (dataviewName: string) => {
|
|||
};
|
||||
|
||||
export const switchDataViewToESQL = () => {
|
||||
openDataViewSwitcher();
|
||||
cy.get(DISCOVER_DATA_VIEW_SWITCHER.TEXT_BASE_LANG_SWICTHER).trigger('click');
|
||||
cy.get(DISCOVER_DATA_VIEW_SWITCHER.TEXT_BASE_LANG_SWITCHER).trigger('click');
|
||||
cy.get(DISCOVER_DATA_VIEW_SWITCHER.BTN).should('contain.text', 'ES|QL');
|
||||
};
|
||||
|
||||
|
@ -56,8 +54,6 @@ export const selectCurrentDiscoverEsqlQuery = (
|
|||
goToEsqlTab();
|
||||
// eslint-disable-next-line cypress/no-force
|
||||
cy.get(discoverEsqlInput).click({ force: true });
|
||||
// eslint-disable-next-line cypress/no-force
|
||||
cy.get(DISCOVER_ESQL_INPUT_EXPAND).click({ force: true });
|
||||
fillEsqlQueryBar(Cypress.platform === 'darwin' ? '{cmd+a}' : '{ctrl+a}');
|
||||
};
|
||||
|
||||
|
|
|
@ -260,9 +260,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.selectTextBaseLang();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.existOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -273,19 +273,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.existOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
await find.clickByCssSelector(
|
||||
'[data-test-subj="unifiedSearch_switch_modal"] .euiModal__closeIcon'
|
||||
'[data-test-subj="discover-esql-to-dataview-modal"] .euiModal__closeIcon'
|
||||
);
|
||||
await retry.try(async () => {
|
||||
await testSubjects.missingOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.missingOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
await PageObjects.discover.saveSearch('esql_test');
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*');
|
||||
await testSubjects.missingOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await testSubjects.missingOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
|
||||
it('should show switch modal when switching to a data view while a saved search with unsaved changes is open', async () => {
|
||||
|
@ -298,9 +298,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.discover.selectIndexPattern('logstash-*', false);
|
||||
await testSubjects.click('switch-to-dataviews');
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail('unifiedSearch_switch_modal');
|
||||
await testSubjects.existOrFail('discover-esql-to-dataview-modal');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -346,7 +346,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
const historyItems = await esql.getHistoryItems();
|
||||
log.debug(historyItems);
|
||||
|
@ -369,7 +368,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
const historyItems = await esql.getHistoryItems();
|
||||
log.debug(historyItems);
|
||||
|
@ -386,7 +384,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
// click a history item
|
||||
await esql.clickHistoryItem(1);
|
||||
|
@ -412,7 +409,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('TextBasedLangEditor-toggle-query-history-button');
|
||||
await testSubjects.click('TextBasedLangEditor-queryHistory-runQuery-button');
|
||||
const historyItem = await esql.getHistoryItem(0);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue