mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* 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> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
e5bfc138a9
commit
3115ae650b
14 changed files with 420 additions and 28 deletions
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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';
|
||||
|
|
|
@ -37,7 +37,6 @@ import {
|
|||
KbnUrl,
|
||||
SavedObjectSaveOpts,
|
||||
unhashUrl,
|
||||
VISUALIZE_EMBEDDABLE_TYPE,
|
||||
} from '../legacy_imports';
|
||||
import { FilterStateManager } from '../../../../data/public';
|
||||
import {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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¶m2=y¶m3=z
|
||||
* output:http://localhost:5601/lib/app/kibana#/dashboard
|
||||
* input: http://localhost:5601/lib/app/kibana#/dashboard/39292992?param1=x¶m2=y¶m3=z
|
||||
* output: http://localhost:5601/lib/app/kibana#/dashboard/39292992
|
||||
* @param url url to parse
|
||||
*/
|
||||
function getUrlWithoutQueryParams(url: string): string {
|
||||
return url.split('?')[0];
|
||||
}
|
|
@ -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,
|
||||
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -80,6 +80,7 @@ describe('Lens App', () => {
|
|||
docId?: string;
|
||||
docStorage: SavedObjectStore;
|
||||
redirectTo: (id?: string) => void;
|
||||
addToDashboardMode?: boolean;
|
||||
}> {
|
||||
return ({
|
||||
editorFrame: createMockFrame(),
|
||||
|
@ -126,6 +127,7 @@ describe('Lens App', () => {
|
|||
docId?: string;
|
||||
docStorage: SavedObjectStore;
|
||||
redirectTo: (id?: string) => void;
|
||||
addToDashboardMode?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
|
@ -306,6 +308,7 @@ describe('Lens App', () => {
|
|||
docId?: string;
|
||||
docStorage: SavedObjectStore;
|
||||
redirectTo: (id?: string) => void;
|
||||
addToDashboardMode?: boolean;
|
||||
}>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -344,14 +347,19 @@ describe('Lens App', () => {
|
|||
|
||||
async function save({
|
||||
initialDocId,
|
||||
addToDashboardMode,
|
||||
...saveProps
|
||||
}: SaveProps & {
|
||||
initialDocId?: string;
|
||||
addToDashboardMode?: boolean;
|
||||
}) {
|
||||
const args = {
|
||||
...defaultArgs,
|
||||
docId: initialDocId,
|
||||
};
|
||||
if (addToDashboardMode) {
|
||||
args.addToDashboardMode = addToDashboardMode;
|
||||
}
|
||||
args.editorFrame = frame;
|
||||
(args.docStorage.load as jest.Mock).mockResolvedValue({
|
||||
id: '1234',
|
||||
|
@ -543,6 +551,23 @@ describe('Lens App', () => {
|
|||
|
||||
expect(getButton(instance).disableButton).toEqual(false);
|
||||
});
|
||||
|
||||
it('saves new doc and redirects to dashboard', async () => {
|
||||
const { args } = await save({
|
||||
initialDocId: undefined,
|
||||
addToDashboardMode: true,
|
||||
newCopyOnSave: false,
|
||||
newTitle: 'hello there',
|
||||
});
|
||||
|
||||
expect(args.docStorage.save).toHaveBeenCalledWith({
|
||||
expression: 'kibana 3',
|
||||
id: undefined,
|
||||
title: 'hello there',
|
||||
});
|
||||
|
||||
expect(args.redirectTo).toHaveBeenCalledWith('aaa');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_s
|
|||
import { AppMountContext, NotificationsStart } from 'src/core/public';
|
||||
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { Document, SavedObjectStore } from '../persistence';
|
||||
import { EditorFrameInstance } from '../types';
|
||||
|
@ -50,6 +51,7 @@ export function App({
|
|||
docId,
|
||||
docStorage,
|
||||
redirectTo,
|
||||
addToDashboardMode,
|
||||
}: {
|
||||
editorFrame: EditorFrameInstance;
|
||||
data: DataPublicPluginStart;
|
||||
|
@ -58,6 +60,7 @@ export function App({
|
|||
docId?: string;
|
||||
docStorage: SavedObjectStore;
|
||||
redirectTo: (id?: string) => void;
|
||||
addToDashboardMode?: boolean;
|
||||
}) {
|
||||
const language =
|
||||
storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage');
|
||||
|
@ -166,6 +169,13 @@ export function App({
|
|||
|
||||
const { TopNavMenu } = npStart.plugins.navigation.ui;
|
||||
|
||||
const confirmButton = addToDashboardMode ? (
|
||||
<FormattedMessage
|
||||
id="xpack.lens.app.saveAddToDashboard"
|
||||
defaultMessage="Save and add to dashboard"
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider
|
||||
|
@ -320,12 +330,13 @@ export function App({
|
|||
persistedDoc: newDoc,
|
||||
lastKnownDoc: newDoc,
|
||||
}));
|
||||
|
||||
if (docId !== id) {
|
||||
redirectTo(id);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(e => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.dir(e);
|
||||
trackUiEvent('save_failed');
|
||||
core.notifications.toasts.addDanger(
|
||||
i18n.translate('xpack.lens.app.docSavingError', {
|
||||
|
@ -337,10 +348,11 @@ export function App({
|
|||
}}
|
||||
onClose={() => setState(s => ({ ...s, isSaveModalVisible: false }))}
|
||||
title={lastKnownDoc.title || ''}
|
||||
showCopyOnSave={true}
|
||||
showCopyOnSave={!addToDashboardMode}
|
||||
objectType={i18n.translate('xpack.lens.app.saveModalType', {
|
||||
defaultMessage: 'Lens visualization',
|
||||
})}
|
||||
confirmButtonLabel={confirmButton}
|
||||
/>
|
||||
)}
|
||||
</KibanaContextProvider>
|
||||
|
|
|
@ -14,11 +14,13 @@ import 'uiExports/visResponseHandlers';
|
|||
import 'uiExports/savedObjectTypes';
|
||||
|
||||
import React from 'react';
|
||||
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { HashRouter, Switch, Route, RouteComponentProps } from 'react-router-dom';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { CoreSetup, CoreStart, SavedObjectsClientContract } from 'src/core/public';
|
||||
import { DataPublicPluginStart } from 'src/plugins/data/public';
|
||||
import rison, { RisonObject, RisonValue } from 'rison-node';
|
||||
import { isObject } from 'lodash';
|
||||
import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public';
|
||||
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { editorFrameSetup, editorFrameStart, editorFrameStop } from '../editor_frame_plugin';
|
||||
|
@ -41,6 +43,11 @@ import {
|
|||
import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../common';
|
||||
import { KibanaLegacySetup } from '../../../../../../src/plugins/kibana_legacy/public';
|
||||
import { EditorFrameStart } from '../types';
|
||||
import {
|
||||
addEmbeddableToDashboardUrl,
|
||||
getUrlVars,
|
||||
getLensUrlFromDashboardAbsoluteUrl,
|
||||
} from '../../../../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper';
|
||||
|
||||
export interface LensPluginSetupDependencies {
|
||||
kibana_legacy: KibanaLegacySetup;
|
||||
|
@ -51,6 +58,9 @@ export interface LensPluginStartDependencies {
|
|||
dataShim: DataStart;
|
||||
}
|
||||
|
||||
export const isRisonObject = (value: RisonValue): value is RisonObject => {
|
||||
return isObject(value);
|
||||
};
|
||||
export class AppPlugin {
|
||||
private startDependencies: {
|
||||
data: DataPublicPluginStart;
|
||||
|
@ -84,7 +94,6 @@ export class AppPlugin {
|
|||
}
|
||||
const { data, savedObjectsClient, editorFrame } = this.startDependencies;
|
||||
addHelpMenuToAppChrome(context.core.chrome);
|
||||
|
||||
const instance = editorFrame.createInstance({});
|
||||
|
||||
setReportManager(
|
||||
|
@ -93,9 +102,60 @@ export class AppPlugin {
|
|||
http: core.http,
|
||||
})
|
||||
);
|
||||
const updateUrlTime = (urlVars: Record<string, string>): void => {
|
||||
const decoded: RisonObject = rison.decode(urlVars._g) as RisonObject;
|
||||
if (!decoded) {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
decoded.time = data.query.timefilter.timefilter.getTime();
|
||||
urlVars._g = rison.encode((decoded as unknown) as RisonObject);
|
||||
};
|
||||
const redirectTo = (
|
||||
routeProps: RouteComponentProps<{ id?: string }>,
|
||||
addToDashboardMode: boolean,
|
||||
id?: string
|
||||
) => {
|
||||
if (!id) {
|
||||
routeProps.history.push('/lens');
|
||||
} else if (!addToDashboardMode) {
|
||||
routeProps.history.push(`/lens/edit/${id}`);
|
||||
} else if (addToDashboardMode && id) {
|
||||
routeProps.history.push(`/lens/edit/${id}`);
|
||||
const url = context.core.chrome.navLinks.get('kibana:dashboard');
|
||||
if (!url) {
|
||||
throw new Error('Cannot get last dashboard url');
|
||||
}
|
||||
const lastDashboardAbsoluteUrl = url.url;
|
||||
const basePath = context.core.http.basePath.get();
|
||||
const lensUrl = getLensUrlFromDashboardAbsoluteUrl(
|
||||
lastDashboardAbsoluteUrl,
|
||||
basePath,
|
||||
id
|
||||
);
|
||||
if (!lastDashboardAbsoluteUrl || !lensUrl) {
|
||||
throw new Error('Cannot get last dashboard url');
|
||||
}
|
||||
window.history.pushState({}, '', lensUrl);
|
||||
const urlVars = getUrlVars(lastDashboardAbsoluteUrl);
|
||||
updateUrlTime(urlVars); // we need to pass in timerange in query params directly
|
||||
const dashboardParsedUrl = addEmbeddableToDashboardUrl(
|
||||
lastDashboardAbsoluteUrl,
|
||||
basePath,
|
||||
id,
|
||||
urlVars
|
||||
);
|
||||
if (!dashboardParsedUrl) {
|
||||
throw new Error('Problem parsing dashboard url');
|
||||
}
|
||||
window.history.pushState({}, '', dashboardParsedUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => {
|
||||
trackUiEvent('loaded');
|
||||
const addToDashboardMode =
|
||||
!!routeProps.location.search && routeProps.location.search.includes('addToDashboard');
|
||||
return (
|
||||
<App
|
||||
core={context.core}
|
||||
|
@ -104,13 +164,8 @@ export class AppPlugin {
|
|||
storage={new Storage(localStorage)}
|
||||
docId={routeProps.match.params.id}
|
||||
docStorage={new SavedObjectIndexStore(savedObjectsClient)}
|
||||
redirectTo={id => {
|
||||
if (!id) {
|
||||
routeProps.history.push('/lens');
|
||||
} else {
|
||||
routeProps.history.push(`/lens/edit/${id}`);
|
||||
}
|
||||
}}
|
||||
redirectTo={id => redirectTo(routeProps, addToDashboardMode, id)}
|
||||
addToDashboardMode={addToDashboardMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -119,6 +174,7 @@ export class AppPlugin {
|
|||
trackUiEvent('loaded_404');
|
||||
return <FormattedMessage id="xpack.lens.app404" defaultMessage="404 Not Found" />;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nProvider>
|
||||
<HashRouter>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export default function({ getPageObjects, getService }) {
|
||||
const log = getService('log');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const dashboardVisualizations = getService('dashboardVisualizations');
|
||||
const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens']);
|
||||
|
||||
describe('empty dashboard', function() {
|
||||
before(async () => {
|
||||
await esArchiver.loadIfNeeded('logstash_functional');
|
||||
await esArchiver.loadIfNeeded('lens/basic');
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.preserveCrossAppState();
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await PageObjects.dashboard.gotoDashboardLandingPage();
|
||||
});
|
||||
|
||||
async function createAndAddLens(title) {
|
||||
log.debug(`createAndAddLens(${title})`);
|
||||
const inViewMode = await PageObjects.dashboard.getIsInViewMode();
|
||||
if (inViewMode) {
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
}
|
||||
await PageObjects.visualize.clickLensWidget();
|
||||
await PageObjects.lens.goToTimeRange();
|
||||
await PageObjects.lens.configureDimension({
|
||||
dimension:
|
||||
'[data-test-subj="lnsXY_xDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]',
|
||||
operation: 'date_histogram',
|
||||
field: '@timestamp',
|
||||
});
|
||||
|
||||
await PageObjects.lens.configureDimension({
|
||||
dimension:
|
||||
'[data-test-subj="lnsXY_yDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]',
|
||||
operation: 'avg',
|
||||
field: 'bytes',
|
||||
});
|
||||
|
||||
await PageObjects.lens.configureDimension({
|
||||
dimension:
|
||||
'[data-test-subj="lnsXY_splitDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]',
|
||||
operation: 'terms',
|
||||
field: 'ip',
|
||||
});
|
||||
await PageObjects.lens.save(title);
|
||||
}
|
||||
|
||||
it('adds Lens visualization to empty dashboard', async () => {
|
||||
const title = 'Dashboard Test Lens';
|
||||
await testSubjects.exists('addVisualizationButton');
|
||||
await testSubjects.click('addVisualizationButton');
|
||||
await dashboardVisualizations.ensureNewVisualizationDialogIsShowing();
|
||||
await createAndAddLens(title);
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await testSubjects.exists(`embeddablePanelHeading-${title}`);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -9,5 +9,6 @@ export default function({ loadTestFile }) {
|
|||
this.tags('ciGroup7');
|
||||
|
||||
loadTestFile(require.resolve('./dashboard_view_mode'));
|
||||
loadTestFile(require.resolve('./dashboard_empty_screen'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue