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

Add notifications plugin, offering basic email service #143303

Merged
merged 61 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2a93a37
Adding unsecured actions client
ymao1 Oct 13, 2022
dc9829e
Merge remote-tracking branch 'remotes/ymao/actions/unsecured-client' …
gsoldevila Oct 13, 2022
2fe5ce5
Removing isESOCanEncrypt check
ymao1 Oct 13, 2022
dbed0a8
Only getting actions client when needed in executor
ymao1 Oct 13, 2022
4af9bc6
Merge remote-tracking branch 'remotes/ymao/actions/unsecured-client' …
gsoldevila Oct 13, 2022
c3ce0b1
First draft
gsoldevila Oct 13, 2022
00c611d
Changing to feature id allowlist. Adding unit tests
ymao1 Oct 14, 2022
b0fba11
Merge branch 'main' of https://github.com/elastic/kibana into actions…
ymao1 Oct 14, 2022
baf051a
Removing execution id
ymao1 Oct 14, 2022
96b696f
Cleanup
ymao1 Oct 14, 2022
6cf7a3a
Fixing unit tests
ymao1 Oct 14, 2022
74aa62d
Merge remote-tracking branch 'remotes/ymao/actions/unsecured-client' …
gsoldevila Oct 17, 2022
fa4a356
Merge branch 'main' of https://github.com/elastic/kibana into actions…
ymao1 Oct 17, 2022
8865a80
Removing slack from allowlist
ymao1 Oct 17, 2022
7c884ae
Make getUnsecuredActionsClient synchronous
ymao1 Oct 17, 2022
f4c1851
Add comment
ymao1 Oct 17, 2022
3b7a388
Merge remote-tracking branch 'remotes/ymao/actions/unsecured-client' …
gsoldevila Oct 17, 2022
4b7f482
Misc enhancements following PR comments
gsoldevila Oct 17, 2022
9a58040
Merge branch 'main' of https://github.com/elastic/kibana into actions…
ymao1 Oct 18, 2022
47f1ca9
Adding functional tests
ymao1 Oct 18, 2022
c87baee
Fixing types
ymao1 Oct 18, 2022
28c6632
Fixing tests
ymao1 Oct 18, 2022
db2dc2a
Merge branch 'main' of https://github.com/elastic/kibana into actions…
ymao1 Oct 18, 2022
d5bf4ff
Merge branch 'main' of https://github.com/elastic/kibana into actions…
ymao1 Oct 19, 2022
026646a
Removing unnecessary Promise.all
ymao1 Oct 19, 2022
ea31342
Merge remote-tracking branch 'remotes/ymao/actions/unsecured-client' …
gsoldevila Oct 19, 2022
802dfa0
Cleanup
ymao1 Oct 19, 2022
c048225
Misc fixes and simplifications
gsoldevila Oct 19, 2022
f149cac
Merge remote-tracking branch 'remotes/ymao/actions/unsecured-client' …
gsoldevila Oct 20, 2022
cd500db
Add missing tsconfig.json
gsoldevila Oct 20, 2022
a293085
[CI] Auto-commit changed files from 'node scripts/build_plugin_list_d…
kibanamachine Oct 20, 2022
ff65387
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Oct 20, 2022
e241d0d
Add dependency to Actions plugin in tsconfig.json
gsoldevila Oct 20, 2022
0079193
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Oct 20, 2022
7f358bf
Separate setup logic from start logic
gsoldevila Oct 20, 2022
8929996
Fix bulkEnqueueExecution params structure
gsoldevila Oct 24, 2022
fd4b2ef
Update README
gsoldevila Oct 24, 2022
c344a61
Add UTs
gsoldevila Oct 24, 2022
b02bd81
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Oct 24, 2022
53575d9
Check license type >platinum for email notifications
gsoldevila Oct 25, 2022
52e7c8c
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Oct 25, 2022
f214324
Fix incorrect UTs
gsoldevila Oct 26, 2022
e0dd0f7
Import types when possible
gsoldevila Oct 26, 2022
8218cc4
Misc enhancements and code cleanup
gsoldevila Oct 26, 2022
0ccf326
Transform factory => provider, update start contract
gsoldevila Oct 27, 2022
a561085
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Oct 27, 2022
2d26740
Fix merge-related issues
gsoldevila Oct 27, 2022
3ed6752
Code cleanup, update README
gsoldevila Oct 27, 2022
9dbe577
Fix TS error
gsoldevila Oct 27, 2022
0238877
Support list of spaces for each related SO
gsoldevila Oct 28, 2022
06ff938
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Oct 28, 2022
dc4c9a6
Accept single spaceId for related SOs. Add convenience mocks
gsoldevila Nov 2, 2022
2654fee
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Nov 2, 2022
1467966
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Nov 2, 2022
091eb74
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Nov 2, 2022
d6e46f5
Fix CI types error
gsoldevila Nov 2, 2022
8d026c5
Address PR remarks
gsoldevila Nov 3, 2022
f275913
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Nov 3, 2022
56f99c9
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Nov 3, 2022
3aef92a
Address PR remarks #2
gsoldevila Nov 3, 2022
439b3e7
Merge branch 'main' into kbn-140743-notifications-api-mvp
gsoldevila Nov 3, 2022
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
/x-pack/test/search_sessions_integration/ @elastic/kibana-app-services
/test/plugin_functional/test_suites/panel_actions @elastic/kibana-app-services
/test/plugin_functional/test_suites/data_plugin @elastic/kibana-app-services
/x-pack/plugins/notifications/ @elastic/kibana-app-services

### Observability Plugins

Expand Down
4 changes: 4 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,10 @@ Elastic.
|This plugin allows for other plugins to add data to Kibana stack monitoring documents.


|{kib-repo}blob/{branch}/x-pack/plugins/notifications/README.md[notifications]
|The Notifications plugin provides a set of services to help Solutions and plugins send notifications to users.


|{kib-repo}blob/{branch}/x-pack/plugins/observability/README.md[observability]
|This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI.

Expand Down
4 changes: 2 additions & 2 deletions src/plugins/telemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ To use the exposed plugin start and setup contracts:

import { TelemetryPluginsStart } from '../telemetry/server`;

interface MyPlyginStartDeps {
interface MyPluginStartDeps {
telemetry?: TelemetryPluginsStart;
}

class MyPlugin {
public async start(
core: CoreStart,
{ telemetry }: MyPlyginStartDeps
{ telemetry }: MyPluginStartDeps
) {
const isOptedIn = await telemetry?.getIsOptedIn();
...
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,8 @@
"@kbn/monitoring-collection-plugin/*": ["x-pack/plugins/monitoring_collection/*"],
"@kbn/monitoring-plugin": ["x-pack/plugins/monitoring"],
"@kbn/monitoring-plugin/*": ["x-pack/plugins/monitoring/*"],
"@kbn/notifications-plugin": ["x-pack/plugins/notifications"],
"@kbn/notifications-plugin/*": ["x-pack/plugins/notifications/*"],
"@kbn/observability-plugin": ["x-pack/plugins/observability"],
"@kbn/observability-plugin/*": ["x-pack/plugins/observability/*"],
"@kbn/osquery-plugin": ["x-pack/plugins/osquery"],
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/actions/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const createStartMock = () => {
isActionTypeEnabled: jest.fn(),
isActionExecutable: jest.fn(),
getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()),
getUnsecuredActionsClient: jest.fn().mockResolvedValue(unsecuredActionsClientMock.create()),
getUnsecuredActionsClient: jest.fn().mockReturnValue(unsecuredActionsClientMock.create()),
getActionsAuthorizationWithRequest: jest
.fn()
.mockReturnValue(actionsAuthorizationMock.create()),
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/actions/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ import { IServiceAbstract, SubActionConnectorType } from './sub_action_framework
import { SubActionConnector } from './sub_action_framework/sub_action_connector';
import { CaseConnector } from './sub_action_framework/case';
import {
IUnsecuredActionsClient,
type IUnsecuredActionsClient,
UnsecuredActionsClient,
} from './unsecured_actions_client/unsecured_actions_client';
import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { IUnsecuredActionsClient } from './unsecured_actions_client';
import type { IUnsecuredActionsClient } from './unsecured_actions_client';

export type UnsecuredActionsClientMock = jest.Mocked<IUnsecuredActionsClient>;

Expand Down
70 changes: 70 additions & 0 deletions x-pack/plugins/notifications/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Kibana Notifications Plugin
pgayvallet marked this conversation as resolved.
Show resolved Hide resolved

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, TIL: 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: 'foo@bar.com',
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
```
8 changes: 8 additions & 0 deletions x-pack/plugins/notifications/common/index.ts
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';
15 changes: 15 additions & 0 deletions x-pack/plugins/notifications/jest.config.js
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}'],
};
12 changes: 12 additions & 0 deletions x-pack/plugins/notifications/kibana.json
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": []
}
29 changes: 29 additions & 0 deletions x-pack/plugins/notifications/server/config/config.ts
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,
};
8 changes: 8 additions & 0 deletions x-pack/plugins/notifications/server/config/index.ts
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';
18 changes: 18 additions & 0 deletions x-pack/plugins/notifications/server/index.ts
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);
}
52 changes: 52 additions & 0 deletions x-pack/plugins/notifications/server/mocks.ts
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();
},
};
107 changes: 107 additions & 0 deletions x-pack/plugins/notifications/server/plugin.test.ts
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));
});
});
});
Loading