From 0f4ec5f8d64e6810e911853c942e256a915432f7 Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 4 Feb 2020 13:03:13 +0200 Subject: [PATCH] Split useSavedQuery and restore capability of changing saved query in URL --- .../ui/search_bar/create_search_bar.tsx | 173 ++++-------------- .../ui/search_bar/lib/clear_saved_query.ts | 33 ++++ .../lib/populate_state_from_saved_query.ts | 49 +++++ .../ui/search_bar/lib/use_saved_query.ts | 89 +++++++++ 4 files changed, 209 insertions(+), 135 deletions(-) create mode 100644 src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts create mode 100644 src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts create mode 100644 src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts 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 8ba3c705d9ea3..6290d86fc048e 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 @@ -18,7 +18,6 @@ */ import React, { useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../kibana_react/public'; @@ -27,6 +26,7 @@ import { QueryStart } from '../../query'; import { SearchBarOwnProps, SearchBar } from './search_bar'; import { useFilterManager } from './lib/use_filter_manager'; import { useTimefilter } from './lib/use_timefilter'; +import { useSavedQuery } from './lib/use_saved_query'; interface StatefulSearchBarDeps { core: CoreStart; @@ -41,12 +41,14 @@ export type StatefulSearchBarProps = SearchBarOwnProps & { onSavedQueryIdChange?: (savedQueryId?: string) => void; }; +// Respond to user changing the filters const defaultFiltersUpdated = (queryService: QueryStart) => { return (filters: esFilters.Filter[]) => { queryService.filterManager.setFilters(filters); }; }; +// Respond to user changing the refresh settings const defaultOnRefreshChange = (queryService: QueryStart) => { const { timefilter } = queryService.timefilter; return (options: { isPaused: boolean; refreshInterval: number }) => { @@ -57,6 +59,7 @@ const defaultOnRefreshChange = (queryService: QueryStart) => { }; }; +// Respond to user changing the query string or time settings const defaultOnQuerySubmit = ( props: StatefulSearchBarProps, queryService: QueryStart, @@ -75,7 +78,7 @@ const defaultOnQuerySubmit = ( timefilter.setTime(payload.dateRange); setQueryStringState(payload.query); } else { - // If no change was detected, fire onQuerySubmit with current state + // Refresh button triggered for an update if (props.onQuerySubmit) props.onQuerySubmit( { @@ -88,91 +91,20 @@ const defaultOnQuerySubmit = ( }; }; -const defaultOnClearSavedQuery = ( - props: StatefulSearchBarProps, - uiSettings: CoreStart['uiSettings'], - queryService: QueryStart, - setQueryStringState: Function, - setSavedQueryState: Function -) => { +// Respond to user clearing a saved query +const defaultOnClearSavedQuery = (props: StatefulSearchBarProps, setSavedQuery: Function) => { if (!props.useDefaultBehaviors) return props.onClearSavedQuery; return () => { - queryService.filterManager.removeAll(); - setQueryStringState({ - query: '', - language: uiSettings.get('search:queryLanguage'), - }); - setSavedQueryState(undefined); + setSavedQuery(undefined); if (props.onSavedQueryIdChange) props.onSavedQueryIdChange(); }; }; -const populateStateFromSavedQuery = ( - props: StatefulSearchBarProps, - queryService: QueryStart, - savedQuery: SavedQuery, - setQueryStringState: Function, - setSavedQueryState: Function -) => { - const { timefilter } = queryService.timefilter; - // timefilter - if (savedQuery.attributes.timefilter) { - timefilter.setTime({ - from: savedQuery.attributes.timefilter.from, - to: savedQuery.attributes.timefilter.to, - }); - if (savedQuery.attributes.timefilter.refreshInterval) { - timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); - } - } - - // query string - setQueryStringState(savedQuery.attributes.query); - if (props.onQuerySubmit) - props.onQuerySubmit({ dateRange: timefilter.getTime(), query: savedQuery.attributes.query }); - - // filters - const savedQueryFilters = savedQuery.attributes.filters || []; - const globalFilters = queryService.filterManager.getGlobalFilters(); - queryService.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); - - setSavedQueryState(savedQuery); -}; - -const defaultOnSavedQueryUpdated = ( - props: StatefulSearchBarProps, - queryService: QueryStart, - setQueryStringState: Function, - setSavedQueryState: Function -) => { +// Respond to user saving or updating a saved query +const defaultOnSavedQueryUpdated = (props: StatefulSearchBarProps, setSavedQuery: Function) => { if (!props.useDefaultBehaviors) return props.onSavedQueryUpdated; return (savedQuery: SavedQuery) => { - populateStateFromSavedQuery( - props, - queryService, - savedQuery, - setQueryStringState, - setSavedQueryState - ); - if (props.onSavedQueryIdChange) props.onSavedQueryIdChange(savedQuery.id); - }; -}; - -const defaultOnQuerySaved = ( - props: StatefulSearchBarProps, - queryService: QueryStart, - setQueryStringState: Function, - setSavedQueryState: Function -) => { - if (!props.useDefaultBehaviors) return props.onSaved; - return (savedQuery: SavedQuery) => { - populateStateFromSavedQuery( - props, - queryService, - savedQuery, - setQueryStringState, - setSavedQueryState - ); + setSavedQuery(savedQuery); if (props.onSavedQueryIdChange) props.onSavedQueryIdChange(savedQuery.id); }; }; @@ -185,14 +117,6 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) // App name should come from the core application service. // Until it's available, we'll ask the user to provide it for the pre-wired component. return (props: StatefulSearchBarProps) => { - const { appName, savedQueryId, onQuerySubmit } = props; - const { filterManager, timefilter, savedQueries } = data.query; - - // handle service state updates. - // i.e. filters being added from a visualization directly to filterManager. - const { filters } = useFilterManager({ filterManager }); - const { timeRange, refreshInterval } = useTimefilter({ timefilter: timefilter.timefilter }); - // Handle queries const [query, setQuery] = useState( props.query || { @@ -201,52 +125,42 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) } ); + // handle service state updates. + // i.e. filters being added from a visualization directly to filterManager. + const { filters } = useFilterManager({ filterManager: data.query.filterManager }); + const { timeRange, refreshInterval } = useTimefilter({ + timefilter: data.query.timefilter.timefilter, + }); + + // Fetch and update UI from saved query + const [savedQuery, setSavedQuery] = useSavedQuery( + { + queryService: data.query, + setQuery, + savedQueryId: props.savedQueryId, + notifications: core.notifications, + uiSettings: core.uiSettings, + }, + [props.savedQueryId] + ); + // Fire onQuerySubmit on query or timerange change useEffect(() => { if (!props.useDefaultBehaviors) return; - if (onQuerySubmit) - onQuerySubmit( + if (props.onQuerySubmit) + props.onQuerySubmit( { dateRange: timeRange, query, }, true ); - }, [onQuerySubmit, props.useDefaultBehaviors, query, timeRange]); - - // Handle saved queries - const [savedQuery, setSavedQuery] = useState(); - useEffect(() => { - const fetchSavedQuery = async () => { - if (savedQueryId) { - try { - const newSavedQuery = await savedQueries.getSavedQuery(savedQueryId); - // Make sure we set the saved query to the most recent one - if (newSavedQuery && newSavedQuery.id === savedQueryId) { - setSavedQuery(newSavedQuery); - } - } catch (error) { - // Clear saved query - setSavedQuery(undefined); - core.notifications.toasts.addWarning({ - title: i18n.translate('data.search.unableToGetSavedQueryToastTitle', { - defaultMessage: 'Unable to load saved query {savedQueryId}', - values: { savedQueryId }, - }), - text: `${error.message}`, - }); - } - } else { - setSavedQuery(undefined); - } - }; - fetchSavedQuery(); - }, [savedQueryId, savedQueries]); + }, [props, props.onQuerySubmit, props.useDefaultBehaviors, query, timeRange]); return ( diff --git a/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts new file mode 100644 index 0000000000000..a1443b753cd93 --- /dev/null +++ b/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts @@ -0,0 +1,33 @@ +/* + * 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 { CoreStart } from 'kibana/public'; +import { QueryStart } from '../../../query'; + +export const clearStateFromSavedQuery = ( + queryService: QueryStart, + setQueryStringState: Function, + uiSettings: CoreStart['uiSettings'] +) => { + queryService.filterManager.removeAll(); + setQueryStringState({ + query: '', + language: uiSettings.get('search:queryLanguage'), + }); +}; diff --git a/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts new file mode 100644 index 0000000000000..7b2a73039e938 --- /dev/null +++ b/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts @@ -0,0 +1,49 @@ +/* + * 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 { QueryStart, SavedQuery } from '../../..'; + +export const populateStateFromSavedQuery = ( + queryService: QueryStart, + savedQuery: SavedQuery, + setQueryStringState: Function +) => { + const { + timefilter: { timefilter }, + filterManager, + } = queryService; + // timefilter + if (savedQuery.attributes.timefilter) { + timefilter.setTime({ + from: savedQuery.attributes.timefilter.from, + to: savedQuery.attributes.timefilter.to, + }); + if (savedQuery.attributes.timefilter.refreshInterval) { + timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); + } + } + + // query string + setQueryStringState(savedQuery.attributes.query); + + // filters + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = filterManager.getGlobalFilters(); + filterManager.setFilters([...globalFilters, ...savedQueryFilters]); +}; diff --git a/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts new file mode 100644 index 0000000000000..32b8db803b4d4 --- /dev/null +++ b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts @@ -0,0 +1,89 @@ +/* + * 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 { useState, useMemo, useEffect, DependencyList, SetStateAction, Dispatch } from 'react'; +import { i18n } from '@kbn/i18n'; +import { CoreStart } from 'kibana/public'; +import { SavedQuery } from '../../../query'; +import { DataPublicPluginStart } from '../../..'; +import { populateStateFromSavedQuery } from './populate_state_from_saved_query'; +import { clearStateFromSavedQuery } from './clear_saved_query'; + +interface UseSavedQueriesProps { + queryService: DataPublicPluginStart['query']; + setQuery: Function; + notifications: CoreStart['notifications']; + uiSettings: CoreStart['uiSettings']; + savedQueryId?: string; +} + +type UseSavedQueriesRet = [ + SavedQuery | undefined, + Dispatch> +]; + +export const useSavedQuery = ( + props: UseSavedQueriesProps, + deps: DependencyList +): UseSavedQueriesRet => { + // Handle saved queries + const [savedQuery, setSavedQuery] = useState(); + useMemo(() => { + const fetchSavedQuery = async () => { + if (props.savedQueryId) { + try { + // fetch saved query + const newSavedQuery = await props.queryService.savedQueries.getSavedQuery( + props.savedQueryId + ); + // Make sure we set the saved query to the most recent one + if (newSavedQuery && newSavedQuery.id === props.savedQueryId) { + setSavedQuery(newSavedQuery); + } + } catch (error) { + // Clear saved query + setSavedQuery(undefined); + // notify of saving error + props.notifications.toasts.addWarning({ + title: i18n.translate('data.search.unableToGetSavedQueryToastTitle', { + defaultMessage: 'Unable to load saved query {savedQueryId}', + values: { savedQueryId: props.savedQueryId }, + }), + text: `${error.message}`, + }); + } + } else { + // Clear saved query + setSavedQuery(undefined); + } + }; + + fetchSavedQuery(); + }, [props.notifications.toasts, props.queryService.savedQueries, props.savedQueryId]); + + useEffect(() => { + if (savedQuery) { + populateStateFromSavedQuery(props.queryService, savedQuery, props.setQuery); + } else { + clearStateFromSavedQuery(props.queryService, props.setQuery, props.uiSettings); + } + }, [savedQuery, props.queryService, props.setQuery, props.uiSettings]); + + return [savedQuery, setSavedQuery]; +};