[Discover] Add Analytics No Data Page (#131965)

* [Discover] Add Analytics No Data Page

* Make showEmptyPrompt parameter optional

* Remove unused import

* Remove unnecessary test

* Fix test

* Update failing test?

* Update failing test

* Changing the order of functional tests

* Fix error handling

* Addressing PR comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Maja Grubic 2022-05-17 15:50:57 +02:00 committed by GitHub
parent a8e8d35a8e
commit 9dd38c49d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 188 additions and 118 deletions

View file

@ -77,9 +77,7 @@ export interface AnalyticsNoDataPageKibanaDependencies {
coreStart: {
application: {
capabilities: {
navLinks: {
integrations: boolean;
};
navLinks: Record<string, boolean>;
};
currentAppId$: Observable<string | undefined>;
navigateToUrl: (url: string) => Promise<void>;

View file

@ -15,6 +15,10 @@ import {
} from '@kbn/data-views-plugin/public';
import { redirectWhenMissing } from '@kbn/kibana-utils-plugin/public';
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
import {
AnalyticsNoDataPageKibanaProvider,
AnalyticsNoDataPage,
} from '@kbn/shared-ux-page-analytics-no-data';
import {
SavedSearch,
getSavedSearch,
@ -45,6 +49,7 @@ export function DiscoverMainRoute() {
data,
toastNotifications,
http: { basePath },
dataViewEditor,
} = services;
const [error, setError] = useState<Error>();
const [savedSearch, setSavedSearch] = useState<SavedSearch>();
@ -52,6 +57,7 @@ export function DiscoverMainRoute() {
const [indexPatternList, setIndexPatternList] = useState<Array<SavedObject<DataViewAttributes>>>(
[]
);
const [showNoDataPage, setShowNoDataPage] = useState<boolean>(false);
const { id } = useParams<DiscoverLandingParams>();
useExecutionContext(core.executionContext, {
@ -60,27 +66,20 @@ export function DiscoverMainRoute() {
id: id || 'new',
});
const navigateToOverview = useCallback(() => {
core.application.navigateToApp('kibanaOverview', { path: '#' });
}, [core.application]);
const checkForDataViews = useCallback(async () => {
const hasUserDataView = await data.dataViews.hasUserDataView().catch(() => true);
if (!hasUserDataView) {
navigateToOverview();
}
const defaultDataView = await data.dataViews.getDefaultDataView();
if (!defaultDataView) {
navigateToOverview();
}
}, [navigateToOverview, data.dataViews]);
useEffect(() => {
const savedSearchId = id;
async function loadDefaultOrCurrentIndexPattern(searchSource: ISearchSource) {
const loadDefaultOrCurrentIndexPattern = useCallback(
async (searchSource: ISearchSource) => {
try {
await checkForDataViews();
const hasUserDataView = await data.dataViews.hasData.hasUserDataView().catch(() => false);
const hasEsData = await data.dataViews.hasData.hasESData().catch(() => false);
if (!hasUserDataView || !hasEsData) {
setShowNoDataPage(true);
return;
}
const defaultDataView = await data.dataViews.getDefaultDataView();
if (!defaultDataView) {
setShowNoDataPage(true);
return;
}
const { appStateContainer } = getState({ history, uiSettings: config });
const { index } = appStateContainer.getState();
const ip = await loadIndexPattern(index || '', data.dataViews, config);
@ -94,78 +93,91 @@ export function DiscoverMainRoute() {
} catch (e) {
setError(e);
}
}
},
[config, data.dataViews, history, toastNotifications]
);
async function loadSavedSearch() {
try {
const currentSavedSearch = await getSavedSearch(savedSearchId, {
search: services.data.search,
savedObjectsClient: core.savedObjects.client,
spaces: services.spaces,
});
const loadSavedSearch = useCallback(async () => {
try {
const currentSavedSearch = await getSavedSearch(id, {
search: services.data.search,
savedObjectsClient: core.savedObjects.client,
spaces: services.spaces,
});
const loadedIndexPattern = await loadDefaultOrCurrentIndexPattern(
currentSavedSearch.searchSource
const loadedIndexPattern = await loadDefaultOrCurrentIndexPattern(
currentSavedSearch.searchSource
);
if (!loadedIndexPattern) {
return;
}
if (!currentSavedSearch.searchSource.getField('index')) {
currentSavedSearch.searchSource.setField('index', loadedIndexPattern);
}
setSavedSearch(currentSavedSearch);
if (currentSavedSearch.id) {
chrome.recentlyAccessed.add(
getSavedSearchFullPathUrl(currentSavedSearch.id),
currentSavedSearch.title ?? '',
currentSavedSearch.id
);
if (!loadedIndexPattern) {
return;
}
if (!currentSavedSearch.searchSource.getField('index')) {
currentSavedSearch.searchSource.setField('index', loadedIndexPattern);
}
setSavedSearch(currentSavedSearch);
if (currentSavedSearch.id) {
chrome.recentlyAccessed.add(
getSavedSearchFullPathUrl(currentSavedSearch.id),
currentSavedSearch.title ?? '',
currentSavedSearch.id
);
}
} catch (e) {
if (e instanceof DataViewSavedObjectConflictError) {
setError(e);
} else {
redirectWhenMissing({
history,
navigateToApp: core.application.navigateToApp,
basePath,
mapping: {
search: '/',
'index-pattern': {
app: 'management',
path: `kibana/objects/savedSearches/${id}`,
},
}
} catch (e) {
if (e instanceof DataViewSavedObjectConflictError) {
setError(e);
} else {
redirectWhenMissing({
history,
navigateToApp: core.application.navigateToApp,
basePath,
mapping: {
search: '/',
'index-pattern': {
app: 'management',
path: `kibana/objects/savedSearches/${id}`,
},
toastNotifications,
onBeforeRedirect() {
getUrlTracker().setTrackedUrl('/');
},
theme: core.theme,
})(e);
}
},
toastNotifications,
onBeforeRedirect() {
getUrlTracker().setTrackedUrl('/');
},
theme: core.theme,
})(e);
}
}
loadSavedSearch();
}, [
core.savedObjects.client,
basePath,
chrome.recentlyAccessed,
config,
core.application.navigateToApp,
data.dataViews,
history,
id,
services,
toastNotifications,
services.data.search,
services.spaces,
core.savedObjects.client,
core.application.navigateToApp,
core.theme,
checkForDataViews,
loadDefaultOrCurrentIndexPattern,
chrome.recentlyAccessed,
history,
basePath,
toastNotifications,
]);
const onDataViewCreated = useCallback(
async (dataView: unknown) => {
if (dataView) {
setShowNoDataPage(false);
setError(undefined);
await loadSavedSearch();
}
},
[loadSavedSearch]
);
useEffect(() => {
loadSavedSearch();
}, [loadSavedSearch]);
useEffect(() => {
chrome.setBreadcrumbs(
savedSearch && savedSearch.title
@ -174,6 +186,19 @@ export function DiscoverMainRoute() {
);
}, [chrome, savedSearch]);
if (showNoDataPage) {
const analyticsServices = {
coreStart: core,
dataViews: data.dataViews,
dataViewEditor,
};
return (
<AnalyticsNoDataPageKibanaProvider {...analyticsServices}>
<AnalyticsNoDataPage onDataViewCreated={onDataViewCreated} />;
</AnalyticsNoDataPageKibanaProvider>
);
}
if (error) {
return <DiscoverError error={error} />;
}

View file

@ -1,30 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'timePicker', 'discover']);
describe('empty state', () => {
before(async () => {
await kibanaServer.uiSettings.unset('defaultIndex');
await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] });
});
it('redirects to Overview app', async () => {
await PageObjects.common.navigateToApp('discover');
const selector = await testSubjects.find('kibanaChrome');
const content = await selector.findByCssSelector('.kbnNoDataPageContents');
expect(content).not.to.be(null);
});
});
}

View file

@ -0,0 +1,77 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const log = getService('log');
const retry = getService('retry');
const find = getService('find');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']);
const createDataView = async (dataViewName: string) => {
await testSubjects.setValue('createIndexPatternNameInput', dataViewName, {
clearWithKeyboard: true,
typeCharByChar: true,
});
await testSubjects.click('saveIndexPatternButton');
};
describe('discover no data', () => {
const kbnDirectory = 'test/functional/fixtures/kbn_archiver/discover';
before(async function () {
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] });
log.debug('load kibana with no data');
await kibanaServer.importExport.unload(kbnDirectory);
await PageObjects.common.navigateToApp('discover');
});
after(async () => {
await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] });
await esArchiver.unload('test/functional/fixtures/es_archiver/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('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] });
await PageObjects.common.navigateToApp('discover');
const button = await testSubjects.find('createDataViewButtonFlyout');
button.click();
await retry.waitForWithTimeout('data view 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.discover.getCurrentlySelectedDataView();
// data view editor will add wildcard symbol by default
// so we need to include it in our original title when comparing
return dataViewTitle === `${dataViewToCreate}*`;
}
);
});
});
}

View file

@ -25,6 +25,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_data_view_editor'));
loadTestFile(require.resolve('./_saved_queries'));
} else {
loadTestFile(require.resolve('./_no_data'));
loadTestFile(require.resolve('./_saved_queries'));
loadTestFile(require.resolve('./_discover'));
loadTestFile(require.resolve('./_discover_histogram'));
@ -57,7 +58,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_chart_hidden'));
loadTestFile(require.resolve('./_context_encoded_url_param'));
loadTestFile(require.resolve('./_data_view_editor'));
loadTestFile(require.resolve('./_empty_state'));
}
});
}

View file

@ -172,13 +172,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await spacesService.delete('custom_space_no_index_patterns');
});
it('Navigates to Kibana Analytics overview when no data views exist', async () => {
it('shows empty prompt when no data views exist', async () => {
await PageObjects.common.navigateToUrl('discover', '', {
basePath: '/s/custom_space_no_index_patterns',
ensureCurrentUrl: false,
shouldUseHashForSubUrl: false,
});
await testSubjects.existOrFail('kbnOverviewAddIntegrations', {
await testSubjects.existOrFail('noDataViewsPrompt', {
timeout: config.get('timeouts.waitFor'),
});
});

View file

@ -50,8 +50,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
pageObjects,
// list paths to the files that contain your plugins tests
testFiles: [
resolve(__dirname, './apps/discover'),
resolve(__dirname, './apps/triggers_actions_ui'),
resolve(__dirname, './apps/discover'),
resolve(__dirname, './apps/uptime'),
resolve(__dirname, './apps/ml'),
resolve(__dirname, './apps/cases'),