Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Add custom integrations API #112481

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b87be67
initial boilerplate
thomasneirynck Sep 16, 2021
5ca5307
setup dependency in fleet
thomasneirynck Sep 16, 2021
c6175ad
add custom cards to UX (boilerplate)
thomasneirynck Sep 20, 2021
46a67e5
remove cruft
thomasneirynck Sep 21, 2021
3844e8a
isolate epr from cards
thomasneirynck Sep 21, 2021
a40cee6
make cards generic
thomasneirynck Sep 21, 2021
950a9a2
remove epr-typing from cards
thomasneirynck Sep 21, 2021
4bc93e0
remove epr tpying from card-lists
thomasneirynck Sep 21, 2021
5c4258e
more typing
thomasneirynck Sep 21, 2021
4d0a763
Merge branch 'master' of github.com:elastic/kibana into fleet/add_cus…
thomasneirynck Sep 21, 2021
df06051
more type fixes
thomasneirynck Sep 21, 2021
fc0ed69
Fix icon issue
thomasneirynck Sep 21, 2021
9f217dd
Merge branch 'master' of github.com:elastic/kibana into fleet/add_cus…
thomasneirynck Sep 21, 2021
2d459d2
resolve a few CI checks
thomasneirynck Sep 22, 2021
3e9e58d
test
thomasneirynck Sep 22, 2021
0a21901
fix linting with new settings
thomasneirynck Sep 22, 2021
d0ba72a
remove faulty import
thomasneirynck Sep 22, 2021
f41f8fc
feedback
thomasneirynck Sep 22, 2021
85753fd
feedback
thomasneirynck Sep 22, 2021
04fe290
rename for clarity
thomasneirynck Sep 22, 2021
e155137
fix typo
thomasneirynck Sep 22, 2021
25c43dc
update plugin manifest
thomasneirynck Sep 22, 2021
69d05b8
home tests
thomasneirynck Sep 22, 2021
6df5f7b
fleet mocks
thomasneirynck Sep 22, 2021
c05ff99
home setup/start test
thomasneirynck Sep 22, 2021
f9b6164
dont merge in place
thomasneirynck Sep 22, 2021
77a571f
ignore ts error in test
thomasneirynck Sep 22, 2021
e1e4c63
move badge-labels back inside card-component
thomasneirynck Sep 23, 2021
cfc5c4d
remove unused
thomasneirynck Sep 23, 2021
e96bc0b
Merge branch 'master' of github.com:elastic/kibana into fleet/add_cus…
thomasneirynck Sep 23, 2021
29e7028
feedback
thomasneirynck Sep 23, 2021
e9e769e
remove cruft
thomasneirynck Sep 23, 2021
d73f38d
add jest boilerplate
thomasneirynck Sep 23, 2021
7fddfb7
Fill in test coverage
thomasneirynck Sep 24, 2021
6d63a02
Merge branch 'master' of github.com:elastic/kibana into fleet/add_cus…
thomasneirynck Sep 24, 2021
0450645
Merge branch 'master' of github.com:elastic/kibana into fleet/add_cus…
thomasneirynck Sep 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

|<<kibana-dashboard-plugin>>
|- Registers the dashboard application.
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,4 @@ pageLoadAssetSize:
expressionTagcloud: 27505
expressions: 239290
securitySolution: 231753
customIntegrations: 28810
9 changes: 9 additions & 0 deletions src/plugins/custom_integrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# customIntegrations
Copy link
Contributor

Choose a reason for hiding this comment

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

The docs will need fleshing out and conversion to .mdx if we want to include them in the new dev docs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thx. my preference is to hold off on dev-docs in this PR. These will evolve fairly quickly. Would prefer to introduce only in separate PR when contract stabilizes.


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.
65 changes: 65 additions & 0 deletions src/plugins/custom_integrations/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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;
}

// todo internationalize
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
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',

updates_available: 'Updates available',
};

export type Category = keyof typeof CATEGORY_DISPLAY;

export interface CustomIntegration {
id: string;
title: string;
name: string;
description: string;
type: 'ui_link';
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
uiInternalPath: string;
isBeta: boolean;
icons: Array<{ src: string; type: string }>;
categories: Category[];
shipper: string;
eprPackageOverlap?: string;
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
}

export const ROUTES_ADDABLECUSTOMINTEGRATIONS = `/api/${PLUGIN_ID}/addableCustomIntegrations`;
export const ROUTES_REPLACEABLECUSTOMINMTEGRATIONS = `/api/${PLUGIN_ID}/replaceableCustomIntegrations`;
16 changes: 16 additions & 0 deletions src/plugins/custom_integrations/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "customIntegrations",
"version": "1.0.0",
"kibanaVersion": "kibana",
"owner": {
"name": "platform",
"githubTeam": ""
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
},
"description": "test",
"ui": true,
"server": true,
"extraPublicDirs": [
"common"
],
"optionalPlugins": []
}
16 changes: 16 additions & 0 deletions src/plugins/custom_integrations/public/index.ts
Original file line number Diff line number Diff line change
@@ -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';
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
36 changes: 36 additions & 0 deletions src/plugins/custom_integrations/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -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<CustomIntegrationsSetup, CustomIntegrationsStart> {
public setup(core: CoreSetup): CustomIntegrationsSetup {
// Return methods that should be available to other plugins
return {
async getAddableCustomIntegrations(): Promise<CustomIntegration[]> {
return core.http.get(ROUTES_ADDABLECUSTOMINTEGRATIONS);
},
async getReplaceableCustomIntegrations(): Promise<CustomIntegration[]> {
return core.http.get(ROUTES_REPLACEABLECUSTOMINMTEGRATIONS);
},
} as CustomIntegrationsSetup;
}

public start(core: CoreStart): CustomIntegrationsStart {
return {};
}

public stop() {}
}
19 changes: 19 additions & 0 deletions src/plugins/custom_integrations/public/types.ts
Original file line number Diff line number Diff line change
@@ -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<CustomIntegration[]>;
getAddableCustomIntegrations: () => Promise<CustomIntegration[]>;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomIntegrationsStart {}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface AppPluginStartDependencies {}
Original file line number Diff line number Diff line change
@@ -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.`);
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
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<Category, number> = new Map<Category, number>();
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;
}
}
21 changes: 21 additions & 0 deletions src/plugins/custom_integrations/server/index.ts
Original file line number Diff line number Diff line change
@@ -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';
55 changes: 55 additions & 0 deletions src/plugins/custom_integrations/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -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<CustomIntegrationsPluginSetup, CustomIntegrationsPluginStart> {
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() {}
}
45 changes: 45 additions & 0 deletions src/plugins/custom_integrations/server/routes/define_routes.ts
Original file line number Diff line number Diff line change
@@ -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,
});
}
);
}
19 changes: 19 additions & 0 deletions src/plugins/custom_integrations/server/types.ts
Original file line number Diff line number Diff line change
@@ -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[];
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomIntegrationsPluginStart {}
Loading