diff --git a/src/plugins/home/public/application/components/tutorial_directory.js b/src/plugins/home/public/application/components/tutorial_directory.js index 094ac302fcebe..1fda865ebd847 100644 --- a/src/plugins/home/public/application/components/tutorial_directory.js +++ b/src/plugins/home/public/application/components/tutorial_directory.js @@ -42,6 +42,8 @@ class TutorialDirectoryUi extends React.Component { constructor(props) { super(props); + const extraTabs = getServices().addDataService.getAddDataTabs(); + this.tabs = [ { id: ALL_TAB_ID, @@ -77,7 +79,13 @@ class TutorialDirectoryUi extends React.Component { id: 'home.tutorial.tabs.sampleDataTitle', defaultMessage: 'Sample data', }), + content: , }, + ...extraTabs.map(({ id, name, component: Component }) => ({ + id, + name, + content: , + })), ]; let openTab = ALL_TAB_ID; @@ -190,8 +198,9 @@ class TutorialDirectoryUi extends React.Component { }; renderTabContent = () => { - if (this.state.selectedTabId === SAMPLE_DATA_TAB_ID) { - return ; + const tab = this.tabs.find(({ id }) => id === this.state.selectedTabId); + if (tab?.content) { + return tab.content; } return ( diff --git a/src/plugins/home/public/application/kibana_services.ts b/src/plugins/home/public/application/kibana_services.ts index 73a8ab41bcfd2..af9f956889547 100644 --- a/src/plugins/home/public/application/kibana_services.ts +++ b/src/plugins/home/public/application/kibana_services.ts @@ -20,6 +20,7 @@ import { UiCounterMetricType } from '@kbn/analytics'; import { TelemetryPluginStart } from '../../../telemetry/public'; import { UrlForwardingStart } from '../../../url_forwarding/public'; import { TutorialService } from '../services/tutorials'; +import { AddDataService } from '../services/add_data'; import { FeatureCatalogueRegistry } from '../services/feature_catalogue'; import { EnvironmentService } from '../services/environment'; import { ConfigSchema } from '../../config'; @@ -44,6 +45,7 @@ export interface HomeKibanaServices { environmentService: EnvironmentService; telemetry?: TelemetryPluginStart; tutorialService: TutorialService; + addDataService: AddDataService; } let services: HomeKibanaServices | null = null; diff --git a/src/plugins/home/public/mocks.ts b/src/plugins/home/public/mocks.ts index 32bec31153ba0..10c186ee3f4e3 100644 --- a/src/plugins/home/public/mocks.ts +++ b/src/plugins/home/public/mocks.ts @@ -10,11 +10,13 @@ import { featureCatalogueRegistryMock } from './services/feature_catalogue/featu import { environmentServiceMock } from './services/environment/environment.mock'; import { configSchema } from '../config'; import { tutorialServiceMock } from './services/tutorials/tutorial_service.mock'; +import { addDataServiceMock } from './services/add_data/add_data_service.mock'; const createSetupContract = () => ({ featureCatalogue: featureCatalogueRegistryMock.createSetup(), environment: environmentServiceMock.createSetup(), tutorials: tutorialServiceMock.createSetup(), + addData: addDataServiceMock.createSetup(), config: configSchema.validate({}), }); diff --git a/src/plugins/home/public/plugin.test.mocks.ts b/src/plugins/home/public/plugin.test.mocks.ts index 779ab2e700352..c3e3c50a2fe0f 100644 --- a/src/plugins/home/public/plugin.test.mocks.ts +++ b/src/plugins/home/public/plugin.test.mocks.ts @@ -9,12 +9,15 @@ import { featureCatalogueRegistryMock } from './services/feature_catalogue/feature_catalogue_registry.mock'; import { environmentServiceMock } from './services/environment/environment.mock'; import { tutorialServiceMock } from './services/tutorials/tutorial_service.mock'; +import { addDataServiceMock } from './services/add_data/add_data_service.mock'; export const registryMock = featureCatalogueRegistryMock.create(); export const environmentMock = environmentServiceMock.create(); export const tutorialMock = tutorialServiceMock.create(); +export const addDataMock = addDataServiceMock.create(); jest.doMock('./services', () => ({ FeatureCatalogueRegistry: jest.fn(() => registryMock), EnvironmentService: jest.fn(() => environmentMock), TutorialService: jest.fn(() => tutorialMock), + AddDataService: jest.fn(() => addDataMock), })); diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index 89c7600a1d85d..b3b5ce487b747 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -24,6 +24,8 @@ import { FeatureCatalogueRegistrySetup, TutorialService, TutorialServiceSetup, + AddDataService, + AddDataServiceSetup, } from './services'; import { ConfigSchema } from '../config'; import { setServices } from './application/kibana_services'; @@ -56,6 +58,7 @@ export class HomePublicPlugin private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); private readonly environmentService = new EnvironmentService(); private readonly tutorialService = new TutorialService(); + private readonly addDataService = new AddDataService(); constructor(private readonly initializerContext: PluginInitializerContext) {} @@ -94,6 +97,7 @@ export class HomePublicPlugin urlForwarding: urlForwardingStart, homeConfig: this.initializerContext.config.get(), tutorialService: this.tutorialService, + addDataService: this.addDataService, featureCatalogue: this.featuresCatalogueRegistry, }); coreStart.chrome.docTitle.change( @@ -126,6 +130,7 @@ export class HomePublicPlugin featureCatalogue, environment: { ...this.environmentService.setup() }, tutorials: { ...this.tutorialService.setup() }, + addData: { ...this.addDataService.setup() }, }; } @@ -163,9 +168,13 @@ export type EnvironmentSetup = EnvironmentServiceSetup; /** @public */ export type TutorialSetup = TutorialServiceSetup; +/** @public */ +export type AddDataSetup = AddDataServiceSetup; + /** @public */ export interface HomePublicPluginSetup { tutorials: TutorialServiceSetup; + addData: AddDataServiceSetup; featureCatalogue: FeatureCatalogueSetup; /** * The environment service is only available for a transition period and will diff --git a/src/plugins/home/public/services/add_data/add_data_service.mock.ts b/src/plugins/home/public/services/add_data/add_data_service.mock.ts new file mode 100644 index 0000000000000..e0b4d12909791 --- /dev/null +++ b/src/plugins/home/public/services/add_data/add_data_service.mock.ts @@ -0,0 +1,31 @@ +/* + * 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 type { PublicMethodsOf } from '@kbn/utility-types'; +import { AddDataService, AddDataServiceSetup } from './add_data_service'; + +const createSetupMock = (): jest.Mocked => { + const setup = { + registerAddDataTab: jest.fn(), + }; + return setup; +}; + +const createMock = (): jest.Mocked> => { + const service = { + setup: jest.fn(), + getAddDataTabs: jest.fn(() => []), + }; + service.setup.mockImplementation(createSetupMock); + return service; +}; + +export const addDataServiceMock = { + createSetup: createSetupMock, + create: createMock, +}; diff --git a/src/plugins/home/public/services/add_data/add_data_service.test.tsx b/src/plugins/home/public/services/add_data/add_data_service.test.tsx new file mode 100644 index 0000000000000..b04b80ac19eec --- /dev/null +++ b/src/plugins/home/public/services/add_data/add_data_service.test.tsx @@ -0,0 +1,49 @@ +/* + * 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 React from 'react'; +import { AddDataService } from './add_data_service'; + +describe('AddDataService', () => { + describe('setup', () => { + test('allows multiple register directory header link calls', () => { + const setup = new AddDataService().setup(); + expect(() => { + setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => 123 }); + setup.registerAddDataTab({ id: 'def', name: 'a b c', component: () => 456 }); + }).not.toThrow(); + }); + + test('throws when same directory header link is registered twice', () => { + const setup = new AddDataService().setup(); + expect(() => { + setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => 123 }); + setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => 456 }); + }).toThrow(); + }); + }); + + describe('getDirectoryHeaderLinks', () => { + test('returns empty array', () => { + const service = new AddDataService(); + expect(service.getAddDataTabs()).toEqual([]); + }); + + test('returns last state of register calls', () => { + const service = new AddDataService(); + const setup = service.setup(); + const links = [ + { id: 'abc', name: 'a b c', component: () => 123 }, + { id: 'def', name: 'a b c', component: () => 456 }, + ]; + setup.registerAddDataTab(links[0]); + setup.registerAddDataTab(links[1]); + expect(service.getAddDataTabs()).toEqual(links); + }); + }); +}); diff --git a/src/plugins/home/public/services/add_data/add_data_service.ts b/src/plugins/home/public/services/add_data/add_data_service.ts new file mode 100644 index 0000000000000..668c373f8314d --- /dev/null +++ b/src/plugins/home/public/services/add_data/add_data_service.ts @@ -0,0 +1,40 @@ +/* + * 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 React from 'react'; + +/** @public */ +export interface AddDataTab { + id: string; + name: string; + component: React.FC; +} + +export class AddDataService { + private addDataTabs: Record = {}; + + public setup() { + return { + /** + * Registers a component that will be rendered as a new tab in the Add data page + */ + registerAddDataTab: (tab: AddDataTab) => { + if (this.addDataTabs[tab.id]) { + throw new Error(`Tab ${tab.id} already exists`); + } + this.addDataTabs[tab.id] = tab; + }, + }; + } + + public getAddDataTabs() { + return Object.values(this.addDataTabs); + } +} + +export type AddDataServiceSetup = ReturnType; diff --git a/src/plugins/home/public/services/add_data/index.ts b/src/plugins/home/public/services/add_data/index.ts new file mode 100644 index 0000000000000..f2367ca320e9f --- /dev/null +++ b/src/plugins/home/public/services/add_data/index.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 { AddDataService } from './add_data_service'; + +export type { AddDataServiceSetup, AddDataTab } from './add_data_service'; diff --git a/src/plugins/home/public/services/index.ts b/src/plugins/home/public/services/index.ts index 8cd4c8d84e0f7..65913df6310b1 100644 --- a/src/plugins/home/public/services/index.ts +++ b/src/plugins/home/public/services/index.ts @@ -26,3 +26,6 @@ export type { TutorialDirectoryHeaderLinkComponent, TutorialModuleNoticeComponent, } from './tutorials'; + +export { AddDataService } from './add_data'; +export type { AddDataServiceSetup, AddDataTab } from './add_data'; diff --git a/x-pack/plugins/file_data_visualizer/kibana.json b/x-pack/plugins/file_data_visualizer/kibana.json index 721352cff7c95..eea52bb6e98b2 100644 --- a/x-pack/plugins/file_data_visualizer/kibana.json +++ b/x-pack/plugins/file_data_visualizer/kibana.json @@ -14,7 +14,8 @@ ], "optionalPlugins": [ "security", - "maps" + "maps", + "home" ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx b/x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx index f291076557bb8..c8f327496842a 100644 --- a/x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx @@ -28,3 +28,7 @@ export const FileDataVisualizer: FC = () => { ); }; + +// exporting as default so it can be used with React.lazy +// eslint-disable-next-line import/no-default-export +export default FileDataVisualizer; diff --git a/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/component_wrapper.tsx b/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/component_wrapper.tsx new file mode 100644 index 0000000000000..e6835d9e7a668 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/component_wrapper.tsx @@ -0,0 +1,18 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +const FileDataVisualizerComponent = React.lazy(() => import('../application/file_datavisualizer')); + +export const FileDataVisualizerWrapper: FC = () => { + return ( + }> + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/plugin.ts b/x-pack/plugins/file_data_visualizer/public/plugin.ts index a94c0fce45cd4..0064f96195eaf 100644 --- a/x-pack/plugins/file_data_visualizer/public/plugin.ts +++ b/x-pack/plugins/file_data_visualizer/public/plugin.ts @@ -5,21 +5,24 @@ * 2.0. */ -import { CoreStart } from 'kibana/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import type { SharePluginStart } from '../../../../src/plugins/share/public'; import { Plugin } from '../../../../src/core/public'; import { setStartServices } from './kibana_services'; -import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import type { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import type { FileUploadPluginStart } from '../../file_upload/public'; import type { MapsStartApi } from '../../maps/public'; import type { SecurityPluginSetup } from '../../security/public'; import { getFileDataVisualizerComponent } from './api'; import { getMaxBytesFormatted } from './application/util/get_max_bytes'; +import { registerHomeAddData } from './register_home'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface FileDataVisualizerSetupDependencies {} +export interface FileDataVisualizerSetupDependencies { + home?: HomePublicPluginSetup; +} export interface FileDataVisualizerStartDependencies { data: DataPublicPluginStart; fileUpload: FileUploadPluginStart; @@ -40,7 +43,11 @@ export class FileDataVisualizerPlugin FileDataVisualizerSetupDependencies, FileDataVisualizerStartDependencies > { - public setup() {} + public setup(core: CoreSetup, plugins: FileDataVisualizerSetupDependencies) { + if (plugins.home) { + registerHomeAddData(plugins.home); + } + } public start(core: CoreStart, plugins: FileDataVisualizerStartDependencies) { setStartServices(core, plugins); diff --git a/x-pack/plugins/file_data_visualizer/public/register_home.ts b/x-pack/plugins/file_data_visualizer/public/register_home.ts new file mode 100644 index 0000000000000..e54c37a8d06bc --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/register_home.ts @@ -0,0 +1,20 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { FileDataVisualizerWrapper } from './lazy_load_bundle/component_wrapper'; + +export function registerHomeAddData(home: HomePublicPluginSetup) { + home.addData.registerAddDataTab({ + id: 'fileDataViz', + name: i18n.translate('xpack.fileDataVisualizer.embeddedTabTitle', { + defaultMessage: 'Upload file', + }), + component: FileDataVisualizerWrapper, + }); +}