From da0a99dbbcc11894fcd55e51159905b61c7f31d5 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 15 Jun 2020 13:54:03 +0200 Subject: [PATCH 1/6] show tour popover when Lens has no data on initial render --- .../query_string_input/query_bar_top_row.tsx | 43 ++++++----- .../ui/search_bar/create_search_bar.tsx | 1 + .../data/public/ui/search_bar/search_bar.tsx | 3 + x-pack/plugins/lens/public/app_plugin/app.tsx | 5 ++ .../public/app_plugin/no_data_popover.tsx | 72 +++++++++++++++++++ .../editor_frame/data_panel_wrapper.tsx | 4 +- .../editor_frame/editor_frame.tsx | 2 + .../public/editor_frame_service/service.tsx | 6 +- .../indexpattern_datasource/datapanel.tsx | 6 +- .../public/indexpattern_datasource/loader.ts | 18 +++++ .../public/indexpattern_datasource/types.ts | 1 + x-pack/plugins/lens/public/types.ts | 2 + 12 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index f65bf97e391e2..c3beabbd7e72e 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -30,6 +30,8 @@ import { EuiSuperDatePicker, EuiFieldText, prettyDuration, + EuiTourStep, + EuiTourStepProps, } from '@elastic/eui'; // @ts-ignore import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; @@ -63,6 +65,7 @@ interface Props { customSubmitButton?: any; isDirty: boolean; timeHistory?: TimeHistoryContract; + datePickerTourComponentProps?: Omit; } export function QueryBarTopRow(props: Props) { @@ -264,23 +267,31 @@ export function QueryBarTopRow(props: Props) { }; }); + let datePickerElement = ( + + ); + + if (props.datePickerTourComponentProps) { + datePickerElement = ( + {datePickerElement} + ); + } + return ( - - - + {datePickerElement} ); } diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 81e84e3198072..aa0a6aa3c4325 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -198,6 +198,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) showSaveQuery={props.showSaveQuery} screenTitle={props.screenTitle} indexPatterns={props.indexPatterns} + datePickerTourComponentProps={props.datePickerTourComponentProps} timeHistory={data.query.timefilter.history} dateRangeFrom={timeRange.from} dateRangeTo={timeRange.to} diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index a5ac227559115..66003527afd7e 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -23,6 +23,7 @@ import classNames from 'classnames'; import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; +import { EuiTourStepProps } from '@elastic/eui'; import { withKibana, KibanaReactContextValue } from '../../../../kibana_react/public'; @@ -75,6 +76,7 @@ export interface SearchBarOwnProps { onClearSavedQuery?: () => void; onRefresh?: (payload: { dateRange: TimeRange }) => void; + datePickerTourComponentProps?: Omit; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -402,6 +404,7 @@ class SearchBarUI extends Component { this.props.customSubmitButton ? this.props.customSubmitButton : undefined } dataTestSubj={this.props.dataTestSubj} + datePickerTourComponentProps={this.props.datePickerTourComponentProps} /> ); } diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index fc8d5dd9eb395..2ae26fb20f753 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -38,6 +38,7 @@ import { SavedQuery, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; +import { useNoDataPopover } from './no_data_popover'; interface State { isLoading: boolean; @@ -91,6 +92,8 @@ export function App({ storage.get('kibana.userQueryLanguage') || core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE); + const [noDataPopoverProps, showNoDataPopover] = useNoDataPopover(storage); + const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); return { @@ -465,6 +468,7 @@ export function App({ query={state.query} dateRangeFrom={state.dateRange.fromDate} dateRangeTo={state.dateRange.toDate} + datePickerTourComponentProps={noDataPopoverProps} /> @@ -479,6 +483,7 @@ export function App({ savedQuery: state.savedQuery, doc: state.persistedDoc, onError, + showNoDataPopover, onChange: ({ filterableIndexPatterns, doc }) => { if (!_.isEqual(state.persistedDoc, doc)) { setState((s) => ({ ...s, lastKnownDoc: doc })); diff --git a/x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx b/x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx new file mode 100644 index 0000000000000..a985c1f8df78a --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback, useState } from 'react'; +import React from 'react'; +import { EuiButtonEmpty, EuiText, EuiTourStepProps } from '@elastic/eui'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { i18n } from '@kbn/i18n'; + +const NO_DATA_POPOVER_STORAGE_KEY = 'xpack.lens.noDataPopover'; + +export function useNoDataPopover( + storage: IStorageWrapper +): [Omit | undefined, () => void] { + const [noDataPopoverDismissed, setNoDataPopoverDismissed] = useState(() => + Boolean(storage.get(NO_DATA_POPOVER_STORAGE_KEY)) + ); + const [noDataPopoverVisible, setNoDataPopoverVisible] = useState(false); + + const showNoDataPopover = useCallback(() => { + if (!noDataPopoverDismissed) { + setNoDataPopoverVisible(true); + } + }, []); + + const noDataPopoverVisibleProps: + | Omit + | undefined = noDataPopoverVisible + ? { + onFinish: () => {}, + closePopover: () => { + setNoDataPopoverVisible(false); + }, + content: ( + +

+ {i18n.translate('lens.noDataPopover.content', { + defaultMessage: + "This time range doesn't contain any data for this index pattern. Increase or adjust the time range to see more fields and create charts", + })} +

+
+ ), + minWidth: 300, + anchorPosition: 'downCenter', + step: 1, + stepsTotal: 1, + isStepOpen: true, + subtitle: i18n.translate('lens.noDataPopover.title', { defaultMessage: 'Lens tip' }), + title: '', + footerAction: ( + { + storage.set(NO_DATA_POPOVER_STORAGE_KEY, true); + setNoDataPopoverDismissed(true); + setNoDataPopoverVisible(false); + }} + > + {i18n.translate('lens.noDataPopover.dismissAction', { + defaultMessage: "Don't show again", + })} + + ), + } + : undefined; + + return [noDataPopoverVisibleProps, showNoDataPopover]; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index afb2719f28e89..20a837abe1fc5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, memo, useContext, useState } from 'react'; +import React, { useMemo, memo, useContext, useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; import { NativeRenderer } from '../../native_renderer'; @@ -19,6 +19,7 @@ interface DataPanelWrapperProps { activeDatasource: string | null; datasourceIsLoading: boolean; dispatch: (action: Action) => void; + showNoDataPopover: () => void; core: DatasourceDataPanelProps['core']; query: Query; dateRange: FramePublicAPI['dateRange']; @@ -46,6 +47,7 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { query: props.query, dateRange: props.dateRange, filters: props.filters, + showNoDataPopover: props.showNoDataPopover, }; const [showDatasourceSwitcher, setDatasourceSwitcher] = useState(false); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 07c76a81ed62d..e679823d62eed 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -49,6 +49,7 @@ export interface EditorFrameProps { filterableIndexPatterns: DatasourceMetaData['filterableIndexPatterns']; doc: Document; }) => void; + showNoDataPopover: () => void; } export function EditorFrame(props: EditorFrameProps) { @@ -256,6 +257,7 @@ export function EditorFrame(props: EditorFrameProps) { query={props.query} dateRange={props.dateRange} filters={props.filters} + showNoDataPopover={props.showNoDataPopover} /> } configPanel={ diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index f57acf3bef62d..47339373b6d1a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -102,7 +102,10 @@ export class EditorFrameService { ]); return { - mount: (element, { doc, onError, dateRange, query, filters, savedQuery, onChange }) => { + mount: ( + element, + { doc, onError, dateRange, query, filters, savedQuery, onChange, showNoDataPopover } + ) => { domElement = element; const firstDatasourceId = Object.keys(resolvedDatasources)[0]; const firstVisualizationId = Object.keys(resolvedVisualizations)[0]; @@ -127,6 +130,7 @@ export class EditorFrameService { filters={filters} savedQuery={savedQuery} onChange={onChange} + showNoDataPopover={showNoDataPopover} /> , domElement diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index ae5632ddae84e..a20e252aa8f6c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -85,6 +85,7 @@ export function IndexPatternDataPanel({ filters, dateRange, changeIndexPattern, + showNoDataPopover, }: Props) { const { indexPatternRefs, indexPatterns, currentIndexPatternId } = state; @@ -131,6 +132,9 @@ export function IndexPatternDataPanel({ syncExistingFields({ dateRange, setState, + isFirstExistenceFetch: state.isFirstExistenceFetch, + currentIndexPatternTitle: indexPatterns[currentIndexPatternId].title, + showNoDataPopover, indexPatterns: indexPatternList, fetchJson: core.http.post, dslQuery, @@ -211,7 +215,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ core, data, existingFields, -}: Pick> & { +}: Omit & { data: DataPublicPluginStart; currentIndexPatternId: string; indexPatternRefs: IndexPatternRef[]; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index c34f4c1d23148..6277a45d88bcf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -101,6 +101,7 @@ export async function loadInitialState({ indexPatterns, showEmptyFields: false, existingFields: {}, + isFirstExistenceFetch: true, }; } @@ -111,6 +112,7 @@ export async function loadInitialState({ layers: {}, showEmptyFields: false, existingFields: {}, + isFirstExistenceFetch: true, }; } @@ -215,13 +217,19 @@ export async function syncExistingFields({ dateRange, fetchJson, setState, + isFirstExistenceFetch, + currentIndexPatternTitle, dslQuery, + showNoDataPopover, }: { dateRange: DateRange; indexPatterns: Array<{ id: string; timeFieldName?: string | null }>; fetchJson: HttpSetup['post']; setState: SetState; + isFirstExistenceFetch: boolean; + currentIndexPatternTitle: string; dslQuery: object; + showNoDataPopover: () => void; }) { const emptinessInfo = await Promise.all( indexPatterns.map((pattern) => { @@ -241,8 +249,18 @@ export async function syncExistingFields({ }) ); + if (isFirstExistenceFetch) { + const fieldsCurrentIndexPattern = emptinessInfo.find( + (info) => info.indexPatternTitle === currentIndexPatternTitle + ); + if (fieldsCurrentIndexPattern && fieldsCurrentIndexPattern.existingFieldNames.length === 0) { + showNoDataPopover(); + } + } + setState((state) => ({ ...state, + isFirstExistenceFetch: false, existingFields: emptinessInfo.reduce((acc, info) => { acc[info.indexPatternTitle] = booleanMap(info.existingFieldNames); return acc; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 563af40ed2720..25124c4cdd9f1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -52,6 +52,7 @@ export type IndexPatternPrivateState = IndexPatternPersistedState & { */ existingFields: Record>; showEmptyFields: boolean; + isFirstExistenceFetch: boolean; }; export interface IndexPatternRef { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index c2437aa3cc3cc..535eabef861eb 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -47,6 +47,7 @@ export interface EditorFrameProps { filterableIndexPatterns: DatasourceMetaData['filterableIndexPatterns']; doc: Document; }) => void; + showNoDataPopover: () => void; } export interface EditorFrameInstance { mount: (element: Element, props: EditorFrameProps) => void; @@ -186,6 +187,7 @@ export interface DatasourceDataPanelProps { state: T; dragDropContext: DragContextState; setState: StateSetter; + showNoDataPopover: () => void; core: Pick; query: Query; dateRange: DateRange; From eb163b1241e1bbdbbfbebfdac09a84b90c7c5739 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 17 Jun 2020 15:42:34 +0200 Subject: [PATCH 2/6] move popover into data plugin and write tests --- .../no_data_popover.test.tsx | 94 +++++++++++++++++++ .../ui/query_string_input/no_data_popover.tsx | 91 ++++++++++++++++++ .../query_string_input/query_bar_top_row.tsx | 47 ++++------ .../ui/search_bar/create_search_bar.tsx | 2 +- .../data/public/ui/search_bar/search_bar.tsx | 5 +- .../lens/public/app_plugin/app.test.tsx | 1 + x-pack/plugins/lens/public/app_plugin/app.tsx | 24 ++++- .../public/app_plugin/no_data_popover.tsx | 72 -------------- .../editor_frame/data_panel_wrapper.tsx | 2 +- .../editor_frame/editor_frame.test.tsx | 1 + .../editor_frame/state_management.test.ts | 1 + .../editor_frame_service/service.test.tsx | 2 + .../__mocks__/loader.ts | 1 + .../datapanel.test.tsx | 7 +- .../indexpattern.test.ts | 5 + .../indexpattern_suggestions.test.tsx | 8 ++ .../indexpattern_datasource/loader.test.ts | 42 +++++++++ .../state_helpers.test.ts | 8 ++ 18 files changed, 304 insertions(+), 109 deletions(-) create mode 100644 src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx create mode 100644 src/plugins/data/public/ui/query_string_input/no_data_popover.tsx delete mode 100644 x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx new file mode 100644 index 0000000000000..01d27e2175fe1 --- /dev/null +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; +import { NoDataPopover } from './no_data_popover'; +import { EuiTourStep } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; + +describe('NoDataPopover', () => { + const createMockStorage = () => ({ + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), + }); + + it('should render children if showNoDataPopover is set to false', () => { + const child = ; + const instance = mount( + + {child} + + ); + expect(instance.childAt(0).getElement()).toStrictEqual(child); + }); + + it('should render children if showNoDataPopover is set to true, but local storage flag is set', () => { + const child = ; + const storage = createMockStorage(); + storage.get.mockReturnValue(true); + const instance = mount( + + {child} + + ); + expect(instance.childAt(0).getElement()).toStrictEqual(child); + }); + + it('should render popover if showNoDataPopover is set to true and local storage flag is not set', () => { + const child = ; + const instance = mount( + + {child} + + ); + expect(instance.find(EuiTourStep)).toHaveLength(1); + }); + + it('should hide popover if it is closed', async () => { + const props = { + children: , + showNoDataPopover: true, + storage: createMockStorage(), + }; + const instance = mount(); + act(() => { + instance.find(EuiTourStep).prop('closePopover')!(); + }); + instance.setProps({ ...props }); + expect(instance.find(EuiTourStep)).toHaveLength(0); + }); + + it('should set local storage flag and hide on closing with button', () => { + const props = { + children: , + showNoDataPopover: true, + storage: createMockStorage(), + }; + const instance = mount(); + act(() => { + instance.find(EuiTourStep).prop('footerAction')!.props.onClick(); + }); + instance.setProps({ ...props }); + expect(props.storage.set).toHaveBeenCalledWith(expect.any(String), true); + expect(instance.find(EuiTourStep)).toHaveLength(0); + }); +}); diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx new file mode 100644 index 0000000000000..ddbb19287102f --- /dev/null +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ReactElement, useEffect, useState } from 'react'; +import React from 'react'; +import { EuiButtonEmpty, EuiText, EuiTourStep } from '@elastic/eui'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { i18n } from '@kbn/i18n'; + +const NO_DATA_POPOVER_STORAGE_KEY = 'data.noDataPopover'; + +export function NoDataPopover({ + showNoDataPopover, + storage, + children, +}: { + showNoDataPopover?: boolean; + storage: IStorageWrapper; + children: ReactElement; +}) { + const [noDataPopoverDismissed, setNoDataPopoverDismissed] = useState(() => + Boolean(storage.get(NO_DATA_POPOVER_STORAGE_KEY)) + ); + const [noDataPopoverVisible, setNoDataPopoverVisible] = useState(false); + + useEffect(() => { + if (showNoDataPopover && !noDataPopoverDismissed) { + setNoDataPopoverVisible(true); + } + }, [noDataPopoverDismissed, showNoDataPopover]); + + return noDataPopoverVisible ? ( + {}} + closePopover={() => { + setNoDataPopoverVisible(false); + }} + content={ + +

+ {i18n.translate('data.noDataPopover.content', { + defaultMessage: + "This time range doesn't contain any data for this index pattern. Increase or adjust the time range to see more fields and create charts", + })} +

+
+ } + minWidth={300} + anchorPosition="downCenter" + step={1} + stepsTotal={1} + isStepOpen={true} + subtitle={i18n.translate('data.noDataPopover.title', { defaultMessage: 'Tip' })} + title="" + footerAction={ + { + storage.set(NO_DATA_POPOVER_STORAGE_KEY, true); + setNoDataPopoverDismissed(true); + setNoDataPopoverVisible(false); + }} + > + {i18n.translate('data.noDataPopover.dismissAction', { + defaultMessage: "Don't show again", + })} + + } + > + {children} +
+ ) : ( + children + ); +} diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index c3beabbd7e72e..58ca74648d8fd 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -30,8 +30,6 @@ import { EuiSuperDatePicker, EuiFieldText, prettyDuration, - EuiTourStep, - EuiTourStepProps, } from '@elastic/eui'; // @ts-ignore import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; @@ -42,6 +40,7 @@ import { useKibana, toMountPoint } from '../../../../kibana_react/public'; import { QueryStringInput } from './query_string_input'; import { doesKueryExpressionHaveLuceneSyntaxError, UI_SETTINGS } from '../../../common'; import { PersistedLog, getQueryLog } from '../../query'; +import { NoDataPopover } from './no_data_popover'; interface Props { query?: Query; @@ -65,7 +64,7 @@ interface Props { customSubmitButton?: any; isDirty: boolean; timeHistory?: TimeHistoryContract; - datePickerTourComponentProps?: Omit; + indicateNoData?: boolean; } export function QueryBarTopRow(props: Props) { @@ -267,31 +266,25 @@ export function QueryBarTopRow(props: Props) { }; }); - let datePickerElement = ( - - ); - - if (props.datePickerTourComponentProps) { - datePickerElement = ( - {datePickerElement} - ); - } - return ( - {datePickerElement} + + + + + ); } diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index aa0a6aa3c4325..a0df7604f23aa 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -198,7 +198,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) showSaveQuery={props.showSaveQuery} screenTitle={props.screenTitle} indexPatterns={props.indexPatterns} - datePickerTourComponentProps={props.datePickerTourComponentProps} + indicateNoData={props.indicateNoData} timeHistory={data.query.timefilter.history} dateRangeFrom={timeRange.from} dateRangeTo={timeRange.to} diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 66003527afd7e..2f740cc476087 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -23,7 +23,6 @@ import classNames from 'classnames'; import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; -import { EuiTourStepProps } from '@elastic/eui'; import { withKibana, KibanaReactContextValue } from '../../../../kibana_react/public'; @@ -76,7 +75,7 @@ export interface SearchBarOwnProps { onClearSavedQuery?: () => void; onRefresh?: (payload: { dateRange: TimeRange }) => void; - datePickerTourComponentProps?: Omit; + indicateNoData?: boolean; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -404,7 +403,7 @@ class SearchBarUI extends Component { this.props.customSubmitButton ? this.props.customSubmitButton : undefined } dataTestSubj={this.props.dataTestSubj} - datePickerTourComponentProps={this.props.datePickerTourComponentProps} + indicateNoData={this.props.indicateNoData} /> ); } diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 53498a8e5afa1..c712ad4a577c9 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -243,6 +243,7 @@ describe('Lens App', () => { "query": "", }, "savedQuery": undefined, + "showNoDataPopover": [Function], }, ], ] diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 2ae26fb20f753..ed1e77db9288e 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -38,9 +38,9 @@ import { SavedQuery, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; -import { useNoDataPopover } from './no_data_popover'; interface State { + indicateNoData: boolean; isLoading: boolean; isSaveModalVisible: boolean; indexPatternsForTopNav: IndexPatternInstance[]; @@ -92,8 +92,6 @@ export function App({ storage.get('kibana.userQueryLanguage') || core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE); - const [noDataPopoverProps, showNoDataPopover] = useNoDataPopover(storage); - const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); return { @@ -107,9 +105,27 @@ export function App({ toDate: currentRange.to, }, filters: [], + indicateNoData: false, }; }); + const showNoDataPopover = useCallback(() => { + setState((prevState) => ({ ...prevState, indicateNoData: true })); + }, [setState]); + + useEffect(() => { + if (state.indicateNoData) { + setState((prevState) => ({ ...prevState, indicateNoData: false })); + } + }, [ + setState, + state.indicateNoData, + state.query, + state.filters, + state.dateRange, + state.indexPatternsForTopNav, + ]); + const { lastKnownDoc } = state; const isSaveable = @@ -468,7 +484,7 @@ export function App({ query={state.query} dateRangeFrom={state.dateRange.fromDate} dateRangeTo={state.dateRange.toDate} - datePickerTourComponentProps={noDataPopoverProps} + indicateNoData={state.indicateNoData} /> diff --git a/x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx b/x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx deleted file mode 100644 index a985c1f8df78a..0000000000000 --- a/x-pack/plugins/lens/public/app_plugin/no_data_popover.tsx +++ /dev/null @@ -1,72 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useCallback, useState } from 'react'; -import React from 'react'; -import { EuiButtonEmpty, EuiText, EuiTourStepProps } from '@elastic/eui'; -import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; -import { i18n } from '@kbn/i18n'; - -const NO_DATA_POPOVER_STORAGE_KEY = 'xpack.lens.noDataPopover'; - -export function useNoDataPopover( - storage: IStorageWrapper -): [Omit | undefined, () => void] { - const [noDataPopoverDismissed, setNoDataPopoverDismissed] = useState(() => - Boolean(storage.get(NO_DATA_POPOVER_STORAGE_KEY)) - ); - const [noDataPopoverVisible, setNoDataPopoverVisible] = useState(false); - - const showNoDataPopover = useCallback(() => { - if (!noDataPopoverDismissed) { - setNoDataPopoverVisible(true); - } - }, []); - - const noDataPopoverVisibleProps: - | Omit - | undefined = noDataPopoverVisible - ? { - onFinish: () => {}, - closePopover: () => { - setNoDataPopoverVisible(false); - }, - content: ( - -

- {i18n.translate('lens.noDataPopover.content', { - defaultMessage: - "This time range doesn't contain any data for this index pattern. Increase or adjust the time range to see more fields and create charts", - })} -

-
- ), - minWidth: 300, - anchorPosition: 'downCenter', - step: 1, - stepsTotal: 1, - isStepOpen: true, - subtitle: i18n.translate('lens.noDataPopover.title', { defaultMessage: 'Lens tip' }), - title: '', - footerAction: ( - { - storage.set(NO_DATA_POPOVER_STORAGE_KEY, true); - setNoDataPopoverDismissed(true); - setNoDataPopoverVisible(false); - }} - > - {i18n.translate('lens.noDataPopover.dismissAction', { - defaultMessage: "Don't show again", - })} - - ), - } - : undefined; - - return [noDataPopoverVisibleProps, showNoDataPopover]; -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index 20a837abe1fc5..0f74abe97c418 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, memo, useContext, useState, useCallback } from 'react'; +import React, { useMemo, memo, useContext, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; import { NativeRenderer } from '../../native_renderer'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index ff9e24f95d1e2..ad4f6e74c9e92 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -56,6 +56,7 @@ function getDefaultProps() { data: dataPluginMock.createStartContract(), expressions: expressionsPluginMock.createStartContract(), }, + showNoDataPopover: jest.fn(), }; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts index e1151b92aac51..969467b5789ec 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts @@ -35,6 +35,7 @@ describe('editor_frame state management', () => { dateRange: { fromDate: 'now-7d', toDate: 'now' }, query: { query: '', language: 'lucene' }, filters: [], + showNoDataPopover: jest.fn(), }; }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx index fbd65c5044d51..7b1d091c1c8fe 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx @@ -51,6 +51,7 @@ describe('editor_frame service', () => { dateRange: { fromDate: '', toDate: '' }, query: { query: '', language: 'lucene' }, filters: [], + showNoDataPopover: jest.fn(), }); instance.unmount(); })() @@ -70,6 +71,7 @@ describe('editor_frame service', () => { dateRange: { fromDate: '', toDate: '' }, query: { query: '', language: 'lucene' }, filters: [], + showNoDataPopover: jest.fn(), }); instance.unmount(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts index fe865edd62986..31d152caf0dbc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts @@ -20,6 +20,7 @@ export function loadInitialState() { }, layers: {}, showEmptyFields: false, + isFirstExistenceFetch: false, }; return result; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index 187ccb8c47563..21025b8089388 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -204,12 +204,15 @@ const initialState: IndexPatternPrivateState = { ], }, }, + isFirstExistenceFetch: false, }; const dslQuery = { bool: { must: [{ match_all: {} }], filter: [], should: [], must_not: [] } }; describe('IndexPattern Data Panel', () => { - let defaultProps: Parameters[0]; + let defaultProps: Parameters[0] & { + showNoDataPopover: () => void; + }; let core: ReturnType; beforeEach(() => { @@ -231,6 +234,7 @@ describe('IndexPattern Data Panel', () => { filters: [], showEmptyFields: false, onToggleEmptyFields: jest.fn(), + showNoDataPopover: jest.fn(), }; }); @@ -304,6 +308,7 @@ describe('IndexPattern Data Panel', () => { indexPatternRefs: [], existingFields: {}, showEmptyFields: false, + isFirstExistenceFetch: false, currentIndexPatternId: 'a', indexPatterns: { a: { id: 'a', title: 'aaa', timeFieldName: 'atime', fields: [] }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index d8449143b569f..7c0eeeb5c077e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -128,6 +128,7 @@ function stateFromPersistedState( indexPatternRefs: [], existingFields: {}, showEmptyFields: true, + isFirstExistenceFetch: false, }; } @@ -403,6 +404,7 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, }; expect(indexPatternDatasource.insertLayer(state, 'newLayer')).toEqual({ ...state, @@ -424,6 +426,7 @@ describe('IndexPattern Data Source', () => { indexPatternRefs: [], existingFields: {}, showEmptyFields: false, + isFirstExistenceFetch: false, indexPatterns: expectedIndexPatterns, layers: { first: { @@ -459,6 +462,7 @@ describe('IndexPattern Data Source', () => { indexPatternRefs: [], existingFields: {}, showEmptyFields: false, + isFirstExistenceFetch: false, indexPatterns: expectedIndexPatterns, layers: { first: { @@ -485,6 +489,7 @@ describe('IndexPattern Data Source', () => { indexPatternRefs: [], existingFields: {}, showEmptyFields: false, + isFirstExistenceFetch: false, indexPatterns: expectedIndexPatterns, layers: { first: { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 5eca55cbfcbda..e29f2a3086852 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -147,6 +147,7 @@ function testInitialState(): IndexPatternPrivateState { }, }, showEmptyFields: false, + isFirstExistenceFetch: false, }; } @@ -306,6 +307,7 @@ describe('IndexPattern Data Source suggestions', () => { existingFields: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, indexPatterns: { 1: { id: '1', @@ -511,6 +513,7 @@ describe('IndexPattern Data Source suggestions', () => { existingFields: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, indexPatterns: { 1: { id: '1', @@ -1050,6 +1053,7 @@ describe('IndexPattern Data Source suggestions', () => { expect( getDatasourceSuggestionsFromCurrentState({ showEmptyFields: false, + isFirstExistenceFetch: false, indexPatternRefs: [], existingFields: {}, indexPatterns: expectedIndexPatterns, @@ -1356,6 +1360,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }, showEmptyFields: true, + isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -1476,6 +1481,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }, showEmptyFields: true, + isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -1530,6 +1536,7 @@ describe('IndexPattern Data Source suggestions', () => { }, }, showEmptyFields: true, + isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, @@ -1561,6 +1568,7 @@ describe('IndexPattern Data Source suggestions', () => { currentIndexPatternId: '1', indexPatterns: expectedIndexPatterns, showEmptyFields: true, + isFirstExistenceFetch: false, layers: { first: { ...initialState.layers.first, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index b54ad3651471d..245c019bf1a6c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -366,6 +366,7 @@ describe('loader', () => { existingFields: {}, layers: {}, showEmptyFields: true, + isFirstExistenceFetch: false, }; await changeIndexPattern({ @@ -396,6 +397,7 @@ describe('loader', () => { indexPatterns: {}, layers: {}, showEmptyFields: true, + isFirstExistenceFetch: false, }; await changeIndexPattern({ @@ -450,6 +452,7 @@ describe('loader', () => { }, }, showEmptyFields: true, + isFirstExistenceFetch: false, }; await changeLayerIndexPattern({ @@ -513,6 +516,7 @@ describe('loader', () => { }, }, showEmptyFields: true, + isFirstExistenceFetch: false, }; await changeLayerIndexPattern({ @@ -563,6 +567,9 @@ describe('loader', () => { indexPatterns: [{ id: 'a' }, { id: 'b' }, { id: 'c' }], setState, dslQuery, + showNoDataPopover: jest.fn(), + currentIndexPatternTitle: 'abc', + isFirstExistenceFetch: false, }); expect(fetchJson).toHaveBeenCalledTimes(3); @@ -576,6 +583,7 @@ describe('loader', () => { expect(newState).toEqual({ foo: 'bar', + isFirstExistenceFetch: false, existingFields: { a: { a_field_1: true, a_field_2: true }, b: { b_field_1: true, b_field_2: true }, @@ -583,5 +591,39 @@ describe('loader', () => { }, }); }); + + it('should call showNoDataPopover callback if current index pattern returns no fields', async () => { + const setState = jest.fn(); + const showNoDataPopover = jest.fn(); + const fetchJson = jest.fn((path: string) => { + const indexPatternTitle = _.last(path.split('/')); + return { + indexPatternTitle, + existingFieldNames: + indexPatternTitle === 'a' + ? ['field_1', 'field_2'].map((fieldName) => `${indexPatternTitle}_${fieldName}`) + : [], + }; + }); + + const args = { + dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fetchJson: fetchJson as any, + indexPatterns: [{ id: 'a' }, { id: 'b' }, { id: 'c' }], + setState, + dslQuery, + showNoDataPopover: jest.fn(), + currentIndexPatternTitle: 'abc', + isFirstExistenceFetch: false, + }; + + await syncExistingFields(args); + + expect(showNoDataPopover).not.toHaveBeenCalled(); + + await syncExistingFields({ ...args, isFirstExistenceFetch: true }); + expect(showNoDataPopover).not.toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts index 074cb8f5bde17..16bde476e2036 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts @@ -43,6 +43,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -97,6 +98,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -148,6 +150,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -189,6 +192,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -223,6 +227,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -285,6 +290,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -338,6 +344,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', @@ -418,6 +425,7 @@ describe('state_helpers', () => { indexPatterns: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', From 16ccebc1f441f3365ed86fad85a8c288a1932b2d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 17 Jun 2020 15:45:20 +0200 Subject: [PATCH 3/6] fix types --- .../dimension_panel/dimension_panel.test.tsx | 2 ++ .../lens/public/indexpattern_datasource/layerpanel.test.tsx | 1 + .../operations/definitions/date_histogram.test.tsx | 1 + .../operations/definitions/terms.test.tsx | 1 + .../indexpattern_datasource/operations/operations.test.ts | 1 + 5 files changed, 6 insertions(+) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index ebf5abd4fbfe9..9ff19a55f0c99 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -80,6 +80,7 @@ describe('IndexPatternDimensionEditorPanel', () => { indexPatterns: expectedIndexPatterns, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, existingFields: { 'my-fake-index-pattern': { timestamp: true, @@ -1259,6 +1260,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { myLayer: { indexPatternId: 'foo', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index 0d16e2d054a77..b65a57e3f69a5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -23,6 +23,7 @@ const initialState: IndexPatternPrivateState = { existingFields: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index defc142d4976e..6d66240b9154e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -52,6 +52,7 @@ describe('date_histogram', () => { existingFields: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, indexPatterns: { 1: { id: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx index 89d02708a900c..1fb4a42e12d20 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx @@ -35,6 +35,7 @@ describe('terms', () => { existingFields: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, layers: { first: { indexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index e5d20839aae3d..a8e30d9fb1da5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -148,6 +148,7 @@ describe('getOperationTypesForField', () => { existingFields: {}, currentIndexPatternId: '1', showEmptyFields: false, + isFirstExistenceFetch: false, indexPatterns: expectedIndexPatterns, layers: { first: { From 3bb804421bc3654f8e973212541357bf51cc9073 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 18 Jun 2020 15:37:38 +0200 Subject: [PATCH 4/6] fix layouting problem and test --- ...na-plugin-plugins-data-public.searchbar.md | 4 +- src/plugins/data/public/public.api.md | 4 +- .../ui/query_string_input/no_data_popover.tsx | 1 + .../query_string_input/query_bar_top_row.tsx | 40 +++++++++---------- test/functional/page_objects/time_picker.ts | 7 ++++ .../test/functional/page_objects/lens_page.ts | 5 ++- 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index fc141b8c89c18..498691c06285d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "timeHistory" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 23213d4d1165a..951c0a371d4d4 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1710,8 +1710,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "timeHistory" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx index ddbb19287102f..c5ca1a6f4cd04 100644 --- a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx @@ -71,6 +71,7 @@ export function NoDataPopover({ footerAction={ { storage.set(NO_DATA_POPOVER_STORAGE_KEY, true); setNoDataPopoverDismissed(true); diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index 58ca74648d8fd..4b0dc579c39ce 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -232,10 +232,12 @@ export function QueryBarTopRow(props: Props) { } return ( - - {renderDatePicker()} - {button} - + + + {renderDatePicker()} + {button} + + ); } @@ -268,22 +270,20 @@ export function QueryBarTopRow(props: Props) { return ( - - - + ); } diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 20ae89fc1a8d0..7ef291c8c7005 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -52,6 +52,13 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo await this.setAbsoluteRange(this.defaultStartTime, this.defaultEndTime); } + async ensureHiddenNoDataPopover() { + const isVisible = await testSubjects.exists('noDataPopoverDismissButton'); + if (isVisible) { + await testSubjects.click('noDataPopoverDismissButton'); + } + } + /** * the provides a quicker way to set the timepicker to the default range, saves a few seconds */ diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 3f048a9ee2aaa..265126831bcf6 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -47,10 +47,11 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont * Move the date filter to the specified time range, defaults to * a range that has data in our dataset. */ - goToTimeRange(fromTime?: string, toTime?: string) { + async goToTimeRange(fromTime?: string, toTime?: string) { + await PageObjects.timePicker.ensureHiddenNoDataPopover(); fromTime = fromTime || PageObjects.timePicker.defaultStartTime; toTime = toTime || PageObjects.timePicker.defaultEndTime; - return PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); }, /** From 23a3615bbb3f3dcfdf594325e91d2e87408d9de4 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 18 Jun 2020 17:43:39 +0200 Subject: [PATCH 5/6] fix functional test --- x-pack/test/functional/apps/lens/persistent_context.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/lens/persistent_context.ts b/x-pack/test/functional/apps/lens/persistent_context.ts index 00d9208772798..b980116c581da 100644 --- a/x-pack/test/functional/apps/lens/persistent_context.ts +++ b/x-pack/test/functional/apps/lens/persistent_context.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'lens', 'header', 'timePicker']); const browser = getService('browser'); const filterBar = getService('filterBar'); const appsMenu = getService('appsMenu'); @@ -19,7 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should carry over time range and pinned filters to discover', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); - await PageObjects.timePicker.setAbsoluteRange( + await PageObjects.lens.goToTimeRange( 'Sep 06, 2015 @ 06:31:44.000', 'Sep 18, 2025 @ 06:31:44.000' ); @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should remember time range and pinned filters from discover', async () => { - await PageObjects.timePicker.setAbsoluteRange( + await PageObjects.lens.goToTimeRange( 'Sep 07, 2015 @ 06:31:44.000', 'Sep 19, 2025 @ 06:31:44.000' ); From 024d1ac822a0901516b4a913b3b3291e572726ba Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sun, 21 Jun 2020 22:15:28 +0200 Subject: [PATCH 6/6] hide popover when datepicker gets focussed --- .../no_data_popover.test.tsx | 19 ++++++++++--------- .../ui/query_string_input/no_data_popover.tsx | 14 +++++++++----- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx index 01d27e2175fe1..27f924d98e6eb 100644 --- a/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx @@ -31,17 +31,18 @@ describe('NoDataPopover', () => { clear: jest.fn(), }); - it('should render children if showNoDataPopover is set to false', () => { - const child = ; + it('should hide popover if showNoDataPopover is set to false', () => { + const Child = () => ; const instance = mount( - {child} + ); - expect(instance.childAt(0).getElement()).toStrictEqual(child); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); + expect(instance.find(EuiTourStep).find(Child)).toHaveLength(1); }); - it('should render children if showNoDataPopover is set to true, but local storage flag is set', () => { + it('should hide popover if showNoDataPopover is set to true, but local storage flag is set', () => { const child = ; const storage = createMockStorage(); storage.get.mockReturnValue(true); @@ -50,7 +51,7 @@ describe('NoDataPopover', () => { {child}
); - expect(instance.childAt(0).getElement()).toStrictEqual(child); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); }); it('should render popover if showNoDataPopover is set to true and local storage flag is not set', () => { @@ -60,7 +61,7 @@ describe('NoDataPopover', () => { {child}
); - expect(instance.find(EuiTourStep)).toHaveLength(1); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(true); }); it('should hide popover if it is closed', async () => { @@ -74,7 +75,7 @@ describe('NoDataPopover', () => { instance.find(EuiTourStep).prop('closePopover')!(); }); instance.setProps({ ...props }); - expect(instance.find(EuiTourStep)).toHaveLength(0); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); }); it('should set local storage flag and hide on closing with button', () => { @@ -89,6 +90,6 @@ describe('NoDataPopover', () => { }); instance.setProps({ ...props }); expect(props.storage.set).toHaveBeenCalledWith(expect.any(String), true); - expect(instance.find(EuiTourStep)).toHaveLength(0); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); }); }); diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx index c5ca1a6f4cd04..2180227161789 100644 --- a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx @@ -45,7 +45,7 @@ export function NoDataPopover({ } }, [noDataPopoverDismissed, showNoDataPopover]); - return noDataPopoverVisible ? ( + return ( {}} closePopover={() => { @@ -65,7 +65,7 @@ export function NoDataPopover({ anchorPosition="downCenter" step={1} stepsTotal={1} - isStepOpen={true} + isStepOpen={noDataPopoverVisible} subtitle={i18n.translate('data.noDataPopover.title', { defaultMessage: 'Tip' })} title="" footerAction={ @@ -84,9 +84,13 @@ export function NoDataPopover({ } > - {children} +
{ + setNoDataPopoverVisible(false); + }} + > + {children} +
- ) : ( - children ); }