mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Save state in URL and fix tests
This commit is contained in:
parent
68c039b289
commit
be172d3e4a
@ -6,26 +6,13 @@ import {
|
||||
clearHistory,
|
||||
hasNonEmptyQuery,
|
||||
} from './explore';
|
||||
import { ExploreState } from 'app/types/explore';
|
||||
import { ExploreUrlState } from 'app/types/explore';
|
||||
import store from 'app/core/store';
|
||||
|
||||
const DEFAULT_EXPLORE_STATE: ExploreState = {
|
||||
const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
|
||||
datasource: null,
|
||||
datasourceError: null,
|
||||
datasourceLoading: null,
|
||||
datasourceMissing: false,
|
||||
exploreDatasources: [],
|
||||
graphInterval: 1000,
|
||||
history: [],
|
||||
initialQueries: [],
|
||||
queryTransactions: [],
|
||||
queries: [],
|
||||
range: DEFAULT_RANGE,
|
||||
showingGraph: true,
|
||||
showingLogs: true,
|
||||
showingTable: true,
|
||||
supportsGraph: null,
|
||||
supportsLogs: null,
|
||||
supportsTable: null,
|
||||
};
|
||||
|
||||
describe('state functions', () => {
|
||||
@ -68,21 +55,19 @@ describe('state functions', () => {
|
||||
it('returns url parameter value for a state object', () => {
|
||||
const state = {
|
||||
...DEFAULT_EXPLORE_STATE,
|
||||
initialDatasource: 'foo',
|
||||
datasource: 'foo',
|
||||
queries: [
|
||||
{
|
||||
expr: 'metric{test="a/b"}',
|
||||
},
|
||||
{
|
||||
expr: 'super{foo="x/z"}',
|
||||
},
|
||||
],
|
||||
range: {
|
||||
from: 'now-5h',
|
||||
to: 'now',
|
||||
},
|
||||
initialQueries: [
|
||||
{
|
||||
refId: '1',
|
||||
expr: 'metric{test="a/b"}',
|
||||
},
|
||||
{
|
||||
refId: '2',
|
||||
expr: 'super{foo="x/z"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(serializeStateToUrlParam(state)).toBe(
|
||||
'{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
|
||||
@ -93,21 +78,19 @@ describe('state functions', () => {
|
||||
it('returns url parameter value for a state object', () => {
|
||||
const state = {
|
||||
...DEFAULT_EXPLORE_STATE,
|
||||
initialDatasource: 'foo',
|
||||
datasource: 'foo',
|
||||
queries: [
|
||||
{
|
||||
expr: 'metric{test="a/b"}',
|
||||
},
|
||||
{
|
||||
expr: 'super{foo="x/z"}',
|
||||
},
|
||||
],
|
||||
range: {
|
||||
from: 'now-5h',
|
||||
to: 'now',
|
||||
},
|
||||
initialQueries: [
|
||||
{
|
||||
refId: '1',
|
||||
expr: 'metric{test="a/b"}',
|
||||
},
|
||||
{
|
||||
refId: '2',
|
||||
expr: 'super{foo="x/z"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(serializeStateToUrlParam(state, true)).toBe(
|
||||
'["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"}]'
|
||||
@ -119,35 +102,24 @@ describe('state functions', () => {
|
||||
it('can parse the serialized state into the original state', () => {
|
||||
const state = {
|
||||
...DEFAULT_EXPLORE_STATE,
|
||||
initialDatasource: 'foo',
|
||||
datasource: 'foo',
|
||||
queries: [
|
||||
{
|
||||
expr: 'metric{test="a/b"}',
|
||||
},
|
||||
{
|
||||
expr: 'super{foo="x/z"}',
|
||||
},
|
||||
],
|
||||
range: {
|
||||
from: 'now - 5h',
|
||||
to: 'now',
|
||||
},
|
||||
initialQueries: [
|
||||
{
|
||||
refId: '1',
|
||||
expr: 'metric{test="a/b"}',
|
||||
},
|
||||
{
|
||||
refId: '2',
|
||||
expr: 'super{foo="x/z"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
const serialized = serializeStateToUrlParam(state);
|
||||
const parsed = parseUrlState(serialized);
|
||||
|
||||
// Account for datasource vs datasourceName
|
||||
const { datasource, queries, ...rest } = parsed;
|
||||
const resultState = {
|
||||
...rest,
|
||||
datasource: DEFAULT_EXPLORE_STATE.datasource,
|
||||
initialDatasource: datasource,
|
||||
initialQueries: queries,
|
||||
};
|
||||
|
||||
expect(state).toMatchObject(resultState);
|
||||
expect(state).toMatchObject(parsed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -142,7 +142,7 @@ export function buildQueryTransaction(
|
||||
};
|
||||
}
|
||||
|
||||
const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
|
||||
export const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
|
||||
|
||||
export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
||||
if (initial) {
|
||||
@ -169,11 +169,6 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
||||
}
|
||||
|
||||
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
|
||||
// const urlState: ExploreUrlState = {
|
||||
// datasource: state.initialDatasource,
|
||||
// queries: state.initialQueries.map(clearQueryKeys),
|
||||
// range: state.range,
|
||||
// };
|
||||
if (compact) {
|
||||
return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import store from 'app/core/store';
|
||||
import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { Emitter } from 'app/core/utils/emitter';
|
||||
import { LogsModel } from 'app/core/logs_model';
|
||||
import TableModel from 'app/core/table_model';
|
||||
|
||||
import {
|
||||
addQueryRow,
|
||||
@ -45,8 +47,6 @@ import Table from './Table';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import { Alert } from './Error';
|
||||
import TimePicker, { parseTime } from './TimePicker';
|
||||
import { LogsModel } from 'app/core/logs_model';
|
||||
import TableModel from 'app/core/table_model';
|
||||
|
||||
interface ExploreProps {
|
||||
StartPage?: any;
|
||||
@ -74,6 +74,7 @@ interface ExploreProps {
|
||||
initialDatasource?: string;
|
||||
initialQueries: DataQuery[];
|
||||
initializeExplore: typeof initializeExplore;
|
||||
initialized: boolean;
|
||||
logsHighlighterExpressions?: string[];
|
||||
logsResult?: LogsModel;
|
||||
modifyQueries: typeof modifyQueries;
|
||||
@ -149,8 +150,9 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { exploreId, split, urlState } = this.props;
|
||||
if (!split) {
|
||||
const { exploreId, initialized, urlState } = this.props;
|
||||
// Don't initialize on split, but need to initialize urlparameters when present
|
||||
if (!initialized) {
|
||||
// Load URL state and parse range
|
||||
const { datasource, queries, range = DEFAULT_RANGE } = (urlState || {}) as ExploreUrlState;
|
||||
const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
|
||||
@ -277,11 +279,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// saveState = () => {
|
||||
// const { stateKey, onSaveState } = this.props;
|
||||
// onSaveState(stateKey, this.cloneState());
|
||||
// };
|
||||
|
||||
render() {
|
||||
const {
|
||||
StartPage,
|
||||
@ -478,6 +475,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
||||
graphResult,
|
||||
initialDatasource,
|
||||
initialQueries,
|
||||
initialized,
|
||||
history,
|
||||
logsHighlighterExpressions,
|
||||
logsResult,
|
||||
@ -504,6 +502,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
||||
graphResult,
|
||||
initialDatasource,
|
||||
initialQueries,
|
||||
initialized,
|
||||
history,
|
||||
logsHighlighterExpressions,
|
||||
logsResult,
|
||||
|
@ -3,51 +3,56 @@ import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
// import { serializeStateToUrlParam, parseUrlState } from 'app/core/utils/explore';
|
||||
import { StoreState } from 'app/types';
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
import { ExploreId, ExploreUrlState } from 'app/types/explore';
|
||||
import { parseUrlState } from 'app/core/utils/explore';
|
||||
|
||||
import { initializeExploreSplit } from './state/actions';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import Explore from './Explore';
|
||||
|
||||
interface WrapperProps {
|
||||
backendSrv?: any;
|
||||
datasourceSrv?: any;
|
||||
initializeExploreSplit: typeof initializeExploreSplit;
|
||||
split: boolean;
|
||||
updateLocation: typeof updateLocation;
|
||||
// urlStates: { [key: string]: string };
|
||||
urlStates: { [key: string]: string };
|
||||
}
|
||||
|
||||
export class Wrapper extends Component<WrapperProps> {
|
||||
// urlStates: { [key: string]: string };
|
||||
initialSplit: boolean;
|
||||
urlStates: { [key: string]: ExploreUrlState };
|
||||
|
||||
constructor(props: WrapperProps) {
|
||||
super(props);
|
||||
// this.urlStates = props.urlStates;
|
||||
this.urlStates = {};
|
||||
const { left, right } = props.urlStates;
|
||||
if (props.urlStates.left) {
|
||||
this.urlStates.leftState = parseUrlState(left);
|
||||
}
|
||||
if (props.urlStates.right) {
|
||||
this.urlStates.rightState = parseUrlState(right);
|
||||
this.initialSplit = true;
|
||||
}
|
||||
}
|
||||
|
||||
// onSaveState = (key: string, state: ExploreState) => {
|
||||
// const urlState = serializeStateToUrlParam(state, true);
|
||||
// this.urlStates[key] = urlState;
|
||||
// this.props.updateLocation({
|
||||
// query: this.urlStates,
|
||||
// });
|
||||
// };
|
||||
componentDidMount() {
|
||||
if (this.initialSplit) {
|
||||
this.props.initializeExploreSplit();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { split } = this.props;
|
||||
// State overrides for props from first Explore
|
||||
// const urlStateLeft = parseUrlState(this.urlStates[STATE_KEY_LEFT]);
|
||||
// const urlStateRight = parseUrlState(this.urlStates[STATE_KEY_RIGHT]);
|
||||
const { leftState, rightState } = this.urlStates;
|
||||
|
||||
return (
|
||||
<div className="explore-wrapper">
|
||||
<ErrorBoundary>
|
||||
<Explore exploreId={ExploreId.left} />
|
||||
<Explore exploreId={ExploreId.left} urlState={leftState} />
|
||||
</ErrorBoundary>
|
||||
{split && (
|
||||
<ErrorBoundary>
|
||||
<Explore exploreId={ExploreId.right} />
|
||||
<Explore exploreId={ExploreId.right} urlState={rightState} />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</div>
|
||||
@ -56,12 +61,13 @@ export class Wrapper extends Component<WrapperProps> {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState) => {
|
||||
// urlStates: state.location.query,
|
||||
const urlStates = state.location.query;
|
||||
const { split } = state.explore;
|
||||
return { split };
|
||||
return { split, urlStates };
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
initializeExploreSplit,
|
||||
updateLocation,
|
||||
};
|
||||
|
||||
|
@ -4,14 +4,17 @@ import { RawTimeRange, TimeRange } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
LAST_USED_DATASOURCE_KEY,
|
||||
clearQueryKeys,
|
||||
ensureQueries,
|
||||
generateEmptyQuery,
|
||||
hasNonEmptyQuery,
|
||||
makeTimeSeriesList,
|
||||
updateHistory,
|
||||
buildQueryTransaction,
|
||||
serializeStateToUrlParam,
|
||||
} from 'app/core/utils/explore';
|
||||
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import store from 'app/core/store';
|
||||
import { DataSourceSelectItem } from 'app/types/datasources';
|
||||
import { DataQuery, StoreState } from 'app/types';
|
||||
@ -25,6 +28,7 @@ import {
|
||||
QueryTransaction,
|
||||
QueryHint,
|
||||
QueryHintGetter,
|
||||
ExploreUrlState,
|
||||
} from 'app/types/explore';
|
||||
import { Emitter } from 'app/core/core';
|
||||
import { ExploreItemState } from './reducers';
|
||||
@ -44,6 +48,7 @@ export enum ActionTypes {
|
||||
ClickTableButton = 'CLICK_TABLE_BUTTON',
|
||||
HighlightLogsExpression = 'HIGHLIGHT_LOGS_EXPRESSION',
|
||||
InitializeExplore = 'INITIALIZE_EXPLORE',
|
||||
InitializeExploreSplit = 'INITIALIZE_EXPLORE_SPLIT',
|
||||
LoadDatasourceFailure = 'LOAD_DATASOURCE_FAILURE',
|
||||
LoadDatasourceMissing = 'LOAD_DATASOURCE_MISSING',
|
||||
LoadDatasourcePending = 'LOAD_DATASOURCE_PENDING',
|
||||
@ -58,6 +63,7 @@ export enum ActionTypes {
|
||||
ScanRange = 'SCAN_RANGE',
|
||||
ScanStart = 'SCAN_START',
|
||||
ScanStop = 'SCAN_STOP',
|
||||
StateSave = 'STATE_SAVE',
|
||||
}
|
||||
|
||||
export interface AddQueryRowAction {
|
||||
@ -123,6 +129,12 @@ export interface ClickTableButtonAction {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface HighlightLogsExpressionAction {
|
||||
type: ActionTypes.HighlightLogsExpression;
|
||||
exploreId: ExploreId;
|
||||
expressions: string[];
|
||||
}
|
||||
|
||||
export interface InitializeExploreAction {
|
||||
type: ActionTypes.InitializeExplore;
|
||||
exploreId: ExploreId;
|
||||
@ -134,10 +146,8 @@ export interface InitializeExploreAction {
|
||||
range: RawTimeRange;
|
||||
}
|
||||
|
||||
export interface HighlightLogsExpressionAction {
|
||||
type: ActionTypes.HighlightLogsExpression;
|
||||
exploreId: ExploreId;
|
||||
expressions: string[];
|
||||
export interface InitializeExploreSplitAction {
|
||||
type: ActionTypes.InitializeExploreSplit;
|
||||
}
|
||||
|
||||
export interface LoadDatasourceFailureAction {
|
||||
@ -224,6 +234,10 @@ export interface ScanStopAction {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface StateSaveAction {
|
||||
type: ActionTypes.StateSave;
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| AddQueryRowAction
|
||||
| ChangeQueryAction
|
||||
@ -238,6 +252,7 @@ export type Action =
|
||||
| ClickTableButtonAction
|
||||
| HighlightLogsExpressionAction
|
||||
| InitializeExploreAction
|
||||
| InitializeExploreSplitAction
|
||||
| LoadDatasourceFailureAction
|
||||
| LoadDatasourceMissingAction
|
||||
| LoadDatasourcePendingAction
|
||||
@ -301,15 +316,14 @@ export function clickClear(exploreId: ExploreId): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch(scanStop(exploreId));
|
||||
dispatch({ type: ActionTypes.ClickClear, exploreId });
|
||||
// TODO save state
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
|
||||
export function clickCloseSplit(): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch({ type: ActionTypes.ClickCloseSplit });
|
||||
// When closing split, remove URL state for split part
|
||||
// TODO save state
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
|
||||
@ -353,7 +367,7 @@ export function clickSplit(): ThunkResult<void> {
|
||||
initialQueries: leftState.modifiedQueries.slice(),
|
||||
};
|
||||
dispatch({ type: ActionTypes.ClickSplit, itemState });
|
||||
// TODO save state
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
|
||||
@ -412,6 +426,12 @@ export function initializeExplore(
|
||||
};
|
||||
}
|
||||
|
||||
export function initializeExploreSplit() {
|
||||
return async dispatch => {
|
||||
dispatch({ type: ActionTypes.InitializeExploreSplit });
|
||||
};
|
||||
}
|
||||
|
||||
export const loadDatasourceFailure = (exploreId: ExploreId, error: string): LoadDatasourceFailureAction => ({
|
||||
type: ActionTypes.LoadDatasourceFailure,
|
||||
exploreId,
|
||||
@ -733,7 +753,7 @@ export function runQueries(exploreId: ExploreId) {
|
||||
if (showingLogs && supportsLogs) {
|
||||
dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' }));
|
||||
}
|
||||
// TODO save state
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
|
||||
@ -792,3 +812,25 @@ export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkRes
|
||||
export function scanStop(exploreId: ExploreId): ScanStopAction {
|
||||
return { type: ActionTypes.ScanStop, exploreId };
|
||||
}
|
||||
|
||||
export function stateSave() {
|
||||
return (dispatch, getState) => {
|
||||
const { left, right, split } = getState().explore;
|
||||
const urlStates: { [index: string]: string } = {};
|
||||
const leftUrlState: ExploreUrlState = {
|
||||
datasource: left.datasourceInstance.name,
|
||||
queries: left.modifiedQueries.map(clearQueryKeys),
|
||||
range: left.range,
|
||||
};
|
||||
urlStates.left = serializeStateToUrlParam(leftUrlState, true);
|
||||
if (split) {
|
||||
const rightUrlState: ExploreUrlState = {
|
||||
datasource: right.datasourceInstance.name,
|
||||
queries: right.modifiedQueries.map(clearQueryKeys),
|
||||
range: right.range,
|
||||
};
|
||||
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
|
||||
}
|
||||
dispatch(updateLocation({ query: urlStates }));
|
||||
};
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ export interface ExploreItemState {
|
||||
history: HistoryItem[];
|
||||
initialDatasource?: string;
|
||||
initialQueries: DataQuery[];
|
||||
initialized: boolean;
|
||||
logsHighlighterExpressions?: string[];
|
||||
logsResult?: LogsModel;
|
||||
modifiedQueries: DataQuery[];
|
||||
@ -74,6 +75,7 @@ const makeExploreItemState = (): ExploreItemState => ({
|
||||
exploreDatasources: [],
|
||||
history: [],
|
||||
initialQueries: [],
|
||||
initialized: false,
|
||||
modifiedQueries: [],
|
||||
queryTransactions: [],
|
||||
queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
|
||||
@ -89,7 +91,7 @@ const makeExploreItemState = (): ExploreItemState => ({
|
||||
});
|
||||
|
||||
const initialExploreState: ExploreState = {
|
||||
split: false,
|
||||
split: null,
|
||||
left: makeExploreItemState(),
|
||||
right: makeExploreItemState(),
|
||||
};
|
||||
@ -236,6 +238,7 @@ const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
range,
|
||||
initialDatasource: action.datasource,
|
||||
initialQueries: action.queries,
|
||||
initialized: true,
|
||||
modifiedQueries: action.queries.slice(),
|
||||
};
|
||||
}
|
||||
@ -436,6 +439,13 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp
|
||||
right: action.itemState,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.InitializeExploreSplit: {
|
||||
return {
|
||||
...state,
|
||||
split: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const { exploreId } = action as any;
|
||||
@ -447,6 +457,8 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp
|
||||
};
|
||||
}
|
||||
|
||||
console.error('Unhandled action', action.type);
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user