[Lens][Dashboard] Adding Lens to Dashboard (#53110) (#54607)

* 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:
Maja Grubic 2020-01-13 19:33:34 +00:00 committed by GitHub
parent e5bfc138a9
commit 3115ae650b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 420 additions and 28 deletions

View file

@ -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'
);
});
});

View file

@ -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';

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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];
}

View file

@ -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);

View file

@ -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();
});
});

View file

@ -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}`);

View file

@ -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();

View file

@ -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');
});
});
});

View file

@ -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>

View file

@ -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>

View file

@ -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}`);
});
});
}

View file

@ -9,5 +9,6 @@ export default function({ loadTestFile }) {
this.tags('ciGroup7');
loadTestFile(require.resolve('./dashboard_view_mode'));
loadTestFile(require.resolve('./dashboard_empty_screen'));
});
}