-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[Discover] Add Analytics No Data Page #131965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
42a9da7
9706e5e
e9a2219
2a9eb17
8fd71ba
a29c53e
d2a3323
2baee7f
a96580c
bd55a86
fc7b28f
0e3ee01
5d9b93a
cd11ce5
72582ba
2c3b3ae
e099301
c05cd5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,13 +49,15 @@ export function DiscoverMainRoute() { | |
| data, | ||
| toastNotifications, | ||
| http: { basePath }, | ||
| dataViewEditor, | ||
| } = services; | ||
| const [error, setError] = useState<Error>(); | ||
| const [savedSearch, setSavedSearch] = useState<SavedSearch>(); | ||
| const indexPattern = savedSearch?.searchSource?.getField('index'); | ||
| const [indexPatternList, setIndexPatternList] = useState<Array<SavedObject<DataViewAttributes>>>( | ||
| [] | ||
| ); | ||
| const [showNoDataPage, setShowNoDataPage] = useState<boolean>(false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's already being tackled in the component itself: #132272 |
||
| 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 loadedIndexPattern = await loadDefaultOrCurrentIndexPattern( | ||
| currentSavedSearch.searchSource | ||
| ); | ||
| const loadSavedSearch = useCallback(async () => { | ||
| try { | ||
| const currentSavedSearch = await getSavedSearch(id, { | ||
| search: services.data.search, | ||
| savedObjectsClient: core.savedObjects.client, | ||
| spaces: services.spaces, | ||
| }); | ||
|
|
||
| if (!loadedIndexPattern) { | ||
| return; | ||
| } | ||
| const loadedIndexPattern = await loadDefaultOrCurrentIndexPattern( | ||
| currentSavedSearch.searchSource | ||
| ); | ||
|
|
||
| if (!currentSavedSearch.searchSource.getField('index')) { | ||
| currentSavedSearch.searchSource.setField('index', loadedIndexPattern); | ||
| } | ||
| if (!loadedIndexPattern) { | ||
| return; | ||
| } | ||
|
|
||
| setSavedSearch(currentSavedSearch); | ||
| if (!currentSavedSearch.searchSource.getField('index')) { | ||
| currentSavedSearch.searchSource.setField('index', loadedIndexPattern); | ||
| } | ||
|
|
||
| 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}`, | ||
| }, | ||
| }, | ||
| toastNotifications, | ||
| onBeforeRedirect() { | ||
| getUrlTracker().setTrackedUrl('/'); | ||
| 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}`, | ||
| }, | ||
| theme: core.theme, | ||
| })(e); | ||
| } | ||
| }, | ||
| toastNotifications, | ||
| onBeforeRedirect() { | ||
| getUrlTracker().setTrackedUrl('/'); | ||
| }, | ||
| theme: core.theme, | ||
| })(e); | ||
| } | ||
| } | ||
|
|
||
| loadSavedSearch(); | ||
| }, [ | ||
| id, | ||
| services.data.search, | ||
| services.spaces, | ||
| core.savedObjects.client, | ||
| basePath, | ||
| chrome.recentlyAccessed, | ||
| config, | ||
| core.application.navigateToApp, | ||
| data.dataViews, | ||
| core.theme, | ||
| loadDefaultOrCurrentIndexPattern, | ||
| chrome.recentlyAccessed, | ||
| history, | ||
| id, | ||
| services, | ||
| basePath, | ||
| toastNotifications, | ||
| core.theme, | ||
| checkForDataViews, | ||
| ]); | ||
|
|
||
| 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} />; | ||
| } | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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}*`; | ||
| } | ||
| ); | ||
| }); | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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')); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to go first, as it's the only way to ensure there is indeed no data in the cluster yet (some tests don't unload their fixtures 😱)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is fine 👍 |
||
| 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')); | ||
| } | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needed to change the order of the tests here. If the Discover test runs first, it will clean up its data & indices. Before my change, in case of no data, Discover would redirect to |
||
| resolve(__dirname, './apps/uptime'), | ||
| resolve(__dirname, './apps/ml'), | ||
| resolve(__dirname, './apps/cases'), | ||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/elastic/kibana/blob/main/src/core/types/capabilities.ts#L18