mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Synchronise time ranges in split mode (#19274)
* Explore: create connected sync button when screen is splitted * Explore: create attachable button to TimePicker * WIP/Explore: set up redux boilerplate for synced state * WIP/Explore: add toggling functionality to sync buttons * WIP/Explore: Fix styling issue * First pass solution working * Explore: Clean up, update comments * Explore: refactore Timepicker, remove newly introduced class names * Explore: refactore ExploreTimeControls * Explore: more semantic variables naming * Explore: run query on syncable item when synced times activated * Explore: Add tooltip to sync times button * Explore: Remove typo * Explore: check exploreId * Explore: refactor ExploreTimeControls * Explore: refactor to include suggested changes * Explore: Create TimeSyncButton component, update colors * Explore: Toggle tooltip, use stylesFactory
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { PureComponent, createRef } from 'react';
|
import React, { PureComponent, createRef } from 'react';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import memoizeOne from 'memoize-one';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { ButtonSelect } from '../Select/ButtonSelect';
|
import { ButtonSelect } from '../Select/ButtonSelect';
|
||||||
@@ -11,15 +14,41 @@ import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper'
|
|||||||
import { isDateTime, DateTime } from '@grafana/data';
|
import { isDateTime, DateTime } from '@grafana/data';
|
||||||
import { rangeUtil } from '@grafana/data';
|
import { rangeUtil } from '@grafana/data';
|
||||||
import { rawToTimeRange } from './time';
|
import { rawToTimeRange } from './time';
|
||||||
|
import { withTheme } from '../../themes/ThemeContext';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TimeRange, TimeOption, TimeZone, TIME_FORMAT, SelectableValue } from '@grafana/data';
|
import { TimeRange, TimeOption, TimeZone, TIME_FORMAT, SelectableValue, dateMath } from '@grafana/data';
|
||||||
import { dateMath } from '@grafana/data';
|
import { GrafanaTheme } from '../../types/theme';
|
||||||
|
import { Themeable } from '../../types';
|
||||||
|
|
||||||
export interface Props {
|
const getStyles = memoizeOne((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
timePickerSynced: css`
|
||||||
|
label: timePickerSynced;
|
||||||
|
border-color: ${theme.colors.orangeDark};
|
||||||
|
background-image: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: ${theme.colors.orangeDark};
|
||||||
|
&:focus,
|
||||||
|
:hover {
|
||||||
|
color: ${theme.colors.orangeDark};
|
||||||
|
background-image: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
noRightBorderStyle: css`
|
||||||
|
label: noRightBorderStyle;
|
||||||
|
border-right: 0;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface Props extends Themeable {
|
||||||
value: TimeRange;
|
value: TimeRange;
|
||||||
selectOptions: TimeOption[];
|
selectOptions: TimeOption[];
|
||||||
timeZone?: TimeZone;
|
timeZone?: TimeZone;
|
||||||
|
timeSyncButton?: JSX.Element;
|
||||||
|
isSynced?: boolean;
|
||||||
onChange: (timeRange: TimeRange) => void;
|
onChange: (timeRange: TimeRange) => void;
|
||||||
onMoveBackward: () => void;
|
onMoveBackward: () => void;
|
||||||
onMoveForward: () => void;
|
onMoveForward: () => void;
|
||||||
@@ -70,7 +99,7 @@ const defaultZoomOutTooltip = () => {
|
|||||||
export interface State {
|
export interface State {
|
||||||
isCustomOpen: boolean;
|
isCustomOpen: boolean;
|
||||||
}
|
}
|
||||||
export class TimePicker extends PureComponent<Props, State> {
|
class UnThemedTimePicker extends PureComponent<Props, State> {
|
||||||
pickerTriggerRef = createRef<HTMLDivElement>();
|
pickerTriggerRef = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
@@ -120,7 +149,19 @@ export class TimePicker extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selectOptions: selectTimeOptions, value, onMoveBackward, onMoveForward, onZoom, timeZone } = this.props;
|
const {
|
||||||
|
selectOptions: selectTimeOptions,
|
||||||
|
value,
|
||||||
|
onMoveBackward,
|
||||||
|
onMoveForward,
|
||||||
|
onZoom,
|
||||||
|
timeZone,
|
||||||
|
timeSyncButton,
|
||||||
|
isSynced,
|
||||||
|
theme,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const styles = getStyles(theme);
|
||||||
const { isCustomOpen } = this.state;
|
const { isCustomOpen } = this.state;
|
||||||
const options = this.mapTimeOptionsToSelectableValues(selectTimeOptions);
|
const options = this.mapTimeOptionsToSelectableValues(selectTimeOptions);
|
||||||
const currentOption = options.find(item => isTimeOptionEqualToTimeRange(item.value, value));
|
const currentOption = options.find(item => isTimeOptionEqualToTimeRange(item.value, value));
|
||||||
@@ -152,7 +193,10 @@ export class TimePicker extends PureComponent<Props, State> {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
className="time-picker-button-select"
|
className={classNames('time-picker-button-select', {
|
||||||
|
[`btn--radius-right-0 ${styles.noRightBorderStyle}`]: timeSyncButton,
|
||||||
|
[styles.timePickerSynced]: timeSyncButton ? isSynced : null,
|
||||||
|
})}
|
||||||
value={currentOption}
|
value={currentOption}
|
||||||
label={label}
|
label={label}
|
||||||
options={options}
|
options={options}
|
||||||
@@ -161,6 +205,9 @@ export class TimePicker extends PureComponent<Props, State> {
|
|||||||
iconClass={'fa fa-clock-o fa-fw'}
|
iconClass={'fa fa-clock-o fa-fw'}
|
||||||
tooltipContent={<TimePickerTooltipContent timeRange={value} />}
|
tooltipContent={<TimePickerTooltipContent timeRange={value} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{timeSyncButton}
|
||||||
|
|
||||||
{isAbsolute && (
|
{isAbsolute && (
|
||||||
<button className="btn navbar-button navbar-button--tight" onClick={onMoveForward}>
|
<button className="btn navbar-button navbar-button--tight" onClick={onMoveForward}>
|
||||||
<i className="fa fa-chevron-right" />
|
<i className="fa fa-chevron-right" />
|
||||||
@@ -195,3 +242,5 @@ const TimePickerTooltipContent = ({ timeRange }: { timeRange: TimeRange }) => (
|
|||||||
function isTimeOptionEqualToTimeRange(option: TimeOption, range: TimeRange): boolean {
|
function isTimeOptionEqualToTimeRange(option: TimeOption, range: TimeRange): boolean {
|
||||||
return range.raw.from === option.from && range.raw.to === option.to;
|
return range.raw.from === option.from && range.raw.to === option.to;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TimePicker = withTheme(UnThemedTimePicker);
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ interface ExploreProps {
|
|||||||
mode: ExploreMode;
|
mode: ExploreMode;
|
||||||
initialUI: ExploreUIState;
|
initialUI: ExploreUIState;
|
||||||
isLive: boolean;
|
isLive: boolean;
|
||||||
|
syncedTimes: boolean;
|
||||||
updateTimeRange: typeof updateTimeRange;
|
updateTimeRange: typeof updateTimeRange;
|
||||||
graphResult?: GraphSeriesXY[];
|
graphResult?: GraphSeriesXY[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
@@ -178,7 +179,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
|
|
||||||
onChangeTime = (rawRange: RawTimeRange) => {
|
onChangeTime = (rawRange: RawTimeRange) => {
|
||||||
const { updateTimeRange, exploreId } = this.props;
|
const { updateTimeRange, exploreId } = this.props;
|
||||||
|
|
||||||
updateTimeRange({ exploreId, rawRange });
|
updateTimeRange({ exploreId, rawRange });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onUpdateTimeRange = (absoluteRange: AbsoluteTimeRange) => {
|
onUpdateTimeRange = (absoluteRange: AbsoluteTimeRange) => {
|
||||||
const { updateTimeRange, exploreId } = this.props;
|
const { exploreId, updateTimeRange } = this.props;
|
||||||
updateTimeRange({ exploreId, absoluteRange });
|
updateTimeRange({ exploreId, absoluteRange });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -263,6 +263,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
showingTable,
|
showingTable,
|
||||||
timeZone,
|
timeZone,
|
||||||
queryResponse,
|
queryResponse,
|
||||||
|
syncedTimes,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const exploreClass = split ? 'explore explore-split' : 'explore';
|
const exploreClass = split ? 'explore explore-split' : 'explore';
|
||||||
const styles = getStyles();
|
const styles = getStyles();
|
||||||
@@ -326,6 +327,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
<LogsContainer
|
<LogsContainer
|
||||||
width={width}
|
width={width}
|
||||||
exploreId={exploreId}
|
exploreId={exploreId}
|
||||||
|
syncedTimes={syncedTimes}
|
||||||
onClickLabel={this.onClickLabel}
|
onClickLabel={this.onClickLabel}
|
||||||
onStartScanning={this.onStartScanning}
|
onStartScanning={this.onStartScanning}
|
||||||
onStopScanning={this.onStopScanning}
|
onStopScanning={this.onStopScanning}
|
||||||
@@ -350,7 +352,7 @@ const getTimeRangeFromUrlMemoized = memoizeOne(getTimeRangeFromUrl);
|
|||||||
|
|
||||||
function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partial<ExploreProps> {
|
function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partial<ExploreProps> {
|
||||||
const explore = state.explore;
|
const explore = state.explore;
|
||||||
const { split } = explore;
|
const { split, syncedTimes } = explore;
|
||||||
const item: ExploreItemState = explore[exploreId];
|
const item: ExploreItemState = explore[exploreId];
|
||||||
const timeZone = getTimeZone(state.user);
|
const timeZone = getTimeZone(state.user);
|
||||||
const {
|
const {
|
||||||
@@ -421,6 +423,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia
|
|||||||
absoluteRange,
|
absoluteRange,
|
||||||
queryResponse,
|
queryResponse,
|
||||||
originPanelId,
|
originPanelId,
|
||||||
|
syncedTimes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { TimeRange, TimeOption, TimeZone, RawTimeRange, dateTimeForTimeZone } fr
|
|||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { TimePicker } from '@grafana/ui';
|
import { TimePicker } from '@grafana/ui';
|
||||||
|
import { TimeSyncButton } from './TimeSyncButton';
|
||||||
|
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { defaultSelectOptions } from '@grafana/ui/src/components/TimePicker/TimePicker';
|
import { defaultSelectOptions } from '@grafana/ui/src/components/TimePicker/TimePicker';
|
||||||
@@ -18,6 +19,9 @@ export interface Props {
|
|||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
range: TimeRange;
|
range: TimeRange;
|
||||||
timeZone: TimeZone;
|
timeZone: TimeZone;
|
||||||
|
splitted: boolean;
|
||||||
|
syncedTimes: boolean;
|
||||||
|
onChangeTimeSync: () => void;
|
||||||
onChangeTime: (range: RawTimeRange) => void;
|
onChangeTime: (range: RawTimeRange) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,18 +71,18 @@ export class ExploreTimeControls extends Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { range, timeZone } = this.props;
|
const { range, timeZone, splitted, syncedTimes, onChangeTimeSync } = this.props;
|
||||||
|
const timeSyncButton = splitted ? <TimeSyncButton onClick={onChangeTimeSync} isSynced={syncedTimes} /> : null;
|
||||||
|
const timePickerCommonProps = {
|
||||||
|
value: range,
|
||||||
|
onChange: this.onChangeTimePicker,
|
||||||
|
timeZone,
|
||||||
|
onMoveBackward: this.onMoveBack,
|
||||||
|
onMoveForward: this.onMoveForward,
|
||||||
|
onZoom: this.onZoom,
|
||||||
|
selectOptions: this.setActiveTimeOption(defaultSelectOptions, range.raw),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return <TimePicker {...timePickerCommonProps} timeSyncButton={timeSyncButton} isSynced={syncedTimes} />;
|
||||||
<TimePicker
|
|
||||||
value={range}
|
|
||||||
onChange={this.onChangeTimePicker}
|
|
||||||
timeZone={timeZone}
|
|
||||||
onMoveBackward={this.onMoveBack}
|
|
||||||
onMoveForward={this.onMoveForward}
|
|
||||||
onZoom={this.onZoom}
|
|
||||||
selectOptions={this.setActiveTimeOption(defaultSelectOptions, range.raw)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
splitClose,
|
splitClose,
|
||||||
runQueries,
|
runQueries,
|
||||||
splitOpen,
|
splitOpen,
|
||||||
|
syncTimes,
|
||||||
changeRefreshInterval,
|
changeRefreshInterval,
|
||||||
changeMode,
|
changeMode,
|
||||||
clearOrigin,
|
clearOrigin,
|
||||||
@@ -60,6 +61,7 @@ interface StateProps {
|
|||||||
timeZone: TimeZone;
|
timeZone: TimeZone;
|
||||||
selectedDatasource: DataSourceSelectItem;
|
selectedDatasource: DataSourceSelectItem;
|
||||||
splitted: boolean;
|
splitted: boolean;
|
||||||
|
syncedTimes: boolean;
|
||||||
refreshInterval: string;
|
refreshInterval: string;
|
||||||
supportedModes: ExploreMode[];
|
supportedModes: ExploreMode[];
|
||||||
selectedMode: ExploreMode;
|
selectedMode: ExploreMode;
|
||||||
@@ -77,6 +79,7 @@ interface DispatchProps {
|
|||||||
runQueries: typeof runQueries;
|
runQueries: typeof runQueries;
|
||||||
closeSplit: typeof splitClose;
|
closeSplit: typeof splitClose;
|
||||||
split: typeof splitOpen;
|
split: typeof splitOpen;
|
||||||
|
syncTimes: typeof syncTimes;
|
||||||
changeRefreshInterval: typeof changeRefreshInterval;
|
changeRefreshInterval: typeof changeRefreshInterval;
|
||||||
changeMode: typeof changeMode;
|
changeMode: typeof changeMode;
|
||||||
clearOrigin: typeof clearOrigin;
|
clearOrigin: typeof clearOrigin;
|
||||||
@@ -112,6 +115,11 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
|
|||||||
changeMode(exploreId, mode);
|
changeMode(exploreId, mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onChangeTimeSync = () => {
|
||||||
|
const { syncTimes, exploreId } = this.props;
|
||||||
|
syncTimes(exploreId);
|
||||||
|
};
|
||||||
|
|
||||||
returnToPanel = async ({ withChanges = false } = {}) => {
|
returnToPanel = async ({ withChanges = false } = {}) => {
|
||||||
const { originPanelId } = this.props;
|
const { originPanelId } = this.props;
|
||||||
|
|
||||||
@@ -148,6 +156,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
|
|||||||
timeZone,
|
timeZone,
|
||||||
selectedDatasource,
|
selectedDatasource,
|
||||||
splitted,
|
splitted,
|
||||||
|
syncedTimes,
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
onChangeTime,
|
onChangeTime,
|
||||||
split,
|
split,
|
||||||
@@ -259,6 +268,9 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
|
|||||||
range={range}
|
range={range}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
onChangeTime={onChangeTime}
|
onChangeTime={onChangeTime}
|
||||||
|
splitted={splitted}
|
||||||
|
syncedTimes={syncedTimes}
|
||||||
|
onChangeTimeSync={this.onChangeTimeSync}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -305,6 +317,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
|
const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
|
||||||
const splitted = state.explore.split;
|
const splitted = state.explore.split;
|
||||||
|
const syncedTimes = state.explore.syncedTimes;
|
||||||
const exploreItem: ExploreItemState = state.explore[exploreId];
|
const exploreItem: ExploreItemState = state.explore[exploreId];
|
||||||
const {
|
const {
|
||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
@@ -343,6 +356,7 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps
|
|||||||
isPaused,
|
isPaused,
|
||||||
originPanelId,
|
originPanelId,
|
||||||
queries,
|
queries,
|
||||||
|
syncedTimes,
|
||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -355,6 +369,7 @@ const mapDispatchToProps: DispatchProps = {
|
|||||||
runQueries,
|
runQueries,
|
||||||
closeSplit: splitClose,
|
closeSplit: splitClose,
|
||||||
split: splitOpen,
|
split: splitOpen,
|
||||||
|
syncTimes,
|
||||||
changeMode: changeMode,
|
changeMode: changeMode,
|
||||||
clearOrigin,
|
clearOrigin,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ interface LogsContainerProps {
|
|||||||
isLive: boolean;
|
isLive: boolean;
|
||||||
updateTimeRange: typeof updateTimeRange;
|
updateTimeRange: typeof updateTimeRange;
|
||||||
range: TimeRange;
|
range: TimeRange;
|
||||||
|
syncedTimes: boolean;
|
||||||
absoluteRange: AbsoluteTimeRange;
|
absoluteRange: AbsoluteTimeRange;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,6 @@ interface LogsContainerProps {
|
|||||||
export class LogsContainer extends PureComponent<LogsContainerProps> {
|
export class LogsContainer extends PureComponent<LogsContainerProps> {
|
||||||
onChangeTime = (absoluteRange: AbsoluteTimeRange) => {
|
onChangeTime = (absoluteRange: AbsoluteTimeRange) => {
|
||||||
const { exploreId, updateTimeRange } = this.props;
|
const { exploreId, updateTimeRange } = this.props;
|
||||||
|
|
||||||
updateTimeRange({ exploreId, absoluteRange });
|
updateTimeRange({ exploreId, absoluteRange });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
60
public/app/features/explore/TimeSyncButton.tsx
Normal file
60
public/app/features/explore/TimeSyncButton.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
|
import { GrafanaTheme, useTheme, stylesFactory } from '@grafana/ui';
|
||||||
|
|
||||||
|
//Components
|
||||||
|
import { Tooltip } from '@grafana/ui';
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
timePickerSynced: css`
|
||||||
|
label: timePickerSynced;
|
||||||
|
border-color: ${theme.colors.orangeDark};
|
||||||
|
background-image: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: ${theme.colors.orangeDark};
|
||||||
|
&:focus,
|
||||||
|
:hover {
|
||||||
|
color: ${theme.colors.orangeDark};
|
||||||
|
background-image: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
noRightBorderStyle: css`
|
||||||
|
label: noRightBorderStyle;
|
||||||
|
border-right: 0;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
interface TimeSyncButtonProps {
|
||||||
|
isSynced: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TimeSyncButton(props: TimeSyncButtonProps) {
|
||||||
|
const { onClick, isSynced } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
const syncTimesTooltip = () => {
|
||||||
|
const { isSynced } = props;
|
||||||
|
const tooltip = isSynced ? 'Unsync all views' : 'Sync all views to this time range';
|
||||||
|
return <>{tooltip}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip content={syncTimesTooltip} placement="bottom">
|
||||||
|
<button
|
||||||
|
className={classNames('btn navbar-button navbar-button--attached', {
|
||||||
|
[styles.timePickerSynced]: isSynced,
|
||||||
|
})}
|
||||||
|
onClick={() => onClick()}
|
||||||
|
>
|
||||||
|
<i className="fa fa-link" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { actionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFact
|
|||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
SplitOpen = 'explore/SPLIT_OPEN',
|
SplitOpen = 'explore/SPLIT_OPEN',
|
||||||
ResetExplore = 'explore/RESET_EXPLORE',
|
ResetExplore = 'explore/RESET_EXPLORE',
|
||||||
|
SyncTimes = 'explore/SYNC_TIMES',
|
||||||
}
|
}
|
||||||
export interface SplitOpenAction {
|
export interface SplitOpenAction {
|
||||||
type: ActionTypes.SplitOpen;
|
type: ActionTypes.SplitOpen;
|
||||||
@@ -26,6 +27,10 @@ export interface ResetExploreAction {
|
|||||||
payload: {};
|
payload: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SyncTimesAction {
|
||||||
|
type: ActionTypes.SyncTimes;
|
||||||
|
payload: { syncedTimes: boolean };
|
||||||
|
}
|
||||||
/** Lower order actions
|
/** Lower order actions
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -165,6 +170,10 @@ export interface SplitOpenPayload {
|
|||||||
itemState: ExploreItemState;
|
itemState: ExploreItemState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SyncTimesPayload {
|
||||||
|
syncedTimes: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ToggleTablePayload {
|
export interface ToggleTablePayload {
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
}
|
}
|
||||||
@@ -352,6 +361,7 @@ export const splitCloseAction = actionCreatorFactory<SplitCloseActionPayload>('e
|
|||||||
*/
|
*/
|
||||||
export const splitOpenAction = actionCreatorFactory<SplitOpenPayload>('explore/SPLIT_OPEN').create();
|
export const splitOpenAction = actionCreatorFactory<SplitOpenPayload>('explore/SPLIT_OPEN').create();
|
||||||
|
|
||||||
|
export const syncTimesAction = actionCreatorFactory<SyncTimesPayload>('explore/SYNC_TIMES').create();
|
||||||
/**
|
/**
|
||||||
* Update state of Explores UI elements (panels visiblity and deduplication strategy)
|
* Update state of Explores UI elements (panels visiblity and deduplication strategy)
|
||||||
*/
|
*/
|
||||||
@@ -410,4 +420,5 @@ export type HigherOrderAction =
|
|||||||
| ActionOf<SplitCloseActionPayload>
|
| ActionOf<SplitCloseActionPayload>
|
||||||
| SplitOpenAction
|
| SplitOpenAction
|
||||||
| ResetExploreAction
|
| ResetExploreAction
|
||||||
|
| SyncTimesAction
|
||||||
| ActionOf<any>;
|
| ActionOf<any>;
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ import {
|
|||||||
queryStreamUpdatedAction,
|
queryStreamUpdatedAction,
|
||||||
queryStoreSubscriptionAction,
|
queryStoreSubscriptionAction,
|
||||||
clearOriginAction,
|
clearOriginAction,
|
||||||
|
syncTimesAction,
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
|
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
|
||||||
import { getTimeZone } from 'app/features/profile/state/selectors';
|
import { getTimeZone } from 'app/features/profile/state/selectors';
|
||||||
@@ -182,12 +183,19 @@ export const updateTimeRange = (options: {
|
|||||||
rawRange?: RawTimeRange;
|
rawRange?: RawTimeRange;
|
||||||
absoluteRange?: AbsoluteTimeRange;
|
absoluteRange?: AbsoluteTimeRange;
|
||||||
}): ThunkResult<void> => {
|
}): ThunkResult<void> => {
|
||||||
return dispatch => {
|
return (dispatch, getState) => {
|
||||||
dispatch(updateTime({ ...options }));
|
const { syncedTimes } = getState().explore;
|
||||||
dispatch(runQueries(options.exploreId));
|
if (syncedTimes) {
|
||||||
|
dispatch(updateTime({ ...options, exploreId: ExploreId.left }));
|
||||||
|
dispatch(runQueries(ExploreId.left));
|
||||||
|
dispatch(updateTime({ ...options, exploreId: ExploreId.right }));
|
||||||
|
dispatch(runQueries(ExploreId.right));
|
||||||
|
} else {
|
||||||
|
dispatch(updateTime({ ...options }));
|
||||||
|
dispatch(runQueries(options.exploreId));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the refresh interval of Explore. Called from the Refresh picker.
|
* Change the refresh interval of Explore. Called from the Refresh picker.
|
||||||
*/
|
*/
|
||||||
@@ -674,6 +682,25 @@ export function splitOpen(): ThunkResult<void> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs time interval, if they are not synced on both panels in a split mode.
|
||||||
|
* Unsyncs time interval, if they are synced on both panels in a split mode.
|
||||||
|
*/
|
||||||
|
export function syncTimes(exploreId: ExploreId): ThunkResult<void> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
if (exploreId === ExploreId.left) {
|
||||||
|
const leftState = getState().explore.left;
|
||||||
|
dispatch(updateTimeRange({ exploreId: ExploreId.right, rawRange: leftState.range.raw }));
|
||||||
|
} else {
|
||||||
|
const rightState = getState().explore.right;
|
||||||
|
dispatch(updateTimeRange({ exploreId: ExploreId.left, rawRange: rightState.range.raw }));
|
||||||
|
}
|
||||||
|
const isTimeSynced = getState().explore.syncedTimes;
|
||||||
|
dispatch(syncTimesAction({ syncedTimes: !isTimeSynced }));
|
||||||
|
dispatch(stateSave());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates action to collapse graph/logs/table panel. When panel is collapsed,
|
* Creates action to collapse graph/logs/table panel. When panel is collapsed,
|
||||||
* queries won't be run
|
* queries won't be run
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export const createEmptyQueryResponse = (): PanelData => ({
|
|||||||
export const initialExploreItemState = makeExploreItemState();
|
export const initialExploreItemState = makeExploreItemState();
|
||||||
export const initialExploreState: ExploreState = {
|
export const initialExploreState: ExploreState = {
|
||||||
split: null,
|
split: null,
|
||||||
|
syncedTimes: false,
|
||||||
left: initialExploreItemState,
|
left: initialExploreItemState,
|
||||||
right: initialExploreItemState,
|
right: initialExploreItemState,
|
||||||
};
|
};
|
||||||
@@ -727,6 +728,9 @@ export const exploreReducer = (state = initialExploreState, action: HigherOrderA
|
|||||||
case ActionTypes.SplitOpen: {
|
case ActionTypes.SplitOpen: {
|
||||||
return { ...state, split: true, right: { ...action.payload.itemState } };
|
return { ...state, split: true, right: { ...action.payload.itemState } };
|
||||||
}
|
}
|
||||||
|
case ActionTypes.SyncTimes: {
|
||||||
|
return { ...state, syncedTimes: action.payload.syncedTimes };
|
||||||
|
}
|
||||||
|
|
||||||
case ActionTypes.ResetExplore: {
|
case ActionTypes.ResetExplore: {
|
||||||
if (action.payload.force || !Number.isInteger(state.left.originPanelId)) {
|
if (action.payload.force || !Number.isInteger(state.left.originPanelId)) {
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ export interface ExploreState {
|
|||||||
* True if split view is active.
|
* True if split view is active.
|
||||||
*/
|
*/
|
||||||
split: boolean;
|
split: boolean;
|
||||||
|
/**
|
||||||
|
* True if time interval for panels are synced. Only possible with split mode.
|
||||||
|
*/
|
||||||
|
syncedTimes: boolean;
|
||||||
/**
|
/**
|
||||||
* Explore state of the left split (left is default in non-split view).
|
* Explore state of the left split (left is default in non-split view).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -72,9 +72,11 @@ export const mockExploreState = (options: any = {}) => {
|
|||||||
range,
|
range,
|
||||||
};
|
};
|
||||||
const split: boolean = options.split || false;
|
const split: boolean = options.split || false;
|
||||||
|
const syncedTimes: boolean = options.syncedTimes || false;
|
||||||
const explore: ExploreState = {
|
const explore: ExploreState = {
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
|
syncedTimes,
|
||||||
split,
|
split,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user