Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions packages/shared-ux/page/analytics_no_data/src/services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@ export interface AnalyticsNoDataPageKibanaDependencies {
coreStart: {
application: {
capabilities: {
navLinks: {
integrations: boolean;
};
navLinks: Record<string, boolean>;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};
currentAppId$: Observable<string | undefined>;
navigateToUrl: (url: string) => Promise<void>;
Expand Down
179 changes: 102 additions & 77 deletions src/plugins/discover/public/application/main/discover_main_route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small suggestion: would be great to avoid jumping from "Welcome to Analytics" to No data view components and show a loading instead.

May-17-2022 12-06-28

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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, {
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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} />;
}
Expand Down
30 changes: 0 additions & 30 deletions test/functional/apps/discover/_empty_state.ts

This file was deleted.

77 changes: 77 additions & 0 deletions test/functional/apps/discover/_no_data.ts
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}*`;
}
);
});
});
}
2 changes: 1 addition & 1 deletion test/functional/apps/discover/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Copy link
Copy Markdown
Contributor Author

@majagrubic majagrubic May 12, 2022

Choose a reason for hiding this comment

The 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 😱)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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'));
Expand Down Expand Up @@ -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
Expand Up @@ -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'),
});
});
Expand Down
2 changes: 1 addition & 1 deletion x-pack/test/functional_with_es_ssl/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Copy link
Copy Markdown
Contributor Author

@majagrubic majagrubic May 12, 2022

Choose a reason for hiding this comment

The 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 kibana_overview app. With this change, in case of no data, it stays within Discover. So the triggers_actions_ui test starts from Discover, and the user for the test doesn't have Discover permissions, so it was throwing an error.
Changing the order of the test seemed like a cleaner solution than adding Discover-specific privileges to the user, since they are not really needed.

resolve(__dirname, './apps/uptime'),
resolve(__dirname, './apps/ml'),
resolve(__dirname, './apps/cases'),
Expand Down