Skip to content

Commit

Permalink
[Lens][Dashboard] Adding Lens to Dashboard (#53110)
Browse files Browse the repository at this point in the history
* First version of adding Lens to dashboard

* Fix failing unit test

* Replacing explicit Lens query param with a more generic one

* Fixing failing unit test

* Adding a unit test for redirect

* Do not show Save New if adding from Dashboard

* Adding functional test

* Adding functional test

* Fixing type issues

* Renaming query params

* Fixing failing unit test

* Removing unused constants

* Fixing erroneous imports

* Fixing erroneous import

* Fixing import

* Fix failing typecheck

* Removing timefilter from Dashboard URL

* Fixing type error

* Replacing time parsing with rison

* Replacing URL regex parsing with legacy URLs

* Fixing failing test

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
Maja Grubic and elasticmachine committed Jan 13, 2020
1 parent e8b2b28 commit 7543b0c
Show file tree
Hide file tree
Showing 14 changed files with 420 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

jest.mock('../', () => ({
DashboardConstants: {
ADD_EMBEDDABLE_ID: 'addEmbeddableId',
ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
},
}));

jest.mock('../legacy_imports', () => {
return {
absoluteToParsedUrl: jest.fn(() => {
return {
basePath: '/pep',
appId: 'kibana',
appPath: '/dashboard?addEmbeddableType=lens&addEmbeddableId=123eb456cd&x=1&y=2&z=3',
hostname: 'localhost',
port: 5601,
protocol: 'http:',
addQueryParameter: () => {},
getAbsoluteUrl: () => {
return 'http://localhost:5601/pep/app/kibana#/dashboard?addEmbeddableType=lens&addEmbeddableId=123eb456cd&x=1&y=2&z=3';
},
};
}),
};
});

import {
addEmbeddableToDashboardUrl,
getLensUrlFromDashboardAbsoluteUrl,
getUrlVars,
} from '../np_ready/url_helper';

describe('Dashboard URL Helper', () => {
beforeEach(() => {
jest.resetModules();
});

it('addEmbeddableToDashboardUrl', () => {
const id = '123eb456cd';
const type = 'lens';
const urlVars = {
x: '1',
y: '2',
z: '3',
};
const basePath = '/pep';
const url =
"http://localhost:5601/pep/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(addEmbeddableToDashboardUrl(url, basePath, id, urlVars, type)).toEqual(
`http://localhost:5601/pep/app/kibana#/dashboard?addEmbeddableType=${type}&addEmbeddableId=${id}&x=1&y=2&z=3`
);
});

it('getUrlVars', () => {
let url =
"http://localhost:5601/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(getUrlVars(url)).toEqual({
_g: '(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))',
_a: "(description:'',filters:!()",
});
url = 'http://mybusiness.mydomain.com/app/kibana#/dashboard?x=y&y=z';
expect(getUrlVars(url)).toEqual({
x: 'y',
y: 'z',
});
url = 'http://notDashboardUrl';
expect(getUrlVars(url)).toEqual({});
url = 'http://localhost:5601/app/kibana#/dashboard/777182';
expect(getUrlVars(url)).toEqual({});
});

it('getLensUrlFromDashboardAbsoluteUrl', () => {
const id = '1244';
const basePath = '/wev';
let url =
"http://localhost:5601/wev/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(getLensUrlFromDashboardAbsoluteUrl(url, basePath, id)).toEqual(
'http://localhost:5601/wev/app/kibana#/lens/edit/1244'
);

url =
"http://localhost:5601/wev/app/kibana#/dashboard/625357282?_a=(description:'',filters:!()&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))";
expect(getLensUrlFromDashboardAbsoluteUrl(url, basePath, id)).toEqual(
'http://localhost:5601/wev/app/kibana#/lens/edit/1244'
);

url = 'http://myserver.mydomain.com:5601/wev/app/kibana#/dashboard/777182';
expect(getLensUrlFromDashboardAbsoluteUrl(url, basePath, id)).toEqual(
'http://myserver.mydomain.com:5601/wev/app/kibana#/lens/edit/1244'
);

url =
"http://localhost:5601/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(getLensUrlFromDashboardAbsoluteUrl(url, '', id)).toEqual(
'http://localhost:5601/app/kibana#/lens/edit/1244'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ export { IInjector } from 'ui/chrome';
export { SavedObjectLoader } from 'ui/saved_objects';
export { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize_embeddable';
export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router';
export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {
KbnUrl,
SavedObjectSaveOpts,
unhashUrl,
VISUALIZE_EMBEDDABLE_TYPE,
} from '../legacy_imports';
import { FilterStateManager } from '../../../../data/public';
import {
Expand Down Expand Up @@ -334,13 +333,12 @@ export class DashboardAppController {
// This code needs to be replaced with a better mechanism for adding new embeddables of
// any type from the add panel. Likely this will happen via creating a visualization "inline",
// without navigating away from the UX.
if ($routeParams[DashboardConstants.NEW_VISUALIZATION_ID_PARAM]) {
container.addSavedObjectEmbeddable(
VISUALIZE_EMBEDDABLE_TYPE,
$routeParams[DashboardConstants.NEW_VISUALIZATION_ID_PARAM]
);
kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM);
kbnUrl.removeParam(DashboardConstants.NEW_VISUALIZATION_ID_PARAM);
if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
container.addSavedObjectEmbeddable(type, id);
kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_TYPE);
kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_ID);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@

export const DashboardConstants = {
ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard',
NEW_VISUALIZATION_ID_PARAM: 'addVisualization',
LANDING_PAGE_PATH: '/dashboards',
CREATE_NEW_DASHBOARD_URL: '/dashboard',
ADD_EMBEDDABLE_ID: 'addEmbeddableId',
ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
};

export function createDashboardEditUrl(id: string) {
Expand Down
102 changes: 102 additions & 0 deletions src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { parse } from 'url';
import { absoluteToParsedUrl } from '../legacy_imports';
import { DashboardConstants } from './dashboard_constants';
/**
* Return query params from URL
* @param url given url
*/
export function getUrlVars(url: string): Record<string, string> {
const vars: Record<string, string> = {};
// @ts-ignore
url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(_, key, value) {
// @ts-ignore
vars[key] = value;
});
return vars;
}

/** *
* Returns dashboard URL with added embeddableType and embeddableId query params
* eg.
* input: url: http://localhost:5601/lib/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now)), embeddableId: 12345, embeddableType: 'lens'
* output: http://localhost:5601/lib/app/kibana#dashboard?addEmbeddableType=lens&addEmbeddableId=12345&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))
* @param url dasbhoard absolute url
* @param embeddableId id of the saved visualization
* @param basePath current base path
* @param urlVars url query params (optional)
* @param embeddableType 'lens' or 'visualization' (optional, default is 'lens')
*/
export function addEmbeddableToDashboardUrl(
url: string | undefined,
basePath: string,
embeddableId: string,
urlVars?: Record<string, string>,
embeddableType?: string
): string | null {
if (!url) {
return null;
}
const dashboardUrl = getUrlWithoutQueryParams(url);
const dashboardParsedUrl = absoluteToParsedUrl(dashboardUrl, basePath);
if (urlVars) {
const keys = Object.keys(urlVars).sort();
keys.forEach(key => {
dashboardParsedUrl.addQueryParameter(key, urlVars[key]);
});
}
dashboardParsedUrl.addQueryParameter(
DashboardConstants.ADD_EMBEDDABLE_TYPE,
embeddableType || 'lens'
);
dashboardParsedUrl.addQueryParameter(DashboardConstants.ADD_EMBEDDABLE_ID, embeddableId);
return dashboardParsedUrl.getAbsoluteUrl();
}

/**
* Return Lens URL from dashboard absolute URL
* @param dashboardAbsoluteUrl
* @param basePath current base path
* @param id Lens id
*/
export function getLensUrlFromDashboardAbsoluteUrl(
dashboardAbsoluteUrl: string | undefined | null,
basePath: string | null | undefined,
id: string
): string | null {
if (!dashboardAbsoluteUrl || basePath === null || basePath === undefined) {
return null;
}
const { host, protocol } = parse(dashboardAbsoluteUrl);
return `${protocol}//${host}${basePath}/app/kibana#/lens/edit/${id}`;
}

/**
* Returns the portion of the URL without query params
* eg.
* input: http://localhost:5601/lib/app/kibana#/dashboard?param1=x&param2=y&param3=z
* output:http://localhost:5601/lib/app/kibana#/dashboard
* input: http://localhost:5601/lib/app/kibana#/dashboard/39292992?param1=x&param2=y&param3=z
* output: http://localhost:5601/lib/app/kibana#/dashboard/39292992
* @param url url to parse
*/
function getUrlWithoutQueryParams(url: string): string {
return url.split('?')[0];
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ import { unhashUrl } from '../../../../../../../plugins/kibana_utils/public';

import { initVisEditorDirective } from './visualization_editor';
import { initVisualizationDirective } from './visualization';

import {
VISUALIZE_EMBEDDABLE_TYPE,
subscribeWithScope,
absoluteToParsedUrl,
KibanaParsedUrl,
Expand Down Expand Up @@ -588,7 +588,11 @@ function VisualizeAppController(
getBasePath()
);
dashboardParsedUrl.addQueryParameter(
DashboardConstants.NEW_VISUALIZATION_ID_PARAM,
DashboardConstants.ADD_EMBEDDABLE_TYPE,
VISUALIZE_EMBEDDABLE_TYPE
);
dashboardParsedUrl.addQueryParameter(
DashboardConstants.ADD_EMBEDDABLE_ID,
savedVis.id
);
kbnUrl.change(dashboardParsedUrl.appPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ describe('NewVisModal', () => {
expect(window.location.assign).toBeCalledWith('#/visualize/create?type=vis&foo=true&bar=42');
});

it('closes if visualization with aliasUrl and addToDashboard in editorParams', () => {
it('closes and redirects properly if visualization with aliasUrl and addToDashboard in editorParams', () => {
const onClose = jest.fn();
window.location.assign = jest.fn();
const wrapper = mountWithIntl(
Expand All @@ -160,7 +160,7 @@ describe('NewVisModal', () => {
);
const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]');
visButton.simulate('click');
expect(window.location.assign).toBeCalledWith('testbasepath/aliasUrl');
expect(window.location.assign).toBeCalledWith('testbasepath/aliasUrl?addToDashboard');
expect(onClose).toHaveBeenCalled();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,18 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
this.trackUiMetric(METRIC_TYPE.CLICK, visType.name);
}

let params;
if ('aliasUrl' in visType) {
window.location.assign(this.props.addBasePath(visType.aliasUrl));
params = this.props.addBasePath(visType.aliasUrl);
if (this.props.editorParams && this.props.editorParams.includes('addToDashboard')) {
params = `${params}?addToDashboard`;
this.props.onClose();
}
window.location.assign(params);
return;
}

let params = [`type=${encodeURIComponent(visType.name)}`];
params = [`type=${encodeURIComponent(visType.name)}`];

if (searchType) {
params.push(`${searchType === 'search' ? 'savedSearchId' : 'indexPattern'}=${searchId}`);
Expand Down
4 changes: 4 additions & 0 deletions test/functional/page_objects/visualize_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide
async () => (await globalNav.getLastBreadcrumb()) === vizName
);
}

public async clickLensWidget() {
await this.clickVisType('lens');
}
}

return new VisualizePage();
Expand Down
Loading

0 comments on commit 7543b0c

Please sign in to comment.