mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
11b9c2924a
commit
395a98bd3e
4 changed files with 123 additions and 20 deletions
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
x-pack/test/functional/apps/lens/group3/no_data.ts
Normal file
72
x-pack/test/functional/apps/lens/group3/no_data.ts
Normal 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}*`;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue