From 7b1f742b3a87ace48533b3ec14c436bbd66d4e96 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:22:18 -0700 Subject: [PATCH] [Discover Next] Fixes Discover styles (#7546) (#7554) * fixes the query bar UI * incorporates Abby's page styling * fixes tests * Changeset file for PR #7546 created/updated --------- (cherry picked from commit 2d8c743266223dd1420c85133c56b50e5d96be46) Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> --- changelogs/fragments/7546.yml | 2 + .../config/global_selectors.json | 4 +- .../dataset_navigator/_dataset_navigator.scss | 11 +- .../dataset_navigator/dataset_navigator.tsx | 36 +- .../ui/filter_bar/_global_filter_group.scss | 12 - .../language_selector.test.tsx.snap | 32 ++ .../data/public/ui/query_editor/_index.scss | 1 + .../public/ui/query_editor/_query_editor.scss | 61 ++- .../default_editor/_default_editor.scss | 21 + .../editors/default_editor/index.tsx | 99 ++++ .../query_editor/editors/dql_editor/index.tsx | 15 + .../public/ui/query_editor/editors/index.ts | 8 + .../public/ui/query_editor/editors/shared.tsx | 108 +++++ .../data/public/ui/query_editor/index.tsx | 2 +- .../ui/query_editor/language_selector.tsx | 37 +- .../public/ui/query_editor/query_editor.tsx | 457 ++++++++++-------- .../query_editor_btn_collapse.tsx | 22 +- .../query_editor_extensions/index.tsx | 2 +- .../ui/query_editor/query_editor_top_row.tsx | 5 +- .../data/public/ui/query_editor/types.ts | 12 + .../ui/query_string_input/_query_bar.scss | 4 + ...d_query_management_component.test.tsx.snap | 1 + .../saved_query_management_component.tsx | 1 + .../public/components/app_container.scss | 10 +- .../public/components/app_container.tsx | 10 +- .../components/chart/_histogram.scss | 15 + .../application/components/chart/chart.tsx | 98 +++- .../components/sidebar/discover_field.scss | 11 + .../sidebar/discover_field_search.tsx | 170 ++++--- .../components/sidebar/discover_sidebar.scss | 18 + .../components/sidebar/discover_sidebar.tsx | 15 +- .../canvas/discover_chart_container.tsx | 65 ++- .../view_components/canvas/index.tsx | 57 +-- .../view_components/panel/index.tsx | 7 +- 34 files changed, 1017 insertions(+), 412 deletions(-) create mode 100644 changelogs/fragments/7546.yml create mode 100644 src/plugins/data/public/ui/query_editor/editors/default_editor/_default_editor.scss create mode 100644 src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx create mode 100644 src/plugins/data/public/ui/query_editor/editors/dql_editor/index.tsx create mode 100644 src/plugins/data/public/ui/query_editor/editors/index.ts create mode 100644 src/plugins/data/public/ui/query_editor/editors/shared.tsx create mode 100644 src/plugins/data/public/ui/query_editor/types.ts diff --git a/changelogs/fragments/7546.yml b/changelogs/fragments/7546.yml new file mode 100644 index 000000000000..8431c60a2a0d --- /dev/null +++ b/changelogs/fragments/7546.yml @@ -0,0 +1,2 @@ +fix: +- Fixes Discover next styling ([#7546](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7546)) \ No newline at end of file diff --git a/packages/osd-stylelint-config/config/global_selectors.json b/packages/osd-stylelint-config/config/global_selectors.json index 285f11f40a2b..ca442760f731 100644 --- a/packages/osd-stylelint-config/config/global_selectors.json +++ b/packages/osd-stylelint-config/config/global_selectors.json @@ -25,7 +25,9 @@ "packages/osd-ui-framework/src/components/button/button_group/_button_group.scss", "src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.scss", "src/plugins/discover/public/application/view_components/canvas/discover_canvas.scss", - "src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss" + "src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss", + "src/plugins/data/public/ui/query_string_input/_query_bar.scss", + "src/plugins/data/public/ui/query_editor/_query_editor.scss" ] } } \ No newline at end of file diff --git a/src/plugins/data/public/ui/dataset_navigator/_dataset_navigator.scss b/src/plugins/data/public/ui/dataset_navigator/_dataset_navigator.scss index 73a8c8719500..82ff0e91e34e 100644 --- a/src/plugins/data/public/ui/dataset_navigator/_dataset_navigator.scss +++ b/src/plugins/data/public/ui/dataset_navigator/_dataset_navigator.scss @@ -2,9 +2,14 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -.datasetNavigator { - min-width: 350px; - border-bottom: $euiBorderThin !important; + +.dataSetNavigator { + padding: $euiSizeXS; + color: $euiColorPrimaryText; + + &__icon { + margin-right: 4px; + } } .dataSetNavigatorFormWrapper { diff --git a/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx b/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx index 8aa58479fba4..8831837e7444 100644 --- a/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx +++ b/src/plugins/data/public/ui/dataset_navigator/dataset_navigator.tsx @@ -10,10 +10,12 @@ import { EuiContextMenu, EuiForm, EuiFormRow, + EuiIcon, EuiLoadingSpinner, EuiPanel, EuiPopover, EuiSelect, + EuiToolTip, } from '@elastic/eui'; import { HttpStart, SavedObjectsClientContract } from 'opensearch-dashboards/public'; import _ from 'lodash'; @@ -563,23 +565,29 @@ export const DataSetNavigator = (props: DataSetNavigatorProps) => { content: <div>{isCatalogCacheFetching(databasesLoadStatus) && createLoadingSpinner()}</div>, }); + const datasetTitle = + selectedDataSetState && + selectedDataSetState?.dataSourceRef && + selectedDataSetState?.dataSourceRef.name + ? `${selectedDataSetState.dataSourceRef?.name}::${selectedDataSetState?.title}` + : selectedDataSetState?.title; + return ( <EuiPopover button={ - <EuiButtonEmpty - className="dataExplorerDSSelect" - color="text" - iconType="arrowDown" - iconSide="right" - flush="left" - onClick={onClick} - > - {selectedDataSetState && - selectedDataSetState?.dataSourceRef && - selectedDataSetState?.dataSourceRef.name - ? `${selectedDataSetState.dataSourceRef?.name}::${selectedDataSetState?.title}` - : selectedDataSetState?.title} - </EuiButtonEmpty> + <EuiToolTip content={datasetTitle}> + <EuiButtonEmpty + className="dataSetNavigator" + color="text" + iconType="arrowDown" + iconSide="right" + flush="left" + onClick={onClick} + > + <EuiIcon type="database" className="dataSetNavigator__icon" /> + {datasetTitle} + </EuiButtonEmpty> + </EuiToolTip> } isOpen={navigatorState.isOpen} closePopover={closePopover} diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss index 9b25e874b190..f44be6910ec6 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss @@ -11,18 +11,6 @@ padding-bottom: $euiSizeS; } -.globalQueryEditor { - padding: 0 $euiSizeXS $euiSizeXS $euiSizeXS; -} - -.globalQueryEditor:first-child { - padding-top: $euiSizeXS; -} - -.globalQueryEditor:not(:empty) { - padding-bottom: $euiSizeXS; -} - .globalFilterGroup__filterBar { margin-top: $euiSizeXS; } diff --git a/src/plugins/data/public/ui/query_editor/__snapshots__/language_selector.test.tsx.snap b/src/plugins/data/public/ui/query_editor/__snapshots__/language_selector.test.tsx.snap index 031b12033176..00dd4ef65d32 100644 --- a/src/plugins/data/public/ui/query_editor/__snapshots__/language_selector.test.tsx.snap +++ b/src/plugins/data/public/ui/query_editor/__snapshots__/language_selector.test.tsx.snap @@ -490,6 +490,7 @@ exports[`LanguageSelector should select DQL if language is kuery 1`] = ` className="languageSelector__button" iconSide="right" iconSize="s" + iconType="arrowDown" onClick={[Function]} > DQL @@ -513,6 +514,7 @@ exports[`LanguageSelector should select DQL if language is kuery 1`] = ` className="languageSelector__button" iconSide="right" iconSize="s" + iconType="arrowDown" onClick={[Function]} > <button @@ -526,6 +528,7 @@ exports[`LanguageSelector should select DQL if language is kuery 1`] = ` className="euiButtonEmpty__content" iconSide="right" iconSize="m" + iconType="arrowDown" textProps={ Object { "className": "euiButtonEmpty__text", @@ -535,6 +538,19 @@ exports[`LanguageSelector should select DQL if language is kuery 1`] = ` <span className="euiButtonContent euiButtonContent--iconRight euiButtonEmpty__content" > + <EuiIcon + className="euiButtonContent__icon" + color="inherit" + size="m" + type="arrowDown" + > + <span + className="euiButtonContent__icon" + color="inherit" + data-euiicon-type="arrowDown" + size="m" + /> + </EuiIcon> <span className="euiButtonEmpty__text" > @@ -1041,6 +1057,7 @@ exports[`LanguageSelector should select lucene if language is lucene 1`] = ` className="languageSelector__button" iconSide="right" iconSize="s" + iconType="arrowDown" onClick={[Function]} > Lucene @@ -1064,6 +1081,7 @@ exports[`LanguageSelector should select lucene if language is lucene 1`] = ` className="languageSelector__button" iconSide="right" iconSize="s" + iconType="arrowDown" onClick={[Function]} > <button @@ -1077,6 +1095,7 @@ exports[`LanguageSelector should select lucene if language is lucene 1`] = ` className="euiButtonEmpty__content" iconSide="right" iconSize="m" + iconType="arrowDown" textProps={ Object { "className": "euiButtonEmpty__text", @@ -1086,6 +1105,19 @@ exports[`LanguageSelector should select lucene if language is lucene 1`] = ` <span className="euiButtonContent euiButtonContent--iconRight euiButtonEmpty__content" > + <EuiIcon + className="euiButtonContent__icon" + color="inherit" + size="m" + type="arrowDown" + > + <span + className="euiButtonContent__icon" + color="inherit" + data-euiicon-type="arrowDown" + size="m" + /> + </EuiIcon> <span className="euiButtonEmpty__text" > diff --git a/src/plugins/data/public/ui/query_editor/_index.scss b/src/plugins/data/public/ui/query_editor/_index.scss index 64fb0056cb71..8da4ff92343c 100644 --- a/src/plugins/data/public/ui/query_editor/_index.scss +++ b/src/plugins/data/public/ui/query_editor/_index.scss @@ -1,2 +1,3 @@ +@import "./editors/default_editor/default_editor"; @import "./language_selector"; @import "./query_editor"; diff --git a/src/plugins/data/public/ui/query_editor/_query_editor.scss b/src/plugins/data/public/ui/query_editor/_query_editor.scss index 32e0e1dd241a..d3495197512d 100644 --- a/src/plugins/data/public/ui/query_editor/_query_editor.scss +++ b/src/plugins/data/public/ui/query_editor/_query_editor.scss @@ -9,7 +9,7 @@ .osdQueryEditor__editorAndSelectorWrapper { z-index: $euiZContentMenu; - max-width: 1000px; + max-width: 1200px; } .osdQueryEditorHeader { @@ -135,3 +135,62 @@ } } } + +// ------------ + +.osdQueryEditor { + display: flex; + flex-direction: column; + margin-bottom: -3px; // TODO: Hack to handle the renderSharingMetaFields adding a new empty div to the UI + + &.expanded.emptyExpanded { + .osdQueryEditor__datasetPicker { + width: auto; + } + } +} + +.osdQueryEditor__topBar { + display: flex; + align-items: center; + padding: $euiSizeXS; + + .osdQueryEditor__collapseBtn { + padding-right: $euiSizeXS; + border-right: $euiBorderThin; + } + + .osdQueryEditor__datasetPicker { + width: 155px; + padding-left: 4px; + + // To prevent overflow due to Oui styles + .euiToolTipAnchor { + display: block; + } + } + + .osdQueryEditor__input { + flex-grow: 1; + } + + #savedQueryPopover { + padding-left: $euiSizeXS; + border-left: $euiBorderThin; + } +} + +// TODO: improve how this is styled. Styling just for the DQL editor. +.osdQueryEditor__body .globalFilterGroup__wrapper { + margin-top: -$euiSizeS; +} + +.osdQuerEditor__singleLine { + padding: $euiSizeS; + background-color: $euiColorEmptyShade; + border: $euiBorderThin; + + .monaco-editor .view-overlays .current-line { + border: none; + } +} diff --git a/src/plugins/data/public/ui/query_editor/editors/default_editor/_default_editor.scss b/src/plugins/data/public/ui/query_editor/editors/default_editor/_default_editor.scss new file mode 100644 index 000000000000..2103512df370 --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/editors/default_editor/_default_editor.scss @@ -0,0 +1,21 @@ +.defaultEditor__footer { + display: flex; + flex-wrap: nowrap; + background-color: $euiColorLightestShade; + padding: $euiSizeXS; + gap: $euiSizeS; + + .defaultEditor__footerSpacer { + flex-grow: 1; + } + + .defaultEditor__footerItem { + padding: 0 $euiSizeXS; + } +} + +.defaultEditor { + border: $euiBorderThin; + border-radius: $euiSizeXS; + margin: 0 $euiSizeXS $euiSizeXS; +} diff --git a/src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx b/src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx new file mode 100644 index 000000000000..20093d806956 --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/editors/default_editor/index.tsx @@ -0,0 +1,99 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiText, EuiTextColorProps } from '@elastic/eui'; +// import { monaco } from '@osd/monaco'; +import { CodeEditor } from '../../../../../../opensearch_dashboards_react/public'; +import { createEditor, SingleLineInput } from '../shared'; + +interface FooterItem { + text: string; + color?: EuiTextColorProps['color']; +} + +interface DefaultInputProps extends React.JSX.IntrinsicAttributes { + languageId: string; + value: string; + onChange: (value: string) => void; + editorDidMount: (editor: any) => void; + footerItems?: { + start?: Array<FooterItem | string>; + end?: Array<FooterItem | string>; + }; + headerRef?: React.RefObject<HTMLDivElement>; + // provideCompletionItems: monaco.languages.CompletionItemProvider['provideCompletionItems']; +} + +const DefaultInput: React.FC<DefaultInputProps> = ({ + languageId, + value, + onChange, + footerItems, + editorDidMount, + headerRef, + // provideCompletionItems, +}) => { + return ( + <div className="defaultEditor"> + <div ref={headerRef} className="defaultEditor__header" /> + <CodeEditor + height={200} + languageId={languageId} + value={value} + onChange={onChange} + editorDidMount={editorDidMount} + options={{ + minimap: { enabled: false }, + scrollBeyondLastLine: false, + fontSize: 14, + fontFamily: 'Roboto Mono', + lineNumbers: 'on', + folding: true, + wordWrap: 'on', + wrappingIndent: 'same', + lineDecorationsWidth: 0, + lineNumbersMinChars: 2, + }} + // suggestionProvider={{ + // provideCompletionItems, + // }} + // languageConfiguration={{ + // language: , + // autoClosingPairs: [ + // { open: '(', close: ')' }, + // { open: '[', close: ']' }, + // { open: '{', close: '}' }, + // { open: '"', close: '"' }, + // { open: "'", close: "'" }, + // ], + // }} + /> + {footerItems && ( + <div className="defaultEditor__footer"> + {footerItems.start?.map((item, index) => ( + <FooterItem key={index} item={item} /> + ))} + <div className="defaultEditor__footerSpacer" /> + {footerItems.end?.map((item, index) => ( + <FooterItem key={index} item={item} /> + ))} + </div> + )} + </div> + ); +}; + +const FooterItem: React.FC<{ item: FooterItem | string }> = ({ item }) => { + const color = typeof item === 'string' ? ('subdued' as const) : item.color; + const text = typeof item === 'string' ? item : item.text; + return ( + <EuiText size="xs" className="defaultEditor__footerItem" color={color}> + {text} + </EuiText> + ); +}; + +export const createDefaultEditor = createEditor(SingleLineInput, null, DefaultInput); diff --git a/src/plugins/data/public/ui/query_editor/editors/dql_editor/index.tsx b/src/plugins/data/public/ui/query_editor/editors/dql_editor/index.tsx new file mode 100644 index 000000000000..b60ca7ee413b --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/editors/dql_editor/index.tsx @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { createEditor, SingleLineInput } from '../shared'; + +interface DQLBodyProps extends React.JSX.IntrinsicAttributes { + filterBar?: any; +} + +const DQLBody: React.FC<DQLBodyProps> = ({ filterBar }) => <div>{filterBar}</div>; + +export const createDQLEditor = createEditor(SingleLineInput, SingleLineInput, DQLBody); diff --git a/src/plugins/data/public/ui/query_editor/editors/index.ts b/src/plugins/data/public/ui/query_editor/editors/index.ts new file mode 100644 index 000000000000..552fd87b94cf --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/editors/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './dql_editor'; +export * from './default_editor'; +export * from './shared'; diff --git a/src/plugins/data/public/ui/query_editor/editors/shared.tsx b/src/plugins/data/public/ui/query_editor/editors/shared.tsx new file mode 100644 index 000000000000..1afc4f5c9f40 --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/editors/shared.tsx @@ -0,0 +1,108 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { CodeEditor } from '../../../../../opensearch_dashboards_react/public'; + +interface SingleLineInputProps extends React.JSX.IntrinsicAttributes { + languageId: string; + value: string; + onChange: (value: string) => void; + editorDidMount: (editor: any) => void; +} + +type CollapsedComponent<T> = React.ComponentType<T>; +type ExpandedComponent<T> = React.ComponentType<T> | null; +type BodyComponent<T> = React.ComponentType<T>; + +export interface Editor<TCollapsed, TExpanded, TBody> { + TopBar: { + Collapsed: CollapsedComponent<TCollapsed>; + Expanded: ExpandedComponent<TExpanded>; + }; + Body: BodyComponent<TBody>; +} + +interface EditorInstance<TCollapsed, TExpanded, TBody> { + TopBar: { + Collapsed: () => React.ReactElement; + Expanded: (() => React.ReactElement) | null; + }; + Body: () => React.ReactElement; +} + +export function createEditor< + TCollapsed extends React.JSX.IntrinsicAttributes, + TExpanded extends React.JSX.IntrinsicAttributes, + TBody extends React.JSX.IntrinsicAttributes +>( + collapsed: CollapsedComponent<TCollapsed>, + expanded: ExpandedComponent<TExpanded>, + body: BodyComponent<TBody> +) { + return ( + collapsedProps: TCollapsed, + expandedProps: TExpanded, + bodyProps: TBody + ): EditorInstance<TCollapsed, TExpanded, TBody> => ({ + TopBar: { + Collapsed: () => React.createElement(collapsed, collapsedProps), + Expanded: expanded ? () => React.createElement(expanded, expandedProps) : null, + }, + Body: () => React.createElement(body, bodyProps), + }); +} + +export const SingleLineInput: React.FC<SingleLineInputProps> = ({ + languageId, + value, + onChange, + editorDidMount, +}) => ( + <div className="osdQuerEditor__singleLine"> + <CodeEditor + height={20} // Adjusted to match lineHeight for a single line + languageId={languageId} + value={value} + onChange={onChange} + editorDidMount={editorDidMount} + options={{ + lineNumbers: 'off', // Disabled line numbers + // lineHeight: 40, + fontSize: 14, + fontFamily: 'Roboto Mono', + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'off', // Disabled word wrapping + wrappingIndent: 'none', // No indent since wrapping is off + folding: false, + glyphMargin: false, + lineDecorationsWidth: 0, + scrollbar: { + vertical: 'hidden', + }, + overviewRulerLanes: 0, + hideCursorInOverviewRuler: true, + cursorStyle: 'line', + wordBasedSuggestions: false, + }} + languageConfiguration={{ + language: languageId, + autoClosingPairs: [ + { + open: '(', + close: ')', + }, + { + open: '"', + close: '"', + }, + ], + }} + /> + </div> +); diff --git a/src/plugins/data/public/ui/query_editor/index.tsx b/src/plugins/data/public/ui/query_editor/index.tsx index 52fba0ecea78..96584ebc9d8f 100644 --- a/src/plugins/data/public/ui/query_editor/index.tsx +++ b/src/plugins/data/public/ui/query_editor/index.tsx @@ -8,7 +8,7 @@ import { withOpenSearchDashboards } from '../../../../opensearch_dashboards_reac import type { QueryEditorTopRowProps } from './query_editor_top_row'; import type { QueryEditorProps } from './query_editor'; -const Fallback = () => <div />; +const Fallback = () => null; const LazyQueryEditorTopRow = React.lazy(() => import('./query_editor_top_row')); export const QueryEditorTopRow = (props: QueryEditorTopRowProps) => ( diff --git a/src/plugins/data/public/ui/query_editor/language_selector.tsx b/src/plugins/data/public/ui/query_editor/language_selector.tsx index ce7c1e17178d..455540d28df2 100644 --- a/src/plugins/data/public/ui/query_editor/language_selector.tsx +++ b/src/plugins/data/public/ui/query_editor/language_selector.tsx @@ -19,7 +19,6 @@ export interface QueryLanguageSelectorProps { onSelectLanguage: (newLanguage: string) => void; anchorPosition?: PopoverAnchorPosition; appName?: string; - isFooter?: boolean; } const mapExternalLanguageToOptions = (language: string) => { @@ -84,21 +83,25 @@ export const QueryLanguageSelector = (props: QueryLanguageSelectorProps) => { uiService.Settings.setUserQueryLanguage(props.language); - const languageOptionsMenu = languageOptions.map((language) => { - return ( - <EuiContextMenuItem - key={language.label} - className="languageSelector__menuItem" - icon={language.label === selectedLanguage.label ? 'check' : 'empty'} - onClick={() => { - setPopover(false); - handleLanguageChange(language.value); - }} - > - {language.label} - </EuiContextMenuItem> - ); - }); + const languageOptionsMenu = languageOptions + .sort((a, b) => { + return a.label.localeCompare(b.label); + }) + .map((language) => { + return ( + <EuiContextMenuItem + key={language.label} + className="languageSelector__menuItem" + icon={language.label === selectedLanguage.label ? 'check' : 'empty'} + onClick={() => { + setPopover(false); + handleLanguageChange(language.value); + }} + > + {language.label} + </EuiContextMenuItem> + ); + }); return ( <EuiPopover className="languageSelector" @@ -108,7 +111,7 @@ export const QueryLanguageSelector = (props: QueryLanguageSelectorProps) => { iconSize="s" onClick={onButtonClick} className="languageSelector__button" - iconType={props.isFooter ? 'arrowDown' : undefined} + iconType={'arrowDown'} > {selectedLanguage.label} </EuiButtonEmpty> diff --git a/src/plugins/data/public/ui/query_editor/query_editor.tsx b/src/plugins/data/public/ui/query_editor/query_editor.tsx index 722a94a589bb..805ba13c01bb 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor.tsx @@ -3,17 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator, PopoverAnchorPosition } from '@elastic/eui'; +import { PopoverAnchorPosition } from '@elastic/eui'; import classNames from 'classnames'; import { isEqual } from 'lodash'; import React, { Component, createRef, RefObject } from 'react'; import { monaco } from '@osd/monaco'; import { Settings } from '..'; import { IDataPluginServices, IFieldType, IIndexPattern, Query, TimeRange } from '../..'; -import { - CodeEditor, - OpenSearchDashboardsReactContextValue, -} from '../../../../opensearch_dashboards_react/public'; +import { OpenSearchDashboardsReactContextValue } from '../../../../opensearch_dashboards_react/public'; import { QuerySuggestion } from '../../autocomplete'; import { fromUser, getQueryLog, PersistedLog, toUser } from '../../query'; import { SuggestionsListSize } from '../typeahead/suggestions_component'; @@ -22,6 +19,7 @@ import { QueryLanguageSelector } from './language_selector'; import { QueryEditorExtensions } from './query_editor_extensions'; import { QueryEditorBtnCollapse } from './query_editor_btn_collapse'; import { SimpleDataSet } from '../../../common'; +import { createDQLEditor, createDefaultEditor } from './editors'; const LANGUAGE_ID = 'SQL'; monaco.languages.register({ id: LANGUAGE_ID }); @@ -33,7 +31,7 @@ export interface QueryEditorProps { settings: Settings; disableAutoFocus?: boolean; screenTitle?: string; - prepend?: any; + queryActions?: any; persistedLog?: PersistedLog; bubbleSubmitEvent?: boolean; placeholder?: string; @@ -225,8 +223,6 @@ export default class QueryEditorUI extends Component<Props, State> { this.setState({ index }); }; - textareaId = htmlIdGenerator()(); - public componentDidMount() { const parsedQuery = fromUser(toUser(this.props.query.query)); if (!isEqual(this.props.query.query, parsedQuery)) { @@ -310,62 +306,8 @@ export default class QueryEditorUI extends Component<Props, State> { // }; // }; - editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => { - this.setState({ lineCount: editor.getModel()?.getLineCount() }); - this.inputRef = editor; - }; - - private onSingleLineInputChange = (value: string) => { - // Replace new lines with an empty string to prevent multi-line input - this.onQueryStringChange(value.replace(/[\r\n]+/gm, '')); - - this.setState({ lineCount: undefined }); - }; - - singleLineEditorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => { - this.inputRef = editor; - - const editorNode = editor.getDomNode(); - if (editorNode) { - const containerId = 'single-line-editor-wrapper'; - const style = document.createElement('style'); - const customCursorHeight = 20; - // eslint-disable-next-line no-unsanitized/property - style.innerHTML = ` - .${containerId} .monaco-editor .view-lines { - padding-left: 15px; - } - .${containerId} .monaco-editor .cursor { - height: ${customCursorHeight}px !important; - margin-top: ${(38 - customCursorHeight) / 2}px !important; - } - `; - - document.head.appendChild(style); - } - const handleEnterPress = () => { - this.onSubmit(this.props.query); - }; - - const disposable = editor.onKeyDown((e) => { - if (e.keyCode === monaco.KeyCode.Enter) { - // Prevent default Enter key behavior - e.preventDefault(); - handleEnterPress(); - } - }); - - // Optional: Cleanup on component unmount - return () => { - disposable.dispose(); - }; - }; - public render() { const className = classNames(this.props.className); - const headerClassName = classNames('osdQueryEditorHeader', this.props.headerClassName); - const bannerClassName = classNames('osdQueryEditorBanner', this.props.bannerClassName); - const footerClassName = classNames('osdQueryEditorFooter', this.props.footerClassName); const useQueryEditor = this.props.query.language !== 'kuery' && this.props.query.language !== 'lucene'; @@ -379,146 +321,259 @@ export default class QueryEditorUI extends Component<Props, State> { /> ); + const baseInputProps = { + languageId: this.props.query.language, + value: this.getQueryString(), + }; + + const defaultInputProps = { + ...baseInputProps, + onChange: this.onInputChange, + editorDidMount: (editor: monaco.editor.IStandaloneCodeEditor) => { + editor.setValue(`\n`.repeat(10)); + this.setState({ lineCount: editor.getModel()?.getLineCount() }); + this.inputRef = editor; + }, + footerItems: { + start: [ + `${this.state.lineCount} ${this.state.lineCount === 1 ? 'line' : 'lines'}`, + typeof this.props.dataSet?.timeFieldName || '', + ], + }, + // provideCompletionItems: this.provideCompletionItems, + }; + + const singleLineInputProps = { + ...baseInputProps, + onChange: (value: string) => { + // Replace new lines with an empty string to prevent multi-line input + this.onQueryStringChange(value.replace(/[\r\n]+/gm, '')); + + this.setState({ lineCount: undefined }); + }, + editorDidMount: (editor: monaco.editor.IStandaloneCodeEditor) => { + this.inputRef = editor; + + const handleEnterPress = () => { + this.onSubmit(this.props.query); + }; + + const disposable = editor.onKeyDown((e) => { + if (e.keyCode === monaco.KeyCode.Enter) { + // Prevent default Enter key behavior + e.preventDefault(); + handleEnterPress(); + } + }); + + // Optional: Cleanup on component unmount + return () => { + disposable.dispose(); + }; + }, + }; + + const languageEditor = useQueryEditor + ? createDefaultEditor(singleLineInputProps, {}, defaultInputProps) + : createDQLEditor(singleLineInputProps, singleLineInputProps, { + filterBar: this.props.filterBar, + }); + return ( - <div className={className}> - <div ref={this.bannerRef} className={bannerClassName} /> - <EuiFlexGroup gutterSize="xs" direction="column"> - <EuiFlexItem grow={false}> - <EuiFlexGroup gutterSize="xs" alignItems="center" className={`${className}__wrapper`}> - <EuiFlexItem className={`${className}__collapseWrapper`}> - <QueryEditorBtnCollapse - onClick={() => this.setState({ isCollapsed: !this.state.isCollapsed })} - isCollapsed={!this.state.isCollapsed} - /> - </EuiFlexItem> - <EuiFlexItem className={`${className}__dataSetWrapper`}> - <div ref={this.props.dataSetContainerRef} /> - </EuiFlexItem> - <EuiFlexItem grow={true}> - <EuiFlexGroup - gutterSize="none" - className={ - !useQueryEditor - ? 'euiFormControlLayout euiFormControlLayout--group osdQueryEditor__editorAndSelectorWrapper' - : '' - } - > - {(this.state.isCollapsed || !useQueryEditor) && ( - <EuiFlexItem grow={9}> - <div className="single-line-editor-wrapper"> - <CodeEditor - height={40} // Adjusted to match lineHeight for a single line - languageId={this.props.query.language} - value={this.getQueryString()} - onChange={this.onSingleLineInputChange} - editorDidMount={this.singleLineEditorDidMount} - options={{ - lineNumbers: 'off', // Disabled line numbers - lineHeight: 40, - fontSize: 14, - fontFamily: 'Roboto Mono', - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'off', // Disabled word wrapping - wrappingIndent: 'none', // No indent since wrapping is off - folding: false, - glyphMargin: false, - lineDecorationsWidth: 0, - scrollbar: { - vertical: 'hidden', - }, - overviewRulerLanes: 0, - hideCursorInOverviewRuler: true, - cursorStyle: 'line', - wordBasedSuggestions: false, - }} - // suggestionProvider={{ - // provideCompletionItems: this.provideCompletionItems, - // }} - /> - </div> - </EuiFlexItem> - )} - {!useQueryEditor && ( - <EuiFlexItem grow={false}> - <QueryLanguageSelector - language={this.props.query.language} - anchorPosition={this.props.languageSwitcherPopoverAnchorPosition} - onSelectLanguage={this.onSelectLanguage} - appName={this.services.appName} - /> - </EuiFlexItem> - )} - </EuiFlexGroup> - </EuiFlexItem> - <EuiFlexItem - className={`${className}__prependWrapper${ - !this.state.isCollapsed && useQueryEditor ? '' : '-isCollapsed' - }`} - > - {this.props.prepend} - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - - <EuiFlexItem onClick={this.onClickInput} grow={true}> - <div ref={this.headerRef} className={headerClassName} /> - {!this.state.isCollapsed && useQueryEditor && ( - <CodeEditor - height={70} - languageId={this.props.query.language} - value={this.getQueryString()} - onChange={this.onInputChange} - editorDidMount={this.editorDidMount} - options={{ - lineNumbers: 'on', - lineHeight: 24, - fontSize: 14, - fontFamily: 'Roboto Mono', - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', - lineDecorationsWidth: 0, - lineNumbersMinChars: 2, - wordBasedSuggestions: false, - }} - // suggestionProvider={{ - // provideCompletionItems: this.provideCompletionItems, - // }} - /> - )} - - <div - className={ - !this.state.isCollapsed && useQueryEditor - ? footerClassName - : 'osdQueryEditorFooter-isHidden' - } - > - <EuiFlexGroup gutterSize="s" responsive={false}> - <EuiFlexItem grow={false}>{languageSelector}</EuiFlexItem> - - <EuiFlexItem grow={false}> - {this.state.lineCount} {this.state.lineCount === 1 ? 'line' : 'lines'} - </EuiFlexItem> - <EuiFlexItem grow={false}> - {this.props.dataSet && `@${this.props.dataSet.timeFieldName}`} - </EuiFlexItem> - </EuiFlexGroup> - </div> - </EuiFlexItem> - - {!this.state.isCollapsed && ( - <EuiFlexItem grow={false}> - <div className="osdQueryEditor__filterBarWrapper">{this.props.filterBar}</div> - </EuiFlexItem> - )} - </EuiFlexGroup> + <div + className={classNames( + className, + 'osdQueryEditor', + this.state.isCollapsed ? 'collapsed' : 'expanded', + !languageEditor.TopBar.Expanded && 'emptyExpanded' + )} + > + <div + ref={this.bannerRef} + className={classNames('osdQueryEditor__banner', this.props.bannerClassName)} + /> + <div className="osdQueryEditor__topBar"> + <QueryEditorBtnCollapse + onClick={() => this.setState({ isCollapsed: !this.state.isCollapsed })} + isCollapsed={!this.state.isCollapsed} + /> + <div ref={this.props.dataSetContainerRef} className="osdQueryEditor__datasetPicker" /> + <div className="osdQueryEditor__input"> + {this.state.isCollapsed + ? languageEditor.TopBar.Collapsed() + : languageEditor.TopBar.Expanded && languageEditor.TopBar.Expanded()} + </div> + {languageSelector} + {this.props.queryActions} + </div> + {!this.state.isCollapsed && ( + <div className="osdQueryEditor__body">{languageEditor.Body()}</div> + )} + + {/* <EuiFlexGroup gutterSize="xs" direction="column"> + <EuiFlexItem grow={false}> + <EuiFlexGroup gutterSize="xs" alignItems="center" className={`${className}__wrapper`}> + <EuiFlexItem className={`${className}__collapseWrapper`}> + <QueryEditorBtnCollapse + onClick={() => this.setState({ isCollapsed: !this.state.isCollapsed })} + isCollapsed={!this.state.isCollapsed} + /> + </EuiFlexItem> + <EuiFlexItem className={`${className}__dataSetWrapper`}> + <div ref={this.props.dataSetContainerRef} /> + </EuiFlexItem> + <EuiFlexItem grow={true}> + <EuiFlexGroup + gutterSize="none" + className={ + !useQueryEditor + ? 'euiFormControlLayout euiFormControlLayout--group osdQueryEditor__editorAndSelectorWrapper' + : '' + } + > + {(this.state.isCollapsed || !useQueryEditor) && ( + <EuiFlexItem grow={9}> + <div className="single-line-editor-wrapper"> + <CodeEditor + height={40} // Adjusted to match lineHeight for a single line + languageId={this.props.query.language} + value={this.getQueryString()} + onChange={this.onSingleLineInputChange} + editorDidMount={this.singleLineEditorDidMount} + options={{ + lineNumbers: 'off', // Disabled line numbers + lineHeight: 40, + fontSize: 14, + fontFamily: 'Roboto Mono', + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'off', // Disabled word wrapping + wrappingIndent: 'none', // No indent since wrapping is off + folding: false, + glyphMargin: false, + lineDecorationsWidth: 0, + scrollbar: { + vertical: 'hidden', + }, + overviewRulerLanes: 0, + hideCursorInOverviewRuler: true, + cursorStyle: 'line', + wordBasedSuggestions: false, + }} + suggestionProvider={{ + provideCompletionItems: this.provideCompletionItems, + }} + languageConfiguration={{ + language: LANGUAGE_ID_KUERY, + autoClosingPairs: [ + { + open: '(', + close: ')', + }, + { + open: '"', + close: '"', + }, + ], + }} + /> + </div> + </EuiFlexItem> + )} + {!useQueryEditor && ( + <EuiFlexItem grow={false}> + <QueryLanguageSelector + language={this.props.query.language} + anchorPosition={this.props.languageSwitcherPopoverAnchorPosition} + onSelectLanguage={this.onSelectLanguage} + appName={this.services.appName} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem + className={`${className}__prependWrapper${ + !this.state.isCollapsed && useQueryEditor ? '' : '-isCollapsed' + }`} + > + {this.props.prepend} + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + + <EuiFlexItem onClick={this.onClickInput} grow={true}> + <div ref={this.headerRef} className={headerClassName} /> + {!this.state.isCollapsed && useQueryEditor && ( + <CodeEditor + height={70} + languageId={this.props.query.language} + value={this.getQueryString()} + onChange={this.onInputChange} + editorDidMount={this.editorDidMount} + options={{ + lineNumbers: 'on', + lineHeight: 24, + fontSize: 14, + fontFamily: 'Roboto Mono', + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + lineDecorationsWidth: 0, + lineNumbersMinChars: 2, + wordBasedSuggestions: false, + }} + suggestionProvider={{ + provideCompletionItems: this.provideCompletionItems, + }} + languageConfiguration={{ + language: LANGUAGE_ID_KUERY, + autoClosingPairs: [ + { + open: '(', + close: ')', + }, + { + open: '"', + close: '"', + }, + ], + }} + /> + )} + + <div + className={ + !this.state.isCollapsed && useQueryEditor + ? footerClassName + : 'osdQueryEditorFooter-isHidden' + } + > + <EuiFlexGroup gutterSize="s" responsive={false}> + <EuiFlexItem grow={false}>{languageSelector}</EuiFlexItem> + + <EuiFlexItem grow={false}> + {this.state.lineCount} {this.state.lineCount === 1 ? 'line' : 'lines'} + </EuiFlexItem> + <EuiFlexItem grow={false}> + {typeof this.props.indexPatterns?.[0] !== 'string' && + '@' + this.props.indexPatterns?.[0].timeFieldName} + </EuiFlexItem> + </EuiFlexGroup> + </div> + </EuiFlexItem> + + {!this.state.isCollapsed && ( + <EuiFlexItem grow={false}> + <div className="osdQueryEditor__filterBarWrapper">{this.props.filterBar}</div> + </EuiFlexItem> + )} + </EuiFlexGroup> */} {this.renderQueryEditorExtensions()} </div> ); diff --git a/src/plugins/data/public/ui/query_editor/query_editor_btn_collapse.tsx b/src/plugins/data/public/ui/query_editor/query_editor_btn_collapse.tsx index 1bde59570fdd..3020cfd1393c 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor_btn_collapse.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor_btn_collapse.tsx @@ -17,15 +17,17 @@ export function QueryEditorBtnCollapse({ onClick, isCollapsed }: Props) { defaultMessage: 'Toggle query editor', }); return ( - <EuiToolTip content={label}> - <EuiButtonIcon - aria-expanded={!isCollapsed} - aria-label={label} - data-test-subj="queryEditorCollapseBtn" - onClick={onClick} - iconType={!isCollapsed ? 'arrowRight' : 'arrowDown'} - iconSize={'s'} - /> - </EuiToolTip> + <div className="osdQueryEditor__collapseBtn"> + <EuiToolTip content={label}> + <EuiButtonIcon + aria-expanded={!isCollapsed} + aria-label={label} + data-test-subj="queryEditorCollapseBtn" + onClick={onClick} + iconType={!isCollapsed ? 'arrowRight' : 'arrowDown'} + iconSize={'m'} + /> + </EuiToolTip> + </div> ); } diff --git a/src/plugins/data/public/ui/query_editor/query_editor_extensions/index.tsx b/src/plugins/data/public/ui/query_editor/query_editor_extensions/index.tsx index 8dcaaa459dd9..c72398b654c4 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor_extensions/index.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor_extensions/index.tsx @@ -5,7 +5,7 @@ import React, { ComponentProps } from 'react'; -const Fallback = () => <div />; +const Fallback = () => null; const LazyQueryEditorExtensions = React.lazy(() => import('./query_editor_extensions')); export const QueryEditorExtensions = (props: ComponentProps<typeof LazyQueryEditorExtensions>) => ( diff --git a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx index 3e6ae92f870c..6c16858f7925 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx @@ -212,7 +212,7 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { <QueryEditor disableAutoFocus={props.disableAutoFocus} dataSet={dataSet} - prepend={props.prepend} + queryActions={props.prepend} query={parsedQuery} dataSetContainerRef={props.dataSetContainerRef} settings={props.settings!} @@ -269,8 +269,9 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { data-test-subj="querySubmitButton" className="euiSuperUpdateButton" iconType="play" + fill > - {props.isDirty ? 'Refresh' : 'Run'} + Run </EuiButton> ); diff --git a/src/plugins/data/public/ui/query_editor/types.ts b/src/plugins/data/public/ui/query_editor/types.ts new file mode 100644 index 000000000000..2e6c627bf0ea --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/types.ts @@ -0,0 +1,12 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface LanguageEditor<T = {}> { + TopBar: { + Expanded: React.ComponentType<T> | React.ReactElement | null; + Collapsed: React.ComponentType<T> | React.ReactElement; + }; + Body: React.ComponentType<{}> | React.ReactElement; +} diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index 65a99ead8ee2..b8a2575f2b0d 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -12,6 +12,10 @@ .osdQueryBar__wrap { max-width: 100%; z-index: $euiZContentMenu; + + .euiToolTipAnchor { + height: 100%; + } } // Uses the append style, but no bordering diff --git a/src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap b/src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap index 19c53a107594..6368d28bfe3c 100644 --- a/src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap +++ b/src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap @@ -12,6 +12,7 @@ exports[`Saved query management component has a popover button 1`] = ` > <EuiSmallButtonEmpty aria-label="See saved queries" + className="osdSavedQueryManagement__popoverButton" data-test-subj="saved-query-management-popover-button" onClick={[Function]} > diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx index 5d946d82859b..fe6409e667ee 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx @@ -181,6 +181,7 @@ export function SavedQueryManagementComponent({ onClick={handleTogglePopover} aria-label={label} data-test-subj="saved-query-management-popover-button" + className="osdSavedQueryManagement__popoverButton" > <EuiIcon type="save" className="euiQuickSelectPopover__buttonText" /> </EuiSmallButtonEmpty> diff --git a/src/plugins/data_explorer/public/components/app_container.scss b/src/plugins/data_explorer/public/components/app_container.scss index 07f070be3b17..f0320d5d9b8e 100644 --- a/src/plugins/data_explorer/public/components/app_container.scss +++ b/src/plugins/data_explorer/public/components/app_container.scss @@ -10,7 +10,11 @@ $osdHeaderOffset: $euiHeaderHeightCompensation; } .deLayout { - height: calc(100vh - #{$osdHeaderOffset}); + height: calc(100vh - #{$osdHeaderOffset * 1}); + + &.dsc--next { + height: calc(100vh - #{$osdHeaderOffset * 2}); + } &__canvas { height: 100%; @@ -19,6 +23,10 @@ $osdHeaderOffset: $euiHeaderHeightCompensation; .headerIsExpanded .deLayout { height: calc(100vh - #{$osdHeaderOffset * 2}); + + &.dsc--next { + height: calc(100vh - #{$osdHeaderOffset * 3}); + } } .mainPage { diff --git a/src/plugins/data_explorer/public/components/app_container.tsx b/src/plugins/data_explorer/public/components/app_container.tsx index d334fb2ae0d3..d3dc6a97ef0d 100644 --- a/src/plugins/data_explorer/public/components/app_container.tsx +++ b/src/plugins/data_explorer/public/components/app_container.tsx @@ -13,6 +13,7 @@ import { useIsWithinBreakpoints, } from '@elastic/eui'; import { Suspense } from 'react'; +import classNames from 'classnames'; import { AppMountParameters } from '../../../../core/public'; import { Sidebar } from './sidebar'; import { NoView } from './no_view'; @@ -29,6 +30,7 @@ export const AppContainer = React.memo( const opensearchDashboards = useOpenSearchDashboards<IDataPluginServices>(); const { uiSettings } = opensearchDashboards.services; + const isEnhancementsEnabled = uiSettings?.get(QUERY_ENHANCEMENT_ENABLED_SETTING); const topLinkRef = useRef<HTMLDivElement>(null); const datePickerRef = useRef<HTMLDivElement>(null); @@ -49,7 +51,7 @@ export const AppContainer = React.memo( // Render the application DOM. return ( <div className="mainPage"> - {uiSettings?.get(QUERY_ENHANCEMENT_ENABLED_SETTING) && ( + {isEnhancementsEnabled && ( <EuiFlexGroup direction="row" className="mainPage navBar" @@ -66,7 +68,11 @@ export const AppContainer = React.memo( </EuiFlexGroup> )} - <EuiPage className="deLayout" paddingSize="none" grow={false}> + <EuiPage + className={classNames('deLayout', isEnhancementsEnabled && 'dsc--next')} + paddingSize="none" + grow={false} + > {/* TODO: improve fallback state */} <Suspense fallback={<div>Loading...</div>}> <Context {...params}> diff --git a/src/plugins/discover/public/application/components/chart/_histogram.scss b/src/plugins/discover/public/application/components/chart/_histogram.scss index 1e625fa064e2..3b1c1799e0f6 100644 --- a/src/plugins/discover/public/application/components/chart/_histogram.scss +++ b/src/plugins/discover/public/application/components/chart/_histogram.scss @@ -9,3 +9,18 @@ .dscHistogram .echChartBackground { background-color: inherit !important; } + +.dscChart__wrapper { + border-top: 1px solid $euiColorLightShade; + border-bottom: 1px solid $euiColorLightShade; + padding: 8px; + gap: 8px; + + .dscChart__chartheader { + align-items: center; + } + + .dscResultCount { + padding-top: 0; + } +} diff --git a/src/plugins/discover/public/application/components/chart/chart.tsx b/src/plugins/discover/public/application/components/chart/chart.tsx index f21ce3e4ef19..a7e9cf51ab40 100644 --- a/src/plugins/discover/public/application/components/chart/chart.tsx +++ b/src/plugins/discover/public/application/components/chart/chart.tsx @@ -5,10 +5,10 @@ import './_histogram.scss'; -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import moment from 'moment'; import dateMath from '@elastic/datemath'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { IUiSettingsClient } from 'opensearch-dashboards/public'; import { DataPublicPluginStart, search } from '../../../../../data/public'; @@ -30,6 +30,8 @@ interface DiscoverChartProps { showResetButton?: boolean; isTimeBased?: boolean; services: DiscoverServices; + isEnhancementsEnabled: boolean; + discoverOptions: any; } export const DiscoverChart = ({ @@ -42,6 +44,8 @@ export const DiscoverChart = ({ isTimeBased, services, showResetButton = false, + isEnhancementsEnabled, + discoverOptions, }: DiscoverChartProps) => { const { refetch$ } = useDiscoverContext(); const { from, to } = data.query.timefilter.timefilter.getTime(); @@ -65,29 +69,77 @@ export const DiscoverChart = ({ }, [data] ); + const [isCollapsed, setIsCollapsed] = useState(false); - return ( - <EuiFlexGroup direction="column" gutterSize="none"> - <EuiFlexItem grow={false} className="dscChart__hitsCounter"> - <HitsCounter - hits={hits > 0 ? hits : 0} - showResetButton={showResetButton} - onResetQuery={resetQuery} - /> + const hitsCounter = ( + <div className="dscChart__hitsCounter"> + <HitsCounter + hits={hits > 0 ? hits : 0} + showResetButton={showResetButton} + onResetQuery={resetQuery} + /> + </div> + ); + + const timeChartHeader = ( + <div className="dscChart__TimechartHeader"> + <TimechartHeader + bucketInterval={bucketInterval} + dateFormat={config.get('dateFormat')} + timeRange={timeRange} + options={search.aggs.intervalOptions} + onChangeInterval={onChangeInterval} + stateInterval={interval || ''} + /> + </div> + ); + + const toggleLabel = i18n.translate('queryEditor.collapse', { + defaultMessage: 'Toggle query editor', + }); + + const toggle = ( + <EuiToolTip content={toggleLabel}> + <EuiButtonIcon + aria-expanded={isCollapsed} + aria-label={toggleLabel} + data-test-subj="queryEditorCollapseBtn" + onClick={() => setIsCollapsed(!isCollapsed)} + iconType={isCollapsed ? 'arrowRight' : 'arrowDown'} + iconSize={'s'} + /> + </EuiToolTip> + ); + + const queryEnhancedHistogramHeader = ( + <EuiFlexGroup direction="row" gutterSize="m" className="dscChart__chartheader"> + <EuiFlexItem grow={false}>{toggle}</EuiFlexItem> + <EuiFlexItem grow={false}>{hitsCounter}</EuiFlexItem> + <EuiFlexItem grow={true} style={{ justifyContent: 'flex-start' }}> + {isTimeBased && timeChartHeader} </EuiFlexItem> - {isTimeBased && ( - <EuiFlexItem className="dscChart__TimechartHeader"> - <TimechartHeader - bucketInterval={bucketInterval} - dateFormat={config.get('dateFormat')} - timeRange={timeRange} - options={search.aggs.intervalOptions} - onChangeInterval={onChangeInterval} - stateInterval={interval || ''} - /> - </EuiFlexItem> - )} - {isTimeBased && chartData && ( + <EuiFlexItem grow={false}>{discoverOptions}</EuiFlexItem> + </EuiFlexGroup> + ); + + const histogramHeader = ( + <EuiFlexGroup direction="column" gutterSize="xs"> + <EuiFlexItem grow={false}>{hitsCounter}</EuiFlexItem> + <EuiFlexItem grow={false}>{isTimeBased && timeChartHeader}</EuiFlexItem> + <EuiFlexItem grow={false}>{discoverOptions}</EuiFlexItem> + </EuiFlexGroup> + ); + + const showHistogram = !isEnhancementsEnabled || !isCollapsed; + + return ( + <EuiFlexGroup + direction="column" + gutterSize="none" + className={isEnhancementsEnabled ? 'dscChart__wrapper' : ''} + > + {isEnhancementsEnabled ? queryEnhancedHistogramHeader : histogramHeader} + {isTimeBased && chartData && showHistogram && ( <EuiFlexItem grow={false}> <section aria-label={i18n.translate('discover.histogramOfFoundDocumentsAriaLabel', { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.scss b/src/plugins/discover/public/application/components/sidebar/discover_field.scss index e869707ebcd3..02abe611eb43 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.scss +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.scss @@ -19,3 +19,14 @@ opacity: 1; } } + +.osdDiscoverSideBar__wrap { + max-width: 100%; + z-index: $euiZContentMenu; +} + +.toggleFieldFilterButton { + span:is([class$="__textShift"]) { + min-width: 0; + } +} diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index b009864411e0..2e726eb0e964 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -47,8 +47,10 @@ import { EuiPanel, EuiSmallFilterButton, EuiFilterGroup, + EuiFieldSearch, } from '@elastic/eui'; import { FormattedMessage } from '@osd/i18n/react'; +import { UI_SETTINGS } from 'src/plugins/data/common'; export const NUM_FILTERS = 3; @@ -75,13 +77,19 @@ export interface Props { * types for the type filter */ types: string[]; + isEnhancementsEnabledOverride: boolean; } /** * Component is Discover's side bar to search of available fields * Additionally there's a button displayed that allows the user to show/hide more filter fields */ -export function DiscoverFieldSearch({ onChange, value, types }: Props) { +export function DiscoverFieldSearch({ + onChange, + value, + types, + isEnhancementsEnabledOverride, +}: Props) { const searchPlaceholder = i18n.translate('discover.fieldChooser.searchPlaceHolder', { defaultMessage: 'Search field names', }); @@ -240,70 +248,100 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { </EuiPanel> ); - return ( - <EuiFlexGroup responsive={false} gutterSize="xs"> - <EuiFlexItem> - <EuiOutsideClickDetector onOutsideClick={() => {}} isDisabled={!isPopoverOpen}> - <EuiCompressedFieldSearch - aria-label={searchPlaceholder} - data-test-subj="fieldFilterSearchInput" - fullWidth - onChange={(event) => onChange('name', event.currentTarget.value)} - placeholder={searchPlaceholder} - value={value} - /> - </EuiOutsideClickDetector> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiFilterGroup> - <EuiPopover - id="dataPanelTypeFilter" - panelClassName="euiFilterGroup__popoverPanel dataPanelTypeFilterPopover" - panelPaddingSize="none" - anchorPosition="downLeft" - display="block" - isOpen={isPopoverOpen} - closePopover={() => { - setPopoverOpen(false); - }} - button={ - <EuiSmallFilterButton - iconType="filter" - iconSide="left" - hasActiveFilters={activeFiltersCount > 0} - aria-label={filterBtnAriaLabel} - data-test-subj="toggleFieldFilterButton" - numFilters={activeFiltersCount} // {NUM_FILTERS} https://github.com/opensearch-project/oui/issues/1219 - onClick={handleFacetButtonClicked} - numActiveFilters={activeFiltersCount} - isSelected={isPopoverOpen} - > - <FormattedMessage - id="discover.fieldChooser.fieldFilterFacetButtonLabel" - defaultMessage="Filter by type" - /> - </EuiSmallFilterButton> - } - > - <EuiPopoverTitle> - {i18n.translate('discover.fieldChooser.filter.filterByTypeLabel', { - defaultMessage: 'Filter by type', - })} - </EuiPopoverTitle> - {selectionPanel} - <EuiPopoverFooter> - <EuiCompressedSwitch - label={i18n.translate('discover.fieldChooser.filter.hideMissingFieldsLabel', { - defaultMessage: 'Hide missing fields', - })} - checked={values.missing} - onChange={handleMissingChange} - data-test-subj="missingSwitch" - /> - </EuiPopoverFooter> - </EuiPopover> - </EuiFilterGroup> - </EuiFlexItem> - </EuiFlexGroup> + const compressedFieldSearch = ( + <EuiOutsideClickDetector onOutsideClick={() => {}} isDisabled={!isPopoverOpen}> + <EuiCompressedFieldSearch + aria-label={searchPlaceholder} + data-test-subj="fieldFilterSearchInput" + fullWidth + onChange={(event) => onChange('name', event.currentTarget.value)} + placeholder={searchPlaceholder} + value={value} + /> + </EuiOutsideClickDetector> + ); + + const fieldSearch = ( + <EuiOutsideClickDetector onOutsideClick={() => {}} isDisabled={!isPopoverOpen}> + <EuiFieldSearch + aria-label={searchPlaceholder} + data-test-subj="fieldFilterSearchInput" + fullWidth + onChange={(event) => onChange('name', event.currentTarget.value)} + placeholder={searchPlaceholder} + value={value} + className="dscSideBar_searchInput" + /> + </EuiOutsideClickDetector> + ); + + const fieldPopover = ( + <EuiPopover + id="dataPanelTypeFilter" + panelClassName="euiFilterGroup__popoverPanel dataPanelTypeFilterPopover" + panelPaddingSize="none" + anchorPosition="downLeft" + display="block" + isOpen={isPopoverOpen} + closePopover={() => { + setPopoverOpen(false); + }} + button={ + <EuiSmallFilterButton + iconType="filter" + iconSide="left" + hasActiveFilters={activeFiltersCount > 0} + aria-label={filterBtnAriaLabel} + data-test-subj="toggleFieldFilterButton" + numFilters={activeFiltersCount} // {NUM_FILTERS} https://github.com/opensearch-project/oui/issues/1219 + onClick={handleFacetButtonClicked} + numActiveFilters={activeFiltersCount} + isSelected={isPopoverOpen} + className={isEnhancementsEnabledOverride ? 'toggleFieldFilterButton' : ''} + > + {!isEnhancementsEnabledOverride && ( + <FormattedMessage + id="discover.fieldChooser.fieldFilterFacetButtonLabel" + defaultMessage="Filter by type" + /> + )} + </EuiSmallFilterButton> + } + > + <EuiPopoverTitle> + {i18n.translate('discover.fieldChooser.filter.filterByTypeLabel', { + defaultMessage: 'Filter by type', + })} + </EuiPopoverTitle> + {selectionPanel} + <EuiPopoverFooter> + <EuiCompressedSwitch + label={i18n.translate('discover.fieldChooser.filter.hideMissingFieldsLabel', { + defaultMessage: 'Hide missing fields', + })} + checked={values.missing} + onChange={handleMissingChange} + data-test-subj="missingSwitch" + /> + </EuiPopoverFooter> + </EuiPopover> ); + + if (isEnhancementsEnabledOverride) { + return ( + <div className="euiFormControlLayout euiFormControlLayout--group osdDiscoverSideBar__wrap"> + {fieldSearch} + {fieldPopover} + </div> + ); + } else { + return ( + <EuiFlexGroup responsive={false} gutterSize="xs"> + <EuiFlexItem>{compressedFieldSearch}</EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiFilterGroup>{fieldPopover}</EuiFilterGroup> + </EuiFlexItem> + </EuiFlexGroup> + ); + } } diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss index 58f168ea88d3..201debc95534 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss @@ -1,3 +1,21 @@ +.deSidebar_panel { + border-left: 0; + + .dscSideBar_searchContainer { + padding: $euiSizeXS; + border-bottom: $euiBorderThin; + + .toggleFieldFilterButton { + border: none; + } + + .dscSideBar_searchInput { + box-shadow: none; + border-right: $euiBorderThin; + } + } +} + .dscSideBar_fieldGroup { width: 100%; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index 528f892af2f5..a2b314a797df 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -38,7 +38,7 @@ import { EuiDraggable, EuiPanel, EuiSplitPanel, - EuiSmallButtonEmpty, + EuiButtonEmpty, } from '@elastic/eui'; import { I18nProvider } from '@osd/i18n/react'; import { DiscoverField } from './discover_field'; @@ -96,6 +96,7 @@ export interface DiscoverSidebarProps { * Currently selected index pattern */ selectedIndexPattern?: IndexPattern; + isEnhancementsEnabledOverride: boolean; } export function DiscoverSidebar(props: DiscoverSidebarProps) { @@ -108,6 +109,7 @@ export function DiscoverSidebar(props: DiscoverSidebarProps) { onNormalize, onCreateIndexPattern, selectedIndexPattern, + isEnhancementsEnabledOverride, } = props; const [fields, setFields] = useState<IndexPatternField[] | null>(null); const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter()); @@ -213,11 +215,16 @@ export function DiscoverSidebar(props: DiscoverSidebarProps) { color="transparent" hasBorder={false} > - <EuiSplitPanel.Inner grow={false} paddingSize="s"> + <EuiSplitPanel.Inner + grow={false} + paddingSize="none" + className="dscSideBar_searchContainer" + > <DiscoverFieldSearch onChange={onChangeFieldSearch} value={fieldFilterState.name} types={fieldTypes} + isEnhancementsEnabledOverride={isEnhancementsEnabledOverride} /> </EuiSplitPanel.Inner> {displayIndexPatternCreation(selectedIndexPattern) ? ( @@ -298,7 +305,7 @@ const FieldList = ({ return ( <> - <EuiSmallButtonEmpty + <EuiButtonEmpty iconSide="right" color="text" iconType={expanded ? 'arrowDown' : 'arrowRight'} @@ -310,7 +317,7 @@ const FieldList = ({ })} > {title} - </EuiSmallButtonEmpty> + </EuiButtonEmpty> {expanded && ( <EuiDroppable droppableId={`${category.toUpperCase()}_FIELDS`} diff --git a/src/plugins/discover/public/application/view_components/canvas/discover_chart_container.tsx b/src/plugins/discover/public/application/view_components/canvas/discover_chart_container.tsx index bade3ecae7a5..5d43b6ecc317 100644 --- a/src/plugins/discover/public/application/view_components/canvas/discover_chart_container.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/discover_chart_container.tsx @@ -3,18 +3,32 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '@osd/i18n'; + import './discover_chart_container.scss'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; +import { + EuiButtonIcon, + EuiCompressedSwitch, + EuiContextMenu, + EuiPanel, + EuiPopover, +} from '@elastic/eui'; import { DiscoverViewServices } from '../../../build_services'; import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; import { useDiscoverContext } from '../context'; import { SearchData } from '../utils/use_search'; import { DiscoverChart } from '../../components/chart/chart'; +import { QUERY_ENHANCEMENT_ENABLED_SETTING } from '../../../../common'; +import { getNewDiscoverSetting, setNewDiscoverSetting } from '../../components/utils/local_storage'; export const DiscoverChartContainer = ({ hits, bucketInterval, chartData }: SearchData) => { const { services } = useOpenSearchDashboards<DiscoverViewServices>(); - const { uiSettings, data, core } = services; + const { uiSettings, data, core, storage } = services; const { indexPattern, savedSearch } = useDiscoverContext(); + const isEnhancementsEnabled = uiSettings.get(QUERY_ENHANCEMENT_ENABLED_SETTING); + const [isOptionsOpen, setOptionsOpen] = useState(false); + const [useLegacy, setUseLegacy] = useState(!getNewDiscoverSetting(storage)); const isTimeBased = useMemo(() => (indexPattern ? indexPattern.isTimeBased() : false), [ indexPattern, @@ -22,6 +36,51 @@ export const DiscoverChartContainer = ({ hits, bucketInterval, chartData }: Sear if (!hits) return null; + const discoverOptions = ( + <EuiPopover + button={ + <EuiButtonIcon + data-test-subj="discoverOptionsButton" + aria-label={i18n.translate('discover.canvas.discoverOptionsButtonLabel', { + defaultMessage: 'Options for discover', + })} + size="s" + iconType="gear" + onClick={() => setOptionsOpen(!isOptionsOpen)} + /> + } + closePopover={() => setOptionsOpen(false)} + isOpen={isOptionsOpen} + panelPaddingSize="none" + > + <EuiContextMenu + initialPanelId={0} + size="s" + panels={[ + { + id: 0, + title: 'Options', + content: ( + <EuiPanel> + <EuiCompressedSwitch + label="Enable legacy Discover" + checked={useLegacy} + data-test-subj="discoverOptionsLegacySwitch" + onChange={(e) => { + const checked = e.target.checked; + setUseLegacy(checked); + setNewDiscoverSetting(!checked, storage); + window.location.reload(); + }} + /> + </EuiPanel> + ), + }, + ]} + /> + </EuiPopover> + ); + return ( <DiscoverChart bucketInterval={bucketInterval} @@ -35,6 +94,8 @@ export const DiscoverChartContainer = ({ hits, bucketInterval, chartData }: Sear services={services} showResetButton={!!savedSearch && !!savedSearch.id} isTimeBased={isTimeBased} + isEnhancementsEnabled={isEnhancementsEnabled} + discoverOptions={discoverOptions} /> ); }; diff --git a/src/plugins/discover/public/application/view_components/canvas/index.tsx b/src/plugins/discover/public/application/view_components/canvas/index.tsx index 54489824227e..26e32c6c2df0 100644 --- a/src/plugins/discover/public/application/view_components/canvas/index.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/index.tsx @@ -118,54 +118,6 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR }; const showSaveQuery = !!capabilities.discover?.saveQuery; - const [isOptionsOpen, setOptionsOpen] = useState(false); - const [useLegacy, setUseLegacy] = useState(!getNewDiscoverSetting(storage)); - const DiscoverOptions = () => ( - <EuiPopover - button={ - <EuiButtonIcon - data-test-subj="discoverOptionsButton" - aria-label={i18n.translate('discover.canvas.discoverOptionsButtonLabel', { - defaultMessage: 'Options for discover', - })} - size="s" - iconType="gear" - onClick={() => setOptionsOpen(!isOptionsOpen)} - /> - } - closePopover={() => setOptionsOpen(false)} - isOpen={isOptionsOpen} - panelPaddingSize="none" - className="dscCanvas_options" - > - <EuiContextMenu - initialPanelId={0} - size="s" - panels={[ - { - id: 0, - title: 'Options', - content: ( - <EuiPanel> - <EuiCompressedSwitch - label="Enable legacy Discover" - checked={useLegacy} - data-test-subj="discoverOptionsLegacySwitch" - onChange={(e) => { - const checked = e.target.checked; - setUseLegacy(checked); - setNewDiscoverSetting(!checked, storage); - window.location.reload(); - }} - /> - </EuiPanel> - ), - }, - ]} - /> - </EuiPopover> - ); - return ( <EuiPanel panelRef={panelRef} @@ -192,11 +144,16 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR <DiscoverUninitialized onRefresh={() => refetch$.next()} /> )} {fetchState.status === ResultStatus.LOADING && <LoadingSpinner />} - {fetchState.status === ResultStatus.READY && ( + {fetchState.status === ResultStatus.READY && isEnhancementsEnabled && ( + <> + <MemoizedDiscoverChartContainer {...fetchState} /> + <MemoizedDiscoverTable rows={rows} scrollToTop={scrollToTop} /> + </> + )} + {fetchState.status === ResultStatus.READY && !isEnhancementsEnabled && ( <EuiPanel hasShadow={false} paddingSize="none" className="dscCanvas_results"> <MemoizedDiscoverChartContainer {...fetchState} /> <MemoizedDiscoverTable rows={rows} scrollToTop={scrollToTop} /> - <DiscoverOptions /> </EuiPanel> )} </EuiPanel> diff --git a/src/plugins/discover/public/application/view_components/panel/index.tsx b/src/plugins/discover/public/application/view_components/panel/index.tsx index f5d0d6db68b3..e88a8f7dbede 100644 --- a/src/plugins/discover/public/application/view_components/panel/index.tsx +++ b/src/plugins/discover/public/application/view_components/panel/index.tsx @@ -16,7 +16,7 @@ import { import { DiscoverSidebar } from '../../components/sidebar'; import { useDiscoverContext } from '../context'; import { ResultStatus, SearchData } from '../utils/use_search'; -import { IndexPatternField, opensearchFilters } from '../../../../../data/public'; +import { IndexPatternField, UI_SETTINGS, opensearchFilters } from '../../../../../data/public'; import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; import { DiscoverViewServices } from '../../../build_services'; import { popularizeField } from '../../helpers/popularize_field'; @@ -108,6 +108,10 @@ export default function DiscoverPanel(props: ViewProps) { await indexPatterns.refreshFields(dataSet!, true); }, [fetchState, indexPattern, indexPatterns]); + const isEnhancementsEnabledOverride = services.uiSettings.get( + UI_SETTINGS.QUERY_ENHANCEMENTS_ENABLED + ); + return ( <DiscoverSidebar columns={columns || []} @@ -146,6 +150,7 @@ export default function DiscoverPanel(props: ViewProps) { onCreateIndexPattern={onCreateIndexPattern} onNormalize={onNormalize} onAddFilter={onAddFilter} + isEnhancementsEnabledOverride={isEnhancementsEnabledOverride} /> ); }