[Response Ops][Connectors] New xpack.actions.email.services.enabled Kibana setting (#223363)

## Summary

Closes #220288

## Release note
New kibana setting `xpack.actions.email.services.enabled` to
enable/disable email services for email connector.
This commit is contained in:
Julian Gernun 2025-06-20 16:10:43 +02:00 committed by GitHub
parent 028660e4e1
commit 411ab215a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 608 additions and 63 deletions

View file

@ -198,7 +198,8 @@ enabled:
- x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/config.ts - x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/config.ts
- x-pack/test/functional_with_es_ssl/apps/embeddable_alerts_table/config.ts - x-pack/test/functional_with_es_ssl/apps/embeddable_alerts_table/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/config.ts - x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/with_aws_ses_kibana_config/config.ts - x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/with_email_aws_ses_kbn_config/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/with_email_services_enabled_kbn_config/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/shared/config.ts - x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/shared/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/webhook_disabled_ssl_pfx/config.ts - x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/webhook_disabled_ssl_pfx/config.ts
- x-pack/test/functional/apps/advanced_settings/config.ts - x-pack/test/functional/apps/advanced_settings/config.ts

View file

@ -150,6 +150,12 @@ $$$action-config-email-domain-allowlist$$$
Data type: `int` Data type: `int`
Default: `465` Default: `465`
`xpack.actions.email.services.enabled` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
: An array of strings indicating all email services that are enabled. Available options are `elastic-cloud`, `google-mail`, `microsoft-outlook`, `amazon-ses`, `microsoft-exchange`, and `other`. If the array is empty, no email services are enabled. The default value is `["*"]`, which enables all email services.
Data type: `string`
Default: `["*"]`
`xpack.actions.enableFooterInEmail` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") `xpack.actions.enableFooterInEmail` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
: A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions. : A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions.

View file

@ -326,6 +326,28 @@ groups:
ess: all ess: all
# example: | # example: |
- setting: xpack.actions.email.services.enabled
id: action-config-email-services-
description: |
An array of strings indicating all email services that are enabled. Available options are `elastic-cloud`, `google-mail`, `microsoft-outlook`, `amazon-ses`, `microsoft-exchange`, and `other`. If the array is empty, no email services are enabled. The default value is `["*"]`, which enables all email services.
# state: deprecated/hidden/tech-preview
# deprecation_details: ""
# note: ""
# tip: ""
# warning: ""
# important: ""
datatype: enum
default: ["*"]
# options:
# - option:
# description: ""
# type: static/dynamic
applies_to:
deployment:
self: all
ess: all
# example: |
- setting: xpack.actions.enableFooterInEmail - setting: xpack.actions.enableFooterInEmail
# id: # id:
description: | description: |

View file

@ -214,6 +214,7 @@ kibana_vars=(
xpack.actions.email.domain_allowlist xpack.actions.email.domain_allowlist
xpack.actions.email.services.ses.host xpack.actions.email.services.ses.host
xpack.actions.email.services.ses.port xpack.actions.email.services.ses.port
xpack.actions.email.services.enabled
xpack.actions.enableFooterInEmail xpack.actions.enableFooterInEmail
xpack.actions.enabledActionTypes xpack.actions.enabledActionTypes
xpack.actions.maxResponseContentLength xpack.actions.maxResponseContentLength

View file

@ -203,6 +203,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'vis_type_xy.readOnly (boolean?|never)', 'vis_type_xy.readOnly (boolean?|never)',
'vis_type_vega.enableExternalUrls (boolean?)', 'vis_type_vega.enableExternalUrls (boolean?)',
'xpack.actions.email.domain_allowlist (array?)', 'xpack.actions.email.domain_allowlist (array?)',
'xpack.actions.email.services.enabled (array?)',
'xpack.actions.webhook.ssl.pfx.enabled (boolean?)', 'xpack.actions.webhook.ssl.pfx.enabled (boolean?)',
'xpack.apm.serviceMapEnabled (boolean?)', 'xpack.apm.serviceMapEnabled (boolean?)',
'xpack.apm.ui.enabled (boolean?)', 'xpack.apm.ui.enabled (boolean?)',

View file

@ -14,12 +14,16 @@ export interface ActionsPublicPluginSetup {
emails: string[], emails: string[],
options?: ValidateEmailAddressesOptions options?: ValidateEmailAddressesOptions
): ValidatedEmail[]; ): ValidatedEmail[];
enabledEmailServices: string[];
isWebhookSslWithPfxEnabled?: boolean; isWebhookSslWithPfxEnabled?: boolean;
} }
export interface Config { export interface Config {
email: { email: {
domain_allowlist: string[]; domain_allowlist: string[];
services: {
enabled: string[];
};
}; };
webhook: { webhook: {
ssl: { ssl: {
@ -32,11 +36,13 @@ export interface Config {
export class Plugin implements CorePlugin<ActionsPublicPluginSetup> { export class Plugin implements CorePlugin<ActionsPublicPluginSetup> {
private readonly allowedEmailDomains: string[] | null = null; private readonly allowedEmailDomains: string[] | null = null;
private readonly enabledEmailServices: string[];
private readonly webhookSslWithPfxEnabled: boolean; private readonly webhookSslWithPfxEnabled: boolean;
constructor(ctx: PluginInitializerContext<Config>) { constructor(ctx: PluginInitializerContext<Config>) {
const config = ctx.config.get(); const config = ctx.config.get();
this.allowedEmailDomains = config.email?.domain_allowlist || null; this.allowedEmailDomains = config.email?.domain_allowlist || null;
this.enabledEmailServices = Array.from(new Set(config.email?.services?.enabled || ['*']));
this.webhookSslWithPfxEnabled = config.webhook?.ssl.pfx.enabled ?? true; this.webhookSslWithPfxEnabled = config.webhook?.ssl.pfx.enabled ?? true;
} }
@ -44,6 +50,7 @@ export class Plugin implements CorePlugin<ActionsPublicPluginSetup> {
return { return {
validateEmailAddresses: (emails: string[], options: ValidateEmailAddressesOptions) => validateEmailAddresses: (emails: string[], options: ValidateEmailAddressesOptions) =>
validateEmails(this.allowedEmailDomains, emails, options), validateEmails(this.allowedEmailDomains, emails, options),
enabledEmailServices: this.enabledEmailServices,
isWebhookSslWithPfxEnabled: this.webhookSslWithPfxEnabled, isWebhookSslWithPfxEnabled: this.webhookSslWithPfxEnabled,
}; };
} }

View file

@ -44,6 +44,7 @@ const createActionsConfigMock = () => {
}, },
}), }),
getAwsSesConfig: jest.fn().mockReturnValue(null), getAwsSesConfig: jest.fn().mockReturnValue(null),
getEnabledEmailServices: jest.fn().mockReturnValue(['*']),
}; };
return mocked; return mocked;
}; };

View file

@ -633,6 +633,14 @@ describe('getAwsSesConfig()', () => {
expect(acu.getAwsSesConfig()).toEqual(null); expect(acu.getAwsSesConfig()).toEqual(null);
}); });
test('returns null when no email.services.ses config set', () => {
const acu = getActionsConfigurationUtilities({
...defaultActionsConfig,
email: { services: {} },
});
expect(acu.getAwsSesConfig()).toEqual(null);
});
test('returns config if set', () => { test('returns config if set', () => {
const acu = getActionsConfigurationUtilities({ const acu = getActionsConfigurationUtilities({
...defaultActionsConfig, ...defaultActionsConfig,
@ -652,3 +660,47 @@ describe('getAwsSesConfig()', () => {
}); });
}); });
}); });
describe('getEnabledEmailServices()', () => {
test('returns all services when no email config set', () => {
const acu = getActionsConfigurationUtilities(defaultActionsConfig);
expect(acu.getEnabledEmailServices()).toEqual(['*']);
});
test('returns all services when no email.services config set', () => {
const acu = getActionsConfigurationUtilities({ ...defaultActionsConfig, email: {} });
expect(acu.getEnabledEmailServices()).toEqual(['*']);
});
test('returns all services when no email.services.enabled config set', () => {
const acu = getActionsConfigurationUtilities({
...defaultActionsConfig,
email: { services: {} },
});
expect(acu.getEnabledEmailServices()).toEqual(['*']);
});
test('returns only enabled services', () => {
const acu = getActionsConfigurationUtilities({
...defaultActionsConfig,
email: {
services: {
enabled: ['google-mail', 'microsoft-exchange'],
},
},
});
expect(acu.getEnabledEmailServices()).toEqual(['google-mail', 'microsoft-exchange']);
});
test('returns all services when enabled is set to "*" in config', () => {
const acu = getActionsConfigurationUtilities({
...defaultActionsConfig,
email: {
services: {
enabled: ['*'],
},
},
});
expect(acu.getEnabledEmailServices()).toEqual(['*']);
});
});

View file

@ -63,6 +63,7 @@ export interface ActionsConfigurationUtilities {
}; };
}; };
getAwsSesConfig: () => AwsSesConfig; getAwsSesConfig: () => AwsSesConfig;
getEnabledEmailServices: () => string[];
} }
function allowListErrorMessage(field: AllowListingField, value: string) { function allowListErrorMessage(field: AllowListingField, value: string) {
@ -243,15 +244,23 @@ export function getActionsConfigurationUtilities(
}; };
}, },
getAwsSesConfig: () => { getAwsSesConfig: () => {
if (config.email?.services?.ses.host && config.email?.services?.ses.port) { if (config.email?.services?.ses?.host && config.email?.services?.ses?.port) {
return { return {
host: config.email?.services?.ses.host, host: config.email?.services?.ses?.host,
port: config.email?.services?.ses.port, port: config.email?.services?.ses?.port,
secure: true, secure: true,
}; };
} }
return null; return null;
}, },
getEnabledEmailServices() {
const emailServices = config.email?.services?.enabled;
if (emailServices) {
return Array.from(new Set(Array.from(emailServices)));
}
return ['*'];
},
}; };
} }

View file

@ -238,7 +238,7 @@ describe('config validation', () => {
config.email = {}; config.email = {};
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"` `"[email]: email.domain_allowlist or email.services must be defined"`
); );
config.email = { domain_allowlist: [] }; config.email = { domain_allowlist: [] };
@ -285,35 +285,35 @@ describe('config validation', () => {
test('validates empty email config', () => { test('validates empty email config', () => {
config.email = {}; config.email = {};
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"` `"[email]: email.domain_allowlist or email.services must be defined"`
); );
}); });
test('validates email config with empty services', () => { test('validates email config with empty services', () => {
config.email = { services: {} }; config.email = { services: {} };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"` `"[email.services]: email.services.enabled or email.services.ses must be defined"`
); );
}); });
test('validates email config with empty ses service', () => { test('validates email config with empty ses service', () => {
config.email = { services: { ses: {} } }; config.email = { services: { ses: {} } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"` `"[email.services.ses.host]: expected value of type [string] but got [undefined]"`
); );
}); });
test('validates ses config with host only', () => { test('validates ses config with host only', () => {
config.email = { services: { ses: { host: 'ses.host.com' } } }; config.email = { services: { ses: { host: 'ses.host.com' } } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires both services.ses.host and services.ses.port to be specified"` `"[email.services.ses.port]: expected value of type [number] but got [undefined]"`
); );
}); });
test('validates ses config with port only', () => { test('validates ses config with port only', () => {
config.email = { services: { ses: { port: 1 } } }; config.email = { services: { ses: { port: 1 } } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires both services.ses.host and services.ses.port to be specified"` `"[email.services.ses.host]: expected value of type [string] but got [undefined]"`
); );
}); });
@ -323,6 +323,43 @@ describe('config validation', () => {
expect(result.email?.services?.ses).toEqual({ host: 'ses.host.com', port: 1 }); expect(result.email?.services?.ses).toEqual({ host: 'ses.host.com', port: 1 });
}); });
}); });
describe('email.services.enabled', () => {
const config: Record<string, unknown> = {};
test('validates email config with empty enabled services', () => {
config.email = { services: { enabled: [] } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email.services.enabled]: array size is [0], but cannot be smaller than [1]"`
);
});
test('validates email config with enabled services', () => {
config.email = { services: { enabled: ['elastic-cloud', 'amazon-ses'] } };
const result = configSchema.validate(config);
expect(result.email?.services?.enabled).toEqual(['elastic-cloud', 'amazon-ses']);
});
test('validates email config with unexistend service', () => {
config.email = { services: { enabled: ['fake-service'] } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(`
"[email.services.enabled.0]: types that failed validation:
- [email.services.enabled.0.0]: expected value to equal [google-mail]
- [email.services.enabled.0.1]: expected value to equal [microsoft-exchange]
- [email.services.enabled.0.2]: expected value to equal [microsoft-outlook]
- [email.services.enabled.0.3]: expected value to equal [amazon-ses]
- [email.services.enabled.0.4]: expected value to equal [elastic-cloud]
- [email.services.enabled.0.5]: expected value to equal [other]
- [email.services.enabled.0.6]: expected value to equal [*]"
`);
});
test('validates enabled services but no ses service', () => {
config.email = { services: { enabled: ['google-mail', 'amazon-ses'] } };
const result = configSchema.validate(config);
expect(result.email?.services?.enabled).toEqual(['google-mail', 'amazon-ses']);
expect(result.email?.services?.ses).toBeUndefined();
});
});
}); });
// object creator that ensures we can create a property named __proto__ on an // object creator that ensures we can create a property named __proto__ on an

View file

@ -125,22 +125,43 @@ export const configSchema = schema.object({
{ {
domain_allowlist: schema.maybe(schema.arrayOf(schema.string())), domain_allowlist: schema.maybe(schema.arrayOf(schema.string())),
services: schema.maybe( services: schema.maybe(
schema.object({ schema.object(
ses: schema.object({ {
host: schema.maybe(schema.string({ minLength: 1 })), enabled: schema.maybe(
port: schema.maybe(schema.number({ min: 1, max: 65535 })), schema.arrayOf(
}), schema.oneOf([
}) schema.literal('google-mail'),
schema.literal('microsoft-exchange'),
schema.literal('microsoft-outlook'),
schema.literal('amazon-ses'),
schema.literal('elastic-cloud'),
schema.literal('other'),
schema.literal('*'),
]),
{ minSize: 1 }
)
),
ses: schema.maybe(
schema.object({
host: schema.string({ minLength: 1 }),
port: schema.number({ min: 1, max: 65535 }),
})
),
},
{
validate: (obj) => {
if (obj && Object.keys(obj).length === 0) {
return 'email.services.enabled or email.services.ses must be defined';
}
},
}
)
), ),
}, },
{ {
validate: (obj) => { validate: (obj) => {
if (!obj.domain_allowlist && !obj.services?.ses.host && !obj.services?.ses.port) { if (obj && Object.keys(obj).length === 0) {
return 'Email configuration requires either domain_allowlist or services.ses to be specified'; return 'email.domain_allowlist or email.services must be defined';
}
if (obj.services?.ses && (!obj.services.ses.host || !obj.services.ses.port)) {
return 'Email configuration requires both services.ses.host and services.ses.port to be specified';
} }
}, },
} }

View file

@ -50,7 +50,7 @@ export type { ServiceParams } from './sub_action_framework/types';
export const config: PluginConfigDescriptor<ActionsConfig> = { export const config: PluginConfigDescriptor<ActionsConfig> = {
schema: configSchema, schema: configSchema,
exposeToBrowser: { exposeToBrowser: {
email: { domain_allowlist: true }, email: { domain_allowlist: true, services: { enabled: true } },
webhook: { ssl: { pfx: { enabled: true } } }, webhook: { ssl: { pfx: { enabled: true } } },
}, },
}; };

View file

@ -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.
*/
export const serviceParamValueToKbnSettingMap = {
gmail: 'google-mail',
outlook365: 'microsoft-outlook',
ses: 'amazon-ses',
elastic_cloud: 'elastic-cloud',
exchange_server: 'microsoft-exchange',
other: 'other',
} as const;

View file

@ -8,7 +8,7 @@
import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry';
import { registerConnectorTypes } from '..'; import { registerConnectorTypes } from '..';
import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types';
import { getEmailServices } from './email'; import { emailServices, getEmailServices } from './email';
import { import {
ValidatedEmail, ValidatedEmail,
InvalidEmailReason, InvalidEmailReason,
@ -17,6 +17,7 @@ import {
} from '@kbn/actions-plugin/common'; } from '@kbn/actions-plugin/common';
import { experimentalFeaturesMock } from '../../mocks'; import { experimentalFeaturesMock } from '../../mocks';
import { ExperimentalFeaturesService } from '../../common/experimental_features_service'; import { ExperimentalFeaturesService } from '../../common/experimental_features_service';
import { serviceParamValueToKbnSettingMap } from '../../../common/email/constants';
const CONNECTOR_TYPE_ID = '.email'; const CONNECTOR_TYPE_ID = '.email';
let connectorTypeModel: ConnectorTypeModel; let connectorTypeModel: ConnectorTypeModel;
@ -65,14 +66,65 @@ describe('connectorTypeRegistry.get() works', () => {
describe('getEmailServices', () => { describe('getEmailServices', () => {
test('should return elastic cloud service if isCloudEnabled is true', () => { test('should return elastic cloud service if isCloudEnabled is true', () => {
const services = getEmailServices(true); const services = getEmailServices(true, ['*']);
expect(services.find((service) => service.value === 'elastic_cloud')).toBeTruthy(); expect(services.find((service) => service.value === 'elastic_cloud')).toBeTruthy();
}); });
test('should not return elastic cloud service if isCloudEnabled is false', () => { test('should not return elastic cloud service if isCloudEnabled is false', () => {
const services = getEmailServices(false); const services = getEmailServices(false, ['*']);
expect(services.find((service) => service.value === 'elastic_cloud')).toBeFalsy(); expect(services.find((service) => service.value === 'elastic_cloud')).toBeFalsy();
}); });
test('should return all services if enabledEmailsServices is *', () => {
const services = getEmailServices(true, ['*']);
expect(services).toEqual(emailServices);
});
test('should return only specified services if enabledEmailsServices is not empty', () => {
const services = getEmailServices(true, [
serviceParamValueToKbnSettingMap.gmail,
serviceParamValueToKbnSettingMap.outlook365,
]);
expect(services).toEqual([
{
['kbn-setting-value']: 'google-mail',
text: 'Gmail',
value: 'gmail',
},
{
['kbn-setting-value']: 'microsoft-outlook',
text: 'Outlook',
value: 'outlook365',
},
]);
});
test('should return enabled services and the current service if specified', () => {
const services = getEmailServices(
true,
[serviceParamValueToKbnSettingMap.gmail, serviceParamValueToKbnSettingMap.outlook365],
serviceParamValueToKbnSettingMap.other
);
expect(services).toEqual([
{
['kbn-setting-value']: 'google-mail',
text: 'Gmail',
value: 'gmail',
},
{
['kbn-setting-value']: 'microsoft-outlook',
text: 'Outlook',
value: 'outlook365',
},
{
['kbn-setting-value']: 'other',
text: 'Other',
value: 'other',
},
]);
});
}); });
describe('action params validation', () => { describe('action params validation', () => {

View file

@ -16,50 +16,76 @@ import type {
} from '@kbn/triggers-actions-ui-plugin/public/types'; } from '@kbn/triggers-actions-ui-plugin/public/types';
import { EmailActionParams, EmailConfig, EmailSecrets } from '../types'; import { EmailActionParams, EmailConfig, EmailSecrets } from '../types';
import { RegistrationServices } from '..'; import { RegistrationServices } from '..';
import { serviceParamValueToKbnSettingMap as emailKbnSettings } from '../../../common/email/constants';
const emailServices: EuiSelectOption[] = [ export const emailServices: Array<EuiSelectOption & { 'kbn-setting-value': string }> = [
{ {
text: i18n.translate('xpack.stackConnectors.components.email.gmailServerTypeLabel', { text: i18n.translate('xpack.stackConnectors.components.email.gmailServerTypeLabel', {
defaultMessage: 'Gmail', defaultMessage: 'Gmail',
}), }),
value: 'gmail', value: 'gmail',
['kbn-setting-value']: emailKbnSettings.gmail,
}, },
{ {
text: i18n.translate('xpack.stackConnectors.components.email.outlookServerTypeLabel', { text: i18n.translate('xpack.stackConnectors.components.email.outlookServerTypeLabel', {
defaultMessage: 'Outlook', defaultMessage: 'Outlook',
}), }),
value: 'outlook365', value: 'outlook365',
['kbn-setting-value']: emailKbnSettings.outlook365,
}, },
{ {
text: i18n.translate('xpack.stackConnectors.components.email.amazonSesServerTypeLabel', { text: i18n.translate('xpack.stackConnectors.components.email.amazonSesServerTypeLabel', {
defaultMessage: 'Amazon SES', defaultMessage: 'Amazon SES',
}), }),
value: 'ses', value: 'ses',
['kbn-setting-value']: emailKbnSettings.ses,
}, },
{ {
text: i18n.translate('xpack.stackConnectors.components.email.elasticCloudServerTypeLabel', { text: i18n.translate('xpack.stackConnectors.components.email.elasticCloudServerTypeLabel', {
defaultMessage: 'Elastic Cloud', defaultMessage: 'Elastic Cloud',
}), }),
value: 'elastic_cloud', value: 'elastic_cloud',
['kbn-setting-value']: emailKbnSettings.elastic_cloud,
}, },
{ {
text: i18n.translate('xpack.stackConnectors.components.email.exchangeServerTypeLabel', { text: i18n.translate('xpack.stackConnectors.components.email.exchangeServerTypeLabel', {
defaultMessage: 'MS Exchange Server', defaultMessage: 'MS Exchange Server',
}), }),
value: 'exchange_server', value: 'exchange_server',
['kbn-setting-value']: emailKbnSettings.exchange_server,
}, },
{ {
text: i18n.translate('xpack.stackConnectors.components.email.otherServerTypeLabel', { text: i18n.translate('xpack.stackConnectors.components.email.otherServerTypeLabel', {
defaultMessage: 'Other', defaultMessage: 'Other',
}), }),
value: 'other', value: 'other',
['kbn-setting-value']: emailKbnSettings.other,
}, },
]; ];
export function getEmailServices(isCloudEnabled: boolean) { // Return the current service regardless of its enabled state to allow users to:
return isCloudEnabled // 1. View the current service in the dropdown UI
// 2. Update the service configuration if needed
// Note: The connector update endpoint will reject updates where the service
// remains unchanged but is disabled.
export function getEmailServices(
isCloudEnabled: boolean,
enabledEmailsServices: string[],
currentService?: string
): Array<EuiSelectOption & { 'kbn-setting-value': string }> {
const allEmailServices = isCloudEnabled
? emailServices ? emailServices
: emailServices.filter((service) => service.value !== 'elastic_cloud'); : emailServices.filter((service) => service.value !== 'elastic_cloud');
if (enabledEmailsServices.includes('*')) {
return allEmailServices;
}
return allEmailServices.filter(
(service) =>
service.value === currentService ||
enabledEmailsServices.includes(service['kbn-setting-value'])
);
} }
export function getConnectorType( export function getConnectorType(

View file

@ -7,22 +7,34 @@
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import { mountWithIntl } from '@kbn/test-jest-helpers'; import { mountWithIntl } from '@kbn/test-jest-helpers';
import { act } from '@testing-library/react'; import { act, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public';
import EmailActionConnectorFields from './email_connector'; import EmailActionConnectorFields from './email_connector';
import * as hooks from './use_email_config';
import { import {
AppMockRenderer, AppMockRenderer,
ConnectorFormTestProvider, ConnectorFormTestProvider,
createAppMockRenderer, createAppMockRenderer,
waitForComponentToUpdate, waitForComponentToUpdate,
} from '../lib/test_utils'; } from '../lib/test_utils';
import { getServiceConfig } from './api';
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana');
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
jest.mock('./api', () => {
return {
getServiceConfig: jest.fn(),
};
});
describe('EmailActionConnectorFields', () => { describe('EmailActionConnectorFields', () => {
const enabledEmailServices = ['*'];
afterEach(() => {
jest.clearAllMocks();
});
test('all connector fields are rendered', async () => { test('all connector fields are rendered', async () => {
const actionConnector = { const actionConnector = {
secrets: { secrets: {
@ -170,12 +182,11 @@ describe('EmailActionConnectorFields', () => {
}); });
test('host, port and secure fields should be disabled when service field is set to well known service', async () => { test('host, port and secure fields should be disabled when service field is set to well known service', async () => {
const getEmailServiceConfig = jest (getServiceConfig as jest.Mock).mockResolvedValue({
.fn() host: 'https://example.com',
.mockResolvedValue({ host: 'https://example.com', port: 80, secure: false }); port: 80,
jest secure: false,
.spyOn(hooks, 'useEmailConfig') });
.mockImplementation(() => ({ isLoading: false, getEmailServiceConfig }));
const actionConnector = { const actionConnector = {
secrets: { secrets: {
@ -214,12 +225,11 @@ describe('EmailActionConnectorFields', () => {
}); });
test('host, port and secure fields should not be disabled when service field is set to other', async () => { test('host, port and secure fields should not be disabled when service field is set to other', async () => {
const getEmailServiceConfig = jest (getServiceConfig as jest.Mock).mockResolvedValue({
.fn() host: 'https://example.com',
.mockResolvedValue({ host: 'https://example.com', port: 80, secure: false }); port: 80,
jest secure: false,
.spyOn(hooks, 'useEmailConfig') });
.mockImplementation(() => ({ isLoading: false, getEmailServiceConfig }));
const actionConnector = { const actionConnector = {
secrets: { secrets: {
@ -292,7 +302,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<EmailActionConnectorFields <EmailActionConnectorFields
readOnly={false} readOnly={false}
@ -356,7 +366,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<EmailActionConnectorFields <EmailActionConnectorFields
readOnly={false} readOnly={false}
@ -415,7 +425,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<EmailActionConnectorFields <EmailActionConnectorFields
readOnly={false} readOnly={false}
@ -463,7 +473,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<EmailActionConnectorFields <EmailActionConnectorFields
readOnly={false} readOnly={false}
@ -509,7 +519,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<EmailActionConnectorFields <EmailActionConnectorFields
readOnly={false} readOnly={false}
@ -554,7 +564,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<Suspense fallback={null}> <Suspense fallback={null}>
<EmailActionConnectorFields <EmailActionConnectorFields
@ -603,7 +613,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<EmailActionConnectorFields <EmailActionConnectorFields
readOnly={false} readOnly={false}
@ -649,7 +659,7 @@ describe('EmailActionConnectorFields', () => {
<ConnectorFormTestProvider <ConnectorFormTestProvider
connector={actionConnector} connector={actionConnector}
onSubmit={onSubmit} onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses }} connectorServices={{ validateEmailAddresses, enabledEmailServices }}
> >
<EmailActionConnectorFields <EmailActionConnectorFields
readOnly={false} readOnly={false}
@ -689,3 +699,102 @@ describe('EmailActionConnectorFields', () => {
}); });
}); });
}); });
describe('when not all email services are enabled', () => {
const enabledEmailServices = ['amazon-ses', 'other', 'microsoft-exchange'];
let appMockRenderer: AppMockRenderer;
const onSubmit = jest.fn();
const validateEmailAddresses = jest.fn();
beforeEach(() => {
appMockRenderer = createAppMockRenderer();
validateEmailAddresses.mockReturnValue([{ valid: true }]);
(getServiceConfig as jest.Mock).mockResolvedValue({
host: 'https://example.com',
port: 2255,
secure: true,
});
});
afterEach(() => {
jest.clearAllMocks();
});
it('only allows enabled services to be selected only', async () => {
const actionConnector = {
secrets: {
user: 'user',
password: 'pass',
},
id: 'test',
actionTypeId: '.email',
name: 'email',
config: {},
isDeprecated: false,
};
appMockRenderer.render(
<ConnectorFormTestProvider
connector={actionConnector}
onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
>
<EmailActionConnectorFields
readOnly={false}
isEdit={false}
registerPreSubmitValidator={() => {}}
/>
</ConnectorFormTestProvider>
);
const emailServiceSelect = screen.getByTestId('emailServiceSelectInput') as HTMLSelectElement;
const options = within(emailServiceSelect).getAllByRole('option');
expect(options).toHaveLength(3);
expect(options[0].textContent).toBe('Amazon SES');
expect(options[1].textContent).toBe('MS Exchange Server');
expect(options[2].textContent).toBe('Other');
});
it('adds the current connector service to the service list even if not enabled', async () => {
const actionConnector = {
secrets: {
user: 'user',
password: 'pass',
},
id: 'test',
actionTypeId: '.email',
name: 'email',
config: {
from: 'test@test.com',
test: 'test',
service: 'gmail', // not enabled
secure: true,
},
isDeprecated: false,
};
appMockRenderer.render(
<ConnectorFormTestProvider
connector={actionConnector}
onSubmit={onSubmit}
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
>
<EmailActionConnectorFields
readOnly={false}
isEdit={false}
registerPreSubmitValidator={() => {}}
/>
</ConnectorFormTestProvider>
);
const emailServiceSelect = screen.getByTestId('emailServiceSelectInput') as HTMLSelectElement;
const options = within(emailServiceSelect).getAllByRole('option');
expect(options).toHaveLength(4);
expect(options[0].textContent).toBe('Gmail');
expect(options[1].textContent).toBe('Amazon SES');
expect(options[2].textContent).toBe('MS Exchange Server');
expect(options[3].textContent).toBe('Other');
});
});

View file

@ -5,7 +5,7 @@
* 2.0. * 2.0.
*/ */
import React, { lazy, useEffect, useMemo } from 'react'; import React, { lazy, useEffect, useMemo, useRef } from 'react';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiSpacer } from '@elastic/eui'; import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
@ -102,7 +102,7 @@ export const EmailActionConnectorFields: React.FunctionComponent<ActionConnector
notifications: { toasts }, notifications: { toasts },
} = useKibana().services; } = useKibana().services;
const { const {
services: { validateEmailAddresses }, services: { validateEmailAddresses, enabledEmailServices },
} = useConnectorContext(); } = useConnectorContext();
const form = useFormContext(); const form = useFormContext();
@ -119,6 +119,15 @@ export const EmailActionConnectorFields: React.FunctionComponent<ActionConnector
const { service = null, hasAuth = false } = config ?? {}; const { service = null, hasAuth = false } = config ?? {};
const disableServiceConfig = shouldDisableEmailConfiguration(service); const disableServiceConfig = shouldDisableEmailConfiguration(service);
const { isLoading, getEmailServiceConfig } = useEmailConfig({ http, toasts }); const { isLoading, getEmailServiceConfig } = useEmailConfig({ http, toasts });
const initialService = useRef(service);
if (!initialService.current && service) {
initialService.current = service;
}
const availableEmailServices = getEmailServices(
isCloud,
enabledEmailServices,
initialService.current
);
useEffect(() => { useEffect(() => {
async function fetchConfig() { async function fetchConfig() {
@ -173,7 +182,7 @@ export const EmailActionConnectorFields: React.FunctionComponent<ActionConnector
componentProps={{ componentProps={{
euiFieldProps: { euiFieldProps: {
'data-test-subj': 'emailServiceSelectInput', 'data-test-subj': 'emailServiceSelectInput',
options: getEmailServices(isCloud), options: availableEmailServices,
fullWidth: true, fullWidth: true,
hasNoInitialSelection: true, hasNoInitialSelection: true,
disabled: readOnly || isLoading, disabled: readOnly || isLoading,

View file

@ -74,6 +74,7 @@ const FormTestProviderComponent: React.FC<FormTestProviderProps> = ({
connectorServices = { connectorServices = {
validateEmailAddresses: jest.fn(), validateEmailAddresses: jest.fn(),
isWebhookSslWithPfxEnabled: true, isWebhookSslWithPfxEnabled: true,
enabledEmailServices: ['*'],
}, },
}) => { }) => {
const { form } = useForm({ defaultValue }); const { form } = useForm({ defaultValue });

View file

@ -34,6 +34,7 @@ import { getConnectorType } from '.';
import type { ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common'; import type { ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common';
import { ActionExecutionSourceType } from '@kbn/actions-plugin/server/types'; import { ActionExecutionSourceType } from '@kbn/actions-plugin/server/types';
import { AdditionalEmailServices } from '../../../common'; import { AdditionalEmailServices } from '../../../common';
import { serviceParamValueToKbnSettingMap } from '../../../common/email/constants';
const sendEmailMock = sendEmail as jest.Mock; const sendEmailMock = sendEmail as jest.Mock;
@ -503,6 +504,75 @@ describe('params validation', () => {
); );
}).not.toThrowError(); }).not.toThrowError();
}); });
test('error when using a service that is not enabled', async () => {
const configUtils = actionsConfigMock.create();
configUtils.getEnabledEmailServices = jest
.fn()
.mockReturnValue([
serviceParamValueToKbnSettingMap.gmail,
serviceParamValueToKbnSettingMap.elastic_cloud,
]);
expect(() =>
validateConfig(
connectorType,
{
service: 'other',
from: 'bob@example.com',
host: 'wrong-host',
port: 123,
secure: true,
hasAuth: true,
},
{ configurationUtilities: configUtils }
)
).toThrowErrorMatchingInlineSnapshot(
`"error validating action type config: [service]: \\"other\\" is not in the list of enabled email services: google-mail,elastic-cloud"`
);
});
test('no error using enabled services = *', async () => {
const configUtils = actionsConfigMock.create();
configUtils.getEnabledEmailServices = jest.fn().mockReturnValue(['*']);
expect(() =>
validateConfig(
connectorType,
{
service: 'other',
from: 'bob@example.com',
host: 'wrong-host',
port: 123,
secure: true,
hasAuth: true,
},
{ configurationUtilities: configUtils }
)
).not.toThrowError();
});
test('does not throw when fetching service enabled in config', () => {
const configUtils = actionsConfigMock.create();
configUtils.getEnabledEmailServices = jest
.fn()
.mockReturnValue([serviceParamValueToKbnSettingMap.elastic_cloud]);
expect(() =>
validateConfig(
connectorType,
{
service: 'elastic_cloud',
from: 'bob@example.com',
host: 'dockerhost',
port: 10025,
secure: false,
hasAuth: false,
},
{ configurationUtilities: configUtils }
)
).not.toThrowError();
});
}); });
describe('execute()', () => { describe('execute()', () => {

View file

@ -34,6 +34,7 @@ import { AdditionalEmailServices } from '../../../common';
import type { SendEmailOptions, Transport } from './send_email'; import type { SendEmailOptions, Transport } from './send_email';
import { sendEmail, JSON_TRANSPORT_SERVICE } from './send_email'; import { sendEmail, JSON_TRANSPORT_SERVICE } from './send_email';
import { portSchema } from '../lib/schemas'; import { portSchema } from '../lib/schemas';
import { serviceParamValueToKbnSettingMap as emailKbnSettings } from '../../../common/email/constants';
export type EmailConnectorType = ConnectorType< export type EmailConnectorType = ConnectorType<
ConnectorTypeConfigType, ConnectorTypeConfigType,
@ -82,6 +83,20 @@ function validateConfig(
const config = configObject; const config = configObject;
const { configurationUtilities } = validatorServices; const { configurationUtilities } = validatorServices;
const awsSesConfig = configurationUtilities.getAwsSesConfig(); const awsSesConfig = configurationUtilities.getAwsSesConfig();
const enabledServices = configurationUtilities.getEnabledEmailServices();
const serviceKey = config.service as keyof typeof emailKbnSettings;
if (
!enabledServices.includes('*') &&
config.service in emailKbnSettings &&
!enabledServices.includes(emailKbnSettings[serviceKey])
) {
throw new Error(
`[service]: "${
emailKbnSettings[serviceKey]
}" is not in the list of enabled email services: ${enabledServices.join(',')}`
);
}
const emails = [config.from]; const emails = [config.from];
const invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails); const invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails);

View file

@ -24,7 +24,7 @@ const FormTestProviderComponent: React.FC<FormTestProviderProps> = ({
children, children,
defaultValue, defaultValue,
onSubmit, onSubmit,
connectorServices = { validateEmailAddresses: jest.fn() }, connectorServices = { validateEmailAddresses: jest.fn(), enabledEmailServices: ['*'] },
}) => { }) => {
const { form } = useForm({ defaultValue }); const { form } = useForm({ defaultValue });
const { submit } = form; const { submit } = form;

View file

@ -90,11 +90,15 @@ export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => { export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => {
const { const {
actions: { validateEmailAddresses, isWebhookSslWithPfxEnabled }, actions: { validateEmailAddresses, enabledEmailServices, isWebhookSslWithPfxEnabled },
} = useKibana().services; } = useKibana().services;
return ( return (
<ConnectorProvider value={{ services: { validateEmailAddresses, isWebhookSslWithPfxEnabled } }}> <ConnectorProvider
value={{
services: { validateEmailAddresses, enabledEmailServices, isWebhookSslWithPfxEnabled },
}}
>
<Routes> <Routes>
<Route <Route
path={`/:section(${sectionsRegex})`} path={`/:section(${sectionsRegex})`}

View file

@ -14,13 +14,13 @@ const style = {
export const RulesListSandbox = () => { export const RulesListSandbox = () => {
const { const {
services: { validateEmailAddresses }, services: { validateEmailAddresses, enabledEmailServices },
} = useConnectorContext(); } = useConnectorContext();
return ( return (
<div style={style}> <div style={style}>
{getRulesListLazy({ {getRulesListLazy({
connectorServices: { validateEmailAddresses }, connectorServices: { validateEmailAddresses, enabledEmailServices },
rulesListProps: {}, rulesListProps: {},
})} })}
</div> </div>

View file

@ -120,12 +120,12 @@ export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => { export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => {
const { const {
actions: { validateEmailAddresses }, actions: { validateEmailAddresses, enabledEmailServices },
application: { navigateToApp }, application: { navigateToApp },
} = useKibana().services; } = useKibana().services;
return ( return (
<ConnectorProvider value={{ services: { validateEmailAddresses } }}> <ConnectorProvider value={{ services: { validateEmailAddresses, enabledEmailServices } }}>
<Routes> <Routes>
<Route <Route
exact exact

View file

@ -27,7 +27,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => {
const licensingPluginMock = licensingMock.createStart(); const licensingPluginMock = licensingMock.createStart();
return { return {
...core, ...core,
actions: { validateEmailAddresses: jest.fn() }, actions: { validateEmailAddresses: jest.fn(), enabledEmailServices: ['*'] },
ruleTypeRegistry: { ruleTypeRegistry: {
has: jest.fn(), has: jest.fn(),
register: jest.fn(), register: jest.fn(),

View file

@ -49,7 +49,7 @@ import { getUntrackModalLazy } from './common/get_untrack_modal';
function createStartMock(): TriggersAndActionsUIPublicPluginStart { function createStartMock(): TriggersAndActionsUIPublicPluginStart {
const actionTypeRegistry = new TypeRegistry<ActionTypeModel>(); const actionTypeRegistry = new TypeRegistry<ActionTypeModel>();
const ruleTypeRegistry = new TypeRegistry<RuleTypeModel>(); const ruleTypeRegistry = new TypeRegistry<RuleTypeModel>();
const connectorServices = { validateEmailAddresses: jest.fn() }; const connectorServices = { validateEmailAddresses: jest.fn(), enabledEmailServices: ['*'] };
return { return {
actionTypeRegistry, actionTypeRegistry,
ruleTypeRegistry, ruleTypeRegistry,

View file

@ -205,6 +205,7 @@ export class Plugin
const ruleTypeRegistry = this.ruleTypeRegistry; const ruleTypeRegistry = this.ruleTypeRegistry;
this.connectorServices = { this.connectorServices = {
validateEmailAddresses: plugins.actions.validateEmailAddresses, validateEmailAddresses: plugins.actions.validateEmailAddresses,
enabledEmailServices: plugins.actions.enabledEmailServices,
}; };
ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures }); ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures });

View file

@ -400,6 +400,7 @@ export interface SnoozeSchedule {
export interface ConnectorServices { export interface ConnectorServices {
validateEmailAddresses: ActionsPublicPluginSetup['validateEmailAddresses']; validateEmailAddresses: ActionsPublicPluginSetup['validateEmailAddresses'];
enabledEmailServices: ActionsPublicPluginSetup['enabledEmailServices'];
isWebhookSslWithPfxEnabled?: ActionsPublicPluginSetup['isWebhookSslWithPfxEnabled']; isWebhookSslWithPfxEnabled?: ActionsPublicPluginSetup['isWebhookSslWithPfxEnabled'];
} }

View file

@ -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.
*/
export const emailEnabledServices = ['google-mail', 'amazon-ses'];
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const baseConfig = await readConfigFile(require.resolve('../../../../config.base.ts'));
return {
...baseConfig.getAll(),
testFiles: [require.resolve('.')],
junit: {
reportName:
'Chrome X-Pack UI Functional Tests with ES SSL - Email services enabled Kibana config',
},
kbnTestServer: {
...baseConfig.getAll().kbnTestServer,
serverArgs: [
...baseConfig.getAll().kbnTestServer.serverArgs,
`--xpack.actions.email.services.enabled=${JSON.stringify(emailEnabledServices)}`,
],
},
};
}

View file

@ -0,0 +1,33 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
const find = getService('find');
describe('Email - with multiple enabled services config', () => {
beforeEach(async () => {
await pageObjects.common.navigateToApp('triggersActionsConnectors');
});
it('should use the kibana config for enabled services', async () => {
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
await testSubjects.click('.email-card');
const emailServicesOptions = await find.allByCssSelector(
'[data-test-subj="emailServiceSelectInput"] > option'
);
expect(emailServicesOptions.length).to.be(3);
expect(await emailServicesOptions[0].getVisibleText()).to.be(' '); // empty option
expect(await emailServicesOptions[1].getVisibleText()).to.be('Gmail');
expect(await emailServicesOptions[2].getVisibleText()).to.be('Amazon SES');
});
});
};

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FtrProviderContext } from '@kbn/test-suites-xpack-platform/alerting_api_integration/common/ftr_provider_context';
import {
buildUp,
tearDown,
} from '@kbn/test-suites-xpack-platform/alerting_api_integration/spaces_only/tests/helpers';
export default function actionsTests({ loadTestFile, getService }: FtrProviderContext) {
describe('Connectors with email services enabled Kibana config', () => {
before(async () => buildUp(getService));
after(async () => tearDown(getService));
loadTestFile(require.resolve('./email'));
});
}