[Home] Adding file upload to add data page (#100863)

* [Home] Adding file upload to add data page

* updating comment

* tiny refactor

* attempting to reduce bundle size

* reverting to original home register import

* lazy load tab contents

* changes based on review

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2021-06-02 15:33:49 +01:00 committed by GitHub
parent f4f8ea73f9
commit ad09cdc509
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 217 additions and 8 deletions

View file

@ -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: <SampleDataSetCards addBasePath={this.props.addBasePath} />,
},
...extraTabs.map(({ id, name, component: Component }) => ({
id,
name,
content: <Component />,
})),
];
let openTab = ALL_TAB_ID;
@ -190,8 +198,9 @@ class TutorialDirectoryUi extends React.Component {
};
renderTabContent = () => {
if (this.state.selectedTabId === SAMPLE_DATA_TAB_ID) {
return <SampleDataSetCards addBasePath={this.props.addBasePath} />;
const tab = this.tabs.find(({ id }) => id === this.state.selectedTabId);
if (tab?.content) {
return tab.content;
}
return (

View file

@ -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;

View file

@ -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({}),
});

View file

@ -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),
}));

View file

@ -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<ConfigSchema>) {}
@ -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

View file

@ -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<AddDataServiceSetup> => {
const setup = {
registerAddDataTab: jest.fn(),
};
return setup;
};
const createMock = (): jest.Mocked<PublicMethodsOf<AddDataService>> => {
const service = {
setup: jest.fn(),
getAddDataTabs: jest.fn(() => []),
};
service.setup.mockImplementation(createSetupMock);
return service;
};
export const addDataServiceMock = {
createSetup: createSetupMock,
create: createMock,
};

View file

@ -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: () => <a>123</a> });
setup.registerAddDataTab({ id: 'def', name: 'a b c', component: () => <a>456</a> });
}).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: () => <a>123</a> });
setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => <a>456</a> });
}).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: () => <a>123</a> },
{ id: 'def', name: 'a b c', component: () => <a>456</a> },
];
setup.registerAddDataTab(links[0]);
setup.registerAddDataTab(links[1]);
expect(service.getAddDataTabs()).toEqual(links);
});
});
});

View file

@ -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<string, AddDataTab> = {};
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<AddDataService['setup']>;

View file

@ -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';

View file

@ -26,3 +26,6 @@ export type {
TutorialDirectoryHeaderLinkComponent,
TutorialModuleNoticeComponent,
} from './tutorials';
export { AddDataService } from './add_data';
export type { AddDataServiceSetup, AddDataTab } from './add_data';

View file

@ -14,7 +14,8 @@
],
"optionalPlugins": [
"security",
"maps"
"maps",
"home"
],
"requiredBundles": [
"kibanaReact",

View file

@ -28,3 +28,7 @@ export const FileDataVisualizer: FC = () => {
</KibanaContextProvider>
);
};
// exporting as default so it can be used with React.lazy
// eslint-disable-next-line import/no-default-export
export default FileDataVisualizer;

View file

@ -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 (
<React.Suspense fallback={<div />}>
<FileDataVisualizerComponent />
</React.Suspense>
);
};

View file

@ -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);

View file

@ -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,
});
}