Commit be172d3e by David Kaltschmidt

Save state in URL and fix tests

parent 68c039b2
......@@ -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',
range: {
from: 'now-5h',
to: 'now',
},
initialQueries: [
datasource: 'foo',
queries: [
{
refId: '1',
expr: 'metric{test="a/b"}',
},
{
refId: '2',
expr: 'super{foo="x/z"}',
},
],
range: {
from: 'now-5h',
to: 'now',
},
};
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',
range: {
from: 'now-5h',
to: 'now',
},
initialQueries: [
datasource: 'foo',
queries: [
{
refId: '1',
expr: 'metric{test="a/b"}',
},
{
refId: '2',
expr: 'super{foo="x/z"}',
},
],
range: {
from: 'now-5h',
to: 'now',
},
};
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',
range: {
from: 'now - 5h',
to: 'now',
},
initialQueries: [
datasource: 'foo',
queries: [
{
refId: '1',
expr: 'metric{test="a/b"}',
},
{
refId: '2',
expr: 'super{foo="x/z"}',
},
],
range: {
from: 'now - 5h',
to: 'now',
},
};
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;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment