diff --git a/src/plugins/discover/common/index.ts b/src/plugins/discover/common/index.ts index 98ce5fc3b0b2b..173264aee731e 100644 --- a/src/plugins/discover/common/index.ts +++ b/src/plugins/discover/common/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +export const APP_ICON = 'discoverApp'; export const DEFAULT_COLUMNS_SETTING = 'defaultColumns'; export const SAMPLE_SIZE_SETTING = 'discover:sampleSize'; export const SORT_DEFAULT_ORDER_SETTING = 'discover:sort:defaultOrder'; diff --git a/src/plugins/discover/common/services/saved_searches/index.ts b/src/plugins/discover/common/services/saved_searches/index.ts new file mode 100644 index 0000000000000..014fdb31ed438 --- /dev/null +++ b/src/plugins/discover/common/services/saved_searches/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { getSavedSearchUrl, getSavedSearchFullPathUrl } from './saved_searches_url'; diff --git a/src/plugins/discover/common/services/saved_searches/saved_searches_url.test.ts b/src/plugins/discover/common/services/saved_searches/saved_searches_url.test.ts new file mode 100644 index 0000000000000..81f4498939b98 --- /dev/null +++ b/src/plugins/discover/common/services/saved_searches/saved_searches_url.test.ts @@ -0,0 +1,25 @@ +/* + * 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 { getSavedSearchUrl, getSavedSearchFullPathUrl } from './saved_searches_url'; + +describe('saved_searches_url', () => { + describe('getSavedSearchUrl', () => { + test('should return valid saved search url', () => { + expect(getSavedSearchUrl()).toBe('#/'); + expect(getSavedSearchUrl('id')).toBe('#/view/id'); + }); + }); + + describe('getSavedSearchFullPathUrl', () => { + test('should return valid full path url', () => { + expect(getSavedSearchFullPathUrl()).toBe('/app/discover#/'); + expect(getSavedSearchFullPathUrl('id')).toBe('/app/discover#/view/id'); + }); + }); +}); diff --git a/src/plugins/discover/common/services/saved_searches/saved_searches_url.ts b/src/plugins/discover/common/services/saved_searches/saved_searches_url.ts new file mode 100644 index 0000000000000..cc5ecdb61f565 --- /dev/null +++ b/src/plugins/discover/common/services/saved_searches/saved_searches_url.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export const getSavedSearchUrl = (id?: string) => (id ? `#/view/${encodeURIComponent(id)}` : '#/'); + +export const getSavedSearchFullPathUrl = (id?: string) => `/app/discover${getSavedSearchUrl(id)}`; diff --git a/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts b/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts index 4a4713badb807..9b42d7557b05c 100644 --- a/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts +++ b/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts @@ -7,8 +7,6 @@ */ import { - getSavedSearchUrl, - getSavedSearchFullPathUrl, fromSavedSearchAttributes, toSavedSearchAttributes, throwErrorOnSavedSearchUrlConflict, @@ -19,20 +17,6 @@ import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import type { SavedSearchAttributes, SavedSearch } from './types'; describe('saved_searches_utils', () => { - describe('getSavedSearchUrl', () => { - test('should return valid saved search url', () => { - expect(getSavedSearchUrl()).toBe('#/'); - expect(getSavedSearchUrl('id')).toBe('#/view/id'); - }); - }); - - describe('getSavedSearchFullPathUrl', () => { - test('should return valid full path url', () => { - expect(getSavedSearchFullPathUrl()).toBe('/app/discover#/'); - expect(getSavedSearchFullPathUrl('id')).toBe('/app/discover#/view/id'); - }); - }); - describe('fromSavedSearchAttributes', () => { test('should convert attributes into SavedSearch', () => { const attributes: SavedSearchAttributes = { diff --git a/src/plugins/discover/public/services/saved_searches/saved_searches_utils.ts b/src/plugins/discover/public/services/saved_searches/saved_searches_utils.ts index 4dbb84613ead8..26b3c0b7cf9b5 100644 --- a/src/plugins/discover/public/services/saved_searches/saved_searches_utils.ts +++ b/src/plugins/discover/public/services/saved_searches/saved_searches_utils.ts @@ -8,9 +8,10 @@ import { i18n } from '@kbn/i18n'; import type { SavedSearchAttributes, SavedSearch } from './types'; -export const getSavedSearchUrl = (id?: string) => (id ? `#/view/${encodeURIComponent(id)}` : '#/'); - -export const getSavedSearchFullPathUrl = (id?: string) => `/app/discover${getSavedSearchUrl(id)}`; +export { + getSavedSearchUrl, + getSavedSearchFullPathUrl, +} from '../../../common/services/saved_searches'; export const getSavedSearchUrlConflictMessage = async (savedSearch: SavedSearch) => i18n.translate('discover.savedSearchEmbeddable.legacyURLConflict.errorMessage', { diff --git a/src/plugins/discover/server/plugin.ts b/src/plugins/discover/server/plugin.ts index 9147f533d28d6..888fcf55c2351 100644 --- a/src/plugins/discover/server/plugin.ts +++ b/src/plugins/discover/server/plugin.ts @@ -8,15 +8,18 @@ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/server'; import type { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; +import type { HomeServerPluginSetup } from '@kbn/home-plugin/server'; import { getUiSettings } from './ui_settings'; import { capabilitiesProvider } from './capabilities_provider'; import { getSavedSearchObjectType } from './saved_objects'; +import { registerSampleData } from './sample_data'; export class DiscoverServerPlugin implements Plugin { public setup( core: CoreSetup, plugins: { data: DataPluginSetup; + home?: HomeServerPluginSetup; } ) { const getSearchSourceMigrations = plugins.data.search.searchSource.getAllMigrations.bind( @@ -26,6 +29,10 @@ export class DiscoverServerPlugin implements Plugin { core.uiSettings.register(getUiSettings(core.docLinks)); core.savedObjects.registerType(getSavedSearchObjectType(getSearchSourceMigrations)); + if (plugins.home) { + registerSampleData(plugins.home.sampleData); + } + return {}; } diff --git a/src/plugins/discover/server/sample_data/index.ts b/src/plugins/discover/server/sample_data/index.ts new file mode 100644 index 0000000000000..43edd42293edf --- /dev/null +++ b/src/plugins/discover/server/sample_data/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { registerSampleData } from './register_sample_data'; diff --git a/src/plugins/discover/server/sample_data/register_sample_data.ts b/src/plugins/discover/server/sample_data/register_sample_data.ts new file mode 100644 index 0000000000000..a1ff9951d9179 --- /dev/null +++ b/src/plugins/discover/server/sample_data/register_sample_data.ts @@ -0,0 +1,44 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { SampleDataRegistrySetup } from '@kbn/home-plugin/server'; +import { APP_ICON } from '../../common'; +import { getSavedSearchFullPathUrl } from '../../common/services/saved_searches'; + +function getDiscoverPathForSampleDataset(objId: string) { + // TODO: remove the time range from the URL query when saved search objects start supporting time range configuration + // https://github.com/elastic/kibana/issues/9761 + return `${getSavedSearchFullPathUrl(objId)}?_g=(time:(from:now-7d,to:now))`; +} + +export function registerSampleData(sampleDataRegistry: SampleDataRegistrySetup) { + const linkLabel = i18n.translate('discover.sampleData.viewLinkLabel', { + defaultMessage: 'Discover', + }); + const { addAppLinksToSampleDataset, getSampleDatasets } = sampleDataRegistry; + const sampleDatasets = getSampleDatasets(); + + sampleDatasets.forEach((sampleDataset) => { + const sampleSavedSearchObject = sampleDataset.savedObjects.find( + (object) => object.type === 'search' + ); + + if (sampleSavedSearchObject) { + addAppLinksToSampleDataset(sampleDataset.id, [ + { + sampleObject: sampleSavedSearchObject, + getPath: getDiscoverPathForSampleDataset, + label: linkLabel, + icon: APP_ICON, + order: -1, + }, + ]); + } + }); +} diff --git a/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap index d9e341394ee00..0d634049305ad 100644 --- a/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap @@ -57,6 +57,90 @@ exports[`should render popover when appLinks is not empty 1`] = ` `; +exports[`should render popover with ordered appLinks 1`] = ` + + View data + + } + closePopover={[Function]} + data-test-subj="launchSampleDataSetecommerce" + display="inlineBlock" + hasArrow={true} + id="sampleDataLinksecommerce" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" +> + , + "name": "myAppLabel[-1]", + "onClick": [Function], + }, + Object { + "data-test-subj": "viewSampleDataSetecommerce-dashboard", + "href": "root/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f", + "icon": , + "name": "Dashboard", + "onClick": [Function], + }, + Object { + "href": "rootapp/myAppPath", + "icon": , + "name": "myAppLabel[3]", + "onClick": [Function], + }, + Object { + "href": "rootapp/myAppPath", + "icon": , + "name": "myAppLabel[5]", + "onClick": [Function], + }, + Object { + "href": "rootapp/myAppPath", + "icon": , + "name": "myAppLabel", + "onClick": [Function], + }, + ], + }, + ] + } + size="m" + /> + +`; + exports[`should render simple button when appLinks is empty 1`] = ` { + const dashboardAppLink = { + path: dashboardPath, + label: i18n.translate('home.sampleDataSetCard.dashboardLinkLabel', { + defaultMessage: 'Dashboard', + }), + icon: 'dashboardApp', + order: 0, + 'data-test-subj': `viewSampleDataSet${this.props.id}-dashboard`, + }; + + const sortedItems = sortBy([dashboardAppLink, ...this.props.appLinks], 'order'); + const items = sortedItems.map(({ path, label, icon, ...rest }) => { return { name: label, icon: , href: this.addBasePath(path), onClick: createAppNavigationHandler(path), + ...(rest['data-test-subj'] ? { 'data-test-subj': rest['data-test-subj'] } : {}), }; }); @@ -75,18 +87,7 @@ export class SampleDataViewDataButton extends React.Component { const panels = [ { id: 0, - items: [ - { - name: i18n.translate('home.sampleDataSetCard.dashboardLinkLabel', { - defaultMessage: 'Dashboard', - }), - icon: , - href: prefixedDashboardPath, - onClick: createAppNavigationHandler(dashboardPath), - 'data-test-subj': `viewSampleDataSet${this.props.id}-dashboard`, - }, - ...additionalItems, - ], + items, }, ]; const popoverButton = ( @@ -124,6 +125,7 @@ SampleDataViewDataButton.propTypes = { path: PropTypes.string.isRequired, label: PropTypes.string.isRequired, icon: PropTypes.string.isRequired, + order: PropTypes.number, }) ).isRequired, }; diff --git a/src/plugins/home/public/application/components/sample_data_view_data_button.test.js b/src/plugins/home/public/application/components/sample_data_view_data_button.test.js index b097b5e322500..f3cfd5a7a661e 100644 --- a/src/plugins/home/public/application/components/sample_data_view_data_button.test.js +++ b/src/plugins/home/public/application/components/sample_data_view_data_button.test.js @@ -48,3 +48,41 @@ test('should render popover when appLinks is not empty', () => { ); expect(component).toMatchSnapshot(); // eslint-disable-line }); + +test('should render popover with ordered appLinks', () => { + const appLinks = [ + { + path: 'app/myAppPath', + label: 'myAppLabel[-1]', + icon: 'logoKibana', + order: -1, // to position it above Dashboard link + }, + { + path: 'app/myAppPath', + label: 'myAppLabel', + icon: 'logoKibana', + }, + { + path: 'app/myAppPath', + label: 'myAppLabel[5]', + icon: 'logoKibana', + order: 5, + }, + { + path: 'app/myAppPath', + label: 'myAppLabel[3]', + icon: 'logoKibana', + order: 3, + }, + ]; + + const component = shallow( + + ); + expect(component).toMatchSnapshot(); // eslint-disable-line +}); diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts index 8d26d08460b5b..9b1212e13b024 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts @@ -58,4 +58,11 @@ export interface AppLinkData { * The icon for this app link. */ icon: string; + /** + * Index of the links (ascending order, smallest will be displayed first). + * Used for ordering in the dropdown. + * + * @remark links without order defined will be displayed last + */ + order?: number; } diff --git a/src/plugins/home/server/services/sample_data/routes/list.ts b/src/plugins/home/server/services/sample_data/routes/list.ts index 39690b3944d0c..a83ee7a57c432 100644 --- a/src/plugins/home/server/services/sample_data/routes/list.ts +++ b/src/plugins/home/server/services/sample_data/routes/list.ts @@ -35,12 +35,12 @@ export const createListRoute = ( ?.foundObjectId ?? id; const appLinks = (appLinksMap.get(sampleDataset.id) ?? []).map((data) => { - const { sampleObject, getPath, label, icon } = data; + const { sampleObject, getPath, label, icon, order } = data; if (sampleObject === null) { - return { path: getPath(''), label, icon }; + return { path: getPath(''), label, icon, order }; } const objectId = findObjectId(sampleObject.type, sampleObject.id); - return { path: getPath(objectId), label, icon }; + return { path: getPath(objectId), label, icon, order }; }); const sampleDataStatus = await getSampleDatasetStatus( context, diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index 1e3e6a9634f4c..4acd8a6e10e95 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -78,6 +78,11 @@ export class HomePageObject extends FtrService { }); } + async launchSampleDiscover(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('Discover'); + } + async launchSampleDashboard(id: string) { await this.launchSampleDataSet(id); await this.find.clickByLinkText('Dashboard'); diff --git a/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts index 150458919d41d..1d2df7a703161 100644 --- a/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getPageObjects, getService }: FtrProviderContext) { +export default function ({ getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'home', 'discover', 'timePicker']); describe('upgrade discover smoke tests', function describeIndexTests() { @@ -18,9 +18,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]; const discoverTests = [ - { name: 'kibana_sample_data_flights', timefield: true, hits: '' }, - { name: 'kibana_sample_data_logs', timefield: true, hits: '' }, - { name: 'kibana_sample_data_ecommerce', timefield: true, hits: '' }, + { name: 'flights', timefield: true, hits: '' }, + { name: 'logs', timefield: true, hits: '' }, + { name: 'ecommerce', timefield: true, hits: '' }, ]; spaces.forEach(({ space, basePath }) => { @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { basePath, }); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.discover.selectIndexPattern(name); + await PageObjects.discover.selectIndexPattern(`kibana_sample_data_${name}`); await PageObjects.discover.waitUntilSearchingHasFinished(); if (timefield) { await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); @@ -52,6 +52,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); }); + + discoverTests.forEach(({ name, timefield, hits }) => { + describe('space: ' + space + ', name: ' + name, () => { + before(async () => { + await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { + basePath, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.launchSampleDiscover(name); + await PageObjects.header.waitUntilLoadingHasFinished(); + if (timefield) { + await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + } + }); + it('shows hit count greater than zero', async () => { + const hitCount = await PageObjects.discover.getHitCount(); + if (hits === '') { + expect(hitCount).to.be.greaterThan(0); + } else { + expect(hitCount).to.be.equal(hits); + } + }); + it('shows table rows not empty', async () => { + const tableRows = await PageObjects.discover.getDocTableRows(); + expect(tableRows.length).to.be.greaterThan(0); + }); + }); + }); }); }); }