Commit 5ec9adb7 by David Kaltschmidt

Explore: compact state URLs

- allow positional state array in URL
- key-based parsing as fallback
- fix issue where split state was kept in URL after closing split
parent f6d33256
...@@ -36,14 +36,40 @@ describe('state functions', () => { ...@@ -36,14 +36,40 @@ describe('state functions', () => {
range: DEFAULT_RANGE, range: DEFAULT_RANGE,
}); });
}); });
it('returns a valid Explore state from URL parameter', () => {
const paramValue =
'%7B"datasource":"Local","queries":%5B%7B"query":"metric"%7D%5D,"range":%7B"from":"now-1h","to":"now"%7D%7D';
expect(parseUrlState(paramValue)).toMatchObject({
datasource: 'Local',
queries: [{ query: 'metric' }],
range: {
from: 'now-1h',
to: 'now',
},
});
});
it('returns a valid Explore state from a compact URL parameter', () => {
const paramValue = '%5B"now-1h","now","Local","metric"%5D';
expect(parseUrlState(paramValue)).toMatchObject({
datasource: 'Local',
queries: [{ query: 'metric' }],
range: {
from: 'now-1h',
to: 'now',
},
});
});
}); });
describe('serializeStateToUrlParam', () => { describe('serializeStateToUrlParam', () => {
it('returns url parameter value for a state object', () => { it('returns url parameter value for a state object', () => {
const state = { const state = {
...DEFAULT_EXPLORE_STATE, ...DEFAULT_EXPLORE_STATE,
datasourceName: 'foo', datasourceName: 'foo',
range: { range: {
from: 'now - 5h', from: 'now-5h',
to: 'now', to: 'now',
}, },
queries: [ queries: [
...@@ -57,10 +83,33 @@ describe('state functions', () => { ...@@ -57,10 +83,33 @@ describe('state functions', () => {
}; };
expect(serializeStateToUrlParam(state)).toBe( expect(serializeStateToUrlParam(state)).toBe(
'{"datasource":"foo","queries":[{"query":"metric{test=\\"a/b\\"}"},' + '{"datasource":"foo","queries":[{"query":"metric{test=\\"a/b\\"}"},' +
'{"query":"super{foo=\\"x/z\\"}"}],"range":{"from":"now - 5h","to":"now"}}' '{"query":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"}}'
);
});
it('returns url parameter value for a state object', () => {
const state = {
...DEFAULT_EXPLORE_STATE,
datasourceName: 'foo',
range: {
from: 'now-5h',
to: 'now',
},
queries: [
{
query: 'metric{test="a/b"}',
},
{
query: 'super{foo="x/z"}',
},
],
};
expect(serializeStateToUrlParam(state, true)).toBe(
'["now-5h","now","foo","metric{test=\\"a/b\\"}","super{foo=\\"x/z\\"}"]'
); );
}); });
}); });
describe('interplay', () => { describe('interplay', () => {
it('can parse the serialized state into the original state', () => { it('can parse the serialized state into the original state', () => {
const state = { const state = {
......
...@@ -60,7 +60,20 @@ export async function getExploreUrl( ...@@ -60,7 +60,20 @@ export async function getExploreUrl(
export function parseUrlState(initial: string | undefined): ExploreUrlState { export function parseUrlState(initial: string | undefined): ExploreUrlState {
if (initial) { if (initial) {
try { try {
return JSON.parse(decodeURI(initial)); const parsed = JSON.parse(decodeURI(initial));
if (Array.isArray(parsed)) {
if (parsed.length <= 3) {
throw new Error('Error parsing compact URL state for Explore.');
}
const range = {
from: parsed[0],
to: parsed[1],
};
const datasource = parsed[2];
const queries = parsed.slice(3).map(query => ({ query }));
return { datasource, queries, range };
}
return parsed;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
...@@ -68,11 +81,19 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { ...@@ -68,11 +81,19 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
return { datasource: null, queries: [], range: DEFAULT_RANGE }; return { datasource: null, queries: [], range: DEFAULT_RANGE };
} }
export function serializeStateToUrlParam(state: ExploreState): string { export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string {
const urlState: ExploreUrlState = { const urlState: ExploreUrlState = {
datasource: state.datasourceName, datasource: state.datasourceName,
queries: state.queries.map(q => ({ query: q.query })), queries: state.queries.map(q => ({ query: q.query })),
range: state.range, range: state.range,
}; };
if (compact) {
return JSON.stringify([
urlState.range.from,
urlState.range.to,
urlState.datasource,
...urlState.queries.map(q => q.query),
]);
}
return JSON.stringify(urlState); return JSON.stringify(urlState);
} }
...@@ -275,7 +275,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -275,7 +275,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
const { onChangeSplit } = this.props; const { onChangeSplit } = this.props;
if (onChangeSplit) { if (onChangeSplit) {
onChangeSplit(false); onChangeSplit(false);
this.saveState();
} }
}; };
...@@ -292,7 +291,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -292,7 +291,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
if (onChangeSplit) { if (onChangeSplit) {
const state = this.cloneState(); const state = this.cloneState();
onChangeSplit(true, state); onChangeSplit(true, state);
this.saveState();
} }
}; };
...@@ -534,12 +532,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -534,12 +532,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
</a> </a>
</div> </div>
) : ( ) : (
<div className="navbar-buttons explore-first-button"> <div className="navbar-buttons explore-first-button">
<button className="btn navbar-button" onClick={this.onClickCloseSplit}> <button className="btn navbar-button" onClick={this.onClickCloseSplit}>
Close Split Close Split
</button> </button>
</div> </div>
)} )}
{!datasourceMissing ? ( {!datasourceMissing ? (
<div className="navbar-buttons"> <div className="navbar-buttons">
<Select <Select
......
...@@ -38,10 +38,17 @@ export class Wrapper extends Component<WrapperProps, WrapperState> { ...@@ -38,10 +38,17 @@ export class Wrapper extends Component<WrapperProps, WrapperState> {
onChangeSplit = (split: boolean, splitState: ExploreState) => { onChangeSplit = (split: boolean, splitState: ExploreState) => {
this.setState({ split, splitState }); this.setState({ split, splitState });
// When closing split, remove URL state for split part
if (!split) {
delete this.urlStates[STATE_KEY_RIGHT];
this.props.updateLocation({
query: this.urlStates,
});
}
}; };
onSaveState = (key: string, state: ExploreState) => { onSaveState = (key: string, state: ExploreState) => {
const urlState = serializeStateToUrlParam(state); const urlState = serializeStateToUrlParam(state, true);
this.urlStates[key] = urlState; this.urlStates[key] = urlState;
this.props.updateLocation({ this.props.updateLocation({
query: this.urlStates, query: this.urlStates,
......
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