Skip to content

Commit

Permalink
Embed timepicker in query bar (elastic#29130)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nreese committed Jan 24, 2019
1 parent a95be51 commit ea5b6da
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ function discoverController(
.catch(notify.error);
};

$scope.updateQueryAndFetch = function (query) {
$scope.updateQueryAndFetch = function ({ query }) {
$state.query = migrateLegacyQuery(query);
$scope.fetch();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ function VisEditor(
}
}

$scope.updateQueryAndFetch = function (query) {
$scope.updateQueryAndFetch = function ({ query }) {
$state.query = migrateLegacyQuery(query);
$scope.fetch();
};
Expand Down
4 changes: 3 additions & 1 deletion src/ui/public/kbn_top_nav/kbn_top_nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
></button>

<!-- Time-picker "menu item" -->
<kbn-global-timepicker></kbn-global-timepicker>
<span ng-if="showTimepickerInTopNav">
<kbn-global-timepicker></kbn-global-timepicker>
</span>
</div>
</div>
</div>
Expand Down
8 changes: 6 additions & 2 deletions src/ui/public/kbn_top_nav/kbn_top_nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* ```
* <kbn-top-nav name="current-app-for-extensions" config="path.to.menuItems"></kbn-top-nav>
* <kbn-top-nav name="current-app-for-extensions" config="path.to.menuItems" ></kbn-top-nav>
* ```
*
* Menu items/templates are passed to the kbnTopNav via the config attribute
Expand Down Expand Up @@ -151,6 +151,10 @@ module.directive('kbnTopNav', function (Private) {

initTopNav(topNavConfig, null);

if (!_.has($scope, 'showTimepickerInTopNav')) {
$scope.showTimepickerInTopNav = true;
}

return $scope.kbnTopNav;
},

Expand Down
131 changes: 108 additions & 23 deletions src/ui/public/query_bar/components/query_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -42,6 +44,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiOutsideClickDetector,
EuiSuperDatePicker,
} from '@elastic/eui';

import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
Expand All @@ -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 {
Expand All @@ -84,6 +98,8 @@ interface State {
suggestions: AutocompleteSuggestion[];
suggestionLimit: number;
currentProps?: Props;
from: string;
to: string;
}

export class QueryBarUI extends Component<Props, State> {
Expand All @@ -92,26 +108,34 @@ export class QueryBarUI extends Component<Props, State> {
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:
Expand All @@ -136,6 +160,8 @@ export class QueryBarUI extends Component<Props, State> {
index: null,
suggestions: [],
suggestionLimit: 50,
from: this.props.from,
to: this.props.to,
};

public updateSuggestions = debounce(async () => {
Expand All @@ -151,7 +177,11 @@ export class QueryBarUI extends Component<Props, State> {
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 = () => {
Expand Down Expand Up @@ -322,6 +352,13 @@ export class QueryBarUI extends Component<Props, State> {
this.onInputChange(event.target.value);
};

public onTimeChange = ({ start, end }: { start: string; end: string }) => {
this.setState({
from: start,
to: end,
});
};

public onKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) {
this.setState({ isSuggestionsVisible: true });
Expand Down Expand Up @@ -408,9 +445,20 @@ export class QueryBarUI extends Component<Props, State> {
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 });
};
Expand All @@ -427,8 +475,14 @@ export class QueryBarUI extends Component<Props, State> {

this.props.store.set('kibana.userQueryLanguage', language);
this.props.onSubmit({
query: '',
language,
query: {
query: '',
language,
},
dateRange: {
from: this.props.from,
to: this.props.to,
},
});
};

Expand Down Expand Up @@ -533,6 +587,7 @@ export class QueryBarUI extends Component<Props, State> {
</div>
</EuiOutsideClickDetector>
</EuiFlexItem>
{this.renderDatePicker()}
<EuiFlexItem grow={false}>
<EuiButton
aria-label={this.props.intl.formatMessage({
Expand All @@ -557,6 +612,36 @@ export class QueryBarUI extends Component<Props, State> {
</EuiFlexGroup>
);
}

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 (
<EuiFlexItem grow={false}>
<EuiSuperDatePicker
start={this.state.from}
end={this.state.to}
isPaused={this.props.isPaused}
refreshInterval={this.props.refreshInterval}
onTimeChange={this.onTimeChange}
onRefreshChange={this.props.onRefreshChange}
showUpdateButton={false}
recentlyUsedRanges={recentlyUsedRanges}
/>
</EuiFlexItem>
);
}
}

export const QueryBar = injectI18n(QueryBarUI);
57 changes: 9 additions & 48 deletions x-pack/plugins/gis/public/actions/store_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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
Expand All @@ -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,
};
}

Expand Down
Loading

0 comments on commit ea5b6da

Please sign in to comment.