From b87be6763e48a49411ff8fc563fae6698e7c1949 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 16 Sep 2021 16:10:17 -0400 Subject: [PATCH 01/31] initial boilerplate --- docs/developer/plugin-list.asciidoc | 2 + src/plugins/custom_integrations/README.md | 9 +++ .../custom_integrations/common/index.ts | 62 ++++++++++++++++ src/plugins/custom_integrations/kibana.json | 16 ++++ .../custom_integrations/public/index.ts | 16 ++++ .../custom_integrations/public/plugin.ts | 36 +++++++++ .../custom_integrations/public/types.ts | 19 +++++ .../server/custom_integration_registry.ts | 74 +++++++++++++++++++ .../custom_integrations/server/index.ts | 21 ++++++ .../custom_integrations/server/plugin.ts | 55 ++++++++++++++ .../server/routes/define_routes.ts | 45 +++++++++++ .../custom_integrations/server/types.ts | 19 +++++ src/plugins/custom_integrations/tsconfig.json | 13 ++++ src/plugins/home/kibana.json | 2 +- src/plugins/home/server/plugin.test.ts | 15 +++- src/plugins/home/server/plugin.ts | 11 ++- .../services/tutorials/lib/tutorial_schema.ts | 3 + .../tutorials/tutorials_registry.test.ts | 13 ++-- .../services/tutorials/tutorials_registry.ts | 34 ++++++++- src/plugins/home/tsconfig.json | 1 + .../maps/server/tutorials/ems/index.ts | 1 + 21 files changed, 452 insertions(+), 15 deletions(-) create mode 100755 src/plugins/custom_integrations/README.md create mode 100755 src/plugins/custom_integrations/common/index.ts create mode 100755 src/plugins/custom_integrations/kibana.json create mode 100755 src/plugins/custom_integrations/public/index.ts create mode 100755 src/plugins/custom_integrations/public/plugin.ts create mode 100755 src/plugins/custom_integrations/public/types.ts create mode 100644 src/plugins/custom_integrations/server/custom_integration_registry.ts create mode 100755 src/plugins/custom_integrations/server/index.ts create mode 100755 src/plugins/custom_integrations/server/plugin.ts create mode 100644 src/plugins/custom_integrations/server/routes/define_routes.ts create mode 100755 src/plugins/custom_integrations/server/types.ts create mode 100644 src/plugins/custom_integrations/tsconfig.json diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 55c53129ba499..64e25de4c4b9d 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -43,6 +43,8 @@ as uiSettings within the code. |{kib-repo}blob/{branch}/src/plugins/console/README.md[console] |Console provides the user with tools for storing and executing requests against Elasticsearch. +|{kib-repo}blob/{branch}/src/plugins/custom_integrations/README.md[customIntegrations] +|Register add-data cards |<> |- Registers the dashboard application. diff --git a/src/plugins/custom_integrations/README.md b/src/plugins/custom_integrations/README.md new file mode 100755 index 0000000000000..e7af518e21ec1 --- /dev/null +++ b/src/plugins/custom_integrations/README.md @@ -0,0 +1,9 @@ +# customIntegrations + +Register add-data cards + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts new file mode 100755 index 0000000000000..fdcff8cf64da7 --- /dev/null +++ b/src/plugins/custom_integrations/common/index.ts @@ -0,0 +1,62 @@ +/* + * 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 PLUGIN_ID = 'customIntegrations'; +export const PLUGIN_NAME = 'customIntegrations'; + +export interface CategoryCount { + count: number; + id: Category; +} + +export const CATEGORY_DISPLAY = { + aws: 'AWS', + azure: 'Azure', + cloud: 'Cloud', + config_management: 'Config management', + containers: 'Containers', + crm: 'CRM', + custom: 'Custom', + datastore: 'Datastore', + elastic_stack: 'Elastic Stack', + google_cloud: 'Google cloud', + kubernetes: 'Kubernetes', + languages: 'Languages', + message_queue: 'Message queue', + monitoring: 'Monitoring', + network: 'Network', + notification: 'Notification', + os_system: 'OS & System', + productivity: 'Productivity', + security: 'Security', + sample_data: 'Sample data', + support: 'Support', + ticketing: 'Ticketing', + version_control: 'Version control', + web: 'Web', + upload_file: 'Upload a file', +}; + +export type Category = keyof typeof CATEGORY_DISPLAY; + +export interface CustomIntegration { + id: string; + title: string; + name: string; + description: string; + type: 'ui_link'; + uiInternalPath: string; + euiIconType: string; + iconPath?: string; + categories: Category[]; + shipper: string; + eprPackageOverlap?: string; +} + +export const ROUTES_ADDABLECUSTOMINTEGRATIONS = `/api/${PLUGIN_ID}/addableCustomIntegrations`; +export const ROUTES_REPLACEABLECUSTOMINMTEGRATIONS = `/api/${PLUGIN_ID}/replaceableCustomIntegrations`; diff --git a/src/plugins/custom_integrations/kibana.json b/src/plugins/custom_integrations/kibana.json new file mode 100755 index 0000000000000..370f49f00fad9 --- /dev/null +++ b/src/plugins/custom_integrations/kibana.json @@ -0,0 +1,16 @@ +{ + "id": "customIntegrations", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "platform", + "githubTeam": "" + }, + "description": "test", + "ui": true, + "server": true, + "extraPublicDirs": [ + "common" + ], + "optionalPlugins": [] +} diff --git a/src/plugins/custom_integrations/public/index.ts b/src/plugins/custom_integrations/public/index.ts new file mode 100755 index 0000000000000..683f498cf3907 --- /dev/null +++ b/src/plugins/custom_integrations/public/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { CustomIntegrationPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new CustomIntegrationPlugin(); +} +export { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; diff --git a/src/plugins/custom_integrations/public/plugin.ts b/src/plugins/custom_integrations/public/plugin.ts new file mode 100755 index 0000000000000..3c2639a26c551 --- /dev/null +++ b/src/plugins/custom_integrations/public/plugin.ts @@ -0,0 +1,36 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; +import { + CustomIntegration, + ROUTES_ADDABLECUSTOMINTEGRATIONS, + ROUTES_REPLACEABLECUSTOMINMTEGRATIONS, +} from '../common'; + +export class CustomIntegrationPlugin + implements Plugin { + public setup(core: CoreSetup): CustomIntegrationsSetup { + // Return methods that should be available to other plugins + return { + async getAddableCustomIntegrations(): Promise { + return core.http.get(ROUTES_ADDABLECUSTOMINTEGRATIONS); + }, + async getReplaceableCustomIntegrations(): Promise { + return core.http.get(ROUTES_REPLACEABLECUSTOMINMTEGRATIONS); + }, + } as CustomIntegrationsSetup; + } + + public start(core: CoreStart): CustomIntegrationsStart { + return {}; + } + + public stop() {} +} diff --git a/src/plugins/custom_integrations/public/types.ts b/src/plugins/custom_integrations/public/types.ts new file mode 100755 index 0000000000000..00ecbfe257baa --- /dev/null +++ b/src/plugins/custom_integrations/public/types.ts @@ -0,0 +1,19 @@ +/* + * 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 { CustomIntegration } from '../common'; + +export interface CustomIntegrationsSetup { + getReplaceableCustomIntegrations: () => Promise; + getAddableCustomIntegrations: () => Promise; +} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CustomIntegrationsStart {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AppPluginStartDependencies {} diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.ts b/src/plugins/custom_integrations/server/custom_integration_registry.ts new file mode 100644 index 0000000000000..32344497ac450 --- /dev/null +++ b/src/plugins/custom_integrations/server/custom_integration_registry.ts @@ -0,0 +1,74 @@ +/* + * 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 { Logger } from 'kibana/server'; +import { CustomIntegration, CategoryCount, Category } from '../common'; + +function isAddable(integration: CustomIntegration) { + return integration.categories.length; +} + +export class CustomIntegrationRegistry { + private readonly _integrations: CustomIntegration[]; + private readonly _logger: Logger; + + constructor(logger: Logger) { + this._integrations = []; + this._logger = logger; + } + + registerCustomIntegration(customIntegration: CustomIntegration) { + if ( + this._integrations.some((integration: CustomIntegration) => { + return integration.name === customIntegration.name; + }) + ) { + this._logger.error(`Integration with id=${customIntegration.name} already exists.`); + return; + } + + this._integrations.push(customIntegration); + } + + getAddableCustomIntegrations(): CustomIntegration[] { + return this._integrations.filter(isAddable); + } + + getReplaceableCustomIntegrations(): CustomIntegration[] { + return this._integrations.filter( + (integration) => typeof integration.eprPackageOverlap !== 'undefined' + ); + } + + getAddableCategories(): CategoryCount[] { + const categories: Map = new Map(); + for (let i = 0; i < this._integrations.length; i++) { + if (!isAddable(this._integrations[i])) { + continue; + } + for (let j = 0; j < this._integrations[i].categories.length; j++) { + const category = this._integrations[i].categories[j]; + if (categories.has(category)) { + // @ts-ignore + categories.set(category, categories.get(category) + 1); + } else { + categories.set(category, 1); + } + } + } + + const list: CategoryCount[] = []; + categories.forEach((value, key) => { + list.push({ + count: value, + id: key, + }); + }); + return list; + } +} diff --git a/src/plugins/custom_integrations/server/index.ts b/src/plugins/custom_integrations/server/index.ts new file mode 100755 index 0000000000000..1823db905ae7f --- /dev/null +++ b/src/plugins/custom_integrations/server/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { PluginInitializerContext } from '../../../core/server'; +import { CustomIntegrationsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new CustomIntegrationsPlugin(initializerContext); +} + +export { CustomIntegrationsPluginSetup, CustomIntegrationsPluginStart } from './types'; + +export type { Category, CategoryCount, CustomIntegration } from '../common'; diff --git a/src/plugins/custom_integrations/server/plugin.ts b/src/plugins/custom_integrations/server/plugin.ts new file mode 100755 index 0000000000000..cb65e634aa4e1 --- /dev/null +++ b/src/plugins/custom_integrations/server/plugin.ts @@ -0,0 +1,55 @@ +/* + * 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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from 'kibana/server'; + +import { CustomIntegrationsPluginSetup, CustomIntegrationsPluginStart } from './types'; +import { CustomIntegration, CategoryCount } from '../common'; +import { CustomIntegrationRegistry } from './custom_integration_registry'; +import { defineRoutes } from './routes/define_routes'; + +export class CustomIntegrationsPlugin + implements Plugin { + private readonly logger: Logger; + private readonly customIngegrationRegistry: CustomIntegrationRegistry; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + this.customIngegrationRegistry = new CustomIntegrationRegistry(this.logger); + } + + public setup(core: CoreSetup) { + this.logger.debug('customIntegrations: Setup'); + + const router = core.http.createRouter(); + defineRoutes(router, this.customIngegrationRegistry); + + return { + registerCustomIntegration: (integration: CustomIntegration) => { + this.customIngegrationRegistry.registerCustomIntegration(integration); + }, + getAddableCustomIntegrations: (): CustomIntegration[] => { + return this.customIngegrationRegistry.getAddableCustomIntegrations(); + }, + + getAddableCategories: (): CategoryCount[] => { + return this.customIngegrationRegistry.getAddableCategories(); + }, + getReplaceableCustomIntegrations: (): CustomIntegration[] => { + return this.customIngegrationRegistry.getReplaceableCustomIntegrations(); + }, + } as CustomIntegrationsPluginSetup; + } + + public start(core: CoreStart) { + this.logger.debug('customIntegrations: Started'); + return {}; + } + + public stop() {} +} diff --git a/src/plugins/custom_integrations/server/routes/define_routes.ts b/src/plugins/custom_integrations/server/routes/define_routes.ts new file mode 100644 index 0000000000000..248be757bdcd2 --- /dev/null +++ b/src/plugins/custom_integrations/server/routes/define_routes.ts @@ -0,0 +1,45 @@ +/* + * 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 { IRouter } from 'src/core/server'; +import { CustomIntegrationRegistry } from '../custom_integration_registry'; +import { + ROUTES_ADDABLECUSTOMINTEGRATIONS, + ROUTES_REPLACEABLECUSTOMINMTEGRATIONS, +} from '../../common'; + +export function defineRoutes( + router: IRouter, + customIntegrationsRegistry: CustomIntegrationRegistry +) { + router.get( + { + path: ROUTES_ADDABLECUSTOMINTEGRATIONS, + validate: false, + }, + async (context, request, response) => { + const integrations = customIntegrationsRegistry.getAddableCustomIntegrations(); + return response.ok({ + body: integrations, + }); + } + ); + + router.get( + { + path: ROUTES_REPLACEABLECUSTOMINMTEGRATIONS, + validate: false, + }, + async (context, request, response) => { + const integrations = customIntegrationsRegistry.getReplaceableCustomIntegrations(); + return response.ok({ + body: integrations, + }); + } + ); +} diff --git a/src/plugins/custom_integrations/server/types.ts b/src/plugins/custom_integrations/server/types.ts new file mode 100755 index 0000000000000..82b44687ee0c4 --- /dev/null +++ b/src/plugins/custom_integrations/server/types.ts @@ -0,0 +1,19 @@ +/* + * 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 { CustomIntegration, CategoryCount } from '../common'; + +export interface CustomIntegrationsPluginSetup { + registerCustomIntegration(customIntegration: CustomIntegration): void; + getAddableCustomIntegrations(): CustomIntegration[]; + getAddableCategories(): CategoryCount[]; + getReplaceableCustomIntegrations(): CustomIntegration[]; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CustomIntegrationsPluginStart {} diff --git a/src/plugins/custom_integrations/tsconfig.json b/src/plugins/custom_integrations/tsconfig.json new file mode 100644 index 0000000000000..2ce7bf9c8112c --- /dev/null +++ b/src/plugins/custom_integrations/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*"], + "references": [ + { "path": "../../core/tsconfig.json" } + ] +} diff --git a/src/plugins/home/kibana.json b/src/plugins/home/kibana.json index b3bd915bee143..319af99061b9b 100644 --- a/src/plugins/home/kibana.json +++ b/src/plugins/home/kibana.json @@ -7,7 +7,7 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["data", "share", "urlForwarding"], + "requiredPlugins": ["data", "share", "urlForwarding", "customIntegrations"], "optionalPlugins": ["usageCollection", "telemetry"], "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/home/server/plugin.test.ts b/src/plugins/home/server/plugin.test.ts index 88e07970cc440..244b390f808f6 100644 --- a/src/plugins/home/server/plugin.test.ts +++ b/src/plugins/home/server/plugin.test.ts @@ -7,9 +7,10 @@ */ import { registryForTutorialsMock, registryForSampleDataMock } from './plugin.test.mocks'; -import { HomeServerPlugin } from './plugin'; +import { HomeServerPlugin, HomeServerPluginSetupDependencies } from './plugin'; import { coreMock, httpServiceMock } from '../../../core/server/mocks'; +const homeServerPluginSetupDependenciesMock = ({} as unknown) as HomeServerPluginSetupDependencies; describe('HomeServerPlugin', () => { beforeEach(() => { registryForTutorialsMock.setup.mockClear(); @@ -31,14 +32,20 @@ describe('HomeServerPlugin', () => { }); test('wires up tutorials provider service and returns registerTutorial and addScopedTutorialContextFactory', () => { - const setup = new HomeServerPlugin(initContext).setup(mockCoreSetup, {}); + const setup = new HomeServerPlugin(initContext).setup( + mockCoreSetup, + homeServerPluginSetupDependenciesMock + ); expect(setup).toHaveProperty('tutorials'); expect(setup.tutorials).toHaveProperty('registerTutorial'); expect(setup.tutorials).toHaveProperty('addScopedTutorialContextFactory'); }); test('wires up sample data provider service and returns registerTutorial and addScopedTutorialContextFactory', () => { - const setup = new HomeServerPlugin(initContext).setup(mockCoreSetup, {}); + const setup = new HomeServerPlugin(initContext).setup( + mockCoreSetup, + homeServerPluginSetupDependenciesMock + ); expect(setup).toHaveProperty('sampleData'); expect(setup.sampleData).toHaveProperty('registerSampleDataset'); expect(setup.sampleData).toHaveProperty('getSampleDatasets'); @@ -48,7 +55,7 @@ describe('HomeServerPlugin', () => { }); test('registers the `/api/home/hits_status` route', () => { - new HomeServerPlugin(initContext).setup(mockCoreSetup, {}); + new HomeServerPlugin(initContext).setup(mockCoreSetup, homeServerPluginSetupDependenciesMock); expect(routerMock.post).toHaveBeenCalledTimes(1); expect(routerMock.post).toHaveBeenCalledWith( diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index a1463c4c8138b..d574867022654 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -19,15 +19,18 @@ import { UsageCollectionSetup } from '../../usage_collection/server'; import { capabilitiesProvider } from './capabilities_provider'; import { sampleDataTelemetry } from './saved_objects'; import { registerRoutes } from './routes'; +import { CustomIntegrationsPluginSetup } from '../../custom_integrations/server'; -interface HomeServerPluginSetupDependencies { +export interface HomeServerPluginSetupDependencies { usageCollection?: UsageCollectionSetup; + customIntegrations: CustomIntegrationsPluginSetup; } export class HomeServerPlugin implements Plugin { constructor(private readonly initContext: PluginInitializerContext) {} private readonly tutorialsRegistry = new TutorialsRegistry(); private readonly sampleDataRegistry = new SampleDataRegistry(this.initContext); + private customIntegrations: CustomIntegrationsPluginSetup | undefined = undefined; public setup(core: CoreSetup, plugins: HomeServerPluginSetupDependencies): HomeServerPluginSetup { core.capabilities.registerProvider(capabilitiesProvider); @@ -36,13 +39,17 @@ export class HomeServerPlugin implements Plugin; diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index a82699c231ad4..c7e1b551be27e 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -18,6 +18,7 @@ import { TutorialsCategory, ScopedTutorialContextFactory, } from './lib/tutorials_registry_types'; +import { CustomIntegrationsPluginSetup } from '../../../../custom_integrations/server'; const INVALID_TUTORIAL: TutorialSchema = { id: 'test', @@ -65,6 +66,8 @@ const VALID_TUTORIAL: TutorialSchema = { const invalidTutorialProvider = INVALID_TUTORIAL; const validTutorialProvider = VALID_TUTORIAL; +const mockCustomIntegrationsPluginSetup = ({} as unknown) as CustomIntegrationsPluginSetup; + describe('TutorialsRegistry', () => { let mockCoreSetup: MockedKeys; let testProvider: TutorialProvider; @@ -83,13 +86,13 @@ describe('TutorialsRegistry', () => { describe('setup', () => { test('exposes proper contract', () => { - const setup = new TutorialsRegistry().setup(mockCoreSetup); + const setup = new TutorialsRegistry().setup(mockCoreSetup, mockCustomIntegrationsPluginSetup); expect(setup).toHaveProperty('registerTutorial'); expect(setup).toHaveProperty('addScopedTutorialContextFactory'); }); test('registerTutorial throws when registering a tutorial with an invalid schema', () => { - const setup = new TutorialsRegistry().setup(mockCoreSetup); + const setup = new TutorialsRegistry().setup(mockCoreSetup, mockCustomIntegrationsPluginSetup); testProvider = ({}) => invalidTutorialProvider; expect(() => setup.registerTutorial(testProvider)).toThrowErrorMatchingInlineSnapshot( `"Unable to register tutorial spec because its invalid. Error: [name]: is not allowed to be empty"` @@ -97,13 +100,13 @@ describe('TutorialsRegistry', () => { }); test('registerTutorial registers a tutorial with a valid schema', () => { - const setup = new TutorialsRegistry().setup(mockCoreSetup); + const setup = new TutorialsRegistry().setup(mockCoreSetup, mockCustomIntegrationsPluginSetup); testProvider = ({}) => validTutorialProvider; expect(() => setup.registerTutorial(testProvider)).not.toThrowError(); }); test('addScopedTutorialContextFactory throws when given a scopedTutorialContextFactory that is not a function', () => { - const setup = new TutorialsRegistry().setup(mockCoreSetup); + const setup = new TutorialsRegistry().setup(mockCoreSetup, mockCustomIntegrationsPluginSetup); const testItem = {} as TutorialProvider; expect(() => setup.addScopedTutorialContextFactory(testItem) @@ -113,7 +116,7 @@ describe('TutorialsRegistry', () => { }); test('addScopedTutorialContextFactory adds a scopedTutorialContextFactory when given a function', () => { - const setup = new TutorialsRegistry().setup(mockCoreSetup); + const setup = new TutorialsRegistry().setup(mockCoreSetup, mockCustomIntegrationsPluginSetup); testScopedTutorialContextFactory = ({}) => 'string'; expect(() => setup.addScopedTutorialContextFactory(testScopedTutorialContextFactory) diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index 05f5600af307a..a75cbf466778e 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -14,12 +14,41 @@ import { } from './lib/tutorials_registry_types'; import { tutorialSchema } from './lib/tutorial_schema'; import { builtInTutorials } from '../../tutorials/register'; +import { CustomIntegrationsPluginSetup } from '../../../../custom_integrations/server'; +import { Category, CATEGORY_DISPLAY } from '../../../../custom_integrations/common'; + +const emptyContext = {}; + +function registerTutorialWithCustomIntegrations( + customIntegrations: CustomIntegrationsPluginSetup, + provider: TutorialProvider +) { + const tutorial = provider(emptyContext); + const allowedCategories: Category[] = (tutorial.integrationBrowserCategories + ? tutorial.integrationBrowserCategories.filter((category) => { + return CATEGORY_DISPLAY.hasOwnProperty(category); + }) + : []) as Category[]; + + customIntegrations.registerCustomIntegration({ + name: tutorial.id, + id: tutorial.name, + title: tutorial.name, + categories: allowedCategories, + type: 'ui_link', + uiInternalPath: `/app/home#/tutorial/${tutorial.id}`, + description: tutorial.shortDescription, + euiIconType: tutorial.euiIconType || '', + eprPackageOverlap: tutorial.eprPackageOverlap, + shipper: 'tutorial', + }); +} export class TutorialsRegistry { private tutorialProviders: TutorialProvider[] = []; // pre-register all the tutorials we know we want in here private readonly scopedTutorialContextFactories: TutorialContextFactory[] = []; - public setup(core: CoreSetup) { + public setup(core: CoreSetup, customIntegrations: CustomIntegrationsPluginSetup) { const router = core.http.createRouter(); router.get( { path: '/api/kibana/home/tutorials', validate: false }, @@ -31,7 +60,6 @@ export class TutorialsRegistry { }, initialContext ); - return res.ok({ body: this.tutorialProviders.map((tutorialProvider) => { return tutorialProvider(scopedContext); // All the tutorialProviders need to be refactored so that they don't need the server. @@ -42,12 +70,12 @@ export class TutorialsRegistry { return { registerTutorial: (specProvider: TutorialProvider) => { try { - const emptyContext = {}; tutorialSchema.validate(specProvider(emptyContext)); } catch (error) { throw new Error(`Unable to register tutorial spec because its invalid. ${error}`); } + registerTutorialWithCustomIntegrations(customIntegrations, specProvider); this.tutorialProviders.push(specProvider); }, diff --git a/src/plugins/home/tsconfig.json b/src/plugins/home/tsconfig.json index f43c40e35349d..fa98b98ff8e1c 100644 --- a/src/plugins/home/tsconfig.json +++ b/src/plugins/home/tsconfig.json @@ -11,6 +11,7 @@ "references": [ { "path": "../../core/tsconfig.json" }, { "path": "../data/tsconfig.json" }, + { "path": "../custom_integrations/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, { "path": "../share/tsconfig.json" }, { "path": "../url_forwarding/tsconfig.json" }, diff --git a/x-pack/plugins/maps/server/tutorials/ems/index.ts b/x-pack/plugins/maps/server/tutorials/ems/index.ts index 3c63850f87291..94da7c6258faa 100644 --- a/x-pack/plugins/maps/server/tutorials/ems/index.ts +++ b/x-pack/plugins/maps/server/tutorials/ems/index.ts @@ -78,5 +78,6 @@ Indexing EMS administrative boundaries in Elasticsearch allows for search on bou previewImagePath: '/plugins/maps/assets/boundaries_screenshot.png', onPrem: instructions, elasticCloud: instructions, + integrationBrowserCategories: ['upload_file'], }); } From 5ca53078d7f128d62ff3edcdde83fdbe6930ab09 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 16 Sep 2021 16:35:09 -0400 Subject: [PATCH 02/31] setup dependency in fleet --- x-pack/plugins/fleet/kibana.json | 2 +- .../fleet/public/mock/plugin_dependencies.ts | 4 ++++ x-pack/plugins/fleet/public/plugin.ts | 7 +++++++ .../public/services/custom_integrations.ts | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/fleet/public/services/custom_integrations.ts diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index f9ad1b0b966a4..c4782156b1982 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -8,7 +8,7 @@ "server": true, "ui": true, "configPath": ["xpack", "fleet"], - "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation"], + "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations"], "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils"] diff --git a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts index 5d1567936bcb0..10047539c7321 100644 --- a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts +++ b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts @@ -10,6 +10,8 @@ import { licensingMock } from '../../../licensing/public/mocks'; import { homePluginMock } from '../../../../../src/plugins/home/public/mocks'; import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks'; +import type { CustomIntegrationsSetup } from '../../../../../src/plugins/custom_integrations/public'; + import type { MockedFleetSetupDeps, MockedFleetStartDeps } from './types'; export const createSetupDepsMock = (): MockedFleetSetupDeps => { @@ -17,6 +19,8 @@ export const createSetupDepsMock = (): MockedFleetSetupDeps => { licensing: licensingMock.createSetup(), data: dataPluginMock.createSetupContract(), home: homePluginMock.createSetupContract(), + // @ts-expect-error + customIntegrations: ({} as unknown) as CustomIntegrationsSetup, }; }; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 2c723a3269737..b5a1983b6c974 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -17,6 +17,8 @@ import { i18n } from '@kbn/i18n'; import type { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '../../../../src/core/public'; +import type { CustomIntegrationsSetup } from '../../../../src/plugins/custom_integrations/public'; + import type { DataPublicPluginSetup, DataPublicPluginStart, @@ -47,6 +49,8 @@ import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extensi export { FleetConfigType } from '../common/types'; +import { setCustomIntegrations } from './services/custom_integrations'; + // We need to provide an object instead of void so that dependent plugins know when Fleet // is disabled. // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -66,6 +70,7 @@ export interface FleetSetupDeps { home?: HomePublicPluginSetup; cloud?: CloudSetup; globalSearch?: GlobalSearchPluginSetup; + customIntegrations: CustomIntegrationsSetup; } export interface FleetStartDeps { @@ -94,6 +99,8 @@ export class FleetPlugin implements Plugin Date: Mon, 20 Sep 2021 14:05:57 -0400 Subject: [PATCH 03/31] add custom cards to UX (boilerplate) --- packages/kbn-optimizer/limits.yml | 2 + .../custom_integrations/common/index.ts | 4 +- .../services/tutorials/tutorials_registry.ts | 11 +++- .../sections/epm/components/package_card.tsx | 60 ++++++++++++------ .../epm/screens/home/category_facets.tsx | 40 ++++++++---- .../sections/epm/screens/home/index.tsx | 63 ++++++++++++++----- .../sections/epm/screens/home/util.ts | 37 +++++++++++ x-pack/plugins/fleet/public/hooks/use_link.ts | 3 + .../fleet/public/hooks/use_request/epm.ts | 9 +++ 9 files changed, 179 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 69cfffe1f08f0..c1d033568f146 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -115,3 +115,5 @@ pageLoadAssetSize: expressionTagcloud: 27505 expressions: 239290 securitySolution: 231753 + customIntegrations: 28810 + foobarFoobar: 28785 diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index fdcff8cf64da7..135b28538fb36 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -51,8 +51,8 @@ export interface CustomIntegration { description: string; type: 'ui_link'; uiInternalPath: string; - euiIconType: string; - iconPath?: string; + isBeta: boolean; + icons: Array<{ src: string; type: string }>; categories: Category[]; shipper: string; eprPackageOverlap?: string; diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index a75cbf466778e..2afefc6b8961f 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -16,6 +16,7 @@ import { tutorialSchema } from './lib/tutorial_schema'; import { builtInTutorials } from '../../tutorials/register'; import { CustomIntegrationsPluginSetup } from '../../../../custom_integrations/server'; import { Category, CATEGORY_DISPLAY } from '../../../../custom_integrations/common'; +import { HOME_APP_BASE_PATH } from '../../../common/constants'; const emptyContext = {}; @@ -36,11 +37,17 @@ function registerTutorialWithCustomIntegrations( title: tutorial.name, categories: allowedCategories, type: 'ui_link', - uiInternalPath: `/app/home#/tutorial/${tutorial.id}`, + uiInternalPath: `${HOME_APP_BASE_PATH}#/tutorial/${tutorial.id}`, description: tutorial.shortDescription, - euiIconType: tutorial.euiIconType || '', + icons: [ + { + type: 'eui', + src: tutorial.euiIconType, + }, + ], eprPackageOverlap: tutorial.eprPackageOverlap, shipper: 'tutorial', + isBeta: false, }); } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index c12e67fdb5718..08cd921a22e8d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -7,7 +7,7 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiCard } from '@elastic/eui'; +import { EuiCard, EuiIcon } from '@elastic/eui'; import type { PackageListItem } from '../../../types'; import { useLink } from '../../../hooks'; @@ -15,7 +15,10 @@ import { PackageIcon } from '../../../components'; import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from './release_badge'; -type PackageCardProps = PackageListItem; +type PackageCardProps = PackageListItem & { + type?: string; + uiInternalPath?: string; +}; // adding the `href` causes EuiCard to use a `a` instead of a `button` // `a` tags use `euiLinkColor` which results in blueish Badge text @@ -32,36 +35,53 @@ export function PackageCard({ status, icons, integration, + type, + uiInternalPath, ...restProps }: PackageCardProps) { - const { getHref } = useLink(); + const { getHref, getAbsolutePath } = useLink(); let urlVersion = version; // if this is an installed package, link to the version installed if ('savedObject' in restProps) { urlVersion = restProps.savedObject.attributes.version || version; } + const icon = + type === 'ui_link' && icons && icons.length ? ( + + ) : ( + + ); + + const href = + type === 'ui_link' + ? getAbsolutePath(uiInternalPath) + : getHref('integration_details_overview', { + pkgkey: `${name}-${urlVersion}`, + ...(integration ? { integration } : {}), + }); + + const betaBadgeLabel = + type === 'ui_link' && release && release !== 'ga' ? RELEASE_BADGE_LABEL[release] : undefined; + const betaBadgeTooltipContent = + type === 'ui_link' && release && release !== 'ga' + ? RELEASE_BADGE_DESCRIPTION[release] + : undefined; + return ( - } - href={getHref('integration_details_overview', { - pkgkey: `${name}-${urlVersion}`, - ...(integration ? { integration } : {}), - })} - betaBadgeLabel={release && release !== 'ga' ? RELEASE_BADGE_LABEL[release] : undefined} - betaBadgeTooltipContent={ - release && release !== 'ga' ? RELEASE_BADGE_DESCRIPTION[release] : undefined - } + icon={icon} + href={href} + betaBadgeLabel={betaBadgeLabel} + betaBadgeTooltipContent={betaBadgeTooltipContent} /> ); } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx index e4d81f1d04118..63120faf6ad35 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx @@ -8,8 +8,11 @@ import { EuiFacetButton, EuiFacetGroup } from '@elastic/eui'; import React from 'react'; +import { i18n } from '@kbn/i18n'; + import { Loading } from '../../../../components'; import type { CategorySummaryItem, CategorySummaryList } from '../../../../types'; +import { CATEGORY_DISPLAY } from '../../../../../../../../../../src/plugins/custom_integrations/common'; export function CategoryFacets({ isLoading, @@ -27,17 +30,32 @@ export function CategoryFacets({ {isLoading ? ( ) : ( - categories.map((category) => ( - onCategoryChange(category)} - > - {category.title} - - )) + categories.map((category) => { + let title; + + if (category.id === 'updates_available') { + title = i18n.translate('xpack.fleet.epmList.updatesAvailableFilterLinkText', { + defaultMessage: 'Updates available', + }); + } else if (category.id === '') { + title = i18n.translate('xpack.fleet.epmList.allPackagesFilterLinkText', { + defaultMessage: 'All', + }); + } else { + title = CATEGORY_DISPLAY[category.id]; + } + return ( + onCategoryChange(category)} + > + {title} + + ); + }) )} ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 5e94fbda2c22a..81f11bf6209da 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -16,13 +16,19 @@ import { INTEGRATIONS_SEARCH_QUERYPARAM, pagePathGetters, } from '../../../../constants'; -import { useGetCategories, useGetPackages, useBreadcrumbs } from '../../../../hooks'; +import { + useGetCategories, + useGetPackages, + useBreadcrumbs, + useGetAddableCustomIntegrations, +} from '../../../../hooks'; import { doesPackageHaveIntegrations } from '../../../../services'; import { DefaultLayout } from '../../../../layouts'; import type { CategorySummaryItem, PackageList } from '../../../../types'; import { PackageListGrid } from '../../components/package_list_grid'; import { CategoryFacets } from './category_facets'; +import { mergeAndReplaceCategoryCounts } from './util'; export interface CategoryParams { category?: string; @@ -213,16 +219,29 @@ const AvailablePackages: React.FC = memo(() => { const { data: categoriesRes, isLoading: isLoadingCategories } = useGetCategories({ include_policy_templates: true, }); - const packages = useMemo( + const eprPackages = useMemo( () => packageListToIntegrationsList(categoryPackagesRes?.response || []), [categoryPackagesRes] ); - const allPackages = useMemo( + const allEprPackages = useMemo( () => packageListToIntegrationsList(allCategoryPackagesRes?.response || []), [allCategoryPackagesRes] ); + const { + loading: isLoadingAddableCustomIntegrations, + value: addableCustomIntegrations, + } = useGetAddableCustomIntegrations(); + const filteredAddableIntegrations = addableCustomIntegrations + ? addableCustomIntegrations.filter((integration) => { + if (!selectedCategory) { + return true; + } + return integration.categories.indexOf(selectedCategory) >= 0; + }) + : []; + const title = useMemo( () => i18n.translate('xpack.fleet.epmList.allTitle', { @@ -231,19 +250,33 @@ const AvailablePackages: React.FC = memo(() => { [] ); - const categories = useMemo( - () => [ + const eprAndCustomPackages = eprPackages.concat(filteredAddableIntegrations); + eprAndCustomPackages.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + + const categories = useMemo(() => { + const eprAndCustomCategories = + isLoadingCategories || + isLoadingAddableCustomIntegrations || + !addableCustomIntegrations || + !categoriesRes + ? [] + : mergeAndReplaceCategoryCounts(categoriesRes.response, addableCustomIntegrations); + return [ { id: '', - title: i18n.translate('xpack.fleet.epmList.allPackagesFilterLinkText', { - defaultMessage: 'All', - }), - count: allPackages?.length || 0, + count: (allEprPackages?.length || 0) + (addableCustomIntegrations?.length || 0), }, - ...(categoriesRes ? categoriesRes.response : []), - ], - [allPackages?.length, categoriesRes] - ); + ...(eprAndCustomCategories ? eprAndCustomCategories : []), + ]; + }, [ + allEprPackages?.length, + addableCustomIntegrations, + categoriesRes, + isLoadingAddableCustomIntegrations, + isLoadingCategories, + ]); if (!categoryExists(selectedCategory, categories)) { history.replace(pagePathGetters.integrations_all({ category: '', searchTerm: searchParam })[1]); @@ -252,7 +285,7 @@ const AvailablePackages: React.FC = memo(() => { const controls = categories ? ( { @@ -267,7 +300,7 @@ const AvailablePackages: React.FC = memo(() => { title={title} controls={controls} initialSearch={searchParam} - list={packages} + list={eprAndCustomPackages} setSelectedCategory={setSelectedCategory} onSearchChange={setSearchTerm} showMissingIntegrationMessage diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts new file mode 100644 index 0000000000000..5e5502ad0a97f --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts @@ -0,0 +1,37 @@ +/* + * 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 type { CategoryCount } from '../../../../../../../../../../src/plugins/custom_integrations/common'; +import type { CustomIntegration } from '../../../../../../../../../../src/plugins/custom_integrations/common'; + +export function mergeAndReplaceCategoryCounts( + eprCounts: CategoryCount[], + addableIntegrations: CustomIntegration[] +) { + addableIntegrations.forEach((integration) => { + integration.categories.forEach((cat) => { + const match = eprCounts.find((c) => { + return c.id === cat; + }); + + if (match) { + match.count += 1; + } else { + eprCounts.push({ + id: cat, + count: 1, + }); + } + }); + }); + + eprCounts.sort((a, b) => { + return a.id.localeCompare(b.id); + }); + + return eprCounts; +} diff --git a/x-pack/plugins/fleet/public/hooks/use_link.ts b/x-pack/plugins/fleet/public/hooks/use_link.ts index 846ca9d0fdafa..b04f51ef790fe 100644 --- a/x-pack/plugins/fleet/public/hooks/use_link.ts +++ b/x-pack/plugins/fleet/public/hooks/use_link.ts @@ -23,6 +23,9 @@ export const useLink = () => { getPath: (page: StaticPage | DynamicPage, values: DynamicPagePathValues = {}): string => { return getSeparatePaths(page, values)[1]; }, + getAbsolutePath: (path: string): string => { + return core.http.basePath.prepend(`${path}`); + }, getAssetsPath: (path: string) => core.http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/${path}`), getHref: (page: StaticPage | DynamicPage, values?: DynamicPagePathValues) => { diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index 8599b4f2c703c..16940476b8f61 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -5,6 +5,8 @@ * 2.0. */ +import useAsync from 'react-use/lib/useAsync'; + import { epmRouteService } from '../../services'; import type { GetCategoriesRequest, @@ -18,8 +20,15 @@ import type { } from '../../types'; import type { GetStatsResponse } from '../../../common'; +import { getCustomIntegrations } from '../../services/custom_integrations'; + import { useRequest, sendRequest } from './use_request'; +export function useGetAddableCustomIntegrations() { + const customIntegrations = getCustomIntegrations(); + return useAsync(customIntegrations.getAddableCustomIntegrations, []); +} + export const useGetCategories = (query: GetCategoriesRequest['query'] = {}) => { return useRequest({ path: epmRouteService.getCategoriesPath(), From 46a67e50dd4e187a91252639ae12ab4744bbadb2 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 09:49:50 -0400 Subject: [PATCH 04/31] remove cruft --- packages/kbn-optimizer/limits.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c1d033568f146..68600e02ed4a7 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -116,4 +116,3 @@ pageLoadAssetSize: expressions: 239290 securitySolution: 231753 customIntegrations: 28810 - foobarFoobar: 28785 From 3844e8abaad202f1ac63f2f0de2d381bfac22713 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 11:10:57 -0400 Subject: [PATCH 05/31] isolate epr from cards --- .../custom_integrations/common/index.ts | 3 ++ .../services/tutorials/tutorials_registry.ts | 14 ++++++---- .../sections/epm/components/package_card.tsx | 24 ++++++++-------- .../epm/screens/home/category_facets.tsx | 6 ++-- .../sections/epm/screens/home/index.tsx | 28 +++++++++++++++++-- .../fleet/public/components/package_icon.tsx | 11 ++++++++ .../public/hooks/use_package_icon_type.ts | 1 + 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index 135b28538fb36..b32914bf1d4b5 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -14,6 +14,7 @@ export interface CategoryCount { id: Category; } +// todo internationalize export const CATEGORY_DISPLAY = { aws: 'AWS', azure: 'Azure', @@ -40,6 +41,8 @@ export const CATEGORY_DISPLAY = { version_control: 'Version control', web: 'Web', upload_file: 'Upload a file', + + updates_available: 'Updates available', }; export type Category = keyof typeof CATEGORY_DISPLAY; diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index 2afefc6b8961f..5869edd2c5e0c 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -39,12 +39,14 @@ function registerTutorialWithCustomIntegrations( type: 'ui_link', uiInternalPath: `${HOME_APP_BASE_PATH}#/tutorial/${tutorial.id}`, description: tutorial.shortDescription, - icons: [ - { - type: 'eui', - src: tutorial.euiIconType, - }, - ], + icons: tutorial.euiIconType + ? [ + { + type: 'eui', + src: tutorial.euiIconType, + }, + ] + : [], eprPackageOverlap: tutorial.eprPackageOverlap, shipper: 'tutorial', isBeta: false, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 08cd921a22e8d..1640c744ddca5 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -11,13 +11,14 @@ import { EuiCard, EuiIcon } from '@elastic/eui'; import type { PackageListItem } from '../../../types'; import { useLink } from '../../../hooks'; -import { PackageIcon } from '../../../components'; +import { CardIcon } from '../../../../../components/package_icon'; import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from './release_badge'; type PackageCardProps = PackageListItem & { type?: string; uiInternalPath?: string; + uiInteralPathUrl: string; }; // adding the `href` causes EuiCard to use a `a` instead of a `button` @@ -46,18 +47,15 @@ export function PackageCard({ urlVersion = restProps.savedObject.attributes.version || version; } - const icon = - type === 'ui_link' && icons && icons.length ? ( - - ) : ( - - ); + const icon = ( + + ); const href = type === 'ui_link' diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx index 63120faf6ad35..b2594036aee2c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Loading } from '../../../../components'; -import type { CategorySummaryItem, CategorySummaryList } from '../../../../types'; +import type { CategoryCount } from '../../../../../../../../../../src/plugins/custom_integrations/common'; import { CATEGORY_DISPLAY } from '../../../../../../../../../../src/plugins/custom_integrations/common'; export function CategoryFacets({ @@ -21,9 +21,9 @@ export function CategoryFacets({ onCategoryChange, }: { isLoading?: boolean; - categories: CategorySummaryList; + categories: CategoryCount[]; selectedCategory: string; - onCategoryChange: (category: CategorySummaryItem) => unknown; + onCategoryChange: (category: CategoryCount) => unknown; }) { const controls = ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 81f11bf6209da..83171211d4d11 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -21,12 +21,17 @@ import { useGetPackages, useBreadcrumbs, useGetAddableCustomIntegrations, + useLink, } from '../../../../hooks'; import { doesPackageHaveIntegrations } from '../../../../services'; import { DefaultLayout } from '../../../../layouts'; import type { CategorySummaryItem, PackageList } from '../../../../types'; import { PackageListGrid } from '../../components/package_list_grid'; +import type { CustomIntegration } from '../../../../../../../../../../src/plugins/custom_integrations/common'; + +import type { PackageListItem } from '../../../../types'; + import { CategoryFacets } from './category_facets'; import { mergeAndReplaceCategoryCounts } from './util'; @@ -109,7 +114,7 @@ const InstalledPackages: React.FC = memo(() => { history.push(url); } function setSearchTerm(search: string) { - // Use .replace so the browser's back button is tied to single keystroke + // Use .replace so the browser's back button is not tied to single keystroke history.replace( pagePathGetters.integrations_installed({ category: selectedCategory, @@ -196,6 +201,8 @@ const AvailablePackages: React.FC = memo(() => { useLocation().search ); const history = useHistory(); + const { getHref, getAbsolutePath } = useLink(); + function setSelectedCategory(categoryId: string) { const url = pagePathGetters.integrations_all({ category: categoryId, @@ -204,7 +211,7 @@ const AvailablePackages: React.FC = memo(() => { history.push(url); } function setSearchTerm(search: string) { - // Use .replace so the browser's back button is tied to single keystroke + // Use .replace so the browser's back button is not tied to single keystroke history.replace( pagePathGetters.integrations_all({ category: selectedCategory, searchTerm: search })[1] ); @@ -294,13 +301,28 @@ const AvailablePackages: React.FC = memo(() => { /> ) : null; + const cards = eprAndCustomPackages.map((item: CustomIntegration | PackageListItem) => { + const uiInternalPathUrl = + item.type === 'ui_link' + ? getAbsolutePath(item.uiInternalPath) + : getHref('integration_details_overview', { + pkgkey: `${name}-${item.urlVersion}`, + ...(item.integration ? { integration: item.integration } : {}), + }); + + return { + uiInternalPathUrl, + ...item, + }; + }); + return ( ; }; + +export const CardIcon: React.FunctionComponent> = ( + props +) => { + const { icons } = props; + if (icons && icons.length === 1 && icons[0].type === 'eui') { + return ; + } else { + return ; + } +}; diff --git a/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts b/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts index c194d9914acff..2ad229d9b409e 100644 --- a/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts +++ b/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts @@ -23,6 +23,7 @@ export interface UsePackageIconType { version: Package['version']; icons?: Package['icons']; tryApi?: boolean; // should it call API to try to find missing icons? + type: 'eui' | undefined; } const CACHED_ICONS = new Map(); From a40cee6c4d8cc9051607aa740eb64f331947d926 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 11:49:08 -0400 Subject: [PATCH 06/31] make cards generic --- .../sections/epm/components/package_card.tsx | 64 +++++++------------ .../sections/epm/screens/home/index.tsx | 60 +++++++++++++---- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 1640c744ddca5..11b17769acb3e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -7,18 +7,17 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiCard, EuiIcon } from '@elastic/eui'; +import { EuiCard } from '@elastic/eui'; import type { PackageListItem } from '../../../types'; -import { useLink } from '../../../hooks'; import { CardIcon } from '../../../../../components/package_icon'; -import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from './release_badge'; - type PackageCardProps = PackageListItem & { type?: string; uiInternalPath?: string; - uiInteralPathUrl: string; + uiInternalPathUrl: string; + betaBadgeLabel?: string; + betaBadgeLabelTooltipContent?: string; }; // adding the `href` causes EuiCard to use a `a` instead of a `button` @@ -27,25 +26,23 @@ const Card = styled(EuiCard)` color: inherit; `; -export function PackageCard({ - description, - name, - title, - version, - release, - status, - icons, - integration, - type, - uiInternalPath, - ...restProps -}: PackageCardProps) { - const { getHref, getAbsolutePath } = useLink(); - let urlVersion = version; - // if this is an installed package, link to the version installed - if ('savedObject' in restProps) { - urlVersion = restProps.savedObject.attributes.version || version; - } +export function PackageCard(props: PackageCardProps) { + const { + description, + name, + title, + version, + release, + status, + icons, + integration, + type, + uiInternalPath, + uiInternalPathUrl, + betaBadgeLabel, + betaBadgeLabelTooltipContent, + ...restProps + } = props; const icon = ( ); - const href = - type === 'ui_link' - ? getAbsolutePath(uiInternalPath) - : getHref('integration_details_overview', { - pkgkey: `${name}-${urlVersion}`, - ...(integration ? { integration } : {}), - }); - - const betaBadgeLabel = - type === 'ui_link' && release && release !== 'ga' ? RELEASE_BADGE_LABEL[release] : undefined; - const betaBadgeTooltipContent = - type === 'ui_link' && release && release !== 'ga' - ? RELEASE_BADGE_DESCRIPTION[release] - : undefined; - return ( ); } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 83171211d4d11..20ea53219c3de 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -32,6 +32,8 @@ import type { CustomIntegration } from '../../../../../../../../../../src/plugin import type { PackageListItem } from '../../../../types'; +import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components/release_badge'; + import { CategoryFacets } from './category_facets'; import { mergeAndReplaceCategoryCounts } from './util'; @@ -51,6 +53,39 @@ function categoryExists(category: string, categories: CategorySummaryItem[]) { return categories.some((c) => c.id === category); } +function mapToCard(getAbsolutePath, getHref, item: CustomIntegration | PackageListItem) { + let uiInternalPathUrl; + if (item.type === 'ui_link') { + uiInternalPathUrl = getAbsolutePath(item.uiInternalPath); + } else { + let urlVersion = item.version; + if ('savedObject' in item) { + urlVersion = item.savedObject.attributes.version || item.version; + } + const url = getHref('integration_details_overview', { + pkgkey: `${item.name}-${urlVersion}`, + ...(item.integration ? { integration: item.integration } : {}), + }); + uiInternalPathUrl = url; + } + + const betaBadgeLabel = + item.type !== 'ui_link' && item.release && item.release !== 'ga' + ? RELEASE_BADGE_LABEL[item.release] + : undefined; + const betaBadgeTooltipContent = + item.type !== 'ui_link' && item.release && item.release !== 'ga' + ? RELEASE_BADGE_DESCRIPTION[item.release] + : undefined; + + return { + uiInternalPathUrl, + betaBadgeLabel, + betaBadgeTooltipContent, + ...item, + }; +} + export const EPMHomePage: React.FC = memo(() => { return ( @@ -100,6 +135,7 @@ const InstalledPackages: React.FC = memo(() => { const { data: allPackages, isLoading: isLoadingPackages } = useGetPackages({ experimental: true, }); + const { getHref, getAbsolutePath } = useLink(); const { selectedCategory, searchParam } = getParams( useParams(), @@ -181,6 +217,13 @@ const InstalledPackages: React.FC = memo(() => { /> ); + const cards = (selectedCategory === 'updates_available' + ? updatablePackages + : allInstalledPackages + ).map((item) => { + return mapToCard(getAbsolutePath, getHref, item); + }); + return ( { onSearchChange={setSearchTerm} initialSearch={searchParam} title={title} - list={selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages} + list={cards} /> ); }); @@ -301,19 +344,8 @@ const AvailablePackages: React.FC = memo(() => { /> ) : null; - const cards = eprAndCustomPackages.map((item: CustomIntegration | PackageListItem) => { - const uiInternalPathUrl = - item.type === 'ui_link' - ? getAbsolutePath(item.uiInternalPath) - : getHref('integration_details_overview', { - pkgkey: `${name}-${item.urlVersion}`, - ...(item.integration ? { integration: item.integration } : {}), - }); - - return { - uiInternalPathUrl, - ...item, - }; + const cards = eprAndCustomPackages.map((item) => { + return mapToCard(getAbsolutePath, getHref, item); }); return ( From 950a9a2a0bef057da9990413b392e069223a4ed8 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 12:50:19 -0400 Subject: [PATCH 07/31] remove epr-typing from cards --- .../sections/epm/components/package_card.tsx | 39 +++++++++---------- .../public/hooks/use_package_icon_type.ts | 1 - 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 11b17769acb3e..5f37f214b935d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -9,16 +9,20 @@ import React from 'react'; import styled from 'styled-components'; import { EuiCard } from '@elastic/eui'; -import type { PackageListItem } from '../../../types'; import { CardIcon } from '../../../../../components/package_icon'; +import type { PackageSpecIcon } from '../../../../../../common/types/models'; -type PackageCardProps = PackageListItem & { - type?: string; - uiInternalPath?: string; +interface PackageCardProps { uiInternalPathUrl: string; betaBadgeLabel?: string; betaBadgeLabelTooltipContent?: string; -}; + description: string; + name: string; + title: string; + version: string; + icons: PackageSpecIcon[]; + integration: string; +} // adding the `href` causes EuiCard to use a `a` instead of a `button` // `a` tags use `euiLinkColor` which results in blueish Badge text @@ -32,33 +36,26 @@ export function PackageCard(props: PackageCardProps) { name, title, version, - release, - status, icons, integration, - type, - uiInternalPath, uiInternalPathUrl, betaBadgeLabel, betaBadgeLabelTooltipContent, - ...restProps } = props; - const icon = ( - - ); - return ( + } href={uiInternalPathUrl} betaBadgeLabel={betaBadgeLabel} betaBadgeTooltipContent={betaBadgeLabelTooltipContent} diff --git a/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts b/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts index 2ad229d9b409e..c194d9914acff 100644 --- a/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts +++ b/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts @@ -23,7 +23,6 @@ export interface UsePackageIconType { version: Package['version']; icons?: Package['icons']; tryApi?: boolean; // should it call API to try to find missing icons? - type: 'eui' | undefined; } const CACHED_ICONS = new Map(); From 4bc93e098dcb0bacc168b0c4dfce7dde6c79edb5 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 14:09:56 -0400 Subject: [PATCH 08/31] remove epr tpying from card-lists --- .../plugins/fleet/common/types/models/epm.ts | 13 +++++++ .../integrations/hooks/use_local_search.tsx | 4 +- .../sections/epm/components/package_card.tsx | 38 +++++++------------ .../epm/components/package_list_grid.tsx | 9 +++-- .../sections/epm/screens/home/index.tsx | 22 ++++++++--- 5 files changed, 50 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index bbb571f963dc9..d3155a5116236 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -362,6 +362,19 @@ export type PackageListItem = Installable & { id: string; }; +export interface IntegrationCardItem { + uiInternalPathUrl: string; + betaBadgeLabel?: string; + betaBadgeLabelTooltipContent?: string; + description: string; + name: string; + title: string; + version: string; + icons: PackageSpecIcon[]; + integration: string; + id: string; +} + export type PackagesGroupedByStatus = Record, PackageList>; export type PackageInfo = | Installable> diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx index fc2966697418a..61838186940f0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx @@ -8,12 +8,12 @@ import { Search as LocalSearch, AllSubstringsIndexStrategy } from 'js-search'; import { useEffect, useRef } from 'react'; -import type { PackageList } from '../../../types'; +import type { IntegrationCardItem } from '../../../../common/types/models'; export const searchIdField = 'id'; export const fieldsToSearch = ['name', 'title']; -export function useLocalSearch(packageList: PackageList) { +export function useLocalSearch(packageList: IntegrationCardItem[]) { const localSearchRef = useRef(null); useEffect(() => { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 5f37f214b935d..9a4504553b9d0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -10,19 +10,9 @@ import styled from 'styled-components'; import { EuiCard } from '@elastic/eui'; import { CardIcon } from '../../../../../components/package_icon'; -import type { PackageSpecIcon } from '../../../../../../common/types/models'; +import type { IntegrationCardItem } from '../../../../../../common/types/models/epm'; -interface PackageCardProps { - uiInternalPathUrl: string; - betaBadgeLabel?: string; - betaBadgeLabelTooltipContent?: string; - description: string; - name: string; - title: string; - version: string; - icons: PackageSpecIcon[]; - integration: string; -} +type PackageCardProps = IntegrationCardItem; // adding the `href` causes EuiCard to use a `a` instead of a `button` // `a` tags use `euiLinkColor` which results in blueish Badge text @@ -30,19 +20,17 @@ const Card = styled(EuiCard)` color: inherit; `; -export function PackageCard(props: PackageCardProps) { - const { - description, - name, - title, - version, - icons, - integration, - uiInternalPathUrl, - betaBadgeLabel, - betaBadgeLabelTooltipContent, - } = props; - +export function PackageCard({ + description, + name, + title, + version, + icons, + integration, + uiInternalPathUrl, + betaBadgeLabel, + betaBadgeLabelTooltipContent, +}: PackageCardProps) { return ( void; onSearchChange: (search: string) => void; @@ -77,7 +78,7 @@ export function PackageListGrid({ } else { const filteredList = searchTerm ? list.filter((item) => - (localSearchRef.current!.search(searchTerm) as PackageList) + (localSearchRef.current!.search(searchTerm) as IntegrationCardItem[]) .map((match) => match[searchIdField]) .includes(item[searchIdField]) ) @@ -141,7 +142,7 @@ function ControlsColumn({ controls, title }: ControlsColumnProps) { } interface GridColumnProps { - list: PackageList; + list: IntegrationCardItem[]; showMissingIntegrationMessage?: boolean; } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 20ea53219c3de..c5cf48d633dea 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -34,8 +34,10 @@ import type { PackageListItem } from '../../../../types'; import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components/release_badge'; -import { CategoryFacets } from './category_facets'; +import type { IntegrationCardItem } from '../../../../../../../common/types/models'; + import { mergeAndReplaceCategoryCounts } from './util'; +import { CategoryFacets } from './category_facets'; export interface CategoryParams { category?: string; @@ -53,7 +55,11 @@ function categoryExists(category: string, categories: CategorySummaryItem[]) { return categories.some((c) => c.id === category); } -function mapToCard(getAbsolutePath, getHref, item: CustomIntegration | PackageListItem) { +function mapToCard( + getAbsolutePath: (p: string) => string, + getHref: (p: string, o: Record) => string, + item: CustomIntegration | PackageListItem +): IntegrationCardItem { let uiInternalPathUrl; if (item.type === 'ui_link') { uiInternalPathUrl = getAbsolutePath(item.uiInternalPath); @@ -73,16 +79,22 @@ function mapToCard(getAbsolutePath, getHref, item: CustomIntegration | PackageLi item.type !== 'ui_link' && item.release && item.release !== 'ga' ? RELEASE_BADGE_LABEL[item.release] : undefined; - const betaBadgeTooltipContent = + const betaBadgeLabelTooltipContent = item.type !== 'ui_link' && item.release && item.release !== 'ga' ? RELEASE_BADGE_DESCRIPTION[item.release] : undefined; return { + id: `${item.type === 'ui_link' ? 'ui_link' : 'epr'}-${item.id}`, + description: item.description, + icons: item.icons || [], + integration: item.integration || '', + name: item.name, + title: item.title, + version: item.version || '', uiInternalPathUrl, betaBadgeLabel, - betaBadgeTooltipContent, - ...item, + betaBadgeLabelTooltipContent, }; } From 5c4258e596fb72d1537ad0780c5cb1257350c67c Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 15:24:48 -0400 Subject: [PATCH 09/31] more typing --- .../sections/epm/screens/home/category_facets.tsx | 9 +++++++-- .../integrations/sections/epm/screens/home/index.tsx | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx index b2594036aee2c..2c72b3d1a18ed 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx @@ -14,6 +14,11 @@ import { Loading } from '../../../../components'; import type { CategoryCount } from '../../../../../../../../../../src/plugins/custom_integrations/common'; import { CATEGORY_DISPLAY } from '../../../../../../../../../../src/plugins/custom_integrations/common'; +export interface ALL_CATEGORY { + id: ''; + count: number; +} + export function CategoryFacets({ isLoading, categories, @@ -21,9 +26,9 @@ export function CategoryFacets({ onCategoryChange, }: { isLoading?: boolean; - categories: CategoryCount[]; + categories: Array; selectedCategory: string; - onCategoryChange: (category: CategoryCount) => unknown; + onCategoryChange: (category: CategoryCount | ALL_CATEGORY) => unknown; }) { const controls = ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index c5cf48d633dea..b64404bc04137 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -38,6 +38,7 @@ import type { IntegrationCardItem } from '../../../../../../../common/types/mode import { mergeAndReplaceCategoryCounts } from './util'; import { CategoryFacets } from './category_facets'; +import type { ALL_CATEGORY } from './category_facets'; export interface CategoryParams { category?: string; @@ -225,7 +226,7 @@ const InstalledPackages: React.FC = memo(() => { setSelectedCategory(id)} + onCategoryChange={({ id }: CategorySummaryItem | ALL_CATEGORY) => setSelectedCategory(id)} /> ); From df06051fcde8deb7441f4803726e7e5f7f8f9af2 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 17:34:33 -0400 Subject: [PATCH 10/31] more type fixes --- .../epm/components/package_card.stories.tsx | 6 + .../sections/epm/components/package_card.tsx | 2 +- .../components/package_list_grid.stories.tsx | 146 +++++++++--------- .../epm/screens/home/category_facets.tsx | 8 +- .../sections/epm/screens/home/index.tsx | 50 +++--- .../sections/epm/screens/home/util.ts | 7 +- 6 files changed, 116 insertions(+), 103 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx index e8814b8b8c877..5116b82ed080c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx @@ -26,6 +26,7 @@ const args: Args = { title: 'Title', description: 'Description', name: 'beats', + // @ts-expect-error release: 'ga', id: 'id', version: '1.0.0', @@ -44,6 +45,8 @@ const argTypes = { export const NotInstalled = ({ width, ...props }: Args) => (
+ {/* + // @ts-ignore */}
); @@ -51,6 +54,7 @@ export const NotInstalled = ({ width, ...props }: Args) => ( export const Installed = ({ width, ...props }: Args) => { const savedObject: SavedObject = { id: props.id, + // @ts-expect-error type: props.type || '', attributes: { name: props.name, @@ -68,6 +72,8 @@ export const Installed = ({ width, ...props }: Args) => { return (
+ {/* + // @ts-ignore */}
); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 9a4504553b9d0..7e7bc28115c70 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -12,7 +12,7 @@ import { EuiCard } from '@elastic/eui'; import { CardIcon } from '../../../../../components/package_icon'; import type { IntegrationCardItem } from '../../../../../../common/types/models/epm'; -type PackageCardProps = IntegrationCardItem; +export type PackageCardProps = IntegrationCardItem; // adding the `href` causes EuiCard to use a `a` instead of a `button` // `a` tags use `euiLinkColor` which results in blueish Badge text diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx index d84e286b6f560..5c090326ab084 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx @@ -13,6 +13,8 @@ import type { SavedObject } from 'src/core/public'; import type { Installation } from '../../../../../../common'; +import type { IntegrationCardItem } from '../../../../../../common'; + import type { ListProps } from './package_list_grid'; import { PackageListGrid } from './package_list_grid'; @@ -57,77 +59,79 @@ export const EmptyList = (props: Args) => ( export const List = (props: Args) => ( ; + categories: CategoryFacet[]; selectedCategory: string; - onCategoryChange: (category: CategoryCount | ALL_CATEGORY) => unknown; + onCategoryChange: (category: CategoryFacet) => unknown; }) { const controls = ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index b64404bc04137..f55ba0fbb01c9 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -11,6 +11,7 @@ import semverLt from 'semver/functions/lt'; import { i18n } from '@kbn/i18n'; import { installationStatuses } from '../../../../../../../common/constants'; +import type { DynamicPage, DynamicPagePathValues, StaticPage } from '../../../../constants'; import { INTEGRATIONS_ROUTING_PATHS, INTEGRATIONS_SEARCH_QUERYPARAM, @@ -25,7 +26,7 @@ import { } from '../../../../hooks'; import { doesPackageHaveIntegrations } from '../../../../services'; import { DefaultLayout } from '../../../../layouts'; -import type { CategorySummaryItem, PackageList } from '../../../../types'; +import type { PackageList } from '../../../../types'; import { PackageListGrid } from '../../components/package_list_grid'; import type { CustomIntegration } from '../../../../../../../../../../src/plugins/custom_integrations/common'; @@ -36,9 +37,11 @@ import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components import type { IntegrationCardItem } from '../../../../../../../common/types/models'; +import type { Category } from '../../../../../../../../../../src/plugins/custom_integrations/common'; + import { mergeAndReplaceCategoryCounts } from './util'; import { CategoryFacets } from './category_facets'; -import type { ALL_CATEGORY } from './category_facets'; +import type { CategoryFacet } from './category_facets'; export interface CategoryParams { category?: string; @@ -52,13 +55,13 @@ function getParams(params: CategoryParams, search: string) { return { selectedCategory, searchParam }; } -function categoryExists(category: string, categories: CategorySummaryItem[]) { +function categoryExists(category: string, categories: CategoryFacet[]) { return categories.some((c) => c.id === category); } function mapToCard( getAbsolutePath: (p: string) => string, - getHref: (p: string, o: Record) => string, + getHref: (page: StaticPage | DynamicPage, values?: DynamicPagePathValues) => string, item: CustomIntegration | PackageListItem ): IntegrationCardItem { let uiInternalPathUrl; @@ -89,10 +92,10 @@ function mapToCard( id: `${item.type === 'ui_link' ? 'ui_link' : 'epr'}-${item.id}`, description: item.description, icons: item.icons || [], - integration: item.integration || '', + integration: 'integration' in item ? item.integration || '' : '', name: item.name, title: item.title, - version: item.version || '', + version: 'version' in item ? item.version || '' : '', uiInternalPathUrl, betaBadgeLabel, betaBadgeLabelTooltipContent, @@ -195,20 +198,14 @@ const InstalledPackages: React.FC = memo(() => { [] ); - const categories = useMemo( + const categories: CategoryFacet[] = useMemo( () => [ { id: '', - title: i18n.translate('xpack.fleet.epmList.allFilterLinkText', { - defaultMessage: 'All', - }), count: allInstalledPackages.length, }, { id: 'updates_available', - title: i18n.translate('xpack.fleet.epmList.updatesAvailableFilterLinkText', { - defaultMessage: 'Updates available', - }), count: updatablePackages.length, }, ], @@ -226,14 +223,11 @@ const InstalledPackages: React.FC = memo(() => { setSelectedCategory(id)} + onCategoryChange={({ id }: CategoryFacet) => setSelectedCategory(id)} /> ); - const cards = (selectedCategory === 'updates_available' - ? updatablePackages - : allInstalledPackages - ).map((item) => { + const cards = (selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages).map((item) => { return mapToCard(getAbsolutePath, getHref, item); }); @@ -297,11 +291,11 @@ const AvailablePackages: React.FC = memo(() => { value: addableCustomIntegrations, } = useGetAddableCustomIntegrations(); const filteredAddableIntegrations = addableCustomIntegrations - ? addableCustomIntegrations.filter((integration) => { + ? addableCustomIntegrations.filter((integration: CustomIntegration) => { if (!selectedCategory) { return true; } - return integration.categories.indexOf(selectedCategory) >= 0; + return integration.categories.indexOf(selectedCategory as Category) >= 0; }) : []; @@ -313,26 +307,32 @@ const AvailablePackages: React.FC = memo(() => { [] ); - const eprAndCustomPackages = eprPackages.concat(filteredAddableIntegrations); + const eprAndCustomPackages: Array = [ + ...eprPackages, + ...filteredAddableIntegrations, + ]; eprAndCustomPackages.sort((a, b) => { return a.name.localeCompare(b.name); }); const categories = useMemo(() => { - const eprAndCustomCategories = + const eprAndCustomCategories: CategoryFacet[] = isLoadingCategories || isLoadingAddableCustomIntegrations || !addableCustomIntegrations || !categoriesRes ? [] - : mergeAndReplaceCategoryCounts(categoriesRes.response, addableCustomIntegrations); + : mergeAndReplaceCategoryCounts( + categoriesRes.response as CategoryFacet[], + addableCustomIntegrations + ); return [ { id: '', count: (allEprPackages?.length || 0) + (addableCustomIntegrations?.length || 0), }, ...(eprAndCustomCategories ? eprAndCustomCategories : []), - ]; + ] as CategoryFacet[]; }, [ allEprPackages?.length, addableCustomIntegrations, @@ -351,7 +351,7 @@ const AvailablePackages: React.FC = memo(() => { isLoading={isLoadingCategories || isLoadingAllPackages || isLoadingAddableCustomIntegrations} categories={categories} selectedCategory={selectedCategory} - onCategoryChange={({ id }: CategorySummaryItem) => { + onCategoryChange={({ id }: CategoryFacet) => { setSelectedCategory(id); }} /> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts index 5e5502ad0a97f..00686c4b74ec4 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts @@ -5,13 +5,14 @@ * 2.0. */ -import type { CategoryCount } from '../../../../../../../../../../src/plugins/custom_integrations/common'; import type { CustomIntegration } from '../../../../../../../../../../src/plugins/custom_integrations/common'; +import type { CategoryFacet } from './category_facets'; + export function mergeAndReplaceCategoryCounts( - eprCounts: CategoryCount[], + eprCounts: CategoryFacet[], addableIntegrations: CustomIntegration[] -) { +): CategoryFacet[] { addableIntegrations.forEach((integration) => { integration.categories.forEach((cat) => { const match = eprCounts.find((c) => { From fc0ed69b19ea34d54369d42e395787bce25957ca Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 21 Sep 2021 18:07:46 -0400 Subject: [PATCH 11/31] Fix icon issue --- .../applications/integrations/hooks/use_links.tsx | 1 - .../integrations/sections/epm/screens/home/index.tsx | 7 +++++-- .../plugins/fleet/public/components/package_icon.tsx | 11 ++++++----- .../fleet/public/hooks/use_package_icon_type.ts | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx index 82a058906cee5..508dce825c6cc 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx @@ -16,7 +16,6 @@ const removeRelativePath = (relativePath: string): string => export function useLinks() { const { http } = useStartServices(); return { - toAssets: (path: string) => http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/${path}`), toSharedAssets: (path: string) => http.basePath.prepend(`/plugins/kibanaReact/assets/${path}`), toPackageImage: ( img: PackageSpecIcon | PackageSpecScreenshot | RegistryImage, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index f55ba0fbb01c9..6b9b26fad9395 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -91,7 +91,7 @@ function mapToCard( return { id: `${item.type === 'ui_link' ? 'ui_link' : 'epr'}-${item.id}`, description: item.description, - icons: item.icons || [], + icons: !item.icons || !item.icons.length ? [] : item.icons, integration: 'integration' in item ? item.integration || '' : '', name: item.name, title: item.title, @@ -227,7 +227,10 @@ const InstalledPackages: React.FC = memo(() => { /> ); - const cards = (selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages).map((item) => { + const cards = (selectedCategory === 'updates_available' + ? updatablePackages + : allInstalledPackages + ).map((item) => { return mapToCard(getAbsolutePath, getHref, item); }); diff --git a/x-pack/plugins/fleet/public/components/package_icon.tsx b/x-pack/plugins/fleet/public/components/package_icon.tsx index 85ae7971f46c2..e056e2a49b438 100644 --- a/x-pack/plugins/fleet/public/components/package_icon.tsx +++ b/x-pack/plugins/fleet/public/components/package_icon.tsx @@ -12,11 +12,12 @@ import { EuiIcon } from '@elastic/eui'; import type { UsePackageIconType } from '../hooks'; import { usePackageIconType } from '../hooks'; -export const PackageIcon: React.FunctionComponent> = - ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => { - const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi }); - return ; - }; +export const PackageIcon: React.FunctionComponent< + UsePackageIconType & Omit +> = ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => { + const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi }); + return ; +}; export const CardIcon: React.FunctionComponent> = ( props diff --git a/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts b/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts index c194d9914acff..a86c68c00c646 100644 --- a/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts +++ b/x-pack/plugins/fleet/public/hooks/use_package_icon_type.ts @@ -46,7 +46,7 @@ export const usePackageIconType = ({ setIconType(CACHED_ICONS.get(cacheKey) || ''); return; } - const svgIcons = (paramIcons || iconList)?.filter( + const svgIcons = (paramIcons && paramIcons.length ? paramIcons : iconList)?.filter( (iconDef) => iconDef.type === 'image/svg+xml' ); const localIconSrc = From 2d459d21f9641afc8e8dca7d8655ea22e74e2e83 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 11:24:06 -0400 Subject: [PATCH 12/31] resolve a few CI checks --- docs/developer/plugin-list.asciidoc | 2 ++ .../fleet/public/applications/integrations/hooks/use_links.tsx | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 6a46aa0d68cf2..edc1821f3b223 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -43,9 +43,11 @@ as uiSettings within the code. |{kib-repo}blob/{branch}/src/plugins/console/README.md[console] |Console provides the user with tools for storing and executing requests against Elasticsearch. + |{kib-repo}blob/{branch}/src/plugins/custom_integrations/README.md[customIntegrations] |Register add-data cards + |<> |- Registers the dashboard application. - Adds a dashboard embeddable that can be used in other applications. diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx index 508dce825c6cc..032554a4ec439 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_links.tsx @@ -6,7 +6,6 @@ */ import { useStartServices } from '../../../hooks/use_core'; -import { PLUGIN_ID } from '../../../constants'; import { epmRouteService } from '../../../services'; import type { PackageSpecIcon, PackageSpecScreenshot, RegistryImage } from '../../../../common'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index aac2d45ec2120..70dbc4907615f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10825,7 +10825,6 @@ "xpack.fleet.epm.updateAvailableTooltip": "更新が利用可能です", "xpack.fleet.epm.usedByLabel": "エージェントポリシー", "xpack.fleet.epm.versionLabel": "バージョン", - "xpack.fleet.epmList.allFilterLinkText": "すべて", "xpack.fleet.epmList.allPackagesFilterLinkText": "すべて", "xpack.fleet.epmList.allTitle": "カテゴリで参照", "xpack.fleet.epmList.installedTitle": "インストールされている統合", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 73ad85a85f427..743ff30f8a216 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10939,7 +10939,6 @@ "xpack.fleet.epm.updateAvailableTooltip": "有可用更新", "xpack.fleet.epm.usedByLabel": "代理策略", "xpack.fleet.epm.versionLabel": "版本", - "xpack.fleet.epmList.allFilterLinkText": "全部", "xpack.fleet.epmList.allPackagesFilterLinkText": "全部", "xpack.fleet.epmList.allTitle": "按类别浏览", "xpack.fleet.epmList.installedTitle": "已安装集成", From 3e9e58d20e1d6ea646bb28091a53af5cac9ff5b9 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 12:20:56 -0400 Subject: [PATCH 13/31] test --- src/plugins/custom_integrations/public/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/custom_integrations/public/plugin.ts b/src/plugins/custom_integrations/public/plugin.ts index 3c2639a26c551..316135599aa3b 100755 --- a/src/plugins/custom_integrations/public/plugin.ts +++ b/src/plugins/custom_integrations/public/plugin.ts @@ -15,7 +15,8 @@ import { } from '../common'; export class CustomIntegrationPlugin - implements Plugin { + implements Plugin +{ public setup(core: CoreSetup): CustomIntegrationsSetup { // Return methods that should be available to other plugins return { From 0a219018ad9edfcb417c0d090c752160dedaa7ba Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 12:27:14 -0400 Subject: [PATCH 14/31] fix linting with new settings --- src/plugins/custom_integrations/server/plugin.ts | 3 ++- src/plugins/home/server/plugin.test.ts | 2 +- .../services/tutorials/tutorials_registry.test.ts | 3 ++- .../server/services/tutorials/tutorials_registry.ts | 12 +++++++----- .../epm/components/package_list_grid.stories.tsx | 4 ++-- .../integrations/sections/epm/screens/home/index.tsx | 11 ++++------- .../plugins/fleet/public/components/package_icon.tsx | 11 +++++------ .../plugins/fleet/public/mock/plugin_dependencies.ts | 2 +- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/plugins/custom_integrations/server/plugin.ts b/src/plugins/custom_integrations/server/plugin.ts index cb65e634aa4e1..8435feacfa93c 100755 --- a/src/plugins/custom_integrations/server/plugin.ts +++ b/src/plugins/custom_integrations/server/plugin.ts @@ -14,7 +14,8 @@ import { CustomIntegrationRegistry } from './custom_integration_registry'; import { defineRoutes } from './routes/define_routes'; export class CustomIntegrationsPlugin - implements Plugin { + implements Plugin +{ private readonly logger: Logger; private readonly customIngegrationRegistry: CustomIntegrationRegistry; diff --git a/src/plugins/home/server/plugin.test.ts b/src/plugins/home/server/plugin.test.ts index 244b390f808f6..a924aa837d62e 100644 --- a/src/plugins/home/server/plugin.test.ts +++ b/src/plugins/home/server/plugin.test.ts @@ -10,7 +10,7 @@ import { registryForTutorialsMock, registryForSampleDataMock } from './plugin.te import { HomeServerPlugin, HomeServerPluginSetupDependencies } from './plugin'; import { coreMock, httpServiceMock } from '../../../core/server/mocks'; -const homeServerPluginSetupDependenciesMock = ({} as unknown) as HomeServerPluginSetupDependencies; +const homeServerPluginSetupDependenciesMock = {} as unknown as HomeServerPluginSetupDependencies; describe('HomeServerPlugin', () => { beforeEach(() => { registryForTutorialsMock.setup.mockClear(); diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index c7e1b551be27e..2d3d8422b2123 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -19,6 +19,7 @@ import { ScopedTutorialContextFactory, } from './lib/tutorials_registry_types'; import { CustomIntegrationsPluginSetup } from '../../../../custom_integrations/server'; +import expect from '../../../../../../packages/kbn-expect'; const INVALID_TUTORIAL: TutorialSchema = { id: 'test', @@ -66,7 +67,7 @@ const VALID_TUTORIAL: TutorialSchema = { const invalidTutorialProvider = INVALID_TUTORIAL; const validTutorialProvider = VALID_TUTORIAL; -const mockCustomIntegrationsPluginSetup = ({} as unknown) as CustomIntegrationsPluginSetup; +const mockCustomIntegrationsPluginSetup = {} as unknown as CustomIntegrationsPluginSetup; describe('TutorialsRegistry', () => { let mockCoreSetup: MockedKeys; diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index 5869edd2c5e0c..3c0af731b889c 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -25,11 +25,13 @@ function registerTutorialWithCustomIntegrations( provider: TutorialProvider ) { const tutorial = provider(emptyContext); - const allowedCategories: Category[] = (tutorial.integrationBrowserCategories - ? tutorial.integrationBrowserCategories.filter((category) => { - return CATEGORY_DISPLAY.hasOwnProperty(category); - }) - : []) as Category[]; + const allowedCategories: Category[] = ( + tutorial.integrationBrowserCategories + ? tutorial.integrationBrowserCategories.filter((category) => { + return CATEGORY_DISPLAY.hasOwnProperty(category); + }) + : [] + ) as Category[]; customIntegrations.registerCustomIntegration({ name: tutorial.id, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx index 5c090326ab084..008786f4216e3 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx @@ -60,7 +60,7 @@ export const EmptyList = (props: Args) => ( export const List = (props: Args) => ( ( status: 'installed', savedObject, }, - ] as unknown) as IntegrationCardItem[] + ] as unknown as IntegrationCardItem[] } onSearchChange={action('onSearchChange')} setSelectedCategory={action('setSelectedCategory')} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 6b9b26fad9395..a59897fade7c4 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -227,9 +227,8 @@ const InstalledPackages: React.FC = memo(() => { /> ); - const cards = (selectedCategory === 'updates_available' - ? updatablePackages - : allInstalledPackages + const cards = ( + selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages ).map((item) => { return mapToCard(getAbsolutePath, getHref, item); }); @@ -289,10 +288,8 @@ const AvailablePackages: React.FC = memo(() => { [allCategoryPackagesRes] ); - const { - loading: isLoadingAddableCustomIntegrations, - value: addableCustomIntegrations, - } = useGetAddableCustomIntegrations(); + const { loading: isLoadingAddableCustomIntegrations, value: addableCustomIntegrations } = + useGetAddableCustomIntegrations(); const filteredAddableIntegrations = addableCustomIntegrations ? addableCustomIntegrations.filter((integration: CustomIntegration) => { if (!selectedCategory) { diff --git a/x-pack/plugins/fleet/public/components/package_icon.tsx b/x-pack/plugins/fleet/public/components/package_icon.tsx index e056e2a49b438..85ae7971f46c2 100644 --- a/x-pack/plugins/fleet/public/components/package_icon.tsx +++ b/x-pack/plugins/fleet/public/components/package_icon.tsx @@ -12,12 +12,11 @@ import { EuiIcon } from '@elastic/eui'; import type { UsePackageIconType } from '../hooks'; import { usePackageIconType } from '../hooks'; -export const PackageIcon: React.FunctionComponent< - UsePackageIconType & Omit -> = ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => { - const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi }); - return ; -}; +export const PackageIcon: React.FunctionComponent> = + ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => { + const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi }); + return ; + }; export const CardIcon: React.FunctionComponent> = ( props diff --git a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts index 10047539c7321..5fc3e5f798835 100644 --- a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts +++ b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts @@ -20,7 +20,7 @@ export const createSetupDepsMock = (): MockedFleetSetupDeps => { data: dataPluginMock.createSetupContract(), home: homePluginMock.createSetupContract(), // @ts-expect-error - customIntegrations: ({} as unknown) as CustomIntegrationsSetup, + customIntegrations: {} as unknown as CustomIntegrationsSetup, }; }; From d0ba72a22d1b4a6677fda3f6cbcaa552aa73baf8 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 12:48:04 -0400 Subject: [PATCH 15/31] remove faulty import --- .../home/server/services/tutorials/tutorials_registry.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index 2d3d8422b2123..32450f40f4302 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -19,7 +19,6 @@ import { ScopedTutorialContextFactory, } from './lib/tutorials_registry_types'; import { CustomIntegrationsPluginSetup } from '../../../../custom_integrations/server'; -import expect from '../../../../../../packages/kbn-expect'; const INVALID_TUTORIAL: TutorialSchema = { id: 'test', From f41f8fcd0a9a677d67c344f3b65c7512df46c543 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 14:21:01 -0400 Subject: [PATCH 16/31] feedback --- .../custom_integrations/common/index.ts | 2 -- .../custom_integrations/public/plugin.ts | 9 +---- .../custom_integrations/public/types.ts | 1 - .../server/custom_integration_registry.ts | 35 +------------------ .../custom_integrations/server/index.ts | 5 +++ .../custom_integrations/server/plugin.ts | 9 +---- .../server/routes/define_routes.ts | 18 +--------- .../custom_integrations/server/types.ts | 4 +-- .../services/tutorials/lib/tutorial_schema.ts | 1 - .../services/tutorials/tutorials_registry.ts | 1 - 10 files changed, 10 insertions(+), 75 deletions(-) diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index b32914bf1d4b5..2facefb47340d 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -58,8 +58,6 @@ export interface CustomIntegration { icons: Array<{ src: string; type: string }>; categories: Category[]; shipper: string; - eprPackageOverlap?: string; } export const ROUTES_ADDABLECUSTOMINTEGRATIONS = `/api/${PLUGIN_ID}/addableCustomIntegrations`; -export const ROUTES_REPLACEABLECUSTOMINMTEGRATIONS = `/api/${PLUGIN_ID}/replaceableCustomIntegrations`; diff --git a/src/plugins/custom_integrations/public/plugin.ts b/src/plugins/custom_integrations/public/plugin.ts index 316135599aa3b..5e5e4b99595d3 100755 --- a/src/plugins/custom_integrations/public/plugin.ts +++ b/src/plugins/custom_integrations/public/plugin.ts @@ -8,11 +8,7 @@ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; -import { - CustomIntegration, - ROUTES_ADDABLECUSTOMINTEGRATIONS, - ROUTES_REPLACEABLECUSTOMINMTEGRATIONS, -} from '../common'; +import { CustomIntegration, ROUTES_ADDABLECUSTOMINTEGRATIONS } from '../common'; export class CustomIntegrationPlugin implements Plugin @@ -23,9 +19,6 @@ export class CustomIntegrationPlugin async getAddableCustomIntegrations(): Promise { return core.http.get(ROUTES_ADDABLECUSTOMINTEGRATIONS); }, - async getReplaceableCustomIntegrations(): Promise { - return core.http.get(ROUTES_REPLACEABLECUSTOMINMTEGRATIONS); - }, } as CustomIntegrationsSetup; } diff --git a/src/plugins/custom_integrations/public/types.ts b/src/plugins/custom_integrations/public/types.ts index 00ecbfe257baa..d324b0adaeee8 100755 --- a/src/plugins/custom_integrations/public/types.ts +++ b/src/plugins/custom_integrations/public/types.ts @@ -9,7 +9,6 @@ import { CustomIntegration } from '../common'; export interface CustomIntegrationsSetup { - getReplaceableCustomIntegrations: () => Promise; getAddableCustomIntegrations: () => Promise; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.ts b/src/plugins/custom_integrations/server/custom_integration_registry.ts index 32344497ac450..520934fc2b580 100644 --- a/src/plugins/custom_integrations/server/custom_integration_registry.ts +++ b/src/plugins/custom_integrations/server/custom_integration_registry.ts @@ -7,7 +7,7 @@ */ import { Logger } from 'kibana/server'; -import { CustomIntegration, CategoryCount, Category } from '../common'; +import { CustomIntegration } from '../common'; function isAddable(integration: CustomIntegration) { return integration.categories.length; @@ -38,37 +38,4 @@ export class CustomIntegrationRegistry { getAddableCustomIntegrations(): CustomIntegration[] { return this._integrations.filter(isAddable); } - - getReplaceableCustomIntegrations(): CustomIntegration[] { - return this._integrations.filter( - (integration) => typeof integration.eprPackageOverlap !== 'undefined' - ); - } - - getAddableCategories(): CategoryCount[] { - const categories: Map = new Map(); - for (let i = 0; i < this._integrations.length; i++) { - if (!isAddable(this._integrations[i])) { - continue; - } - for (let j = 0; j < this._integrations[i].categories.length; j++) { - const category = this._integrations[i].categories[j]; - if (categories.has(category)) { - // @ts-ignore - categories.set(category, categories.get(category) + 1); - } else { - categories.set(category, 1); - } - } - } - - const list: CategoryCount[] = []; - categories.forEach((value, key) => { - list.push({ - count: value, - id: key, - }); - }); - return list; - } } diff --git a/src/plugins/custom_integrations/server/index.ts b/src/plugins/custom_integrations/server/index.ts index 1823db905ae7f..423a06009ac4b 100755 --- a/src/plugins/custom_integrations/server/index.ts +++ b/src/plugins/custom_integrations/server/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { schema } from '@kbn/config-schema'; import { PluginInitializerContext } from '../../../core/server'; import { CustomIntegrationsPlugin } from './plugin'; @@ -19,3 +20,7 @@ export function plugin(initializerContext: PluginInitializerContext) { export { CustomIntegrationsPluginSetup, CustomIntegrationsPluginStart } from './types'; export type { Category, CategoryCount, CustomIntegration } from '../common'; + +export const config = { + schema: schema.object({}), +}; diff --git a/src/plugins/custom_integrations/server/plugin.ts b/src/plugins/custom_integrations/server/plugin.ts index 8435feacfa93c..d6332b16df479 100755 --- a/src/plugins/custom_integrations/server/plugin.ts +++ b/src/plugins/custom_integrations/server/plugin.ts @@ -9,7 +9,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from 'kibana/server'; import { CustomIntegrationsPluginSetup, CustomIntegrationsPluginStart } from './types'; -import { CustomIntegration, CategoryCount } from '../common'; +import { CustomIntegration } from '../common'; import { CustomIntegrationRegistry } from './custom_integration_registry'; import { defineRoutes } from './routes/define_routes'; @@ -37,13 +37,6 @@ export class CustomIntegrationsPlugin getAddableCustomIntegrations: (): CustomIntegration[] => { return this.customIngegrationRegistry.getAddableCustomIntegrations(); }, - - getAddableCategories: (): CategoryCount[] => { - return this.customIngegrationRegistry.getAddableCategories(); - }, - getReplaceableCustomIntegrations: (): CustomIntegration[] => { - return this.customIngegrationRegistry.getReplaceableCustomIntegrations(); - }, } as CustomIntegrationsPluginSetup; } diff --git a/src/plugins/custom_integrations/server/routes/define_routes.ts b/src/plugins/custom_integrations/server/routes/define_routes.ts index 248be757bdcd2..f36636f307eb1 100644 --- a/src/plugins/custom_integrations/server/routes/define_routes.ts +++ b/src/plugins/custom_integrations/server/routes/define_routes.ts @@ -8,10 +8,7 @@ import { IRouter } from 'src/core/server'; import { CustomIntegrationRegistry } from '../custom_integration_registry'; -import { - ROUTES_ADDABLECUSTOMINTEGRATIONS, - ROUTES_REPLACEABLECUSTOMINMTEGRATIONS, -} from '../../common'; +import { ROUTES_ADDABLECUSTOMINTEGRATIONS } from '../../common'; export function defineRoutes( router: IRouter, @@ -29,17 +26,4 @@ export function defineRoutes( }); } ); - - router.get( - { - path: ROUTES_REPLACEABLECUSTOMINMTEGRATIONS, - validate: false, - }, - async (context, request, response) => { - const integrations = customIntegrationsRegistry.getReplaceableCustomIntegrations(); - return response.ok({ - body: integrations, - }); - } - ); } diff --git a/src/plugins/custom_integrations/server/types.ts b/src/plugins/custom_integrations/server/types.ts index 82b44687ee0c4..498e7a2404189 100755 --- a/src/plugins/custom_integrations/server/types.ts +++ b/src/plugins/custom_integrations/server/types.ts @@ -6,13 +6,11 @@ * Side Public License, v 1. */ -import { CustomIntegration, CategoryCount } from '../common'; +import { CustomIntegration } from '../common'; export interface CustomIntegrationsPluginSetup { registerCustomIntegration(customIntegration: CustomIntegration): void; getAddableCustomIntegrations(): CustomIntegration[]; - getAddableCategories(): CategoryCount[]; - getReplaceableCustomIntegrations(): CustomIntegration[]; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts index dfbd9fdef36ee..9870b19da7bd5 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts @@ -156,7 +156,6 @@ export const tutorialSchema = schema.object({ customStatusCheckName: schema.maybe(schema.string()), integrationBrowserCategories: schema.maybe(schema.arrayOf(schema.string())), - eprPackageOverlap: schema.maybe(schema.string()), }); export type TutorialSchema = TypeOf; diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index 3c0af731b889c..c6be576ae259d 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -49,7 +49,6 @@ function registerTutorialWithCustomIntegrations( }, ] : [], - eprPackageOverlap: tutorial.eprPackageOverlap, shipper: 'tutorial', isBeta: false, }); From 85753fd7a1dc939f71316a5774137bdb840eeb52 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 14:35:09 -0400 Subject: [PATCH 17/31] feedback --- .../server/custom_integration_registry.ts | 11 +++++++++-- src/plugins/custom_integrations/server/plugin.ts | 12 +++++++++--- src/plugins/custom_integrations/server/types.ts | 2 +- .../server/services/tutorials/tutorials_registry.ts | 5 ++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.ts b/src/plugins/custom_integrations/server/custom_integration_registry.ts index 520934fc2b580..bbb2b41b85222 100644 --- a/src/plugins/custom_integrations/server/custom_integration_registry.ts +++ b/src/plugins/custom_integrations/server/custom_integration_registry.ts @@ -16,10 +16,12 @@ function isAddable(integration: CustomIntegration) { export class CustomIntegrationRegistry { private readonly _integrations: CustomIntegration[]; private readonly _logger: Logger; + private readonly _isDev: boolean; - constructor(logger: Logger) { + constructor(logger: Logger, isDev: boolean) { this._integrations = []; this._logger = logger; + this._isDev = isDev; } registerCustomIntegration(customIntegration: CustomIntegration) { @@ -28,7 +30,12 @@ export class CustomIntegrationRegistry { return integration.name === customIntegration.name; }) ) { - this._logger.error(`Integration with id=${customIntegration.name} already exists.`); + const message = `Integration with id=${customIntegration.name} already exists.`; + if (this._isDev) { + this._logger.debug(message); + } else { + this._logger.debug(message); + } return; } diff --git a/src/plugins/custom_integrations/server/plugin.ts b/src/plugins/custom_integrations/server/plugin.ts index d6332b16df479..b33140b0ecc42 100755 --- a/src/plugins/custom_integrations/server/plugin.ts +++ b/src/plugins/custom_integrations/server/plugin.ts @@ -21,7 +21,10 @@ export class CustomIntegrationsPlugin constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); - this.customIngegrationRegistry = new CustomIntegrationRegistry(this.logger); + this.customIngegrationRegistry = new CustomIntegrationRegistry( + this.logger, + initializerContext.env.mode.dev + ); } public setup(core: CoreSetup) { @@ -31,8 +34,11 @@ export class CustomIntegrationsPlugin defineRoutes(router, this.customIngegrationRegistry); return { - registerCustomIntegration: (integration: CustomIntegration) => { - this.customIngegrationRegistry.registerCustomIntegration(integration); + registerCustomIntegration: (integration: Omit) => { + this.customIngegrationRegistry.registerCustomIntegration({ + type: 'ui_link', + ...integration, + }); }, getAddableCustomIntegrations: (): CustomIntegration[] => { return this.customIngegrationRegistry.getAddableCustomIntegrations(); diff --git a/src/plugins/custom_integrations/server/types.ts b/src/plugins/custom_integrations/server/types.ts index 498e7a2404189..3913e0fcc6856 100755 --- a/src/plugins/custom_integrations/server/types.ts +++ b/src/plugins/custom_integrations/server/types.ts @@ -9,7 +9,7 @@ import { CustomIntegration } from '../common'; export interface CustomIntegrationsPluginSetup { - registerCustomIntegration(customIntegration: CustomIntegration): void; + registerCustomIntegration(customIntegration: Omit): void; getAddableCustomIntegrations(): CustomIntegration[]; } diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index c6be576ae259d..0b8843ac030b3 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -34,11 +34,10 @@ function registerTutorialWithCustomIntegrations( ) as Category[]; customIntegrations.registerCustomIntegration({ - name: tutorial.id, - id: tutorial.name, + name: tutorial.name, + id: tutorial.id, title: tutorial.name, categories: allowedCategories, - type: 'ui_link', uiInternalPath: `${HOME_APP_BASE_PATH}#/tutorial/${tutorial.id}`, description: tutorial.shortDescription, icons: tutorial.euiIconType From 04fe2908b634c8a13bceb31e5f14d06d2e887d24 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 14:42:48 -0400 Subject: [PATCH 18/31] rename for clarity --- src/plugins/custom_integrations/public/plugin.ts | 2 +- src/plugins/custom_integrations/public/types.ts | 2 +- .../custom_integrations/server/custom_integration_registry.ts | 2 +- src/plugins/custom_integrations/server/plugin.ts | 4 ++-- .../custom_integrations/server/routes/define_routes.ts | 2 +- src/plugins/custom_integrations/server/types.ts | 2 +- x-pack/plugins/fleet/public/hooks/use_request/epm.ts | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/plugins/custom_integrations/public/plugin.ts b/src/plugins/custom_integrations/public/plugin.ts index 5e5e4b99595d3..fdd4daad94ea6 100755 --- a/src/plugins/custom_integrations/public/plugin.ts +++ b/src/plugins/custom_integrations/public/plugin.ts @@ -16,7 +16,7 @@ export class CustomIntegrationPlugin public setup(core: CoreSetup): CustomIntegrationsSetup { // Return methods that should be available to other plugins return { - async getAddableCustomIntegrations(): Promise { + async getAppendCustomIntegrations(): Promise { return core.http.get(ROUTES_ADDABLECUSTOMINTEGRATIONS); }, } as CustomIntegrationsSetup; diff --git a/src/plugins/custom_integrations/public/types.ts b/src/plugins/custom_integrations/public/types.ts index d324b0adaeee8..911194171b4c4 100755 --- a/src/plugins/custom_integrations/public/types.ts +++ b/src/plugins/custom_integrations/public/types.ts @@ -9,7 +9,7 @@ import { CustomIntegration } from '../common'; export interface CustomIntegrationsSetup { - getAddableCustomIntegrations: () => Promise; + getAppendCustomIntegrations: () => Promise; } // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface CustomIntegrationsStart {} diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.ts b/src/plugins/custom_integrations/server/custom_integration_registry.ts index bbb2b41b85222..422936f5b1f8c 100644 --- a/src/plugins/custom_integrations/server/custom_integration_registry.ts +++ b/src/plugins/custom_integrations/server/custom_integration_registry.ts @@ -42,7 +42,7 @@ export class CustomIntegrationRegistry { this._integrations.push(customIntegration); } - getAddableCustomIntegrations(): CustomIntegration[] { + getAppendCustomIntegrations(): CustomIntegration[] { return this._integrations.filter(isAddable); } } diff --git a/src/plugins/custom_integrations/server/plugin.ts b/src/plugins/custom_integrations/server/plugin.ts index b33140b0ecc42..f1ddd70b6945a 100755 --- a/src/plugins/custom_integrations/server/plugin.ts +++ b/src/plugins/custom_integrations/server/plugin.ts @@ -40,8 +40,8 @@ export class CustomIntegrationsPlugin ...integration, }); }, - getAddableCustomIntegrations: (): CustomIntegration[] => { - return this.customIngegrationRegistry.getAddableCustomIntegrations(); + getAppendCustomIntegrations: (): CustomIntegration[] => { + return this.customIngegrationRegistry.getAppendCustomIntegrations(); }, } as CustomIntegrationsPluginSetup; } diff --git a/src/plugins/custom_integrations/server/routes/define_routes.ts b/src/plugins/custom_integrations/server/routes/define_routes.ts index f36636f307eb1..f5e952a0c1ebd 100644 --- a/src/plugins/custom_integrations/server/routes/define_routes.ts +++ b/src/plugins/custom_integrations/server/routes/define_routes.ts @@ -20,7 +20,7 @@ export function defineRoutes( validate: false, }, async (context, request, response) => { - const integrations = customIntegrationsRegistry.getAddableCustomIntegrations(); + const integrations = customIntegrationsRegistry.getAppendCustomIntegrations(); return response.ok({ body: integrations, }); diff --git a/src/plugins/custom_integrations/server/types.ts b/src/plugins/custom_integrations/server/types.ts index 3913e0fcc6856..b21bfd157a96e 100755 --- a/src/plugins/custom_integrations/server/types.ts +++ b/src/plugins/custom_integrations/server/types.ts @@ -10,7 +10,7 @@ import { CustomIntegration } from '../common'; export interface CustomIntegrationsPluginSetup { registerCustomIntegration(customIntegration: Omit): void; - getAddableCustomIntegrations(): CustomIntegration[]; + getAppendCustomIntegrations(): CustomIntegration[]; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index 16940476b8f61..650667000409a 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -26,7 +26,7 @@ import { useRequest, sendRequest } from './use_request'; export function useGetAddableCustomIntegrations() { const customIntegrations = getCustomIntegrations(); - return useAsync(customIntegrations.getAddableCustomIntegrations, []); + return useAsync(customIntegrations.getAppendCustomIntegrations, []); } export const useGetCategories = (query: GetCategoriesRequest['query'] = {}) => { From e1551378477ad586ef3433f8fb8a06e67b7c53f8 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 14:43:59 -0400 Subject: [PATCH 19/31] fix typo --- .../custom_integrations/server/custom_integration_registry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.ts b/src/plugins/custom_integrations/server/custom_integration_registry.ts index 422936f5b1f8c..82e5fb8f135de 100644 --- a/src/plugins/custom_integrations/server/custom_integration_registry.ts +++ b/src/plugins/custom_integrations/server/custom_integration_registry.ts @@ -32,7 +32,7 @@ export class CustomIntegrationRegistry { ) { const message = `Integration with id=${customIntegration.name} already exists.`; if (this._isDev) { - this._logger.debug(message); + this._logger.error(message); } else { this._logger.debug(message); } From 25c43dc4478f671f1fe53318cee2cf7efd0366f9 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 14:46:26 -0400 Subject: [PATCH 20/31] update plugin manifest --- src/plugins/custom_integrations/kibana.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/custom_integrations/kibana.json b/src/plugins/custom_integrations/kibana.json index 370f49f00fad9..3a78270d9ef09 100755 --- a/src/plugins/custom_integrations/kibana.json +++ b/src/plugins/custom_integrations/kibana.json @@ -3,10 +3,10 @@ "version": "1.0.0", "kibanaVersion": "kibana", "owner": { - "name": "platform", - "githubTeam": "" + "name": "Fleet", + "githubTeam": "fleet" }, - "description": "test", + "description": "Add custom data integrations so they can be displayed in the Fleet integrations app", "ui": true, "server": true, "extraPublicDirs": [ From 69d05b898ae21b38f76cb9085e645b06961c6e0d Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 15:07:53 -0400 Subject: [PATCH 21/31] home tests --- .../custom_integrations/server/mocks.ts | 24 +++++++++++++++ .../tutorials/tutorials_registry.test.ts | 29 +++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/plugins/custom_integrations/server/mocks.ts diff --git a/src/plugins/custom_integrations/server/mocks.ts b/src/plugins/custom_integrations/server/mocks.ts new file mode 100644 index 0000000000000..661c7e567aef6 --- /dev/null +++ b/src/plugins/custom_integrations/server/mocks.ts @@ -0,0 +1,24 @@ +/* + * 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 { MockedKeys } from '@kbn/utility-types/jest'; + +import { CustomIntegrationsPluginSetup } from '../server'; + +function createCustomIntegrationsSetup(): MockedKeys { + const mock = { + registerCustomIntegration: jest.fn(), + getAppendCustomIntegrations: jest.fn(), + }; + + return mock as MockedKeys; +} + +export const customIntegrationsMock = { + createSetup: createCustomIntegrationsSetup, +}; diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index 32450f40f4302..970241fc7e6bd 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -19,6 +19,7 @@ import { ScopedTutorialContextFactory, } from './lib/tutorials_registry_types'; import { CustomIntegrationsPluginSetup } from '../../../../custom_integrations/server'; +import { customIntegrationsMock } from '../../../../custom_integrations/server/mocks'; const INVALID_TUTORIAL: TutorialSchema = { id: 'test', @@ -66,12 +67,15 @@ const VALID_TUTORIAL: TutorialSchema = { const invalidTutorialProvider = INVALID_TUTORIAL; const validTutorialProvider = VALID_TUTORIAL; -const mockCustomIntegrationsPluginSetup = {} as unknown as CustomIntegrationsPluginSetup; - describe('TutorialsRegistry', () => { let mockCoreSetup: MockedKeys; let testProvider: TutorialProvider; let testScopedTutorialContextFactory: ScopedTutorialContextFactory; + let mockCustomIntegrationsPluginSetup: MockedKeys; + + beforeEach(() => { + mockCustomIntegrationsPluginSetup = customIntegrationsMock.createSetup(); + }); describe('GET /api/kibana/home/tutorials', () => { beforeEach(() => { @@ -103,6 +107,27 @@ describe('TutorialsRegistry', () => { const setup = new TutorialsRegistry().setup(mockCoreSetup, mockCustomIntegrationsPluginSetup); testProvider = ({}) => validTutorialProvider; expect(() => setup.registerTutorial(testProvider)).not.toThrowError(); + + expect(mockCustomIntegrationsPluginSetup.registerCustomIntegration.mock.calls).toEqual([ + [ + { + name: 'new tutorial provider', + id: 'test', + title: 'new tutorial provider', + categories: [], + uiInternalPath: '/app/home#/tutorial/test', + description: 'short description', + icons: [ + { + src: 'alert', + type: 'eui', + }, + ], + shipper: 'tutorial', + isBeta: false, + }, + ], + ]); }); test('addScopedTutorialContextFactory throws when given a scopedTutorialContextFactory that is not a function', () => { From 6df5f7bb57bdf70fff15dd93b8fbd93de47059ab Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 15:20:15 -0400 Subject: [PATCH 22/31] fleet mocks --- .../custom_integrations/public/mocks.ts | 21 +++++++++++++++++++ .../fleet/public/mock/plugin_dependencies.ts | 6 ++---- 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/plugins/custom_integrations/public/mocks.ts diff --git a/src/plugins/custom_integrations/public/mocks.ts b/src/plugins/custom_integrations/public/mocks.ts new file mode 100644 index 0000000000000..e6462751368a3 --- /dev/null +++ b/src/plugins/custom_integrations/public/mocks.ts @@ -0,0 +1,21 @@ +/* + * 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 { CustomIntegrationsSetup } from './types'; + +function createCustomIntegrationsSetup(): jest.Mocked { + const mock = { + getAppendCustomIntegrations: jest.fn(), + }; + + return mock; +} + +export const customIntegrationsMock = { + createSetup: createCustomIntegrationsSetup, +}; diff --git a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts index 5fc3e5f798835..5f3ee5c188b45 100644 --- a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts +++ b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts @@ -9,8 +9,7 @@ import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { licensingMock } from '../../../licensing/public/mocks'; import { homePluginMock } from '../../../../../src/plugins/home/public/mocks'; import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks'; - -import type { CustomIntegrationsSetup } from '../../../../../src/plugins/custom_integrations/public'; +import { customIntegrationsMock } from '../../../../../src/plugins/custom_integrations/public/mocks'; import type { MockedFleetSetupDeps, MockedFleetStartDeps } from './types'; @@ -19,8 +18,7 @@ export const createSetupDepsMock = (): MockedFleetSetupDeps => { licensing: licensingMock.createSetup(), data: dataPluginMock.createSetupContract(), home: homePluginMock.createSetupContract(), - // @ts-expect-error - customIntegrations: {} as unknown as CustomIntegrationsSetup, + customIntegrations: customIntegrationsMock.createSetup(), }; }; From c05ff99bf7a290e33c20080f597f8a68c39f70e5 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 15:31:20 -0400 Subject: [PATCH 23/31] home setup/start test --- src/plugins/home/server/plugin.test.ts | 16 ++++++++++++---- src/plugins/home/server/plugin.ts | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/plugins/home/server/plugin.test.ts b/src/plugins/home/server/plugin.test.ts index a924aa837d62e..fdf671e10ad09 100644 --- a/src/plugins/home/server/plugin.test.ts +++ b/src/plugins/home/server/plugin.test.ts @@ -9,23 +9,29 @@ import { registryForTutorialsMock, registryForSampleDataMock } from './plugin.test.mocks'; import { HomeServerPlugin, HomeServerPluginSetupDependencies } from './plugin'; import { coreMock, httpServiceMock } from '../../../core/server/mocks'; +import { customIntegrationsMock } from '../../custom_integrations/server/mocks'; -const homeServerPluginSetupDependenciesMock = {} as unknown as HomeServerPluginSetupDependencies; describe('HomeServerPlugin', () => { + let homeServerPluginSetupDependenciesMock: HomeServerPluginSetupDependencies; + let mockCoreSetup: ReturnType; + beforeEach(() => { registryForTutorialsMock.setup.mockClear(); registryForTutorialsMock.start.mockClear(); registryForSampleDataMock.setup.mockClear(); registryForSampleDataMock.start.mockClear(); + + homeServerPluginSetupDependenciesMock = { + customIntegrations: customIntegrationsMock.createSetup(), + }; + mockCoreSetup = coreMock.createSetup(); }); describe('setup', () => { - let mockCoreSetup: ReturnType; let initContext: ReturnType; let routerMock: ReturnType; beforeEach(() => { - mockCoreSetup = coreMock.createSetup(); routerMock = httpServiceMock.createRouter(); mockCoreSetup.http.createRouter.mockReturnValue(routerMock); initContext = coreMock.createPluginInitializerContext(); @@ -70,7 +76,9 @@ describe('HomeServerPlugin', () => { describe('start', () => { const initContext = coreMock.createPluginInitializerContext(); test('is defined', () => { - const start = new HomeServerPlugin(initContext).start(); + const plugin = new HomeServerPlugin(initContext); + plugin.setup(mockCoreSetup, homeServerPluginSetupDependenciesMock); // setup() must always be called before start() + const start = plugin.start(); expect(start).toBeDefined(); expect(start).toHaveProperty('tutorials'); expect(start).toHaveProperty('sampleData'); diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index d574867022654..a7b3f3f2e9714 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -48,7 +48,7 @@ export class HomeServerPlugin implements Plugin Date: Wed, 22 Sep 2021 16:13:11 -0400 Subject: [PATCH 24/31] dont merge in place --- .../sections/epm/screens/home/util.ts | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts index 00686c4b74ec4..2f0e4f71ea59d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/util.ts @@ -5,7 +5,10 @@ * 2.0. */ -import type { CustomIntegration } from '../../../../../../../../../../src/plugins/custom_integrations/common'; +import type { + CustomIntegration, + Category, +} from '../../../../../../../../../../src/plugins/custom_integrations/common'; import type { CategoryFacet } from './category_facets'; @@ -13,26 +16,35 @@ export function mergeAndReplaceCategoryCounts( eprCounts: CategoryFacet[], addableIntegrations: CustomIntegration[] ): CategoryFacet[] { - addableIntegrations.forEach((integration) => { - integration.categories.forEach((cat) => { - const match = eprCounts.find((c) => { - return c.id === cat; + const merged: CategoryFacet[] = []; + + const addIfMissing = (category: string, count: number) => { + const match = merged.find((c) => { + return c.id === category; + }); + + if (match) { + match.count += count; + } else { + merged.push({ + id: category as Category, + count, }); + } + }; - if (match) { - match.count += 1; - } else { - eprCounts.push({ - id: cat, - count: 1, - }); - } + eprCounts.forEach((facet) => { + addIfMissing(facet.id, facet.count); + }); + addableIntegrations.forEach((integration) => { + integration.categories.forEach((cat) => { + addIfMissing(cat, 1); }); }); - eprCounts.sort((a, b) => { + merged.sort((a, b) => { return a.id.localeCompare(b.id); }); - return eprCounts; + return merged; } From 77a571fa25f18e2d496a0a90a950ec963b4a9fde Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 22 Sep 2021 16:15:29 -0400 Subject: [PATCH 25/31] ignore ts error in test --- .../home/server/services/tutorials/tutorials_registry.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index 970241fc7e6bd..8db9ee74c3996 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -108,6 +108,7 @@ describe('TutorialsRegistry', () => { testProvider = ({}) => validTutorialProvider; expect(() => setup.registerTutorial(testProvider)).not.toThrowError(); + // @ts-expect-error expect(mockCustomIntegrationsPluginSetup.registerCustomIntegration.mock.calls).toEqual([ [ { From e1e4c639e6f0a05975082d96341a2397d06bcb68 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 23 Sep 2021 14:15:16 -0400 Subject: [PATCH 26/31] move badge-labels back inside card-component --- x-pack/plugins/fleet/common/types/models/epm.ts | 3 +-- .../sections/epm/components/package_card.stories.tsx | 6 +++--- .../sections/epm/components/package_card.tsx | 9 +++++++-- .../epm/components/package_list_grid.stories.tsx | 12 ++++++------ .../integrations/sections/epm/screens/home/index.tsx | 12 +----------- 5 files changed, 18 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 502edcf0303c2..371304765a5f8 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -363,8 +363,7 @@ export type PackageListItem = Installable & { export interface IntegrationCardItem { uiInternalPathUrl: string; - betaBadgeLabel?: string; - betaBadgeLabelTooltipContent?: string; + release?: 'beta' | 'experimental' | 'ga'; description: string; name: string; title: string; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx index 5116b82ed080c..69c70bba5be1d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx @@ -26,12 +26,12 @@ const args: Args = { title: 'Title', description: 'Description', name: 'beats', - // @ts-expect-error release: 'ga', id: 'id', version: '1.0.0', - download: '/', - path: 'path', + uiInternalPathUrl: '/', + icons: [], + integration: '', }; const argTypes = { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 7e7bc28115c70..8c7cd47e950f0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -12,6 +12,8 @@ import { EuiCard } from '@elastic/eui'; import { CardIcon } from '../../../../../components/package_icon'; import type { IntegrationCardItem } from '../../../../../../common/types/models/epm'; +import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from './release_badge'; + export type PackageCardProps = IntegrationCardItem; // adding the `href` causes EuiCard to use a `a` instead of a `button` @@ -28,9 +30,12 @@ export function PackageCard({ icons, integration, uiInternalPathUrl, - betaBadgeLabel, - betaBadgeLabelTooltipContent, + release, }: PackageCardProps) { + const betaBadgeLabel = release && release !== 'ga' ? RELEASE_BADGE_LABEL[release] : undefined; + const betaBadgeLabelTooltipContent = + release && release !== 'ga' ? RELEASE_BADGE_DESCRIPTION[release] : undefined; + return ( ( release: 'ga', id: 'id', version: '1.0.0', - download: '/', + uiInternalPath: '/', path: 'path', status: 'not_installed', }, @@ -79,7 +79,7 @@ export const List = (props: Args) => ( release: 'beta', id: 'id', version: '1.0.0', - download: '/', + uiInternalPath: '/', path: 'path', status: 'not_installed', }, @@ -90,7 +90,7 @@ export const List = (props: Args) => ( release: 'experimental', id: 'id', version: '1.0.0', - download: '/', + uiInternalPath: '/', path: 'path', status: 'not_installed', }, @@ -101,7 +101,7 @@ export const List = (props: Args) => ( release: 'ga', id: 'id', version: '1.0.0', - download: '/', + uiInternalPath: '/', path: 'path', status: 'installed', savedObject, @@ -113,7 +113,7 @@ export const List = (props: Args) => ( release: 'beta', id: 'id', version: '1.0.0', - download: '/', + uiInternalPath: '/', path: 'path', status: 'installed', savedObject, @@ -125,7 +125,7 @@ export const List = (props: Args) => ( release: 'experimental', id: 'id', version: '1.0.0', - download: '/', + uiInternalPath: '/', path: 'path', status: 'installed', savedObject, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index a59897fade7c4..669c7635b4f43 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -79,15 +79,6 @@ function mapToCard( uiInternalPathUrl = url; } - const betaBadgeLabel = - item.type !== 'ui_link' && item.release && item.release !== 'ga' - ? RELEASE_BADGE_LABEL[item.release] - : undefined; - const betaBadgeLabelTooltipContent = - item.type !== 'ui_link' && item.release && item.release !== 'ga' - ? RELEASE_BADGE_DESCRIPTION[item.release] - : undefined; - return { id: `${item.type === 'ui_link' ? 'ui_link' : 'epr'}-${item.id}`, description: item.description, @@ -96,9 +87,8 @@ function mapToCard( name: item.name, title: item.title, version: 'version' in item ? item.version || '' : '', + release: 'release' in item ? item.release : undefined, uiInternalPathUrl, - betaBadgeLabel, - betaBadgeLabelTooltipContent, }; } From cfc5c4d4e8b53417cc7716da1a5828e5a5606cee Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 23 Sep 2021 14:15:58 -0400 Subject: [PATCH 27/31] remove unused --- .../integrations/sections/epm/screens/home/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 669c7635b4f43..7ddf783580ed9 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -33,8 +33,6 @@ import type { CustomIntegration } from '../../../../../../../../../../src/plugin import type { PackageListItem } from '../../../../types'; -import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components/release_badge'; - import type { IntegrationCardItem } from '../../../../../../../common/types/models'; import type { Category } from '../../../../../../../../../../src/plugins/custom_integrations/common'; From 29e7028aa7bf7732800170f5480c753b9e50521f Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 23 Sep 2021 17:47:47 -0400 Subject: [PATCH 28/31] feedback --- src/plugins/home/kibana.json | 4 +-- src/plugins/home/server/plugin.ts | 7 +---- .../services/tutorials/tutorials_registry.ts | 27 +++++++++---------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/plugins/home/kibana.json b/src/plugins/home/kibana.json index 319af99061b9b..3f1916f3142ff 100644 --- a/src/plugins/home/kibana.json +++ b/src/plugins/home/kibana.json @@ -7,7 +7,7 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["data", "share", "urlForwarding", "customIntegrations"], - "optionalPlugins": ["usageCollection", "telemetry"], + "requiredPlugins": ["data", "share", "urlForwarding"], + "optionalPlugins": ["usageCollection", "telemetry", "customIntegrations"], "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index a7b3f3f2e9714..7c830dd8d5bc3 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -23,14 +23,13 @@ import { CustomIntegrationsPluginSetup } from '../../custom_integrations/server' export interface HomeServerPluginSetupDependencies { usageCollection?: UsageCollectionSetup; - customIntegrations: CustomIntegrationsPluginSetup; + customIntegrations?: CustomIntegrationsPluginSetup; } export class HomeServerPlugin implements Plugin { constructor(private readonly initContext: PluginInitializerContext) {} private readonly tutorialsRegistry = new TutorialsRegistry(); private readonly sampleDataRegistry = new SampleDataRegistry(this.initContext); - private customIntegrations: CustomIntegrationsPluginSetup | undefined = undefined; public setup(core: CoreSetup, plugins: HomeServerPluginSetupDependencies): HomeServerPluginSetup { core.capabilities.registerProvider(capabilitiesProvider); @@ -39,7 +38,6 @@ export class HomeServerPlugin implements Plugin { - return CATEGORY_DISPLAY.hasOwnProperty(category); - }) - : [] + const allowedCategories: Category[] = (tutorial.integrationBrowserCategories ?? []).filter( + (category) => { + return CATEGORY_DISPLAY.hasOwnProperty(category); + } ) as Category[]; customIntegrations.registerCustomIntegration({ @@ -57,7 +52,7 @@ export class TutorialsRegistry { private tutorialProviders: TutorialProvider[] = []; // pre-register all the tutorials we know we want in here private readonly scopedTutorialContextFactories: TutorialContextFactory[] = []; - public setup(core: CoreSetup, customIntegrations: CustomIntegrationsPluginSetup) { + public setup(core: CoreSetup, customIntegrations?: CustomIntegrationsPluginSetup) { const router = core.http.createRouter(); router.get( { path: '/api/kibana/home/tutorials', validate: false }, @@ -78,13 +73,17 @@ export class TutorialsRegistry { ); return { registerTutorial: (specProvider: TutorialProvider) => { + const emptyContext = {}; + let tutorial: TutorialSchema; try { - tutorialSchema.validate(specProvider(emptyContext)); + tutorial = tutorialSchema.validate(specProvider(emptyContext)); } catch (error) { throw new Error(`Unable to register tutorial spec because its invalid. ${error}`); } - registerTutorialWithCustomIntegrations(customIntegrations, specProvider); + if (customIntegrations && tutorial) { + registerTutorialWithCustomIntegrations(customIntegrations, tutorial); + } this.tutorialProviders.push(specProvider); }, From e9e769ef9b79006a968f4d61f325c91e4cdc8166 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 23 Sep 2021 18:01:17 -0400 Subject: [PATCH 29/31] remove cruft --- src/plugins/custom_integrations/common/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index 2facefb47340d..a9d9a0e86824e 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -14,7 +14,6 @@ export interface CategoryCount { id: Category; } -// todo internationalize export const CATEGORY_DISPLAY = { aws: 'AWS', azure: 'Azure', From d73f38dec5e9f1440c9fd7dbc412931705612fe5 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 23 Sep 2021 18:43:48 -0400 Subject: [PATCH 30/31] add jest boilerplate --- .../custom_integrations/common/index.ts | 1 - .../custom_integrations/jest.config.js | 17 ++++ .../custom_integration_registry.test.ts | 98 +++++++++++++++++++ .../server/custom_integration_registry.ts | 4 +- .../services/tutorials/tutorials_registry.ts | 1 - .../sections/epm/screens/home/index.tsx | 2 +- 6 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/plugins/custom_integrations/jest.config.js create mode 100644 src/plugins/custom_integrations/server/custom_integration_registry.test.ts diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index a9d9a0e86824e..380f21d6ee0d2 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -49,7 +49,6 @@ export type Category = keyof typeof CATEGORY_DISPLAY; export interface CustomIntegration { id: string; title: string; - name: string; description: string; type: 'ui_link'; uiInternalPath: string; diff --git a/src/plugins/custom_integrations/jest.config.js b/src/plugins/custom_integrations/jest.config.js new file mode 100644 index 0000000000000..15aed3f602073 --- /dev/null +++ b/src/plugins/custom_integrations/jest.config.js @@ -0,0 +1,17 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/custom_integrations'], + testRunner: 'jasmine2', + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/custom_integrations', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/src/plugins/data/{common,public,server}/**/*.{ts,tsx}'], +}; diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.test.ts b/src/plugins/custom_integrations/server/custom_integration_registry.test.ts new file mode 100644 index 0000000000000..5c4bfaa51d204 --- /dev/null +++ b/src/plugins/custom_integrations/server/custom_integration_registry.test.ts @@ -0,0 +1,98 @@ +/* + * 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 { CustomIntegrationRegistry } from './custom_integration_registry'; +import { loggerMock, MockedLogger } from '@kbn/logging/mocks'; +import { CustomIntegration } from '../common'; + +describe('CustomIntegrationsRegistry', () => { + let mockLogger: MockedLogger; + + const integration: CustomIntegration = { + id: 'foo', + title: 'Foo', + description: 'test integration', + type: 'ui_link', + uiInternalPath: '/path/to/foo', + isBeta: false, + icons: [], + categories: ['upload_file'], + shipper: 'tests', + }; + + beforeEach(() => { + mockLogger = loggerMock.create(); + }); + + describe('register', () => { + describe('should log to console on duplicate id', () => { + test('with an error in dev', () => { + const registry = new CustomIntegrationRegistry(mockLogger, true); + registry.registerCustomIntegration(integration); + registry.registerCustomIntegration(integration); + expect(mockLogger.error.mock.calls.length).toBe(1); + }); + test('with a debug in prod', () => { + const registry = new CustomIntegrationRegistry(mockLogger, false); + registry.registerCustomIntegration(integration); + registry.registerCustomIntegration(integration); + expect(mockLogger.debug.mock.calls.length).toBe(1); + }); + }); + }); + + describe('getAppendCustomCategories', () => { + test('should return', () => { + const registry = new CustomIntegrationRegistry(mockLogger, true); + registry.registerCustomIntegration(integration); + registry.registerCustomIntegration({ ...integration, id: 'bar' }); + expect(registry.getAppendCustomIntegrations()).toEqual([ + { + categories: ['upload_file'], + description: 'test integration', + icons: [], + id: 'foo', + isBeta: false, + shipper: 'tests', + title: 'Foo', + type: 'ui_link', + uiInternalPath: '/path/to/foo', + }, + { + categories: ['upload_file'], + description: 'test integration', + icons: [], + id: 'bar', + isBeta: false, + shipper: 'tests', + title: 'Foo', + type: 'ui_link', + uiInternalPath: '/path/to/foo', + }, + ]); + }); + test('should ignore duplicate ids', () => { + const registry = new CustomIntegrationRegistry(mockLogger, true); + registry.registerCustomIntegration(integration); + registry.registerCustomIntegration(integration); + expect(registry.getAppendCustomIntegrations()).toEqual([ + { + categories: ['upload_file'], + description: 'test integration', + icons: [], + id: 'foo', + isBeta: false, + shipper: 'tests', + title: 'Foo', + type: 'ui_link', + uiInternalPath: '/path/to/foo', + }, + ]); + }); + }); +}); diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.ts b/src/plugins/custom_integrations/server/custom_integration_registry.ts index 82e5fb8f135de..fa216ced5bd92 100644 --- a/src/plugins/custom_integrations/server/custom_integration_registry.ts +++ b/src/plugins/custom_integrations/server/custom_integration_registry.ts @@ -27,10 +27,10 @@ export class CustomIntegrationRegistry { registerCustomIntegration(customIntegration: CustomIntegration) { if ( this._integrations.some((integration: CustomIntegration) => { - return integration.name === customIntegration.name; + return integration.id === customIntegration.id; }) ) { - const message = `Integration with id=${customIntegration.name} already exists.`; + const message = `Integration with id=${customIntegration.id} already exists.`; if (this._isDev) { this._logger.error(message); } else { diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index cb0578ba76281..8f7ecd7d7ccf5 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -29,7 +29,6 @@ function registerTutorialWithCustomIntegrations( ) as Category[]; customIntegrations.registerCustomIntegration({ - name: tutorial.name, id: tutorial.id, title: tutorial.name, categories: allowedCategories, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 7ddf783580ed9..a8f8683d989ed 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -82,7 +82,7 @@ function mapToCard( description: item.description, icons: !item.icons || !item.icons.length ? [] : item.icons, integration: 'integration' in item ? item.integration || '' : '', - name: item.name, + name: 'name' in item ? item.name || '' : '', title: item.title, version: 'version' in item ? item.version || '' : '', release: 'release' in item ? item.release : undefined, From 7fddfb754d3e28ad8aa075c77f9a9a42b385a552 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Fri, 24 Sep 2021 16:07:54 -0400 Subject: [PATCH 31/31] Fill in test coverage --- .../custom_integrations/common/index.ts | 2 +- .../custom_integrations/public/index.ts | 4 +-- .../custom_integrations/public/plugin.test.ts | 28 +++++++++++++++++ .../custom_integrations/public/plugin.ts | 2 +- .../custom_integration_registry.test.ts | 19 ++++++++++++ .../custom_integrations/server/plugin.test.ts | 31 +++++++++++++++++++ .../tutorials/tutorials_registry.test.ts | 1 - .../apis/custom_integration/index.ts | 15 +++++++++ .../apis/custom_integration/integrations.ts | 26 ++++++++++++++++ test/api_integration/apis/index.ts | 1 + .../sections/epm/screens/home/index.tsx | 2 +- 11 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 src/plugins/custom_integrations/public/plugin.test.ts create mode 100644 src/plugins/custom_integrations/server/plugin.test.ts create mode 100644 test/api_integration/apis/custom_integration/index.ts create mode 100644 test/api_integration/apis/custom_integration/integrations.ts diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index 380f21d6ee0d2..24ed44f3e5cfe 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -58,4 +58,4 @@ export interface CustomIntegration { shipper: string; } -export const ROUTES_ADDABLECUSTOMINTEGRATIONS = `/api/${PLUGIN_ID}/addableCustomIntegrations`; +export const ROUTES_ADDABLECUSTOMINTEGRATIONS = `/api/${PLUGIN_ID}/appendCustomIntegrations`; diff --git a/src/plugins/custom_integrations/public/index.ts b/src/plugins/custom_integrations/public/index.ts index 683f498cf3907..9e979dd6692bc 100755 --- a/src/plugins/custom_integrations/public/index.ts +++ b/src/plugins/custom_integrations/public/index.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { CustomIntegrationPlugin } from './plugin'; +import { CustomIntegrationsPlugin } from './plugin'; // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. export function plugin() { - return new CustomIntegrationPlugin(); + return new CustomIntegrationsPlugin(); } export { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; diff --git a/src/plugins/custom_integrations/public/plugin.test.ts b/src/plugins/custom_integrations/public/plugin.test.ts new file mode 100644 index 0000000000000..32f25754fe8e0 --- /dev/null +++ b/src/plugins/custom_integrations/public/plugin.test.ts @@ -0,0 +1,28 @@ +/* + * 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 { CustomIntegrationsPlugin } from './plugin'; + +import { coreMock } from '../../../core/public/mocks'; + +describe('CustomIntegrationsPlugin', () => { + beforeEach(() => {}); + + describe('setup', () => { + let mockCoreSetup: ReturnType; + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + }); + + test('wires up tutorials provider service and returns registerTutorial and addScopedTutorialContextFactory', () => { + const setup = new CustomIntegrationsPlugin().setup(mockCoreSetup); + expect(setup).toHaveProperty('getAppendCustomIntegrations'); + }); + }); +}); diff --git a/src/plugins/custom_integrations/public/plugin.ts b/src/plugins/custom_integrations/public/plugin.ts index fdd4daad94ea6..821c08ce84e31 100755 --- a/src/plugins/custom_integrations/public/plugin.ts +++ b/src/plugins/custom_integrations/public/plugin.ts @@ -10,7 +10,7 @@ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { CustomIntegrationsSetup, CustomIntegrationsStart } from './types'; import { CustomIntegration, ROUTES_ADDABLECUSTOMINTEGRATIONS } from '../common'; -export class CustomIntegrationPlugin +export class CustomIntegrationsPlugin implements Plugin { public setup(core: CoreSetup): CustomIntegrationsSetup { diff --git a/src/plugins/custom_integrations/server/custom_integration_registry.test.ts b/src/plugins/custom_integrations/server/custom_integration_registry.test.ts index 5c4bfaa51d204..2e211cfb4c93d 100644 --- a/src/plugins/custom_integrations/server/custom_integration_registry.test.ts +++ b/src/plugins/custom_integrations/server/custom_integration_registry.test.ts @@ -94,5 +94,24 @@ describe('CustomIntegrationsRegistry', () => { }, ]); }); + test('should ignore integrations without category', () => { + const registry = new CustomIntegrationRegistry(mockLogger, true); + registry.registerCustomIntegration(integration); + registry.registerCustomIntegration({ ...integration, id: 'bar', categories: [] }); + + expect(registry.getAppendCustomIntegrations()).toEqual([ + { + categories: ['upload_file'], + description: 'test integration', + icons: [], + id: 'foo', + isBeta: false, + shipper: 'tests', + title: 'Foo', + type: 'ui_link', + uiInternalPath: '/path/to/foo', + }, + ]); + }); }); }); diff --git a/src/plugins/custom_integrations/server/plugin.test.ts b/src/plugins/custom_integrations/server/plugin.test.ts new file mode 100644 index 0000000000000..08f68a70a3c70 --- /dev/null +++ b/src/plugins/custom_integrations/server/plugin.test.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 { CustomIntegrationsPlugin } from './plugin'; + +import { coreMock } from '../../../core/server/mocks'; + +describe('CustomIntegrationsPlugin', () => { + beforeEach(() => {}); + + describe('setup', () => { + let mockCoreSetup: ReturnType; + let initContext: ReturnType; + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + initContext = coreMock.createPluginInitializerContext(); + }); + + test('wires up tutorials provider service and returns registerTutorial and addScopedTutorialContextFactory', () => { + const setup = new CustomIntegrationsPlugin(initContext).setup(mockCoreSetup); + expect(setup).toHaveProperty('registerCustomIntegration'); + expect(setup).toHaveProperty('getAppendCustomIntegrations'); + }); + }); +}); diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index 8db9ee74c3996..5c7ec0a3382bf 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -112,7 +112,6 @@ describe('TutorialsRegistry', () => { expect(mockCustomIntegrationsPluginSetup.registerCustomIntegration.mock.calls).toEqual([ [ { - name: 'new tutorial provider', id: 'test', title: 'new tutorial provider', categories: [], diff --git a/test/api_integration/apis/custom_integration/index.ts b/test/api_integration/apis/custom_integration/index.ts new file mode 100644 index 0000000000000..d3d34fc3ccfce --- /dev/null +++ b/test/api_integration/apis/custom_integration/index.ts @@ -0,0 +1,15 @@ +/* + * 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 ({ loadTestFile }: FtrProviderContext) { + describe('custom integrations', () => { + loadTestFile(require.resolve('./integrations')); + }); +} diff --git a/test/api_integration/apis/custom_integration/integrations.ts b/test/api_integration/apis/custom_integration/integrations.ts new file mode 100644 index 0000000000000..d8f098fdc1fcf --- /dev/null +++ b/test/api_integration/apis/custom_integration/integrations.ts @@ -0,0 +1,26 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('get list of append integrations', () => { + it('should return list of custom integrations that can be appended', async () => { + const resp = await supertest + .get(`/api/customIntegrations/appendCustomIntegrations`) + .set('kbn-xsrf', 'kibana') + .expect(200); + + expect(resp.body).to.be.an('array'); + expect(resp.body.length).to.be.above(0); + }); + }); +} diff --git a/test/api_integration/apis/index.ts b/test/api_integration/apis/index.ts index 998c0b834d224..a6b8b746f68cf 100644 --- a/test/api_integration/apis/index.ts +++ b/test/api_integration/apis/index.ts @@ -12,6 +12,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('apis', () => { loadTestFile(require.resolve('./console')); loadTestFile(require.resolve('./core')); + loadTestFile(require.resolve('./custom_integration')); loadTestFile(require.resolve('./general')); loadTestFile(require.resolve('./home')); loadTestFile(require.resolve('./index_pattern_field_editor')); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index a8f8683d989ed..48a9dc0f6b63c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -300,7 +300,7 @@ const AvailablePackages: React.FC = memo(() => { ...filteredAddableIntegrations, ]; eprAndCustomPackages.sort((a, b) => { - return a.name.localeCompare(b.name); + return a.title.localeCompare(b.title); }); const categories = useMemo(() => {