From ea5b6daa1e207266669030204448e2e47e3e9e86 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 22 Jan 2019 12:54:16 -0700 Subject: [PATCH 01/20] Embed timepicker in query bar (#29130) * replace kbnTimepicker directive with EuiSuperDatePicker * remove kbnTimepicker directive * remove bootstrap datepicker * embed timepicker in query bar * flesh out date picker in query bar for maps app * wire up refresh config --- .../kibana/public/dashboard/dashboard_app.js | 2 +- .../public/discover/controllers/discover.js | 2 +- .../kibana/public/visualize/editor/editor.js | 2 +- src/ui/public/kbn_top_nav/kbn_top_nav.html | 4 +- src/ui/public/kbn_top_nav/kbn_top_nav.js | 8 +- .../public/query_bar/components/query_bar.tsx | 131 +++++++++++++++--- .../gis/public/actions/store_actions.js | 57 ++------ .../gis/public/angular/get_initial_query.js | 30 ++++ .../angular/get_initial_refresh_config.js | 28 ++++ .../angular/get_initial_time_filters.js | 24 ++++ x-pack/plugins/gis/public/angular/map.html | 6 + .../gis/public/angular/map_controller.js | 107 +++++++++----- .../gis/public/components/gis_map/index.js | 12 +- .../gis/public/components/gis_map/view.js | 30 ++-- x-pack/plugins/gis/public/store/map.js | 15 +- 15 files changed, 317 insertions(+), 141 deletions(-) create mode 100644 x-pack/plugins/gis/public/angular/get_initial_query.js create mode 100644 x-pack/plugins/gis/public/angular/get_initial_refresh_config.js create mode 100644 x-pack/plugins/gis/public/angular/get_initial_time_filters.js diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js index ed3edbe6b69bb3..5d441c56165b76 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js @@ -215,7 +215,7 @@ app.directive('dashboardApp', function ($injector) { dashboardStateManager.getPanels().find((panel) => panel.panelIndex === panelIndex); }; - $scope.updateQueryAndFetch = function (query) { + $scope.updateQueryAndFetch = function ({ query }) { const oldQuery = $scope.model.query; if (_.isEqual(oldQuery, query)) { // The user can still request a reload in the query bar, even if the diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index db8119fa784c88..1aaf1f0956d0a7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -643,7 +643,7 @@ function discoverController( .catch(notify.error); }; - $scope.updateQueryAndFetch = function (query) { + $scope.updateQueryAndFetch = function ({ query }) { $state.query = migrateLegacyQuery(query); $scope.fetch(); }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 6039285048e229..b9eed586d9dd17 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -378,7 +378,7 @@ function VisEditor( } } - $scope.updateQueryAndFetch = function (query) { + $scope.updateQueryAndFetch = function ({ query }) { $state.query = migrateLegacyQuery(query); $scope.fetch(); }; diff --git a/src/ui/public/kbn_top_nav/kbn_top_nav.html b/src/ui/public/kbn_top_nav/kbn_top_nav.html index bfad2b891d35a4..733e20e8a2e2ae 100644 --- a/src/ui/public/kbn_top_nav/kbn_top_nav.html +++ b/src/ui/public/kbn_top_nav/kbn_top_nav.html @@ -32,7 +32,9 @@ > - + + + diff --git a/src/ui/public/kbn_top_nav/kbn_top_nav.js b/src/ui/public/kbn_top_nav/kbn_top_nav.js index c827028bbba236..4a82ca1cb17d50 100644 --- a/src/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/ui/public/kbn_top_nav/kbn_top_nav.js @@ -38,10 +38,10 @@ /** * kbnTopNav directive * - * The top section that shows the timepicker, load, share and save dialogues. + * The top section that optionally shows the timepicker and menu items. * * ``` - * + * * ``` * * Menu items/templates are passed to the kbnTopNav via the config attribute @@ -151,6 +151,10 @@ module.directive('kbnTopNav', function (Private) { initTopNav(topNavConfig, null); + if (!_.has($scope, 'showTimepickerInTopNav')) { + $scope.showTimepickerInTopNav = true; + } + return $scope.kbnTopNav; }, diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index af91fea37eb437..ea8cc7d4b4206f 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -19,12 +19,14 @@ import { IndexPattern } from 'ui/index_patterns'; -import { compact, debounce, isEqual } from 'lodash'; +import { compact, debounce, get, isEqual } from 'lodash'; import React, { Component } from 'react'; import { getFromLegacyIndexPattern } from 'ui/index_patterns/static_utils'; import { kfetch } from 'ui/kfetch'; import { PersistedLog } from 'ui/persisted_log'; import { Storage } from 'ui/storage'; +// @ts-ignore +import { timeHistory } from 'ui/timefilter/time_history'; import { AutocompleteSuggestion, AutocompleteSuggestionType, @@ -42,6 +44,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector, + EuiSuperDatePicker, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; @@ -66,14 +69,25 @@ interface Query { language: string; } +interface DateRange { + from: string; + to: string; +} + interface Props { query: Query; - onSubmit: (query: { query: string | object; language: string }) => void; + onSubmit: ({ dateRange, query }: { dateRange: DateRange; query: Query }) => void; disableAutoFocus?: boolean; appName: string; indexPatterns: IndexPattern[]; store: Storage; intl: InjectedIntl; + showDatePicker: boolean; + from: string; + to: string; + isPaused: boolean; + refreshInterval: number; + onRefreshChange?: (isPaused: boolean, refreshInterval: number) => void; } interface State { @@ -84,6 +98,8 @@ interface State { suggestions: AutocompleteSuggestion[]; suggestionLimit: number; currentProps?: Props; + from: string; + to: string; } export class QueryBarUI extends Component { @@ -92,26 +108,34 @@ export class QueryBarUI extends Component { return null; } - if (nextProps.query.query !== prevState.query.query) { - return { - query: { - query: toUser(nextProps.query.query), - language: nextProps.query.language, - }, - currentProps: nextProps, - }; - } else if (nextProps.query.language !== prevState.query.language) { - return { - query: { - query: '', - language: nextProps.query.language, - }, - currentProps: nextProps, + let nextDateRange = null; + if ( + nextProps.from !== get(prevState, 'currentProps.from') || + nextProps.to !== get(prevState, 'currentProps.to') + ) { + nextDateRange = { + from: nextProps.from, + to: nextProps.to, }; } - return { currentProps: nextProps }; + const nextState = { + currentProps: nextProps, + }; + if (nextQuery) { + nextState.query = nextQuery; + } + if (nextDateRange) { + nextState.from = nextDateRange.from; + nextState.to = nextDateRange.to; + } + return nextState; } + private static defaultProps = { + showDatePicker: false, + from: 'now-15m', + to: 'now', + }; /* Keep the "draft" value in local state until the user actually submits the query. There are a couple advantages: @@ -136,6 +160,8 @@ export class QueryBarUI extends Component { index: null, suggestions: [], suggestionLimit: 50, + from: this.props.from, + to: this.props.to, }; public updateSuggestions = debounce(async () => { @@ -151,7 +177,11 @@ export class QueryBarUI extends Component { private persistedLog: PersistedLog | null = null; public isDirty = () => { - return this.state.query.query !== this.props.query.query; + return ( + this.state.query.query !== this.props.query.query || + this.state.from !== this.props.from || + this.state.to !== this.props.to + ); }; public increaseLimit = () => { @@ -322,6 +352,13 @@ export class QueryBarUI extends Component { this.onInputChange(event.target.value); }; + public onTimeChange = ({ start, end }: { start: string; end: string }) => { + this.setState({ + from: start, + to: end, + }); + }; + public onKeyUp = (event: React.KeyboardEvent) => { if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) { this.setState({ isSuggestionsVisible: true }); @@ -408,9 +445,20 @@ export class QueryBarUI extends Component { this.persistedLog.add(this.state.query.query); } + timeHistory.add({ + from: this.state.from, + to: this.state.to, + }); + this.props.onSubmit({ - query: fromUser(this.state.query.query), - language: this.state.query.language, + query: { + query: fromUser(this.state.query.query), + language: this.state.query.language, + }, + dateRange: { + from: this.state.from, + to: this.state.to, + }, }); this.setState({ isSuggestionsVisible: false }); }; @@ -427,8 +475,14 @@ export class QueryBarUI extends Component { this.props.store.set('kibana.userQueryLanguage', language); this.props.onSubmit({ - query: '', - language, + query: { + query: '', + language, + }, + dateRange: { + from: this.props.from, + to: this.props.to, + }, }); }; @@ -533,6 +587,7 @@ export class QueryBarUI extends Component { + {this.renderDatePicker()} { ); } + + private renderDatePicker() { + if (!this.props.showDatePicker) { + return null; + } + + const recentlyUsedRanges = timeHistory + .get() + .map(({ from, to }: { from: string; to: string }) => { + return { + start: from, + end: to, + }; + }); + + return ( + + + + ); + } } export const QueryBar = injectI18n(QueryBarUI); diff --git a/x-pack/plugins/gis/public/actions/store_actions.js b/x-pack/plugins/gis/public/actions/store_actions.js index dc6a170b6f4703..d1fc388c1071ec 100644 --- a/x-pack/plugins/gis/public/actions/store_actions.js +++ b/x-pack/plugins/gis/public/actions/store_actions.js @@ -16,7 +16,6 @@ import { getMapReady, getWaitingForMapReadyLayerListRaw, } from '../selectors/map_selectors'; -import { timeService } from '../kibana_services'; export const SET_SELECTED_LAYER = 'SET_SELECTED_LAYER'; export const UPDATE_LAYER_ORDER = 'UPDATE_LAYER_ORDER'; @@ -34,7 +33,6 @@ export const LAYER_DATA_LOAD_STARTED = 'LAYER_DATA_LOAD_STARTED'; export const LAYER_DATA_LOAD_ENDED = 'LAYER_DATA_LOAD_ENDED'; export const LAYER_DATA_LOAD_ERROR = 'LAYER_DATA_LOAD_ERROR'; export const SET_JOINS = 'SET_JOINS'; -export const SET_TIME_FILTERS = 'SET_TIME_FILTERS'; export const SET_QUERY = 'SET_QUERY'; export const TRIGGER_REFRESH_TIMER = 'TRIGGER_REFRESH_TIMER'; export const UPDATE_LAYER_PROP = 'UPDATE_LAYER_PROP'; @@ -385,43 +383,17 @@ export function removeLayer(id) { } export function setMeta(metaJson) { - return async dispatch => { - dispatch({ - type: SET_META, - meta: metaJson - }); - }; -} - -export function setTimeFiltersToKbnGlobalTime() { - return (dispatch) => { - dispatch(setTimeFilters(timeService.getTime())); - }; -} - -export function setTimeFilters({ from, to }) { - return async (dispatch, getState) => { - dispatch({ - type: SET_TIME_FILTERS, - from, - to, - }); - - // Update Kibana global time - const kbnTime = timeService.getTime(); - if ((to && to !== kbnTime.to) || (from && from !== kbnTime.from)) { - timeService.setTime({ from, to }); - } - - const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(getState, dispatch, dataFilters); + return { + type: SET_META, + meta: metaJson }; } -export function setQuery({ query }) { +export function setQuery({ query, timeFilters }) { return async (dispatch, getState) => { dispatch({ type: SET_QUERY, + timeFilters, query: { ...query, // ensure query changes to trigger re-fetch even when query is the same because "Refresh" clicked @@ -435,21 +407,10 @@ export function setQuery({ query }) { } export function setRefreshConfig({ isPaused, interval }) { - return async (dispatch) => { - dispatch({ - type: SET_REFRESH_CONFIG, - isPaused, - interval, - }); - - // Update Kibana global refresh - const kbnRefresh = timeService.getRefreshInterval(); - if (isPaused !== kbnRefresh.pause || interval !== kbnRefresh.value) { - timeService.setRefreshInterval({ - pause: isPaused, - value: interval, - }); - } + return { + type: SET_REFRESH_CONFIG, + isPaused, + interval, }; } diff --git a/x-pack/plugins/gis/public/angular/get_initial_query.js b/x-pack/plugins/gis/public/angular/get_initial_query.js new file mode 100644 index 00000000000000..4743832c113bed --- /dev/null +++ b/x-pack/plugins/gis/public/angular/get_initial_query.js @@ -0,0 +1,30 @@ +/* + * 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. + */ + +const DEFAULT_QUERY_LANGUAGE = 'kuery'; + +export function getInitialQuery({ + mapStateJSON, + appState = {}, + userQueryLanguage, +}) { + + if (appState.query) { + return appState.query; + } + + if (mapStateJSON) { + const mapState = JSON.parse(mapStateJSON); + if (mapState.query) { + return mapState.query; + } + } + + return { + query: '', + language: userQueryLanguage || DEFAULT_QUERY_LANGUAGE + }; +} diff --git a/x-pack/plugins/gis/public/angular/get_initial_refresh_config.js b/x-pack/plugins/gis/public/angular/get_initial_refresh_config.js new file mode 100644 index 00000000000000..5d042028c1ef88 --- /dev/null +++ b/x-pack/plugins/gis/public/angular/get_initial_refresh_config.js @@ -0,0 +1,28 @@ +/* + * 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 chrome from 'ui/chrome'; + +const uiSettings = chrome.getUiSettingsClient(); + +export function getInitialRefreshConfig({ + mapStateJSON, + globalState = {}, +}) { + + if (mapStateJSON) { + const mapState = JSON.parse(mapStateJSON); + if (mapState.refreshConfig) { + return mapState.refreshConfig; + } + } + + const defaultRefreshConfig = uiSettings.get('timepicker:refreshIntervalDefaults'); + const refreshInterval = { ...defaultRefreshConfig, ...globalState.refreshInterval }; + return { + isPaused: refreshInterval.pause, + interval: refreshInterval.value, + }; +} diff --git a/x-pack/plugins/gis/public/angular/get_initial_time_filters.js b/x-pack/plugins/gis/public/angular/get_initial_time_filters.js new file mode 100644 index 00000000000000..8ce57e0d0991bf --- /dev/null +++ b/x-pack/plugins/gis/public/angular/get_initial_time_filters.js @@ -0,0 +1,24 @@ +/* + * 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 chrome from 'ui/chrome'; + +const uiSettings = chrome.getUiSettingsClient(); + +export function getInitialTimeFilters({ + mapStateJSON, + globalState = {}, +}) { + + if (mapStateJSON) { + const mapState = JSON.parse(mapStateJSON); + if (mapState.timeFilters) { + return mapState.timeFilters; + } + } + + const defaultTime = uiSettings.get('timepicker:timeDefaults'); + return { ...defaultTime, ...globalState.time }; +} diff --git a/x-pack/plugins/gis/public/angular/map.html b/x-pack/plugins/gis/public/angular/map.html index 34f953f0525ea6..bb30c7461cbe18 100644 --- a/x-pack/plugins/gis/public/angular/map.html +++ b/x-pack/plugins/gis/public/angular/map.html @@ -27,6 +27,12 @@ app-name="'maps'" on-submit="updateQueryAndDispatch" index-patterns="indexPatterns" + show-date-picker="showDatePicker" + from="time.from" + to="time.to" + is-paused="refreshConfig.isPaused" + refresh-interval="refreshConfig.interval" + on-refresh-change="onRefreshChange" > diff --git a/x-pack/plugins/gis/public/angular/map_controller.js b/x-pack/plugins/gis/public/angular/map_controller.js index 64b171701c3a2a..0fdf632ab45905 100644 --- a/x-pack/plugins/gis/public/angular/map_controller.js +++ b/x-pack/plugins/gis/public/angular/map_controller.js @@ -8,13 +8,11 @@ import chrome from 'ui/chrome'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { uiModules } from 'ui/modules'; -import { timefilter } from 'ui/timefilter'; import { Provider } from 'react-redux'; import { getStore } from '../store/store'; import { GisMap } from '../components/gis_map'; import { setSelectedLayer, - setTimeFilters, setRefreshConfig, setGoto, replaceLayerList, @@ -28,40 +26,95 @@ import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_s import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { toastNotifications } from 'ui/notify'; import { getInitialLayers } from './get_initial_layers'; +import { getInitialQuery } from './get_initial_query'; +import { getInitialTimeFilters } from './get_initial_time_filters'; +import { getInitialRefreshConfig } from './get_initial_refresh_config'; const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-gis-root'; -const DEFAULT_QUERY_LANGUAGE = 'kuery'; + const app = uiModules.get('app/gis', []); -app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage, AppState) => { +app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage, AppState, globalState) => { const savedMap = $scope.map = $route.current.locals.map; let unsubscribe; inspectorAdapters.requests.reset(); + $scope.$listen(globalState, 'fetch_with_changes', (diff) => { + if (diff.includes('time')) { + $scope.updateQueryAndDispatch({ query: $scope.query, dateRange: globalState.time }); + } + if (diff.includes('refreshInterval')) { + $scope.onRefreshChange({ isPaused: globalState.pause, refreshInterval: globalState.value }); + } + }); + const $state = new AppState(); $scope.$listen($state, 'fetch_with_changes', function (diff) { if (diff.includes('query')) { - $scope.updateQueryAndDispatch($state.query); + $scope.updateQueryAndDispatch({ query: $state.query }); } }); - $scope.query = {}; + + function syncAppAndGlobalState() { + $scope.$evalAsync(() => { + $state.query = $scope.query; + $state.save(); + globalState.time = $scope.time; + globalState.refreshInterval = { + pause: $scope.refreshConfig.isPaused, + value: $scope.refreshConfig.interval, + }; + globalState.save(); + }); + } + + $scope.query = getInitialQuery({ + mapStateJSON: savedMap.mapStateJSON, + appState: $state, + userQueryLanguage: localStorage.get('kibana.userQueryLanguage') + }); + $scope.time = getInitialTimeFilters({ + mapStateJSON: savedMap.mapStateJSON, + globalState: globalState, + }); + $scope.refreshConfig = getInitialRefreshConfig({ + mapStateJSON: savedMap.mapStateJSON, + globalState: globalState, + }); + syncAppAndGlobalState(); + $scope.indexPatterns = []; - $scope.updateQueryAndDispatch = function (newQuery) { - $scope.query = newQuery; + $scope.updateQueryAndDispatch = function ({ dateRange, query }) { + $scope.query = query; + $scope.time = dateRange; getStore().then(store => { // ignore outdated query - if ($scope.query !== newQuery) { + if ($scope.query !== query && $scope.time !== dateRange) { return; } - store.dispatch(setQuery({ query: $scope.query })); + store.dispatch(setQuery({ query: $scope.query, timeFilters: $scope.time })); - // update appState - $state.query = $scope.query; - $state.save(); + syncAppAndGlobalState(); + }); + }; + $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { + $scope.refreshConfig = { + isPaused, + interval: refreshInterval ? refreshInterval : $scope.refreshConfig.interval + }; + getStore().then(store => { + // ignore outdated + if ($scope.refreshConfig.isPaused !== isPaused && $scope.refreshConfig.interval !== refreshInterval) { + return; + } + + store.dispatch(setRefreshConfig($scope.refreshConfig)); + + syncAppAndGlobalState(); }); }; @@ -76,36 +129,20 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage }); // sync store with savedMap mapState - let queryFromSavedObject; if (savedMap.mapStateJSON) { const mapState = JSON.parse(savedMap.mapStateJSON); - queryFromSavedObject = mapState.query; - const timeFilters = mapState.timeFilters ? mapState.timeFilters : timefilter.getTime(); - store.dispatch(setTimeFilters(timeFilters)); store.dispatch(setGoto({ lat: mapState.center.lat, lon: mapState.center.lon, zoom: mapState.zoom, })); - if (mapState.refreshConfig) { - store.dispatch(setRefreshConfig(mapState.refreshConfig)); - } } const layerList = getInitialLayers(savedMap.layerListJSON, getDataSources(store.getState())); store.dispatch(replaceLayerList(layerList)); - // Initialize query, syncing appState and store - if ($state.query) { - $scope.updateQueryAndDispatch($state.query); - } else if (queryFromSavedObject) { - $scope.updateQueryAndDispatch(queryFromSavedObject); - } else { - $scope.updateQueryAndDispatch({ - query: '', - language: localStorage.get('kibana.userQueryLanguage') || DEFAULT_QUERY_LANGUAGE - }); - } + store.dispatch(setRefreshConfig($scope.refreshConfig)); + store.dispatch(setQuery({ query: $scope.query, timeFilters: $scope.time })); const root = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID); render( @@ -136,9 +173,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage } function handleStoreChanges(store) { - const state = store.getState(); - - const nextIndexPatternIds = getUniqueIndexPatternIds(state); + const nextIndexPatternIds = getUniqueIndexPatternIds(store.getState()); if (nextIndexPatternIds !== prevIndexPatternIds) { prevIndexPatternIds = nextIndexPatternIds; updateIndexPatterns(nextIndexPatternIds); @@ -197,6 +232,8 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage return { id }; } + $scope.showTimepickerInTopNav = false; // used by kbn-top-nav directive to disable timepicker in top nav + $scope.showDatePicker = true; // used by query-bar directive to enable timepikcer in query bar $scope.topNavMenu = [{ key: 'inspect', description: 'Open Inspector', @@ -238,6 +275,4 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage showSaveModal(saveModal); } }]; - timefilter.enableTimeRangeSelector(); - timefilter.enableAutoRefreshSelector(); }); diff --git a/x-pack/plugins/gis/public/components/gis_map/index.js b/x-pack/plugins/gis/public/components/gis_map/index.js index b0e31b7e2f4705..cff886a1ff1234 100644 --- a/x-pack/plugins/gis/public/components/gis_map/index.js +++ b/x-pack/plugins/gis/public/components/gis_map/index.js @@ -7,26 +7,22 @@ import { connect } from 'react-redux'; import { GisMap } from './view'; import { getFlyoutDisplay, FLYOUT_STATE } from '../../store/ui'; -import { - setTimeFiltersToKbnGlobalTime, - triggerRefreshTimer, - setRefreshConfig -} from '../../actions/store_actions'; +import { triggerRefreshTimer } from '../../actions/store_actions'; +import { getRefreshConfig } from '../../selectors/map_selectors'; function mapStateToProps(state = {}) { const flyoutDisplay = getFlyoutDisplay(state); return { layerDetailsVisible: flyoutDisplay === FLYOUT_STATE.LAYER_PANEL, addLayerVisible: flyoutDisplay === FLYOUT_STATE.ADD_LAYER_WIZARD, - noFlyoutVisible: flyoutDisplay === FLYOUT_STATE.NONE + noFlyoutVisible: flyoutDisplay === FLYOUT_STATE.NONE, + refreshConfig: getRefreshConfig(state), }; } function mapDispatchToProps(dispatch) { return { - setTimeFiltersToKbnGlobalTime: () => dispatch(setTimeFiltersToKbnGlobalTime()), triggerRefreshTimer: () => dispatch(triggerRefreshTimer()), - setRefreshConfig: (({ isPaused, interval }) => dispatch(setRefreshConfig({ isPaused, interval }))), }; } diff --git a/x-pack/plugins/gis/public/components/gis_map/view.js b/x-pack/plugins/gis/public/components/gis_map/view.js index d3a08630e970cc..2abad91c4b9743 100644 --- a/x-pack/plugins/gis/public/components/gis_map/view.js +++ b/x-pack/plugins/gis/public/components/gis_map/view.js @@ -12,39 +12,41 @@ import { AddLayerPanel } from '../layer_addpanel/index'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Toasts } from '../toasts'; -import { timeService } from '../../kibana_services'; - export class GisMap extends Component { componentDidMount() { - timeService.on('timeUpdate', this.props.setTimeFiltersToKbnGlobalTime); - timeService.on('refreshIntervalUpdate', this.setRefreshTimer); + this.setRefreshTimer(); + } + + componentDidUpdate() { this.setRefreshTimer(); } componentWillUnmount() { - timeService.off('timeUpdate', this.props.setTimeFiltersToKbnGlobalTime); - timeService.off('refreshIntervalUpdate', this.setRefreshTimer); this.clearRefreshTimer(); } setRefreshTimer = () => { + const { isPaused, interval } = this.props.refreshConfig; + + if (this.isPaused === isPaused && this.interval === interval) { + // refreshConfig is the same, nothing to do + return; + } + + this.isPaused = isPaused; + this.interval = interval; + this.clearRefreshTimer(); - const { value, pause } = timeService.getRefreshInterval(); - if (!pause && value > 0) { + if (!isPaused && interval > 0) { this.refreshTimerId = setInterval( () => { this.props.triggerRefreshTimer(); }, - value + interval ); } - - this.props.setRefreshConfig({ - isPaused: pause, - interval: value, - }); } clearRefreshTimer = () => { diff --git a/x-pack/plugins/gis/public/store/map.js b/x-pack/plugins/gis/public/store/map.js index 78cc2560ae7e3d..753e1b34cc17a5 100644 --- a/x-pack/plugins/gis/public/store/map.js +++ b/x-pack/plugins/gis/public/store/map.js @@ -19,7 +19,6 @@ import { MAP_EXTENT_CHANGED, MAP_READY, MAP_DESTROYED, - SET_TIME_FILTERS, SET_QUERY, UPDATE_LAYER_PROP, UPDATE_LAYER_STYLE_FOR_SELECTED_LAYER, @@ -160,12 +159,16 @@ export function map(state = INITIAL_STATE, action) { buffer: action.mapState.buffer, }; return { ...state, mapState: { ...state.mapState, ...newMapState } }; - case SET_TIME_FILTERS: - const { from, to } = action; - return { ...state, mapState: { ...state.mapState, timeFilters: { from, to } } }; case SET_QUERY: - const { query } = action; - return { ...state, mapState: { ...state.mapState, query } }; + const { query, timeFilters } = action; + return { + ...state, + mapState: { + ...state.mapState, + query, + timeFilters, + } + }; case SET_REFRESH_CONFIG: const { isPaused, interval } = action; return { From bb7cc6746150e83588531b839843b553745795bd Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 22 Jan 2019 13:07:23 -0700 Subject: [PATCH 02/20] fix bug with way update function called by watcher --- .../core_plugins/kibana/public/dashboard/dashboard_app.js | 4 +++- .../kibana/public/discover/controllers/discover.js | 4 +++- .../core_plugins/kibana/public/visualize/editor/editor.js | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js index 5d441c56165b76..959ebfc892bc62 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js @@ -236,7 +236,9 @@ app.directive('dashboardApp', function ($injector) { $scope.indexPatterns = dashboardStateManager.getPanelIndexPatterns(); }; - $scope.$watch('model.query', $scope.updateQueryAndFetch); + $scope.$watch('model.query', (query) => { + $scope.updateQueryAndFetch({ query }); + }); $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { dashboardStateManager.handleTimeChange(timefilter.getTime()); diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index 1aaf1f0956d0a7..2f5eb1ffd839ab 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -525,7 +525,9 @@ function discoverController( } }); - $scope.$watch('state.query', $scope.updateQueryAndFetch); + $scope.$watch('state.query', (query) => { + $scope.updateQueryAndFetch({ query }); + }); $scope.$watchMulti([ 'rows', diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index b9eed586d9dd17..01e004e0cf1c3d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -304,7 +304,9 @@ function VisEditor( $appStatus.dirty = status.dirty || !savedVis.id; }); - $scope.$watch('state.query', $scope.updateQueryAndFetch); + $scope.$watch('state.query', (query) => { + $scope.updateQueryAndFetch({ query }); + }); $state.replace(); From 3f671a16962886dfd3f0ff574fce1cc419052355 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 22 Jan 2019 16:32:03 -0700 Subject: [PATCH 03/20] get maps application functional tests working with new timepicker --- .../public/query_bar/components/query_bar.tsx | 12 ++ test/functional/config.js | 2 + test/functional/page_objects/header_page.js | 32 +---- test/functional/page_objects/index.js | 1 + test/functional/page_objects/time_picker.js | 111 ++++++++++++++++++ .../apps/gis/saved_object_management.js | 13 +- .../apps/monitoring/_get_lifecycle_methods.js | 4 +- .../test/functional/page_objects/gis_page.js | 6 +- 8 files changed, 141 insertions(+), 40 deletions(-) create mode 100644 test/functional/page_objects/time_picker.js diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index ea8cc7d4b4206f..ec3971c2a2e476 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -627,6 +627,16 @@ export class QueryBarUI extends Component { }; }); + const commonlyUsedRanges = config + .get('timepicker:quickRanges') + .map(({ from, to, display }: { from: string; to: string; display: string }) => { + return { + start: from, + end: to, + label: display, + }; + }); + return ( { onRefreshChange={this.props.onRefreshChange} showUpdateButton={false} recentlyUsedRanges={recentlyUsedRanges} + commonlyUsedRanges={commonlyUsedRanges} + dateFormat={config.get('dateFormat')} /> ); diff --git a/test/functional/config.js b/test/functional/config.js index aabc2c5eaea32f..86d38e1782d0a3 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -33,6 +33,7 @@ import { VisualBuilderPageProvider, TimelionPageProvider, SharePageProvider, + TimePickerPageProvider, } from './page_objects'; import { @@ -94,6 +95,7 @@ export default async function ({ readConfigFile }) { visualBuilder: VisualBuilderPageProvider, timelion: TimelionPageProvider, share: SharePageProvider, + timePicker: TimePickerPageProvider, }, services: { es: commonConfig.get('services.es'), diff --git a/test/functional/page_objects/header_page.js b/test/functional/page_objects/header_page.js index 47f9dc231fce1b..99512197f89bb2 100644 --- a/test/functional/page_objects/header_page.js +++ b/test/functional/page_objects/header_page.js @@ -174,36 +174,6 @@ export function HeaderPageProvider({ getService, getPageObjects }) { await find.clickByLinkText(quickTime); } - async getAutoRefreshState() { - return testSubjects.getAttribute('globalTimepickerAutoRefreshButton', 'data-test-subj-state'); - } - - async getRefreshConfig() { - const refreshState = await testSubjects.getAttribute('globalTimepickerAutoRefreshButton', 'data-test-subj-state'); - const refreshConfig = await testSubjects.getVisibleText('globalRefreshButton'); - return `${refreshState} ${refreshConfig}`; - } - - // check if the auto refresh state is active and to pause it - async pauseAutoRefresh() { - let result = false; - if ((await this.getAutoRefreshState()) === 'active') { - await testSubjects.click('globalTimepickerAutoRefreshButton'); - result = true; - } - return result; - } - - // check if the auto refresh state is inactive and to resume it - async resumeAutoRefresh() { - let result = false; - if ((await this.getAutoRefreshState()) === 'inactive') { - await testSubjects.click('globalTimepickerAutoRefreshButton'); - result = true; - } - return result; - } - async getToastMessage(findTimeout = defaultFindTimeout) { const toastMessage = await find.displayedByCssSelector( 'kbn-truncated.kbnToast__message', @@ -249,6 +219,7 @@ export function HeaderPageProvider({ getService, getPageObjects }) { await testSubjects.find('kibanaChrome', defaultFindTimeout * 10); } + // replaced with timePicker.getTimeConfig async getPrettyDuration() { return await testSubjects.getVisibleText('globalTimepickerRange'); } @@ -256,6 +227,7 @@ export function HeaderPageProvider({ getService, getPageObjects }) { async isSharedTimefilterEnabled() { return await find.existsByCssSelector('[shared-timefilter=true]'); } + } return new HeaderPage(); diff --git a/test/functional/page_objects/index.js b/test/functional/page_objects/index.js index 04fc7240480ffa..5d561a42e2fa25 100644 --- a/test/functional/page_objects/index.js +++ b/test/functional/page_objects/index.js @@ -32,3 +32,4 @@ export { PointSeriesPageProvider } from './point_series_page'; export { VisualBuilderPageProvider } from './visual_builder_page'; export { TimelionPageProvider } from './timelion_page'; export { SharePageProvider } from './share_page'; +export { TimePickerPageProvider } from './time_picker'; diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js new file mode 100644 index 00000000000000..4c8f7ba68a5047 --- /dev/null +++ b/test/functional/page_objects/time_picker.js @@ -0,0 +1,111 @@ +/* + * 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. + */ + +export function TimePickerPageProvider({ getService }) { + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + class TimePickerPage { + + async isQuickSelectMenuOpen() { + return await testSubjects.exists('superDatePickerQuickMenu'); + } + + async openQuickSelectTimeMenu() { + log.debug('openQuickSelectTimeMenu'); + const isMenuOpen = await this.isQuickSelectMenuOpen(); + if (!isMenuOpen) { + log.debug('opening quick select menu'); + await retry.try(async () => { + await testSubjects.click('superDatePickerToggleQuickMenuButton'); + }); + } + } + + async closeQuickSelectTimeMenu() { + log.debug('closeQuickSelectTimeMenu'); + const isMenuOpen = await this.isQuickSelectMenuOpen(); + if (isMenuOpen) { + log.debug('closing quick select menu'); + await retry.try(async () => { + await testSubjects.click('superDatePickerToggleQuickMenuButton'); + }); + } + } + + async showStartEndTimes() { + const isShowDatesButton = await testSubjects.exists('superDatePickerShowDatesButton'); + if (isShowDatesButton) { + await testSubjects.click('superDatePickerShowDatesButton'); + } + } + + async getRefreshConfig(keepQuickSelectOpen = false) { + await this.openQuickSelectTimeMenu(); + const interval = await testSubjects.getAttribute('superDatePickerRefreshIntervalInput', 'value'); + const units = await testSubjects.getAttribute('superDatePickerRefreshIntervalUnitsSelect', 'value'); + const toggleButtonText = await testSubjects.getVisibleText('superDatePickerToggleRefreshButton'); + if (!keepQuickSelectOpen) { + await this.closeQuickSelectTimeMenu(); + } + + return { + interval, + units, + isPaused: toggleButtonText === 'Start' ? true : false + }; + } + + async getTimeConfig() { + await this.showStartEndTimes(); + const start = await testSubjects.getVisibleText('superDatePickerstartDatePopoverButton'); + const end = await testSubjects.getVisibleText('superDatePickerendDatePopoverButton'); + return { + start, + end + }; + } + + async pauseAutoRefresh() { + log.debug('pauseAutoRefresh'); + const refreshConfig = await this.getRefreshConfig(true); + if (!refreshConfig.isPaused) { + log.debug('pause auto refresh'); + await testSubjects.click('superDatePickerToggleRefreshButton'); + await this.closeQuickSelectTimeMenu(); + } + + await this.closeQuickSelectTimeMenu(); + } + + async resumeAutoRefresh() { + log.debug('resumeAutoRefresh'); + const refreshConfig = await this.getRefreshConfig(true); + if (refreshConfig.isPaused) { + log.debug('resume auto refresh'); + await testSubjects.click('superDatePickerToggleRefreshButton'); + } + + await this.closeQuickSelectTimeMenu(); + } + } + + return new TimePickerPage(); +} diff --git a/x-pack/test/functional/apps/gis/saved_object_management.js b/x-pack/test/functional/apps/gis/saved_object_management.js index bbfb6c7a2dd242..f65a37c8eb365b 100644 --- a/x-pack/test/functional/apps/gis/saved_object_management.js +++ b/x-pack/test/functional/apps/gis/saved_object_management.js @@ -8,7 +8,7 @@ import expect from 'expect.js'; export default function ({ getPageObjects, getService }) { - const PageObjects = getPageObjects(['gis', 'header']); + const PageObjects = getPageObjects(['gis', 'header', 'timePicker']); const queryBar = getService('queryBar'); const browser = getService('browser'); const inspector = getService('inspector'); @@ -25,13 +25,16 @@ export default function ({ getPageObjects, getService }) { }); it('should update global Kibana time to value stored with map', async () => { - const kibanaTime = await PageObjects.header.getPrettyDuration(); - expect(kibanaTime).to.equal('Last 17m'); + const timeConfig = await PageObjects.timePicker.getTimeConfig(); + expect(timeConfig.start).to.equal('~ 17 minutes ago'); + expect(timeConfig.end).to.equal('now'); }); it('should update global Kibana refresh config to value stored with map', async () => { - const kibanaRefreshConfig = await PageObjects.header.getRefreshConfig(); - expect(kibanaRefreshConfig).to.equal('inactive 1 second'); + const kibanaRefreshConfig = await PageObjects.timePicker.getRefreshConfig(); + expect(kibanaRefreshConfig.interval).to.equal('0.016666666666666666'); + expect(kibanaRefreshConfig.units).to.equal('minutes'); + expect(kibanaRefreshConfig.isPaused).to.equal(true); }); it('should set map location to value stored with map', async () => { diff --git a/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js b/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js index 252eb6e34bacfd..518162593a1217 100644 --- a/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js +++ b/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js @@ -6,7 +6,7 @@ export const getLifecycleMethods = (getService, getPageObjects) => { const esArchiver = getService('esArchiver'); - const PageObjects = getPageObjects(['monitoring', 'header']); + const PageObjects = getPageObjects(['monitoring', 'header', 'timePicker']); const noData = getService('monitoringNoData'); let _archive; @@ -30,7 +30,7 @@ export const getLifecycleMethods = (getService, getPageObjects) => { // pause autorefresh in the time filter because we don't wait any ticks, // and we don't want ES to log a warning when data gets wiped out - await PageObjects.header.pauseAutoRefresh(); + await PageObjects.timePicker.pauseAutoRefresh(); await PageObjects.header.setAbsoluteRange(from, to); }, diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js index 0ee327e984c879..75a5bf22b800bc 100644 --- a/x-pack/test/functional/page_objects/gis_page.js +++ b/x-pack/test/functional/page_objects/gis_page.js @@ -5,7 +5,7 @@ */ export function GisPageProvider({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common', 'header']); + const PageObjects = getPageObjects(['common', 'header', 'timePicker']); const log = getService('log'); const testSubjects = getService('testSubjects'); @@ -204,10 +204,10 @@ export function GisPageProvider({ getService, getPageObjects }) { async triggerSingleRefresh(refreshInterval) { log.debug(`triggerSingleRefresh, refreshInterval: ${refreshInterval}`); - await PageObjects.header.resumeAutoRefresh(); + await PageObjects.timePicker.resumeAutoRefresh(); log.debug('waiting to give time for refresh timer to fire'); await PageObjects.common.sleep(refreshInterval + (refreshInterval / 2)); - await PageObjects.header.pauseAutoRefresh(); + await PageObjects.timePicker.pauseAutoRefresh(); await PageObjects.header.waitUntilLoadingHasFinished(); } } From 0660067ffa6e997ae9966ff3b569e799296c0b2c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 19:00:38 -0700 Subject: [PATCH 04/20] remove some changes outside of scoped work --- test/functional/page_objects/header_page.js | 32 +++++++++++++++++-- .../apps/monitoring/_get_lifecycle_methods.js | 4 +-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/test/functional/page_objects/header_page.js b/test/functional/page_objects/header_page.js index 99512197f89bb2..47f9dc231fce1b 100644 --- a/test/functional/page_objects/header_page.js +++ b/test/functional/page_objects/header_page.js @@ -174,6 +174,36 @@ export function HeaderPageProvider({ getService, getPageObjects }) { await find.clickByLinkText(quickTime); } + async getAutoRefreshState() { + return testSubjects.getAttribute('globalTimepickerAutoRefreshButton', 'data-test-subj-state'); + } + + async getRefreshConfig() { + const refreshState = await testSubjects.getAttribute('globalTimepickerAutoRefreshButton', 'data-test-subj-state'); + const refreshConfig = await testSubjects.getVisibleText('globalRefreshButton'); + return `${refreshState} ${refreshConfig}`; + } + + // check if the auto refresh state is active and to pause it + async pauseAutoRefresh() { + let result = false; + if ((await this.getAutoRefreshState()) === 'active') { + await testSubjects.click('globalTimepickerAutoRefreshButton'); + result = true; + } + return result; + } + + // check if the auto refresh state is inactive and to resume it + async resumeAutoRefresh() { + let result = false; + if ((await this.getAutoRefreshState()) === 'inactive') { + await testSubjects.click('globalTimepickerAutoRefreshButton'); + result = true; + } + return result; + } + async getToastMessage(findTimeout = defaultFindTimeout) { const toastMessage = await find.displayedByCssSelector( 'kbn-truncated.kbnToast__message', @@ -219,7 +249,6 @@ export function HeaderPageProvider({ getService, getPageObjects }) { await testSubjects.find('kibanaChrome', defaultFindTimeout * 10); } - // replaced with timePicker.getTimeConfig async getPrettyDuration() { return await testSubjects.getVisibleText('globalTimepickerRange'); } @@ -227,7 +256,6 @@ export function HeaderPageProvider({ getService, getPageObjects }) { async isSharedTimefilterEnabled() { return await find.existsByCssSelector('[shared-timefilter=true]'); } - } return new HeaderPage(); diff --git a/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js b/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js index 518162593a1217..252eb6e34bacfd 100644 --- a/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js +++ b/x-pack/test/functional/apps/monitoring/_get_lifecycle_methods.js @@ -6,7 +6,7 @@ export const getLifecycleMethods = (getService, getPageObjects) => { const esArchiver = getService('esArchiver'); - const PageObjects = getPageObjects(['monitoring', 'header', 'timePicker']); + const PageObjects = getPageObjects(['monitoring', 'header']); const noData = getService('monitoringNoData'); let _archive; @@ -30,7 +30,7 @@ export const getLifecycleMethods = (getService, getPageObjects) => { // pause autorefresh in the time filter because we don't wait any ticks, // and we don't want ES to log a warning when data gets wiped out - await PageObjects.timePicker.pauseAutoRefresh(); + await PageObjects.header.pauseAutoRefresh(); await PageObjects.header.setAbsoluteRange(from, to); }, From b5ca764a4227ba24e5a2fe81ebccce8a37a72e4e Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 12:54:03 -0700 Subject: [PATCH 05/20] clean up typescript lint problems --- src/ui/public/query_bar/components/query_bar.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index ec3971c2a2e476..e18c262fda42c2 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -44,9 +44,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector, - EuiSuperDatePicker, } from '@elastic/eui'; +// @ts-ignore +import { EuiSuperDatePicker } from '@elastic/eui'; + import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; const KEY_CODES = { @@ -119,7 +121,7 @@ export class QueryBarUI extends Component { }; } - const nextState = { + const nextState: any = { currentProps: nextProps, }; if (nextQuery) { @@ -131,6 +133,8 @@ export class QueryBarUI extends Component { } return nextState; } + + // @ts-ignore private static defaultProps = { showDatePicker: false, from: 'now-15m', @@ -656,4 +660,5 @@ export class QueryBarUI extends Component { } } +// @ts-ignore export const QueryBar = injectI18n(QueryBarUI); From 8851074311bdbd03e8d94ef8cfae31ae471faa6e Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 19:03:11 -0700 Subject: [PATCH 06/20] fix query_bar I18n lint error --- src/ui/public/query_bar/components/query_bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index e18c262fda42c2..07a23dabefded9 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -78,7 +78,7 @@ interface DateRange { interface Props { query: Query; - onSubmit: ({ dateRange, query }: { dateRange: DateRange; query: Query }) => void; + onSubmit: (payload: { dateRange: DateRange; query: Query }) => void; disableAutoFocus?: boolean; appName: string; indexPatterns: IndexPattern[]; From 9d30d7136ab68e1a7eceecba7390a9f562a4a2e7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 15:56:45 -0700 Subject: [PATCH 07/20] update query_bar jest test --- .../query_bar/components/query_bar.test.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ui/public/query_bar/components/query_bar.test.tsx b/src/ui/public/query_bar/components/query_bar.test.tsx index 363464a5ca96ea..8df14e4cf32108 100644 --- a/src/ui/public/query_bar/components/query_bar.test.tsx +++ b/src/ui/public/query_bar/components/query_bar.test.tsx @@ -207,8 +207,14 @@ describe('QueryBar', () => { component.find(QueryLanguageSwitcher).simulate('selectLanguage', 'lucene'); expect(mockStorage.set).toHaveBeenCalledWith('kibana.userQueryLanguage', 'lucene'); expect(mockCallback).toHaveBeenCalledWith({ - query: '', - language: 'lucene', + dateRange: { + from: 'now-15m', + to: 'now', + }, + query: { + query: '', + language: 'lucene', + }, }); }); @@ -235,8 +241,14 @@ describe('QueryBar', () => { expect(mockCallback).toHaveBeenCalledTimes(1); expect(mockCallback).toHaveBeenCalledWith({ - query: 'extension:jpg', - language: 'kuery', + dateRange: { + from: 'now-15m', + to: 'now', + }, + query: { + query: 'extension:jpg', + language: 'kuery', + }, }); }); From d4368adc6baa13a5c96ea9e4847214f0c11db8e1 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 19:10:44 -0700 Subject: [PATCH 08/20] grab some parts missing from one cherry-pick --- src/ui/public/query_bar/components/query_bar.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index 07a23dabefded9..66489901962e5c 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -110,6 +110,19 @@ export class QueryBarUI extends Component { return null; } + let nextQuery = null; + if (nextProps.query.query !== prevState.query.query) { + nextQuery = { + query: toUser(nextProps.query.query), + language: nextProps.query.language, + }; + } else if (nextProps.query.language !== prevState.query.language) { + nextQuery = { + query: '', + language: nextProps.query.language, + }; + } + let nextDateRange = null; if ( nextProps.from !== get(prevState, 'currentProps.from') || From 28ab882aa100aa27683a40b30a3cf28c2e03e724 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 19:19:36 -0700 Subject: [PATCH 09/20] pass dateRange to updateQueryAndDispatch when app state changes --- x-pack/plugins/gis/public/angular/map_controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/gis/public/angular/map_controller.js b/x-pack/plugins/gis/public/angular/map_controller.js index 0fdf632ab45905..6f8abb9e9ba6c9 100644 --- a/x-pack/plugins/gis/public/angular/map_controller.js +++ b/x-pack/plugins/gis/public/angular/map_controller.js @@ -54,7 +54,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage const $state = new AppState(); $scope.$listen($state, 'fetch_with_changes', function (diff) { if (diff.includes('query')) { - $scope.updateQueryAndDispatch({ query: $state.query }); + $scope.updateQueryAndDispatch({ query: $state.query, dateRange: $scope.time }); } }); From a87a478b3d0714ca82e5b3afad470659cb1309f6 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 19:32:08 -0700 Subject: [PATCH 10/20] use timefilter disable methods to hide timepicker from top naav --- src/ui/public/kbn_top_nav/kbn_top_nav.html | 4 +--- src/ui/public/kbn_top_nav/kbn_top_nav.js | 8 ++------ x-pack/plugins/gis/public/angular/map_controller.js | 5 ++++- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ui/public/kbn_top_nav/kbn_top_nav.html b/src/ui/public/kbn_top_nav/kbn_top_nav.html index 733e20e8a2e2ae..bfad2b891d35a4 100644 --- a/src/ui/public/kbn_top_nav/kbn_top_nav.html +++ b/src/ui/public/kbn_top_nav/kbn_top_nav.html @@ -32,9 +32,7 @@ > - - - + diff --git a/src/ui/public/kbn_top_nav/kbn_top_nav.js b/src/ui/public/kbn_top_nav/kbn_top_nav.js index 4a82ca1cb17d50..c827028bbba236 100644 --- a/src/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/ui/public/kbn_top_nav/kbn_top_nav.js @@ -38,10 +38,10 @@ /** * kbnTopNav directive * - * The top section that optionally shows the timepicker and menu items. + * The top section that shows the timepicker, load, share and save dialogues. * * ``` - * + * * ``` * * Menu items/templates are passed to the kbnTopNav via the config attribute @@ -151,10 +151,6 @@ module.directive('kbnTopNav', function (Private) { initTopNav(topNavConfig, null); - if (!_.has($scope, 'showTimepickerInTopNav')) { - $scope.showTimepickerInTopNav = true; - } - return $scope.kbnTopNav; }, diff --git a/x-pack/plugins/gis/public/angular/map_controller.js b/x-pack/plugins/gis/public/angular/map_controller.js index 6f8abb9e9ba6c9..a0e4f4f801426f 100644 --- a/x-pack/plugins/gis/public/angular/map_controller.js +++ b/x-pack/plugins/gis/public/angular/map_controller.js @@ -8,6 +8,7 @@ import chrome from 'ui/chrome'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { uiModules } from 'ui/modules'; +import { timefilter } from 'ui/timefilter'; import { Provider } from 'react-redux'; import { getStore } from '../store/store'; import { GisMap } from '../components/gis_map'; @@ -232,7 +233,9 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage return { id }; } - $scope.showTimepickerInTopNav = false; // used by kbn-top-nav directive to disable timepicker in top nav + // Hide angular timepicer/refresh UI from top nav + timefilter.disableTimeRangeSelector(); + timefilter.disableAutoRefreshSelector(); $scope.showDatePicker = true; // used by query-bar directive to enable timepikcer in query bar $scope.topNavMenu = [{ key: 'inspect', From fcf05217b845299278c599af3dfc21a3a3d029ce Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 23 Jan 2019 21:15:05 -0700 Subject: [PATCH 11/20] get selected refresh unit --- test/functional/page_objects/time_picker.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js index 4c8f7ba68a5047..77ba480c2907f6 100644 --- a/test/functional/page_objects/time_picker.js +++ b/test/functional/page_objects/time_picker.js @@ -20,6 +20,7 @@ export function TimePickerPageProvider({ getService }) { const log = getService('log'); const retry = getService('retry'); + const find = getService('find'); const testSubjects = getService('testSubjects'); class TimePickerPage { @@ -60,7 +61,17 @@ export function TimePickerPageProvider({ getService }) { async getRefreshConfig(keepQuickSelectOpen = false) { await this.openQuickSelectTimeMenu(); const interval = await testSubjects.getAttribute('superDatePickerRefreshIntervalInput', 'value'); - const units = await testSubjects.getAttribute('superDatePickerRefreshIntervalUnitsSelect', 'value'); + + let selectedUnit; + const select = await testSubjects.find('superDatePickerRefreshIntervalUnitsSelect'); + const options = await find.allDescendantDisplayedByCssSelector('option', select); + await Promise.all(options.map(async (optionElement) => { + const isSelected = await optionElement.isSelected(); + if (isSelected) { + selectedUnit = await optionElement.getVisibleText(); + } + })); + const toggleButtonText = await testSubjects.getVisibleText('superDatePickerToggleRefreshButton'); if (!keepQuickSelectOpen) { await this.closeQuickSelectTimeMenu(); @@ -68,7 +79,7 @@ export function TimePickerPageProvider({ getService }) { return { interval, - units, + units: selectedUnit, isPaused: toggleButtonText === 'Start' ? true : false }; } From 28e3dbc46db4af1a99d9803f1c16874a4027c7a5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 25 Jan 2019 10:00:21 -0700 Subject: [PATCH 12/20] use EuiSuperUpdate button --- .../public/query_bar/components/query_bar.tsx | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index 66489901962e5c..d8ef72881f422a 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -38,16 +38,10 @@ import { matchPairs } from '../lib/match_pairs'; import { QueryLanguageSwitcher } from './language_switcher'; import { SuggestionsComponent } from './typeahead/suggestions_component'; -import { - EuiButton, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiOutsideClickDetector, -} from '@elastic/eui'; +import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from '@elastic/eui'; // @ts-ignore -import { EuiSuperDatePicker } from '@elastic/eui'; +import { EuiSuperDatePicker, EuiSuperUpdateButton } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; @@ -606,25 +600,11 @@ export class QueryBarUI extends Component { {this.renderDatePicker()} - - {this.isDirty() ? ( - - ) : ( - - )} - + data-test-subj="querySubmitButton" + /> ); From 066a4fe375d777868aa5c2272dca8d0f3707f50a Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Fri, 25 Jan 2019 13:59:19 -0500 Subject: [PATCH 13/20] Fix responsive sizing of datepicker (#29) --- src/ui/public/query_bar/_index.scss | 2 +- .../public/query_bar/components/_index.scss | 2 + .../query_bar/components/_query_bar.scss | 14 +++++++ .../public/query_bar/components/query_bar.tsx | 42 ++++++++++++++----- 4 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 src/ui/public/query_bar/components/_index.scss create mode 100644 src/ui/public/query_bar/components/_query_bar.scss diff --git a/src/ui/public/query_bar/_index.scss b/src/ui/public/query_bar/_index.scss index 81a69fd89db998..b6634debbfa96e 100644 --- a/src/ui/public/query_bar/_index.scss +++ b/src/ui/public/query_bar/_index.scss @@ -1,4 +1,4 @@ // SASSTODO: Formalize this color in Kibana's styling constants $typeaheadConjunctionColor: #7800A6; -@import 'components/typeahead/index'; \ No newline at end of file +@import './components/index'; diff --git a/src/ui/public/query_bar/components/_index.scss b/src/ui/public/query_bar/components/_index.scss new file mode 100644 index 00000000000000..e17c416c135469 --- /dev/null +++ b/src/ui/public/query_bar/components/_index.scss @@ -0,0 +1,2 @@ +@import './query_bar'; +@import './typeahead/index'; diff --git a/src/ui/public/query_bar/components/_query_bar.scss b/src/ui/public/query_bar/components/_query_bar.scss new file mode 100644 index 00000000000000..f104c20daf064e --- /dev/null +++ b/src/ui/public/query_bar/components/_query_bar.scss @@ -0,0 +1,14 @@ +@include euiBreakpoint('xs', 's') { + .kbnQueryBar--withDatePicker { + > :last-child { + // EUI Flexbox adds too much margin between responded items, this just moves the last one up + margin-top: -$euiSize; + } + } +} + +@include euiBreakpoint('m', 'l', 'xl') { + .kbnQueryBar__datePickerWrapper { + max-width: 40vw; + } +} diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index d8ef72881f422a..5455336b0307cd 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -19,6 +19,7 @@ import { IndexPattern } from 'ui/index_patterns'; +import classNames from 'classnames'; import { compact, debounce, get, isEqual } from 'lodash'; import React, { Component } from 'react'; import { getFromLegacyIndexPattern } from 'ui/index_patterns/static_utils'; @@ -527,8 +528,16 @@ export class QueryBarUI extends Component { } public render() { + const classes = classNames('kbnQueryBar', { + 'kbnQueryBar--withDatePicker': this.props.showDatePicker, + }); + return ( - + {/* position:relative required on container so the suggestions appear under the query bar*/} @@ -598,18 +607,31 @@ export class QueryBarUI extends Component { - {this.renderDatePicker()} - - - + {this.renderUpdateButton()} ); } + private renderUpdateButton() { + const button = ( + + ); + if (this.props.showDatePicker) { + return ( + + {this.renderDatePicker()} + {button} + + ); + } else { + return button; + } + } + private renderDatePicker() { if (!this.props.showDatePicker) { return null; @@ -635,7 +657,7 @@ export class QueryBarUI extends Component { }); return ( - + Date: Fri, 25 Jan 2019 16:02:19 -0700 Subject: [PATCH 14/20] set isDisabled on EuiSuperUpdateButton --- src/ui/public/query_bar/components/query_bar.tsx | 16 ++++++++++++++-- .../apps/gis/saved_object_management.js | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index 5455336b0307cd..ed0167419bdc6e 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -44,7 +44,7 @@ import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from // @ts-ignore import { EuiSuperDatePicker, EuiSuperUpdateButton } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; const KEY_CODES = { LEFT: 37, @@ -97,6 +97,7 @@ interface State { currentProps?: Props; from: string; to: string; + isDateRangeInvalid: boolean; } export class QueryBarUI extends Component { @@ -174,6 +175,7 @@ export class QueryBarUI extends Component { suggestionLimit: 50, from: this.props.from, to: this.props.to, + isDateRangeInvalid: false, }; public updateSuggestions = debounce(async () => { @@ -364,10 +366,19 @@ export class QueryBarUI extends Component { this.onInputChange(event.target.value); }; - public onTimeChange = ({ start, end }: { start: string; end: string }) => { + public onTimeChange = ({ + start, + end, + isInvalid, + }: { + start: string; + end: string; + isInvalid: boolean; + }) => { this.setState({ from: start, to: end, + isDateRangeInvalid: isInvalid, }); }; @@ -616,6 +627,7 @@ export class QueryBarUI extends Component { const button = ( diff --git a/x-pack/test/functional/apps/gis/saved_object_management.js b/x-pack/test/functional/apps/gis/saved_object_management.js index f65a37c8eb365b..6ad4415bb423d6 100644 --- a/x-pack/test/functional/apps/gis/saved_object_management.js +++ b/x-pack/test/functional/apps/gis/saved_object_management.js @@ -32,7 +32,7 @@ export default function ({ getPageObjects, getService }) { it('should update global Kibana refresh config to value stored with map', async () => { const kibanaRefreshConfig = await PageObjects.timePicker.getRefreshConfig(); - expect(kibanaRefreshConfig.interval).to.equal('0.016666666666666666'); + expect(kibanaRefreshConfig.interval).to.equal('0.02'); expect(kibanaRefreshConfig.units).to.equal('minutes'); expect(kibanaRefreshConfig.isPaused).to.equal(true); }); From 1c9137f22ee2b33e4f85ad4a5959d6f61d818172 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 28 Jan 2019 19:19:14 -0700 Subject: [PATCH 15/20] review feedback --- .../public/query_bar/components/query_bar.tsx | 57 +++++++++---------- src/ui/public/timefilter/time_history.d.ts | 31 ++++++++++ x-pack/plugins/gis/public/angular/map.html | 6 +- 3 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 src/ui/public/timefilter/time_history.d.ts diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index ed0167419bdc6e..24e100c5345183 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -26,7 +26,6 @@ import { getFromLegacyIndexPattern } from 'ui/index_patterns/static_utils'; import { kfetch } from 'ui/kfetch'; import { PersistedLog } from 'ui/persisted_log'; import { Storage } from 'ui/storage'; -// @ts-ignore import { timeHistory } from 'ui/timefilter/time_history'; import { AutocompleteSuggestion, @@ -80,9 +79,9 @@ interface Props { store: Storage; intl: InjectedIntl; showDatePicker: boolean; - from: string; - to: string; - isPaused: boolean; + dateRangeFrom: string; + dateRangeTo: string; + isRefreshPaused: boolean; refreshInterval: number; onRefreshChange?: (isPaused: boolean, refreshInterval: number) => void; } @@ -95,8 +94,8 @@ interface State { suggestions: AutocompleteSuggestion[]; suggestionLimit: number; currentProps?: Props; - from: string; - to: string; + dateRangeFrom: string; + dateRangeTo: string; isDateRangeInvalid: boolean; } @@ -121,12 +120,12 @@ export class QueryBarUI extends Component { let nextDateRange = null; if ( - nextProps.from !== get(prevState, 'currentProps.from') || - nextProps.to !== get(prevState, 'currentProps.to') + nextProps.dateRangeFrom !== get(prevState, 'currentProps.dateRangeFrom') || + nextProps.dateRangeTo !== get(prevState, 'currentProps.dateRangeTo') ) { nextDateRange = { - from: nextProps.from, - to: nextProps.to, + dateRangeFrom: nextProps.dateRangeFrom, + dateRangeTo: nextProps.dateRangeTo, }; } @@ -137,8 +136,8 @@ export class QueryBarUI extends Component { nextState.query = nextQuery; } if (nextDateRange) { - nextState.from = nextDateRange.from; - nextState.to = nextDateRange.to; + nextState.dateRangeFrom = nextDateRange.dateRangeFrom; + nextState.dateRangeTo = nextDateRange.dateRangeTo; } return nextState; } @@ -146,8 +145,8 @@ export class QueryBarUI extends Component { // @ts-ignore private static defaultProps = { showDatePicker: false, - from: 'now-15m', - to: 'now', + dateRangeFrom: 'now-15m', + dateRangeTo: 'now', }; /* @@ -173,8 +172,8 @@ export class QueryBarUI extends Component { index: null, suggestions: [], suggestionLimit: 50, - from: this.props.from, - to: this.props.to, + dateRangeFrom: this.props.dateRangeFrom, + dateRangeTo: this.props.dateRangeTo, isDateRangeInvalid: false, }; @@ -193,8 +192,8 @@ export class QueryBarUI extends Component { public isDirty = () => { return ( this.state.query.query !== this.props.query.query || - this.state.from !== this.props.from || - this.state.to !== this.props.to + this.state.dateRangeFrom !== this.props.dateRangeFrom || + this.state.dateRangeTo !== this.props.dateRangeTo ); }; @@ -376,8 +375,8 @@ export class QueryBarUI extends Component { isInvalid: boolean; }) => { this.setState({ - from: start, - to: end, + dateRangeFrom: start, + dateRangeTo: end, isDateRangeInvalid: isInvalid, }); }; @@ -469,8 +468,8 @@ export class QueryBarUI extends Component { } timeHistory.add({ - from: this.state.from, - to: this.state.to, + from: this.state.dateRangeFrom, + to: this.state.dateRangeTo, }); this.props.onSubmit({ @@ -479,8 +478,8 @@ export class QueryBarUI extends Component { language: this.state.query.language, }, dateRange: { - from: this.state.from, - to: this.state.to, + from: this.state.dateRangeFrom, + to: this.state.dateRangeTo, }, }); this.setState({ isSuggestionsVisible: false }); @@ -503,8 +502,8 @@ export class QueryBarUI extends Component { language, }, dateRange: { - from: this.props.from, - to: this.props.to, + from: this.props.dateRangeFrom, + to: this.props.dateRangeTo, }, }); }; @@ -671,9 +670,9 @@ export class QueryBarUI extends Component { return ( void; + get: () => TimeRange[]; +} + +export const timeHistory: TimeHistory; diff --git a/x-pack/plugins/gis/public/angular/map.html b/x-pack/plugins/gis/public/angular/map.html index bb30c7461cbe18..8bffe59548dcde 100644 --- a/x-pack/plugins/gis/public/angular/map.html +++ b/x-pack/plugins/gis/public/angular/map.html @@ -28,9 +28,9 @@ on-submit="updateQueryAndDispatch" index-patterns="indexPatterns" show-date-picker="showDatePicker" - from="time.from" - to="time.to" - is-paused="refreshConfig.isPaused" + date-range-from="time.from" + date-range-to="time.to" + is-refresh-paused="refreshConfig.isPaused" refresh-interval="refreshConfig.interval" on-refresh-change="onRefreshChange" > From 5aebec31535b0edd78ded3d5af6df9823b75ec4c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 30 Jan 2019 10:25:11 -0700 Subject: [PATCH 16/20] remove ts-ignore comment from defaultProps, fix query_bar snapshot --- .../__snapshots__/query_bar.test.tsx.snap | 57 ++++++------------- .../public/query_bar/components/query_bar.tsx | 13 ++--- 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap b/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap index fb092e9f684c01..ec7b3c074e89b3 100644 --- a/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap +++ b/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap @@ -3,6 +3,7 @@ exports[`QueryBar Should disable autoFocus on EuiFieldText when disableAutoFocus prop is true 1`] = ` - - - + /> `; @@ -112,6 +105,7 @@ exports[`QueryBar Should disable autoFocus on EuiFieldText when disableAutoFocus exports[`QueryBar Should pass the query language to the language switcher 1`] = ` - - - + /> `; @@ -221,6 +207,7 @@ exports[`QueryBar Should pass the query language to the language switcher 1`] = exports[`QueryBar Should render the given query 1`] = ` - - - + /> `; diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index 24e100c5345183..068aa3735c78e1 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -100,6 +100,12 @@ interface State { } export class QueryBarUI extends Component { + public static defaultProps = { + showDatePicker: false, + dateRangeFrom: 'now-15m', + dateRangeTo: 'now', + }; + public static getDerivedStateFromProps(nextProps: Props, prevState: State) { if (isEqual(prevState.currentProps, nextProps)) { return null; @@ -142,13 +148,6 @@ export class QueryBarUI extends Component { return nextState; } - // @ts-ignore - private static defaultProps = { - showDatePicker: false, - dateRangeFrom: 'now-15m', - dateRangeTo: 'now', - }; - /* Keep the "draft" value in local state until the user actually submits the query. There are a couple advantages: From 338ef57ae6b5cdb7ef0b15203668fae919b41830 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 30 Jan 2019 11:28:59 -0700 Subject: [PATCH 17/20] make new props optional --- src/ui/public/query_bar/components/query_bar.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index 068aa3735c78e1..85037221578034 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -78,11 +78,11 @@ interface Props { indexPatterns: IndexPattern[]; store: Storage; intl: InjectedIntl; - showDatePicker: boolean; - dateRangeFrom: string; - dateRangeTo: string; - isRefreshPaused: boolean; - refreshInterval: number; + showDatePicker?: boolean; + dateRangeFrom?: string; + dateRangeTo?: string; + isRefreshPaused?: boolean; + refreshInterval?: number; onRefreshChange?: (isPaused: boolean, refreshInterval: number) => void; } From f28b1b26e47c28adeeba6a0a7bac1c2e47b1287d Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 30 Jan 2019 12:31:02 -0700 Subject: [PATCH 18/20] fighting with ts linter --- src/ui/public/query_bar/components/query_bar.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index 85037221578034..f311de386c0986 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -20,6 +20,7 @@ import { IndexPattern } from 'ui/index_patterns'; import classNames from 'classnames'; +import _ from 'lodash'; import { compact, debounce, get, isEqual } from 'lodash'; import React, { Component } from 'react'; import { getFromLegacyIndexPattern } from 'ui/index_patterns/static_utils'; @@ -100,12 +101,6 @@ interface State { } export class QueryBarUI extends Component { - public static defaultProps = { - showDatePicker: false, - dateRangeFrom: 'now-15m', - dateRangeTo: 'now', - }; - public static getDerivedStateFromProps(nextProps: Props, prevState: State) { if (isEqual(prevState.currentProps, nextProps)) { return null; @@ -171,8 +166,8 @@ export class QueryBarUI extends Component { index: null, suggestions: [], suggestionLimit: 50, - dateRangeFrom: this.props.dateRangeFrom, - dateRangeTo: this.props.dateRangeTo, + dateRangeFrom: _.get(this.props, 'dateRangeFrom', 'now-15m'), + dateRangeTo: _.get(this.props, 'dateRangeTo', 'now'), isDateRangeInvalid: false, }; @@ -501,8 +496,8 @@ export class QueryBarUI extends Component { language, }, dateRange: { - from: this.props.dateRangeFrom, - to: this.props.dateRangeTo, + from: this.state.dateRangeFrom, + to: this.state.dateRangeTo, }, }); }; From 9f1ee6e86d6b6ca7813afd568aabf9d254356f06 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 30 Jan 2019 13:31:50 -0700 Subject: [PATCH 19/20] do not include dateRangeFrom and dateRangeTo in isDirty when shoDateRange is not true --- src/ui/public/query_bar/components/query_bar.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index f311de386c0986..5ffae8dc24743f 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -184,6 +184,10 @@ export class QueryBarUI extends Component { private persistedLog: PersistedLog | null = null; public isDirty = () => { + if (!this.props.showDatePicker) { + return this.state.query.query !== this.props.query.query; + } + return ( this.state.query.query !== this.props.query.query || this.state.dateRangeFrom !== this.props.dateRangeFrom || From 0b9c432c606cd15d1abe5b1883581fa042358f49 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 30 Jan 2019 16:00:32 -0700 Subject: [PATCH 20/20] pull initial query from UI settings --- x-pack/plugins/gis/public/angular/get_initial_query.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/gis/public/angular/get_initial_query.js b/x-pack/plugins/gis/public/angular/get_initial_query.js index 4743832c113bed..e30e7e4ad9f58d 100644 --- a/x-pack/plugins/gis/public/angular/get_initial_query.js +++ b/x-pack/plugins/gis/public/angular/get_initial_query.js @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -const DEFAULT_QUERY_LANGUAGE = 'kuery'; +import chrome from 'ui/chrome'; + +const settings = chrome.getUiSettingsClient(); export function getInitialQuery({ mapStateJSON, @@ -25,6 +27,6 @@ export function getInitialQuery({ return { query: '', - language: userQueryLanguage || DEFAULT_QUERY_LANGUAGE + language: userQueryLanguage || settings.get('search:queryLanguage') }; }