[Lens] "No data" page in Lens (#132264)

* 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>
This commit is contained in:
Joe Reuter 2022-05-17 18:02:19 +02:00 committed by GitHub
parent 11b9c2924a
commit 395a98bd3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 20 deletions

View file

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

View file

@ -312,12 +312,6 @@ 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,
@ -325,18 +319,15 @@ export class LensPlugin {
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'

View file

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

View file

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