Skip to content

Commit

Permalink
[Infra UI] Add time controls and live streaming to waffle map ui (#22637
Browse files Browse the repository at this point in the history
)

This adds a datepicker and an accompanying auto-refresh toggle button to the toolbar of the waffle map.
  • Loading branch information
weltenwort authored Sep 6, 2018
1 parent 491d4cc commit 3773fe2
Show file tree
Hide file tree
Showing 16 changed files with 328 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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 { EuiButtonEmpty, EuiDatePicker, EuiFormControlLayout } from '@elastic/eui';
import moment, { Moment } from 'moment';
import React from 'react';

interface WaffleTimeControlsProps {
currentTime: number;
isLiveStreaming?: boolean;
onChangeTime?: (time: number) => void;
startLiveStreaming?: () => void;
stopLiveStreaming?: () => void;
}

export class WaffleTimeControls extends React.Component<WaffleTimeControlsProps> {
public render() {
const { currentTime, isLiveStreaming } = this.props;

const currentMoment = moment(currentTime);

const liveStreamingButton = isLiveStreaming ? (
<EuiButtonEmpty
color="primary"
iconSide="left"
iconType="pause"
onClick={this.stopLiveStreaming}
>
Stop refreshing
</EuiButtonEmpty>
) : (
<EuiButtonEmpty iconSide="left" iconType="play" onClick={this.startLiveStreaming}>
Auto-refresh
</EuiButtonEmpty>
);

return (
<EuiFormControlLayout append={liveStreamingButton}>
<EuiDatePicker
className="euiFieldText--inGroup"
dateFormat="L LTS"
disabled={isLiveStreaming}
injectTimes={currentMoment ? [currentMoment] : []}
isLoading={isLiveStreaming}
onChange={this.handleChangeDate}
popperPlacement="top-end"
selected={currentMoment}
shouldCloseOnSelect
showTimeSelect
timeFormat="LT"
/>
</EuiFormControlLayout>
);
}

private handleChangeDate = (time: Moment) => {
const { onChangeTime } = this.props;

if (onChangeTime) {
onChangeTime(time.valueOf());
}
};

private startLiveStreaming = () => {
const { startLiveStreaming } = this.props;

if (startLiveStreaming) {
startLiveStreaming();
}
};

private stopLiveStreaming = () => {
const { stopLiveStreaming } = this.props;

if (stopLiveStreaming) {
stopLiveStreaming();
}
};
}
26 changes: 26 additions & 0 deletions x-pack/plugins/infra/public/containers/waffle/with_waffle_time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 { connect } from 'react-redux';

import { State, waffleTimeActions, waffleTimeSelectors } from '../../store';
import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux';

export const withWaffleTime = connect(
(state: State) => ({
currentTime: waffleTimeSelectors.selectCurrentTime(state),
currentTimeRange: waffleTimeSelectors.selectCurrentTimeRange(state),
isAutoReloading: waffleTimeSelectors.selectIsAutoReloading(state),
}),
bindPlainActionCreators({
jumpToTime: waffleTimeActions.jumpToTime,
startAutoReload: waffleTimeActions.startAutoReload,
stopAutoReload: waffleTimeActions.stopAutoReload,
})
);

export const WithWaffleTime = asChildFunctionRenderer(withWaffleTime);
46 changes: 36 additions & 10 deletions x-pack/plugins/infra/public/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import { Toolbar } from '../components/eui/toolbar';
import { Header } from '../components/header';
import { ColumnarPage, PageContent } from '../components/page';
import { Waffle } from '../components/waffle';
import { WaffleTimeControls } from '../components/waffle/waffle_time_controls';

import { WithWaffleFilter } from '../containers/waffle/with_waffle_filters';
import { WithWaffleNodes } from '../containers/waffle/with_waffle_nodes';
import { WithWaffleTime } from '../containers/waffle/with_waffle_time';
import { WithKueryAutocompletion } from '../containers/with_kuery_autocompletion';
import { WithOptions } from '../containers/with_options';

Expand All @@ -23,7 +26,7 @@ export class HomePage extends React.PureComponent {
<ColumnarPage>
<Header />
<Toolbar>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="none">
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m">
<EuiFlexItem>
<WithKueryAutocompletion>
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
Expand All @@ -49,22 +52,45 @@ export class HomePage extends React.PureComponent {
)}
</WithKueryAutocompletion>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<WithWaffleTime>
{({
currentTime,
isAutoReloading,
jumpToTime,
startAutoReload,
stopAutoReload,
}) => (
<WaffleTimeControls
currentTime={currentTime}
isLiveStreaming={isAutoReloading}
onChangeTime={jumpToTime}
startLiveStreaming={startAutoReload}
stopLiveStreaming={stopAutoReload}
/>
)}
</WithWaffleTime>
</EuiFlexItem>
</EuiFlexGroup>
</Toolbar>
<PageContent>
<WithOptions>
{({ wafflemap }) => (
<WithWaffleFilter>
{({ filterQueryAsJson }) => (
<WithWaffleNodes
filterQuery={filterQueryAsJson}
metrics={wafflemap.metrics}
path={wafflemap.path}
sourceId={wafflemap.sourceId}
timerange={wafflemap.timerange}
>
{({ nodes }) => <Waffle map={nodes} options={wafflemap} />}
</WithWaffleNodes>
<WithWaffleTime>
{({ currentTimeRange }) => (
<WithWaffleNodes
filterQuery={filterQueryAsJson}
metrics={wafflemap.metrics}
path={wafflemap.path}
sourceId={wafflemap.sourceId}
timerange={currentTimeRange}
>
{({ nodes }) => <Waffle map={nodes} options={wafflemap} />}
</WithWaffleNodes>
)}
</WithWaffleTime>
)}
</WithWaffleFilter>
)}
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/infra/public/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export {
logPositionActions,
logTextviewActions,
waffleFilterActions,
waffleTimeActions,
} from './local';
export { logEntriesActions, logSummaryActions } from './remote';
1 change: 1 addition & 0 deletions x-pack/plugins/infra/public/store/local/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { logMinimapActions } from './log_minimap';
export { logPositionActions } from './log_position';
export { logTextviewActions } from './log_textview';
export { waffleFilterActions } from './waffle_filter';
export { waffleTimeActions } from './waffle_time';
4 changes: 3 additions & 1 deletion x-pack/plugins/infra/public/store/local/epic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
import { combineEpics } from 'redux-observable';

import { createLogPositionEpic } from './log_position';
import { createWaffleTimeEpic } from './waffle_time';

export const createLocalEpic = <State>() => combineEpics(createLogPositionEpic<State>());
export const createLocalEpic = <State>() =>
combineEpics(createLogPositionEpic<State>(), createWaffleTimeEpic<State>());
6 changes: 5 additions & 1 deletion x-pack/plugins/infra/public/store/local/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@ import { initialLogMinimapState, logMinimapReducer, LogMinimapState } from './lo
import { initialLogPositionState, logPositionReducer, LogPositionState } from './log_position';
import { initialLogTextviewState, logTextviewReducer, LogTextviewState } from './log_textview';
import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter';
import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './waffle_time';

export interface LocalState {
logFilter: LogFilterState;
logMinimap: LogMinimapState;
logPosition: LogPositionState;
logTextview: LogTextviewState;
waffleFilter: WaffleFilterState;
waffleTime: WaffleTimeState;
}

export const initialLocalState = {
export const initialLocalState: LocalState = {
logFilter: initialLogFilterState,
logMinimap: initialLogMinimapState,
logPosition: initialLogPositionState,
logTextview: initialLogTextviewState,
waffleFilter: initialWaffleFilterState,
waffleTime: initialWaffleTimeState,
};

export const localReducer = combineReducers<LocalState>({
Expand All @@ -34,4 +37,5 @@ export const localReducer = combineReducers<LocalState>({
logPosition: logPositionReducer,
logTextview: logTextviewReducer,
waffleFilter: waffleFilterReducer,
waffleTime: waffleTimeReducer,
});
6 changes: 6 additions & 0 deletions x-pack/plugins/infra/public/store/local/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { logPositionSelectors as innerLogPositionSelectors } from './log_positio
import { logTextviewSelectors as innerLogTextviewSelectors } from './log_textview';
import { LocalState } from './reducer';
import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter';
import { waffleTimeSelectors as innerWaffleTimeSelectors } from './waffle_time';

export const logFilterSelectors = globalizeSelectors(
(state: LocalState) => state.logFilter,
Expand All @@ -36,3 +37,8 @@ export const waffleFilterSelectors = globalizeSelectors(
(state: LocalState) => state.waffleFilter,
innerWaffleFilterSelectors
);

export const waffleTimeSelectors = globalizeSelectors(
(state: LocalState) => state.waffleTime,
innerWaffleTimeSelectors
);
15 changes: 15 additions & 0 deletions x-pack/plugins/infra/public/store/local/waffle_time/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_time');

export const jumpToTime = actionCreator<number>('JUMP_TO_TIME');

export const startAutoReload = actionCreator('START_AUTO_RELOAD');

export const stopAutoReload = actionCreator('STOP_AUTO_RELOAD');
41 changes: 41 additions & 0 deletions x-pack/plugins/infra/public/store/local/waffle_time/epic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { Action } from 'redux';
import { Epic } from 'redux-observable';
import { timer } from 'rxjs';
import { exhaustMap, filter, map, takeUntil, withLatestFrom } from 'rxjs/operators';

import { jumpToTime, startAutoReload, stopAutoReload } from './actions';

interface WaffleTimeEpicDependencies<State> {
selectWaffleTimeUpdatePolicyInterval: (state: State) => number | null;
}

export const createWaffleTimeEpic = <State>(): Epic<
Action,
Action,
State,
WaffleTimeEpicDependencies<State>
> => (action$, state$, { selectWaffleTimeUpdatePolicyInterval }) => {
const updateInterval$ = state$.pipe(
map(selectWaffleTimeUpdatePolicyInterval),
filter(isNotNull)
);

return action$.pipe(
filter(startAutoReload.match),
withLatestFrom(updateInterval$),
exhaustMap(([action, updateInterval]) =>
timer(0, updateInterval).pipe(
map(() => jumpToTime(Date.now())),
takeUntil(action$.pipe(filter(stopAutoReload.match)))
)
)
);
};

const isNotNull = <T>(value: T | null): value is T => value !== null;
12 changes: 12 additions & 0 deletions x-pack/plugins/infra/public/store/local/waffle_time/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 * as waffleTimeActions from './actions';
import * as waffleTimeSelectors from './selectors';

export { waffleTimeActions, waffleTimeSelectors };
export * from './epic';
export * from './reducer';
52 changes: 52 additions & 0 deletions x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 { combineReducers } from 'redux';
import { reducerWithInitialState } from 'typescript-fsa-reducers';

import { jumpToTime, startAutoReload, stopAutoReload } from './actions';

interface ManualTimeUpdatePolicy {
policy: 'manual';
}

interface IntervalTimeUpdatePolicy {
policy: 'interval';
interval: number;
}

type TimeUpdatePolicy = ManualTimeUpdatePolicy | IntervalTimeUpdatePolicy;

export interface WaffleTimeState {
currentTime: number;
updatePolicy: TimeUpdatePolicy;
}

export const initialWaffleTimeState: WaffleTimeState = {
currentTime: Date.now(),
updatePolicy: {
policy: 'manual',
},
};

const currentTimeReducer = reducerWithInitialState(initialWaffleTimeState.currentTime).case(
jumpToTime,
(currentTime, targetTime) => targetTime
);

const updatePolicyReducer = reducerWithInitialState(initialWaffleTimeState.updatePolicy)
.case(startAutoReload, () => ({
policy: 'interval',
interval: 5000,
}))
.case(stopAutoReload, () => ({
policy: 'manual',
}));

export const waffleTimeReducer = combineReducers<WaffleTimeState>({
currentTime: currentTimeReducer,
updatePolicy: updatePolicyReducer,
});
Loading

0 comments on commit 3773fe2

Please sign in to comment.