mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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:
parent
a8e8d35a8e
commit
9dd38c49d3
7 changed files with 188 additions and 118 deletions
|
@ -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>;
|
||||
|
|
|
@ -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} />;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
77
test/functional/apps/discover/_no_data.ts
Normal file
77
test/functional/apps/discover/_no_data.ts
Normal 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}*`;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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'),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue