Skip to content

Commit

Permalink
[Lens] "No data" page in Lens (#132264)
Browse files Browse the repository at this point in the history
* nodata page in Lens

* do not navigate away for no data page to not lose context

Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
  • Loading branch information
flash1293 and stratoula authored May 17, 2022
1 parent 11b9c29 commit 395a98b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 20 deletions.
41 changes: 40 additions & 1 deletion x-pack/plugins/lens/public/app_plugin/mounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { FC, useCallback } from 'react';
import React, { FC, useCallback, useEffect, useState } from 'react';
import { PreloadedState } from '@reduxjs/toolkit';
import { AppMountParameters, CoreSetup, CoreStart } from '@kbn/core/public';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
Expand All @@ -15,10 +15,15 @@ import { render, unmountComponentAtNode } from 'react-dom';
import { i18n } from '@kbn/i18n';
import { Provider } from 'react-redux';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import {
AnalyticsNoDataPageKibanaProvider,
AnalyticsNoDataPage,
} from '@kbn/shared-ux-page-analytics-no-data';

import { ACTION_VISUALIZE_LENS_FIELD } from '@kbn/ui-actions-plugin/public';
import { ACTION_CONVERT_TO_LENS } from '@kbn/visualizations-plugin/public';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { EuiLoadingSpinner } from '@elastic/eui';
import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry';

import { App } from './app';
Expand Down Expand Up @@ -214,12 +219,24 @@ export async function mountApp(

const EditorRenderer = React.memo(
(props: { id?: string; history: History<unknown>; editByValue?: boolean }) => {
const [editorState, setEditorState] = useState<'loading' | 'no_data' | 'data'>('loading');
const redirectCallback = useCallback(
(id?: string) => {
redirectTo(props.history, id);
},
[props.history]
);
useEffect(() => {
(async () => {
const hasUserDataView = await data.dataViews.hasData.hasUserDataView().catch(() => false);
const hasEsData = await data.dataViews.hasData.hasESData().catch(() => true);
if (!hasUserDataView || !hasEsData) {
setEditorState('no_data');
return;
}
setEditorState('data');
})();
}, [props.history]);
trackUiEvent('loaded');
const initialInput = getInitialInput(props.id, props.editByValue);

Expand All @@ -232,6 +249,28 @@ export async function mountApp(
lensStore.dispatch(setState(getPreloadedState(storeDeps) as LensAppState));
lensStore.dispatch(loadInitial({ redirectCallback, initialInput, history: props.history }));

if (editorState === 'loading') {
return <EuiLoadingSpinner />;
}

if (editorState === 'no_data') {
const analyticsServices = {
coreStart,
dataViews: data.dataViews,
dataViewEditor: startDependencies.dataViewEditor,
};
return (
<AnalyticsNoDataPageKibanaProvider {...analyticsServices}>
<AnalyticsNoDataPage
onDataViewCreated={() => {
setEditorState('data');
}}
/>
;
</AnalyticsNoDataPageKibanaProvider>
);
}

return (
<Provider store={lensStore}>
<App
Expand Down
27 changes: 9 additions & 18 deletions x-pack/plugins/lens/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,31 +312,22 @@ export class LensPlugin {
const getPresentationUtilContext = () =>
startServices().plugins.presentationUtil.ContextProvider;

const ensureDefaultDataView = () => {
// make sure a default index pattern exists
// if not, the page will be redirected to management and visualize won't be rendered
startServices().plugins.dataViews.ensureDefaultDataView();
};

core.application.register({
id: APP_ID,
title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
mount: async (params: AppMountParameters) => {
const { core: coreStart, plugins: deps } = startServices();

await Promise.all([
this.initParts(
core,
data,
charts,
expressions,
fieldFormats,
deps.fieldFormats.deserialize,
eventAnnotation
),
ensureDefaultDataView(),
]);
await this.initParts(
core,
data,
charts,
expressions,
fieldFormats,
deps.fieldFormats.deserialize,
eventAnnotation
);

const { mountApp, stopReportManager, getLensAttributeService } = await import(
'./async_services'
Expand Down
3 changes: 2 additions & 1 deletion x-pack/test/functional/apps/lens/group3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
loadTestFile(require.resolve('./lens_tagging'));
loadTestFile(require.resolve('./lens_reporting'));
loadTestFile(require.resolve('./tsvb_open_in_lens'));
// has to be last one in the suite because it overrides saved objects
// keep these two last in the group in this order because they are messing with the default saved objects
loadTestFile(require.resolve('./rollup'));
loadTestFile(require.resolve('./no_data'));
});
};
72 changes: 72 additions & 0 deletions x-pack/test/functional/apps/lens/group3/no_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const find = getService('find');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'lens', 'header', 'timePicker']);

const createDataView = async (dataViewName: string) => {
await testSubjects.setValue('createIndexPatternNameInput', dataViewName, {
clearWithKeyboard: true,
typeCharByChar: true,
});
await testSubjects.click('saveIndexPatternButton');
};

describe('lens no data', () => {
before(async function () {
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.savedObjects.clean({ types: ['index-pattern'] });
await PageObjects.common.navigateToApp('lens');
});

after(async () => {
await kibanaServer.savedObjects.clean({ types: ['index-pattern'] });
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
});

it('when no data opens integrations', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();

const addIntegrations = await testSubjects.find('kbnOverviewAddIntegrations');
await addIntegrations.click();
await PageObjects.common.waitUntilUrlIncludes('integrations/browse');
});

it('adds a new data view when no data views', async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.savedObjects.clean({ types: ['index-pattern'] });
await PageObjects.common.navigateToApp('lens');

const button = await testSubjects.find('createDataViewButtonFlyout');
button.click();
await retry.waitForWithTimeout('index pattern editor form to be visible', 15000, async () => {
return await (await find.byClassName('indexPatternEditor__form')).isDisplayed();
});

const dataViewToCreate = 'logstash';
await createDataView(dataViewToCreate);
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.waitForWithTimeout(
'data view selector to include a newly created dataview',
5000,
async () => {
const dataViewTitle = await PageObjects.lens.getDataPanelIndexPattern();
// data view editor will add wildcard symbol by default
// so we need to include it in our original title when comparing
return dataViewTitle === `${dataViewToCreate}*`;
}
);
});
});
}

0 comments on commit 395a98b

Please sign in to comment.