-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Add notifications plugin, offering basic email service #143303
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
Changes from 51 commits
2a93a37
dc9829e
2fe5ce5
dbed0a8
4af9bc6
c3ce0b1
00c611d
b0fba11
baf051a
96b696f
6cf7a3a
74aa62d
fa4a356
8865a80
7c884ae
f4c1851
3b7a388
4b7f482
9a58040
47f1ca9
c87baee
28c6632
db2dc2a
d5bf4ff
026646a
ea31342
802dfa0
c048225
f149cac
cd500db
a293085
ff65387
e241d0d
0079193
7f358bf
8929996
fd4b2ef
c344a61
b02bd81
53575d9
52e7c8c
f214324
e0dd0f7
8218cc4
0ccf326
a561085
2d26740
3ed6752
9dbe577
0238877
06ff938
dc4c9a6
2654fee
1467966
091eb74
d6e46f5
8d026c5
f275913
56f99c9
3aef92a
439b3e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # Kibana Notifications Plugin | ||
|
|
||
| The Notifications plugin provides a set of services to help Solutions and plugins send notifications to users. | ||
|
|
||
| ## Notifications Plugin public API | ||
|
|
||
| ### Start | ||
|
|
||
| The `start` function exposes the following interface: | ||
|
|
||
| - `isEmailServiceAvailable(): boolean`: | ||
| A function to check whether the deployment is properly configured and the EmailService can be correctly retrieved. | ||
| - `getEmailService(): EmailService`: | ||
| - A function to get the basic EmailService, which can be used to send plain text emails. If the EmailService is not available, trying to retrieve it will result in an Exception. | ||
|
|
||
|
|
||
| ### Usage | ||
|
|
||
| To use the exposed plugin start contract: | ||
|
|
||
| 1. Make sure `notifications` is in your `optionalPlugins` in the `kibana.json` file: | ||
|
|
||
| ```json5 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, TIL: |
||
| // <plugin>/kibana.json | ||
| { | ||
| "id": "...", | ||
| "requiredPlugins": ["notifications"] | ||
| } | ||
| ``` | ||
|
|
||
| 2. Use the exposed contract: | ||
|
|
||
| ```ts | ||
| // <plugin>/server/plugin.ts | ||
| import { NotificationsPluginStart } from '../notifications/server`; | ||
|
|
||
| interface MyPluginStartDeps { | ||
| notifications?: NotificationsPluginStart; | ||
| } | ||
|
|
||
| class MyPlugin { | ||
| public start( | ||
| core: CoreStart, | ||
| { notifications }: MyPluginStartDeps | ||
| ) { | ||
| if (notifications.isEmailServiceAvailable()) { | ||
| const emailService = notifications.getEmailService(); | ||
| emailService.sendPlainTextEmail({ | ||
| to: '[email protected]', | ||
| subject: 'Some subject', | ||
| message: 'Hello world!', | ||
| }); | ||
| } | ||
| ... | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Requirements | ||
|
|
||
| - This plugin currently depends on the `'actions'` plugin, as it uses `Connectors` under the hood. | ||
| - Note also that for each notification channel the corresponding connector must be preconfigured. E.g. to enable email notifications, an `Email` connector must exist in the system. | ||
| - Once the appropriate connectors are preconfigured in `kibana.yaml`, you can configure the `'notifications'` plugin by adding: | ||
|
|
||
| ```yaml | ||
| notifications: | ||
| connectors: | ||
| default: | ||
| email: elastic-cloud-email # The identifier of the configured connector | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /* | ||
| * 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. | ||
| */ | ||
|
|
||
| export const PLUGIN_ID = 'notifications'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| module.exports = { | ||
| preset: '@kbn/test/jest_node', | ||
| rootDir: '../../..', | ||
| roots: ['<rootDir>/x-pack/plugins/notifications'], | ||
| coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/notifications', | ||
| coverageReporters: ['text', 'html'], | ||
| collectCoverageFrom: ['<rootDir>/x-pack/plugins/notifications/{common,server}/**/*.{js,ts,tsx}'], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "id": "notifications", | ||
| "owner": { | ||
| "name": "App Services", | ||
| "githubTeam": "kibana-app-services" | ||
| }, | ||
| "version": "kibana", | ||
| "server": true, | ||
| "ui": false, | ||
| "requiredPlugins": ["actions", "licensing"], | ||
| "optionalPlugins": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import type { PluginConfigDescriptor } from '@kbn/core/server'; | ||
| import { configSchema, type ConnectorsEmailConfigType } from './connectors_email_config'; | ||
|
|
||
| export type NotificationsConfigType = ConnectorsEmailConfigType; | ||
|
|
||
| export const config: PluginConfigDescriptor<ConnectorsEmailConfigType> = { | ||
| schema: configSchema, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * 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 { schema, type TypeOf } from '@kbn/config-schema'; | ||
| import type { PluginConfigDescriptor } from '@kbn/core/server'; | ||
|
|
||
| export const configSchema = schema.object( | ||
| { | ||
| connectors: schema.maybe( | ||
| schema.object({ | ||
| default: schema.maybe( | ||
| schema.object({ | ||
| email: schema.maybe(schema.string()), | ||
| }) | ||
| ), | ||
| }) | ||
| ), | ||
| }, | ||
| { defaultValue: {} } | ||
| ); | ||
|
|
||
| export type ConnectorsEmailConfigType = TypeOf<typeof configSchema>; | ||
|
|
||
| export const config: PluginConfigDescriptor<ConnectorsEmailConfigType> = { | ||
| schema: configSchema, | ||
| }; | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /* | ||
| * 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. | ||
| */ | ||
|
|
||
| export { type NotificationsConfigType, config } from './config'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import type { PluginInitializerContext } from '@kbn/core/server'; | ||
| import { NotificationsPlugin } from './plugin'; | ||
| export { config } from './config'; | ||
|
|
||
| // This exports static code and TypeScript types, | ||
| // as well as, Kibana Platform `plugin()` initializer. | ||
| export type { NotificationsPluginStart } from './types'; | ||
|
|
||
| export function plugin(initializerContext: PluginInitializerContext) { | ||
| return new NotificationsPlugin(initializerContext); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| /* | ||
| * 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 { coreMock } from '@kbn/core/server/mocks'; | ||
| import { actionsMock } from '@kbn/actions-plugin/server/mocks'; | ||
| import type { NotificationsConfigType } from './config'; | ||
| import { NotificationsPlugin } from './plugin'; | ||
| import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; | ||
| import { EmailServiceProvider } from './services/connectors_email_service_provider'; | ||
| import { EmailServiceStart } from './services'; | ||
|
|
||
| jest.mock('./services/connectors_email_service_provider'); | ||
|
|
||
| const emailServiceProviderMock = EmailServiceProvider as jest.MockedClass< | ||
| typeof EmailServiceProvider | ||
| >; | ||
|
|
||
| const validConnectorConfig = { | ||
| connectors: { | ||
| default: { | ||
| email: 'validConnectorId', | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| const createNotificationsPlugin = (config: NotificationsConfigType) => { | ||
| const context = coreMock.createPluginInitializerContext<NotificationsConfigType>(config); | ||
| const plugin = new NotificationsPlugin(context); | ||
| const coreSetup = coreMock.createSetup(); | ||
| const coreStart = coreMock.createStart(); | ||
|
|
||
| const actionsSetup = actionsMock.createSetup(); | ||
| actionsSetup.isPreconfiguredConnector.mockImplementationOnce( | ||
| (connectorId) => connectorId === 'validConnectorId' | ||
| ); | ||
| const pluginSetup = { | ||
| actions: actionsSetup, | ||
| licensing: licensingMock.createSetup(), | ||
| }; | ||
|
|
||
| const actionsStart = actionsMock.createStart(); | ||
| const pluginStart = { | ||
| actions: actionsStart, | ||
| licensing: licensingMock.createStart(), | ||
| }; | ||
|
|
||
| return { | ||
| context, | ||
| logger: context.logger.get(), | ||
| plugin, | ||
| coreSetup, | ||
| coreStart, | ||
| actionsSetup, | ||
| pluginSetup, | ||
| actionsStart, | ||
| pluginStart, | ||
| }; | ||
| }; | ||
|
|
||
| describe('Notifications Plugin', () => { | ||
| beforeEach(() => emailServiceProviderMock.mockClear()); | ||
|
|
||
| it('should create an EmailServiceProvider passing in the configuration and logger from the initializer context', () => { | ||
| const { logger } = createNotificationsPlugin(validConnectorConfig); | ||
| expect(emailServiceProviderMock).toHaveBeenCalledTimes(1); | ||
| expect(emailServiceProviderMock).toHaveBeenCalledWith(validConnectorConfig, logger); | ||
| }); | ||
|
|
||
| describe('setup()', () => { | ||
| it('should call setup() on the created EmailServiceProvider, passing in the setup plugin dependencies', () => { | ||
| const { plugin, coreSetup, pluginSetup } = createNotificationsPlugin(validConnectorConfig); | ||
| plugin.setup(coreSetup, pluginSetup); | ||
| expect(emailServiceProviderMock.mock.instances[0].setup).toHaveBeenCalledTimes(1); | ||
| expect(emailServiceProviderMock.mock.instances[0].setup).toBeCalledWith(pluginSetup); | ||
| }); | ||
| }); | ||
|
|
||
| describe('start()', () => { | ||
| it('should call start() on the created EmailServiceProvider, passing in the setup plugin dependencies', () => { | ||
| const { plugin, coreStart, pluginStart } = createNotificationsPlugin(validConnectorConfig); | ||
| plugin.start(coreStart, pluginStart); | ||
| expect(emailServiceProviderMock.mock.instances[0].start).toHaveBeenCalledTimes(1); | ||
| expect(emailServiceProviderMock.mock.instances[0].start).toBeCalledWith(pluginStart); | ||
| }); | ||
|
|
||
| it('should return EmailServiceProvider.start() contract as part of its contract', () => { | ||
| const { plugin, coreStart, pluginStart } = createNotificationsPlugin(validConnectorConfig); | ||
|
|
||
| const emailStart: EmailServiceStart = { | ||
| getEmailService: jest.fn(), | ||
| isEmailServiceAvailable: jest.fn(), | ||
| }; | ||
|
|
||
| const providerMock = emailServiceProviderMock.mock | ||
| .instances[0] as jest.Mocked<EmailServiceProvider>; | ||
| providerMock.start.mockReturnValue(emailStart); | ||
| const start = plugin.start(coreStart, pluginStart); | ||
| expect(emailServiceProviderMock.mock.instances[0].start).toHaveBeenCalledTimes(1); | ||
| expect(emailServiceProviderMock.mock.instances[0].start).toBeCalledWith(pluginStart); | ||
| expect(start).toEqual(expect.objectContaining(emailStart)); | ||
| }); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.