From e0aa76d35788bbf9ab22a4027cc38c7b2180a9f2 Mon Sep 17 00:00:00 2001 From: terencecho Date: Thu, 31 Mar 2022 08:01:46 -0700 Subject: [PATCH] Create CustomerIO email notification client (#11220) * Create CustomerIO email notification client * remove unused docker yaml changes * Remove unused comments * Add unit test * Rename to customerio specific notification client * Rename email to customerio * re-build --- airbyte-api/src/main/openapi/config.yaml | 6 +- .../CustomerioNotificationConfiguration.yaml | 8 + .../main/resources/types/Notification.yaml | 2 + .../resources/types/NotificationType.yaml | 1 + .../CustomeriolNotificationClient.java | 141 ++++++++++++++++++ .../notification/NotificationClient.java | 25 +++- .../notification/SlackNotificationClient.java | 26 +++- .../auto_disable_notification_template.json | 19 +++ ...disable_warning_notification_template.json | 19 +++ .../failure_slack_notification_template.txt | 0 .../success_slack_notification_template.txt | 0 .../CustomerioNotificationClientTest.java | 69 +++++++++ .../scheduler/persistence/JobNotifier.java | 47 +++++- .../api/generated-api-html/index.html | 43 ++++-- 14 files changed, 380 insertions(+), 26 deletions(-) create mode 100644 airbyte-config/models/src/main/resources/types/CustomerioNotificationConfiguration.yaml create mode 100644 airbyte-notification/src/main/java/io/airbyte/notification/CustomeriolNotificationClient.java create mode 100644 airbyte-notification/src/main/resources/customerio/auto_disable_notification_template.json create mode 100644 airbyte-notification/src/main/resources/customerio/auto_disable_warning_notification_template.json rename airbyte-notification/src/main/resources/{ => slack}/failure_slack_notification_template.txt (100%) rename airbyte-notification/src/main/resources/{ => slack}/success_slack_notification_template.txt (100%) create mode 100644 airbyte-notification/src/test/java/io/airbyte/notification/CustomerioNotificationClientTest.java diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index b5060b6f38a8..2ecce6ed82d3 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -2290,6 +2290,8 @@ components: default: true slackConfiguration: $ref: "#/components/schemas/SlackNotificationConfiguration" + customerioConfiguration: + $ref: "#/components/schemas/CustomerioNotificationConfiguration" SlackNotificationConfiguration: type: object required: @@ -2297,11 +2299,13 @@ components: properties: webhook: type: string + CustomerioNotificationConfiguration: + type: object NotificationType: type: string enum: - slack - # - email + - customerio # - webhook NotificationRead: type: object diff --git a/airbyte-config/models/src/main/resources/types/CustomerioNotificationConfiguration.yaml b/airbyte-config/models/src/main/resources/types/CustomerioNotificationConfiguration.yaml new file mode 100644 index 000000000000..11ad66bb6d1e --- /dev/null +++ b/airbyte-config/models/src/main/resources/types/CustomerioNotificationConfiguration.yaml @@ -0,0 +1,8 @@ +--- +"$schema": http://json-schema.org/draft-07/schema# +"$id": https://github.com/airbytehq/airbyte/blob/master/airbyte-config/models/src/main/resources/types/CustomerioNotificationConfiguration.yaml +title: CustomerioNotificationConfiguration +description: Customer.io Notification Settings +type: object +additionalProperties: false +properties: diff --git a/airbyte-config/models/src/main/resources/types/Notification.yaml b/airbyte-config/models/src/main/resources/types/Notification.yaml index 3214c8cf4835..f35a9c410e6c 100644 --- a/airbyte-config/models/src/main/resources/types/Notification.yaml +++ b/airbyte-config/models/src/main/resources/types/Notification.yaml @@ -20,3 +20,5 @@ properties: default: true slackConfiguration: "$ref": SlackNotificationConfiguration.yaml + customerioConfiguration: + "$ref": CustomerioNotificationConfiguration.yaml diff --git a/airbyte-config/models/src/main/resources/types/NotificationType.yaml b/airbyte-config/models/src/main/resources/types/NotificationType.yaml index d291f0079177..cac050641b77 100644 --- a/airbyte-config/models/src/main/resources/types/NotificationType.yaml +++ b/airbyte-config/models/src/main/resources/types/NotificationType.yaml @@ -6,3 +6,4 @@ description: Type of notification type: string enum: - slack + - customerio diff --git a/airbyte-notification/src/main/java/io/airbyte/notification/CustomeriolNotificationClient.java b/airbyte-notification/src/main/java/io/airbyte/notification/CustomeriolNotificationClient.java new file mode 100644 index 000000000000..1d7ca81bd5f4 --- /dev/null +++ b/airbyte-notification/src/main/java/io/airbyte/notification/CustomeriolNotificationClient.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.notification; + +import com.google.common.annotations.VisibleForTesting; +import io.airbyte.commons.resources.MoreResources; +import io.airbyte.config.Notification; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import org.apache.commons.lang3.NotImplementedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Notification client that uses customer.io API send emails. + */ +public class CustomeriolNotificationClient extends NotificationClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(CustomeriolNotificationClient.class); + + // Once the configs are editable through the UI, these should be stored in + // airbyte-config/models/src/main/resources/types/CustomerioNotificationConfiguration.yaml + // - SENDER_EMAIL + // - receiver email + // - customer.io identifier email + // - customer.io TRANSACTION_MESSAGE_ID + private static final String SENDER_EMAIL = "Airbyte Notification "; + private static final String TRANSACTION_MESSAGE_ID = "6"; + + private static final String CUSTOMERIO_EMAIL_API_ENDPOINT = "https://api.customer.io/v1/send/email"; + private static final String AUTO_DISABLE_NOTIFICATION_TEMPLATE_PATH = "customerio/auto_disable_notification_template.json"; + private static final String AUTO_DISABLE_WARNING_NOTIFICATION_TEMPLATE_PATH = "customerio/auto_disable_warning_notification_template.json"; + + private final HttpClient httpClient; + private final String apiToken; + private final String emailApiEndpoint; + + public CustomeriolNotificationClient(final Notification notification) { + super(notification); + this.apiToken = System.getenv("CUSTOMERIO_API_KEY"); + this.emailApiEndpoint = CUSTOMERIO_EMAIL_API_ENDPOINT; + this.httpClient = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) + .build(); + } + + @VisibleForTesting + public CustomeriolNotificationClient(final Notification notification, + final String apiToken, + final String emailApiEndpoint, + final HttpClient httpClient) { + super(notification); + this.apiToken = apiToken; + this.emailApiEndpoint = emailApiEndpoint; + this.httpClient = httpClient; + } + + @Override + public boolean notifyJobFailure(final String sourceConnector, final String destinationConnector, final String jobDescription, final String logUrl) + throws IOException, InterruptedException { + throw new NotImplementedException(); + } + + @Override + public boolean notifyJobSuccess(final String sourceConnector, final String destinationConnector, final String jobDescription, final String logUrl) + throws IOException, InterruptedException { + throw new NotImplementedException(); + } + + @Override + public boolean notifyConnectionDisabled(final String receiverEmail, + final String sourceConnector, + final String destinationConnector, + final String jobDescription, + final String logUrl) + throws IOException, InterruptedException { + final String requestBody = renderTemplate(AUTO_DISABLE_NOTIFICATION_TEMPLATE_PATH, TRANSACTION_MESSAGE_ID, SENDER_EMAIL, receiverEmail, + receiverEmail, sourceConnector, destinationConnector, jobDescription, logUrl); + return notifyByEmail(requestBody); + } + + @Override + public boolean notifyConnectionDisableWarning( + final String receiverEmail, + final String sourceConnector, + final String destinationConnector, + final String jobDescription, + final String logUrl) + throws IOException, InterruptedException { + final String requestBody = renderTemplate(AUTO_DISABLE_WARNING_NOTIFICATION_TEMPLATE_PATH, TRANSACTION_MESSAGE_ID, SENDER_EMAIL, receiverEmail, + receiverEmail, sourceConnector, destinationConnector, jobDescription, logUrl); + return notifyByEmail(requestBody); + } + + @Override + public boolean notifySuccess(final String message) throws IOException, InterruptedException { + throw new NotImplementedException(); + } + + @Override + public boolean notifyFailure(final String message) throws IOException, InterruptedException { + throw new NotImplementedException(); + } + + private boolean notifyByEmail(final String requestBody) throws IOException, InterruptedException { + final HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .uri(URI.create(emailApiEndpoint)) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + apiToken) + .build(); + + final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + if (isSuccessfulHttpResponse(response.statusCode())) { + LOGGER.info("Successful notification ({}): {}", response.statusCode(), response.body()); + return true; + } else { + final String errorMessage = String.format("Failed to deliver notification (%s): %s", response.statusCode(), response.body()); + throw new IOException(errorMessage); + } + } + + /** + * Use an integer division to check successful HTTP status codes (i.e., those from 200-299), not + * just 200. https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + */ + private static boolean isSuccessfulHttpResponse(final int httpStatusCode) { + return httpStatusCode / 100 == 2; + } + + public String renderTemplate(final String templateFile, final String... data) throws IOException { + final String template = MoreResources.readResource(templateFile); + return String.format(template, data); + } + +} diff --git a/airbyte-notification/src/main/java/io/airbyte/notification/NotificationClient.java b/airbyte-notification/src/main/java/io/airbyte/notification/NotificationClient.java index 7de40e659fea..65bd08b67c8f 100644 --- a/airbyte-notification/src/main/java/io/airbyte/notification/NotificationClient.java +++ b/airbyte-notification/src/main/java/io/airbyte/notification/NotificationClient.java @@ -5,7 +5,6 @@ package io.airbyte.notification; import io.airbyte.config.Notification; -import io.airbyte.config.Notification.NotificationType; import java.io.IOException; public abstract class NotificationClient { @@ -32,16 +31,30 @@ public abstract boolean notifyJobSuccess( String logUrl) throws IOException, InterruptedException; + public abstract boolean notifyConnectionDisabled(String receiverEmail, + String sourceConnector, + String destinationConnector, + String jobDescription, + String logUrl) + throws IOException, InterruptedException; + + public abstract boolean notifyConnectionDisableWarning(String receiverEmail, + String sourceConnector, + String destinationConnector, + String jobDescription, + String logUrl) + throws IOException, InterruptedException; + public abstract boolean notifySuccess(String message) throws IOException, InterruptedException; public abstract boolean notifyFailure(String message) throws IOException, InterruptedException; public static NotificationClient createNotificationClient(final Notification notification) { - if (notification.getNotificationType() == NotificationType.SLACK) { - return new SlackNotificationClient(notification); - } else { - throw new IllegalArgumentException("Unknown notification type:" + notification.getNotificationType()); - } + return switch (notification.getNotificationType()) { + case SLACK -> new SlackNotificationClient(notification); + case CUSTOMERIO -> new CustomeriolNotificationClient(notification); + default -> throw new IllegalArgumentException("Unknown notification type:" + notification.getNotificationType()); + }; } } diff --git a/airbyte-notification/src/main/java/io/airbyte/notification/SlackNotificationClient.java b/airbyte-notification/src/main/java/io/airbyte/notification/SlackNotificationClient.java index 533781fb033b..1aa1135c56c0 100644 --- a/airbyte-notification/src/main/java/io/airbyte/notification/SlackNotificationClient.java +++ b/airbyte-notification/src/main/java/io/airbyte/notification/SlackNotificationClient.java @@ -15,6 +15,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import org.apache.commons.lang3.NotImplementedException; import org.apache.logging.log4j.util.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +48,7 @@ public SlackNotificationClient(final Notification notification) { public boolean notifyJobFailure(final String sourceConnector, final String destinationConnector, final String jobDescription, final String logUrl) throws IOException, InterruptedException { return notifyFailure(renderJobData( - "failure_slack_notification_template.txt", + "slack/failure_slack_notification_template.txt", sourceConnector, destinationConnector, jobDescription, @@ -58,13 +59,34 @@ public boolean notifyJobFailure(final String sourceConnector, final String desti public boolean notifyJobSuccess(final String sourceConnector, final String destinationConnector, final String jobDescription, final String logUrl) throws IOException, InterruptedException { return notifySuccess(renderJobData( - "success_slack_notification_template.txt", + "slack/success_slack_notification_template.txt", sourceConnector, destinationConnector, jobDescription, logUrl)); } + @Override + public boolean notifyConnectionDisabled(final String receiverEmail, + final String sourceConnector, + final String destinationConnector, + final String jobDescription, + final String logUrl) + throws IOException, InterruptedException { + throw new NotImplementedException(); + } + + @Override + public boolean notifyConnectionDisableWarning( + final String receiverEmail, + final String sourceConnector, + final String destinationConnector, + final String jobDescription, + final String logUrl) + throws IOException, InterruptedException { + throw new NotImplementedException(); + } + private String renderJobData(final String templateFile, final String sourceConnector, final String destinationConnector, diff --git a/airbyte-notification/src/main/resources/customerio/auto_disable_notification_template.json b/airbyte-notification/src/main/resources/customerio/auto_disable_notification_template.json new file mode 100644 index 000000000000..29d407b46c66 --- /dev/null +++ b/airbyte-notification/src/main/resources/customerio/auto_disable_notification_template.json @@ -0,0 +1,19 @@ +{ + "transactional_message_id": "%s", + "from": "%s", + "subject": "Connection Auto-Disabled", + "to": "%s", + "identifiers": { + "email": "%s" + }, + "message_data": { + "email_title": "Connection Auto-Disabled", + "email_body": "Your connection from %s to %s was disabled because it failed consecutively 100 times or that there were only failed jobs in the past 14 days.

Please address the failing issues and re-enable the connection. The most recent attempted %s You can access its logs here: %s" + }, + + "disable_message_retention": false, + "send_to_unsubscribed": true, + "tracked": true, + "queue_draft": false, + "disable_css_preprocessing": true +} diff --git a/airbyte-notification/src/main/resources/customerio/auto_disable_warning_notification_template.json b/airbyte-notification/src/main/resources/customerio/auto_disable_warning_notification_template.json new file mode 100644 index 000000000000..3157b664f148 --- /dev/null +++ b/airbyte-notification/src/main/resources/customerio/auto_disable_warning_notification_template.json @@ -0,0 +1,19 @@ +{ + "transactional_message_id": "%s", + "from": "%s", + "subject": "Connection Auto-Disabled Warning", + "to": "%s", + "identifiers": { + "email": "%s" + }, + "message_data": { + "email_title": "Connection Auto-Disabled Warning", + "email_body": "Your connection from %s to %s is about to be disabled because it failed consecutively 50 times or that there were only failed jobs in the past 7 days. Once it has failed 100 times consecutively or has been failing for 14 days in a row, the connection will be automatically disabled.

Please address the failing issues and re-enable the connection. The most recent attempted %s You can access its logs here: %s" + }, + + "disable_message_retention": false, + "send_to_unsubscribed": true, + "tracked": true, + "queue_draft": false, + "disable_css_preprocessing": true +} diff --git a/airbyte-notification/src/main/resources/failure_slack_notification_template.txt b/airbyte-notification/src/main/resources/slack/failure_slack_notification_template.txt similarity index 100% rename from airbyte-notification/src/main/resources/failure_slack_notification_template.txt rename to airbyte-notification/src/main/resources/slack/failure_slack_notification_template.txt diff --git a/airbyte-notification/src/main/resources/success_slack_notification_template.txt b/airbyte-notification/src/main/resources/slack/success_slack_notification_template.txt similarity index 100% rename from airbyte-notification/src/main/resources/success_slack_notification_template.txt rename to airbyte-notification/src/main/resources/slack/success_slack_notification_template.txt diff --git a/airbyte-notification/src/test/java/io/airbyte/notification/CustomerioNotificationClientTest.java b/airbyte-notification/src/test/java/io/airbyte/notification/CustomerioNotificationClientTest.java new file mode 100644 index 000000000000..d6f342ee5c95 --- /dev/null +++ b/airbyte-notification/src/test/java/io/airbyte/notification/CustomerioNotificationClientTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.notification; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import io.airbyte.config.Notification; +import io.airbyte.config.Notification.NotificationType; +import io.airbyte.config.StandardWorkspace; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +class CustomerioNotificationClientTest { + + private static final String API_KEY = "api-key"; + private static final String URI_BASE = "https://customer.io"; + private static final UUID WORKSPACE_ID = UUID.randomUUID(); + private static final StandardWorkspace WORKSPACE = new StandardWorkspace() + .withWorkspaceId(WORKSPACE_ID) + .withName("workspace-name") + .withEmail("test@airbyte.io"); + private static final String RANDOM_INPUT = "input"; + + @Mock + private HttpClient mHttpClient; + + @BeforeEach + void setUp() { + mHttpClient = mock(HttpClient.class); + } + + // this only tests that the headers are set correctly and that a http post request is sent to the + // correct URI + // this test does _not_ check the body of the request. + @Test + void notifyConnectionDisabled() throws IOException, InterruptedException { + final CustomeriolNotificationClient customeriolNotificationClient = new CustomeriolNotificationClient(new Notification() + .withNotificationType(NotificationType.CUSTOMERIO), API_KEY, URI_BASE, mHttpClient); + + final HttpRequest expectedRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString("")) + .uri(URI.create(URI_BASE)) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + API_KEY) + .build(); + + final HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(mHttpClient.send(Mockito.any(), Mockito.any())).thenReturn(httpResponse); + Mockito.when(httpResponse.statusCode()).thenReturn(200); + + final boolean result = + customeriolNotificationClient.notifyConnectionDisabled(WORKSPACE.getEmail(), RANDOM_INPUT, RANDOM_INPUT, RANDOM_INPUT, RANDOM_INPUT); + Mockito.verify(mHttpClient).send(expectedRequest, HttpResponse.BodyHandlers.ofString()); + + assertTrue(result); + } + +} diff --git a/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/JobNotifier.java b/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/JobNotifier.java index 4733d0f65242..678d9f1ce817 100644 --- a/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/JobNotifier.java +++ b/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/JobNotifier.java @@ -23,6 +23,8 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; +import java.util.Collections; +import java.util.List; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +35,8 @@ public class JobNotifier { public static final String FAILURE_NOTIFICATION = "Failure Notification"; public static final String SUCCESS_NOTIFICATION = "Success Notification"; + public static final String CONNECTION_DISABLED_WARNING_NOTIFICATION = "Connection Disabled Warning Notification"; + public static final String CONNECTION_DISABLED_NOTIFICATION = "Connection Disabled Notification"; private final String connectionPageUrl; private final ConfigRepository configRepository; @@ -54,6 +58,21 @@ public JobNotifier(final String webappUrl, } private void notifyJob(final String reason, final String action, final Job job) { + try { + final UUID workspaceId = workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(job.getId()); + final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); + notifyJob(reason, action, job, workspaceId, workspace, workspace.getNotifications()); + } catch (final Exception e) { + LOGGER.error("Unable to read configuration:", e); + } + } + + private void notifyJob(final String reason, + final String action, + final Job job, + final UUID workspaceId, + final StandardWorkspace workspace, + final List notifications) { final UUID connectionId = UUID.fromString(job.getScope()); try { final StandardSourceDefinition sourceDefinition = configRepository.getSourceDefinitionFromConnection(connectionId); @@ -73,12 +92,10 @@ private void notifyJob(final String reason, final String action, final Job job) final String jobDescription = String.format("sync started on %s, running for%s%s.", formatter.format(jobStartedDate), durationString, failReason); final String logUrl = connectionPageUrl + connectionId; - final UUID workspaceId = workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(job.getId()); - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); final ImmutableMap jobMetadata = TrackingMetadata.generateJobAttemptMetadata(job); final ImmutableMap sourceMetadata = TrackingMetadata.generateSourceDefinitionMetadata(sourceDefinition); final ImmutableMap destinationMetadata = TrackingMetadata.generateDestinationDefinitionMetadata(destinationDefinition); - for (final Notification notification : workspace.getNotifications()) { + for (final Notification notification : notifications) { final NotificationClient notificationClient = getNotificationClient(notification); try { final Builder notificationMetadata = ImmutableMap.builder(); @@ -87,6 +104,8 @@ private void notifyJob(final String reason, final String action, final Job job) notification.getSlackConfiguration().getWebhook().contains("hooks.slack.com")) { // flag as slack if the webhook URL is also pointing to slack notificationMetadata.put("notification_type", NotificationType.SLACK); + } else if (notification.getNotificationType().equals(NotificationType.CUSTOMERIO)) { + notificationMetadata.put("notification_type", NotificationType.CUSTOMERIO); } else { // Slack Notification type could be "hacked" and re-used for custom webhooks notificationMetadata.put("notification_type", "N/A"); @@ -103,6 +122,16 @@ private void notifyJob(final String reason, final String action, final Job job) if (!notificationClient.notifyJobSuccess(sourceConnector, destinationConnector, jobDescription, logUrl)) { LOGGER.warn("Failed to successfully notify success: {}", notification); } + // alert message currently only supported by email through customer.io + } else if (CONNECTION_DISABLED_NOTIFICATION.equals(action) && notification.getNotificationType().equals(NotificationType.CUSTOMERIO)) { + if (!notificationClient.notifyConnectionDisabled(workspace.getEmail(), sourceConnector, destinationConnector, jobDescription, logUrl)) { + LOGGER.warn("Failed to successfully notify auto-disable connection: {}", notification); + } + } else if (CONNECTION_DISABLED_WARNING_NOTIFICATION.equals(action) + && notification.getNotificationType().equals(NotificationType.CUSTOMERIO)) { + if (!notificationClient.notifyConnectionDisabled(workspace.getEmail(), sourceConnector, destinationConnector, jobDescription, logUrl)) { + LOGGER.warn("Failed to successfully notify auto-disable connection warning: {}", notification); + } } } catch (final Exception e) { LOGGER.error("Failed to notify: {} due to an exception", notification, e); @@ -113,6 +142,18 @@ private void notifyJob(final String reason, final String action, final Job job) } } + public void notifyJobByEmail(final String reason, final String action, final Job job) { + final Notification emailNotification = new Notification(); + emailNotification.setNotificationType(NotificationType.CUSTOMERIO); + try { + final UUID workspaceId = workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(job.getId()); + final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); + notifyJob(reason, action, job, workspaceId, workspace, Collections.singletonList(emailNotification)); + } catch (final Exception e) { + LOGGER.error("Unable to read configuration:", e); + } + } + public void failJob(final String reason, final Job job) { notifyJob(reason, FAILURE_NOTIFICATION, job); } diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 84e32e6e0af0..a238c54c7ee6 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -9101,13 +9101,15 @@

Example data

"webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" }, { "slackConfiguration" : { "webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" } ], "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } @@ -9228,13 +9230,15 @@

Example data

"webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" }, { "slackConfiguration" : { "webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" } ], "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } @@ -9310,13 +9314,15 @@

Example data

"webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" }, { "slackConfiguration" : { "webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" } ], "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } @@ -9381,13 +9387,15 @@

Example data

"webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" }, { "slackConfiguration" : { "webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" } ], "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" }, { @@ -9407,13 +9415,15 @@

Example data

"webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" }, { "slackConfiguration" : { "webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" } ], "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } ] @@ -9484,13 +9494,15 @@

Example data

"webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" }, { "slackConfiguration" : { "webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" } ], "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } @@ -9611,13 +9623,15 @@

Example data

"webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" }, { "slackConfiguration" : { "webhook" : "webhook" }, "sendOnSuccess" : false, - "sendOnFailure" : true + "sendOnFailure" : true, + "customerioConfiguration" : "{}" } ], "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } @@ -10519,6 +10533,7 @@

Notification - sendOnSuccess
sendOnFailure
slackConfiguration (optional)
+
customerioConfiguration (optional)