Explore: Remove support for legacy, compact format URLs (#49350)

* Remove function, start changing tests

* Fix tests

* Remove deprecated parameter from function
This commit is contained in:
Kristina 2022-05-23 08:18:49 -05:00 committed by GitHub
parent be0d043673
commit a9cc3225ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 135 deletions

View File

@ -196,15 +196,11 @@ export const urlUtil = {
};
/**
* Create an string that is used in URL to represent the Explore state. This is basically just a stringified json
* that is that used as a state of a single Explore pane so it does not represent full Explore URL.
* Create an string that is used in URL to represent the Explore state. This is stringified json
* that is used as a state of a single Explore pane - it does not represent full Explore URL.
*
* @param urlState
* @param compact this parameter is deprecated and will be removed in a future release.
*/
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
if (compact !== undefined) {
console.warn('`compact` parameter is deprecated and will be removed in a future release');
}
export function serializeStateToUrlParam(urlState: ExploreUrlState): string {
return JSON.stringify(urlState);
}

View File

@ -48,45 +48,6 @@ describe('state functions', () => {
},
});
});
it('returns a valid Explore state from a compact URL parameter', () => {
const paramValue = '["now-1h","now","Local",{"expr":"metric"},{"ui":[true,true,true,"none"]}]';
expect(parseUrlState(paramValue)).toMatchObject({
datasource: 'Local',
queries: [{ expr: 'metric' }],
range: {
from: 'now-1h',
to: 'now',
},
});
});
it('should not return a query for mode in the url', () => {
// Previous versions of Grafana included "Explore mode" in the URL; this should not be treated as a query.
const paramValue =
'["now-1h","now","x-ray-datasource",{"queryType":"getTraceSummaries"},{"mode":"Metrics"},{"ui":[true,true,true,"none"]}]';
expect(parseUrlState(paramValue)).toMatchObject({
datasource: 'x-ray-datasource',
queries: [{ queryType: 'getTraceSummaries' }],
range: {
from: 'now-1h',
to: 'now',
},
});
});
it('should return queries if queryType is present in the url', () => {
const paramValue =
'["now-1h","now","x-ray-datasource",{"queryType":"getTraceSummaries"},{"ui":[true,true,true,"none"]}]';
expect(parseUrlState(paramValue)).toMatchObject({
datasource: 'x-ray-datasource',
queries: [{ queryType: 'getTraceSummaries' }],
range: {
from: 'now-1h',
to: 'now',
},
});
});
});
describe('serializeStateToUrlParam', () => {

View File

@ -165,16 +165,6 @@ export function buildQueryTransaction(
export const clearQueryKeys: (query: DataQuery) => DataQuery = ({ key, ...rest }) => rest;
const isSegment = (segment: { [key: string]: string }, ...props: string[]) =>
props.some((prop) => segment.hasOwnProperty(prop));
enum ParseUrlStateIndex {
RangeFrom = 0,
RangeTo = 1,
Datasource = 2,
SegmentsStart = 3,
}
export const safeParseJson = (text?: string): any | undefined => {
if (!text) {
return;
@ -229,25 +219,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
return errorResult;
}
if (!Array.isArray(parsed)) {
return parsed;
}
if (parsed.length <= ParseUrlStateIndex.SegmentsStart) {
console.error('Error parsing compact URL state for Explore.');
return errorResult;
}
const range = {
from: parsed[ParseUrlStateIndex.RangeFrom],
to: parsed[ParseUrlStateIndex.RangeTo],
};
const datasource = parsed[ParseUrlStateIndex.Datasource];
const parsedSegments = parsed.slice(ParseUrlStateIndex.SegmentsStart);
const queries = parsedSegments.filter((segment) => !isSegment(segment, 'ui', 'mode', '__panelsState'));
const panelsState = parsedSegments.find((segment) => isSegment(segment, '__panelsState'))?.__panelsState;
return { datasource, queries, range, panelsState };
return parsed;
}
export function generateKey(index = 0): string {

View File

@ -12,6 +12,40 @@ import { splitOpen } from './state/main';
type Mock = jest.Mock;
type overrideParamsType = {
datasource?: string;
exprValue?: string;
rightDatasource?: string;
rightExprValue?: string;
};
const defaultUrlParams = ({
datasource = 'loki',
exprValue = '{label="value"}',
rightDatasource,
rightExprValue,
}: overrideParamsType) => {
type urlParamsType = { left: string; right?: string };
const urlParams: urlParamsType = {
left: serializeStateToUrlParam({
datasource: datasource,
queries: [{ refId: 'A', expr: exprValue }],
range: { from: 'now-1h', to: 'now' },
}),
};
if (rightDatasource) {
urlParams.right = serializeStateToUrlParam({
datasource: rightDatasource,
queries: [{ refId: 'A', expr: rightExprValue ? rightExprValue : exprValue }],
range: { from: 'now-1h', to: 'now' },
});
}
return urlParams;
};
jest.mock('app/core/core', () => {
return {
contextSrv: {
@ -61,13 +95,7 @@ describe('Wrapper', () => {
});
it('runs query when url contains query and renders results', async () => {
const urlParams = {
left: serializeStateToUrlParam({
datasource: 'loki',
queries: [{ refId: 'A', expr: '{ label="value"}' }],
range: { from: 'now-1h', to: 'now' },
}),
};
const urlParams = defaultUrlParams({});
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
@ -78,7 +106,7 @@ describe('Wrapper', () => {
await screen.findByText(/custom log line/i);
// And that the editor gets the expr from the url
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
// We did not change the url
expect(locationService.getSearchObject()).toEqual({
@ -89,12 +117,12 @@ describe('Wrapper', () => {
// We called the data source query method once
expect(datasources.loki.query).toBeCalledTimes(1);
expect((datasources.loki.query as Mock).mock.calls[0][0]).toMatchObject({
targets: [{ expr: '{ label="value"}' }],
targets: [{ expr: '{label="value"}' }],
});
});
it('handles url change and runs the new query', async () => {
const urlParams = { left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]) };
const urlParams = defaultUrlParams({});
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
// Wait for rendering the logs
@ -102,29 +130,25 @@ describe('Wrapper', () => {
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse('different log'));
locationService.partial({
left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="different"}' }]),
});
locationService.partial(defaultUrlParams({ exprValue: '{label="different"}' }));
// Editor renders the new query
await screen.findByText(`loki Editor input: { label="different"}`);
await screen.findByText(`loki Editor input: {label="different"}`);
// Renders new response
await screen.findByText(/different log/i);
});
it('handles url change and runs the new query with different datasource', async () => {
const urlParams = { left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]) };
const urlParams = defaultUrlParams({});
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
// Wait for rendering the logs
await screen.findByText(/custom log line/i);
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
(datasources.elastic.query as Mock).mockReturnValueOnce(makeMetricsQueryResponse());
locationService.partial({
left: JSON.stringify(['now-1h', 'now', 'elastic', { expr: 'other query' }]),
});
locationService.partial(defaultUrlParams({ datasource: 'elastic', exprValue: 'other query' }));
// Editor renders the new query
await screen.findByText(`elastic Editor input: other query`);
@ -133,7 +157,7 @@ describe('Wrapper', () => {
});
it('handles changing the datasource manually', async () => {
const urlParams = { left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}', refId: 'A' }]) };
const urlParams = defaultUrlParams({});
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
await waitForExplore();
@ -163,18 +187,7 @@ describe('Wrapper', () => {
});
it('inits with two panes if specified in url', async () => {
const urlParams = {
left: serializeStateToUrlParam({
datasource: 'loki',
queries: [{ refId: 'A', expr: '{ label="value"}' }],
range: { from: 'now-1h', to: 'now' },
}),
right: serializeStateToUrlParam({
datasource: 'elastic',
queries: [{ refId: 'A', expr: 'error' }],
range: { from: 'now-1h', to: 'now' },
}),
};
const urlParams = defaultUrlParams({ rightDatasource: 'elastic', rightExprValue: 'error' });
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
@ -191,7 +204,7 @@ describe('Wrapper', () => {
expect(logsLines.length).toBe(2);
// And that the editor gets the expr from the url
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
await screen.findByText(`elastic Editor input: error`);
// We did not change the url
@ -203,7 +216,7 @@ describe('Wrapper', () => {
// We called the data source query method once
expect(datasources.loki.query).toBeCalledTimes(1);
expect((datasources.loki.query as Mock).mock.calls[0][0]).toMatchObject({
targets: [{ expr: '{ label="value"}' }],
targets: [{ expr: '{label="value"}' }],
});
expect(datasources.elastic.query).toBeCalledTimes(1);
@ -213,11 +226,11 @@ describe('Wrapper', () => {
});
it('can close a pane from a split', async () => {
const urlParams = {
left: JSON.stringify(['now-1h', 'now', 'loki', { refId: 'A' }]),
right: JSON.stringify(['now-1h', 'now', 'elastic', { refId: 'A' }]),
};
setupExplore({ urlParams });
const urlParams = defaultUrlParams({ rightDatasource: 'elastic' });
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
(datasources.elastic.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
const closeButtons = await screen.findAllByTitle(/Close split pane/i);
await userEvent.click(closeButtons[1]);
@ -228,65 +241,55 @@ describe('Wrapper', () => {
});
it('handles url change to split view', async () => {
const urlParams = {
left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
};
const urlParams = defaultUrlParams({});
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
(datasources.elastic.query as Mock).mockReturnValue(makeLogsQueryResponse());
locationService.partial({
left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
right: JSON.stringify(['now-1h', 'now', 'elastic', { expr: 'error' }]),
});
locationService.partial(defaultUrlParams({ rightDatasource: 'elastic', rightExprValue: 'error' }));
// Editor renders the new query
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
await screen.findByText(`elastic Editor input: error`);
});
it('handles opening split with split open func', async () => {
const urlParams = {
left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
};
const urlParams = defaultUrlParams({});
const { datasources, store } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
(datasources.elastic.query as Mock).mockReturnValue(makeLogsQueryResponse());
// This is mainly to wait for render so that the left pane state is initialized as that is needed for splitOpen
// to work
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
store.dispatch(splitOpen<any>({ datasourceUid: 'elastic', query: { expr: 'error' } }) as any);
// Editor renders the new query
await screen.findByText(`elastic Editor input: error`);
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
});
it('changes the document title of the explore page to include the datasource in use', async () => {
const urlParams = {
left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
};
const urlParams = defaultUrlParams({});
const { datasources } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
// This is mainly to wait for render so that the left pane state is initialized as that is needed for the title
// to include the datasource
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
await waitFor(() => expect(document.title).toEqual('Explore - loki - Grafana'));
});
it('changes the document title to include the two datasources in use in split view mode', async () => {
const urlParams = {
left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
};
const urlParams = defaultUrlParams({});
const { datasources, store } = setupExplore({ urlParams });
(datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
(datasources.elastic.query as Mock).mockReturnValue(makeLogsQueryResponse());
// This is mainly to wait for render so that the left pane state is initialized as that is needed for splitOpen
// to work
await screen.findByText(`loki Editor input: { label="value"}`);
await screen.findByText(`loki Editor input: {label="value"}`);
store.dispatch(splitOpen<any>({ datasourceUid: 'elastic', query: { expr: 'error' } }) as any);
await waitFor(() => expect(document.title).toEqual('Explore - loki | elastic - Grafana'));