-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add notifications plugin, offering basic email service (#143303)
* Misc enhancements following PR comments * Adding functional tests * Fixing types * Fixing tests * Removing unnecessary Promise.all * Cleanup * Misc fixes and simplifications * Add missing tsconfig.json * [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' * Add dependency to Actions plugin in tsconfig.json * Separate setup logic from start logic * Fix bulkEnqueueExecution params structure * Update README * Add UTs * Check license type >platinum for email notifications * Fix incorrect UTs * Import types when possible * Misc enhancements and code cleanup * Transform factory => provider, update start contract * Code cleanup, update README * Fix TS error * Fix CI types error * Address PR remarks * Address PR remarks #2 Co-authored-by: Ying Mao <[email protected]> Co-authored-by: kibanamachine <[email protected]>
- Loading branch information
1 parent
e5271bd
commit 8881539
Showing
27 changed files
with
1,108 additions
and
5 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
// <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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* 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 NotificationsConfigType = TypeOf<typeof configSchema>; | ||
|
||
export const config: PluginConfigDescriptor<NotificationsConfigType> = { | ||
schema: configSchema, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* 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 { PublicMethodsOf } from '@kbn/utility-types'; | ||
import type { EmailService } from './services'; | ||
import type { NotificationsPluginStart } from './types'; | ||
import type { NotificationsPlugin } from './plugin'; | ||
|
||
const emailServiceMock: jest.Mocked<EmailService> = { | ||
sendPlainTextEmail: jest.fn(), | ||
}; | ||
|
||
const createEmailServiceMock = () => { | ||
return emailServiceMock; | ||
}; | ||
|
||
const startMock: jest.Mocked<NotificationsPluginStart> = { | ||
isEmailServiceAvailable: jest.fn(), | ||
getEmailService: jest.fn(createEmailServiceMock), | ||
}; | ||
|
||
const createStartMock = () => { | ||
return startMock; | ||
}; | ||
|
||
const notificationsPluginMock: jest.Mocked<PublicMethodsOf<NotificationsPlugin>> = { | ||
setup: jest.fn(), | ||
start: jest.fn(createStartMock) as jest.Mock<NotificationsPluginStart>, | ||
stop: jest.fn(), | ||
}; | ||
|
||
const createNotificationsPluginMock = () => { | ||
return notificationsPluginMock; | ||
}; | ||
|
||
export const notificationsMock = { | ||
createNotificationsPlugin: createNotificationsPluginMock, | ||
createEmailService: createEmailServiceMock, | ||
createStart: createStartMock, | ||
clear: () => { | ||
emailServiceMock.sendPlainTextEmail.mockClear(); | ||
startMock.getEmailService.mockClear(); | ||
startMock.isEmailServiceAvailable.mockClear(); | ||
notificationsPluginMock.setup.mockClear(); | ||
notificationsPluginMock.start.mockClear(); | ||
notificationsPluginMock.stop.mockClear(); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.