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

feat(sendgrid): attachment support #3891

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"keywords" : [ ]
},
"documentationRef" : "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/sendgrid/",
"version" : 3,
"version" : 4,
"category" : {
"id" : "connectors",
"name" : "Connectors"
Expand Down Expand Up @@ -233,6 +233,18 @@
"type" : "simple"
},
"type" : "Text"
}, {
"id" : "attachments",
"label" : "attachments",
"description" : "List of <a href=\"https://docs.camunda.io/docs/next/apis-tools/camunda-api-rest/specifications/create-document-link/\">Camunda Documents</a>",
"optional" : true,
"feel" : "required",
"group" : "content",
"binding" : {
"name" : "attachments",
"type" : "zeebe:input"
},
"type" : "String"
}, {
"id" : "resultVariable",
"label" : "Result variable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"keywords" : [ ]
},
"documentationRef" : "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/sendgrid/",
"version" : 3,
"version" : 4,
"category" : {
"id" : "connectors",
"name" : "Connectors"
Expand Down Expand Up @@ -228,6 +228,18 @@
"type" : "simple"
},
"type" : "Text"
}, {
"id" : "attachments",
"label" : "attachments",
"description" : "List of <a href=\"https://docs.camunda.io/docs/next/apis-tools/camunda-api-rest/specifications/create-document-link/\">Camunda Documents</a>",
"optional" : true,
"feel" : "required",
"group" : "content",
"binding" : {
"name" : "attachments",
"type" : "zeebe:input"
},
"type" : "String"
}, {
"id" : "resultVariable",
"label" : "Result variable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,29 @@
import com.sendgrid.Response;
import com.sendgrid.SendGrid;
import com.sendgrid.helpers.mail.Mail;
import com.sendgrid.helpers.mail.objects.Attachments;
import com.sendgrid.helpers.mail.objects.Personalization;
import io.camunda.connector.api.annotation.OutboundConnector;
import io.camunda.connector.api.outbound.OutboundConnectorContext;
import io.camunda.connector.api.outbound.OutboundConnectorFunction;
import io.camunda.connector.generator.java.annotation.ElementTemplate;
import io.camunda.connector.sendgrid.model.SendGridRequest;
import io.camunda.document.Document;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@OutboundConnector(
name = "SendGrid",
inputVariables = {"apiKey", "from", "to", "template", "content"},
inputVariables = {"apiKey", "from", "to", "template", "content", "attachments"},
type = "io.camunda:sendgrid:1")
@ElementTemplate(
id = "io.camunda.connectors.SendGrid.v2",
name = "SendGrid Outbound Connector",
description = "Send an email via SendGrid",
inputDataClass = SendGridRequest.class,
version = 3,
version = 4,
propertyGroups = {
@ElementTemplate.PropertyGroup(id = "authentication", label = "Authentication"),
@ElementTemplate.PropertyGroup(id = "sender", label = "Sender"),
Expand All @@ -43,11 +46,9 @@
icon = "icon.svg")
public class SendGridFunction implements OutboundConnectorFunction {

private static final Logger LOGGER = LoggerFactory.getLogger(SendGridFunction.class);

protected static final ObjectMapper objectMapper =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

private static final Logger LOGGER = LoggerFactory.getLogger(SendGridFunction.class);
private final SendGridClientSupplier sendGridSupplier;

public SendGridFunction() {
Expand Down Expand Up @@ -84,6 +85,7 @@ private Mail createEmail(final SendGridRequest request) {
mail.setFrom(request.getInnerSenGridEmailFrom());
addContentIfPresent(mail, request);
addTemplateIfPresent(mail, request);
addAttachmentIfPresent(mail, request.getAttachments());

return mail;
}
Expand All @@ -98,6 +100,18 @@ private void addTemplateIfPresent(final Mail mail, final SendGridRequest request
}
}

private void addAttachmentIfPresent(final Mail mail, List<Document> documents) {
if (documents != null && !documents.isEmpty()) {
documents.forEach(
document -> {
Attachments attachments =
new Attachments.Builder(document.metadata().getFileName(), document.asInputStream())
.build();
mail.addAttachments(attachments);
});
}
}

private void addContentIfPresent(final Mail mail, final SendGridRequest request) {
if (request.hasContent()) {
final SendGridRequest.Content content = request.getContent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
import io.camunda.connector.generator.java.annotation.TemplateProperty.DropdownPropertyChoice;
import io.camunda.connector.generator.java.annotation.TemplateProperty.PropertyBinding;
import io.camunda.connector.generator.java.annotation.TemplateProperty.PropertyCondition;
import io.camunda.document.Document;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -99,6 +101,16 @@ public record Content(
@Valid
private Content content;

@TemplateProperty(
id = "attachments",
group = "content",
label = "attachments",
optional = true,
feel = Property.FeelMode.required,
description =
"List of <a href=\"https://docs.camunda.io/docs/next/apis-tools/camunda-api-rest/specifications/create-document-link/\">Camunda Documents</a>")
private List<Document> attachments;

@AssertTrue(message = "must not be empty")
private boolean isSenderName() {
return from != null && isNotBlank(from.name());
Expand Down Expand Up @@ -192,6 +204,14 @@ public void setMailType(MailType mailType) {
this.mailType = mailType;
}

public List<Document> getAttachments() {
return attachments;
}

public void setAttachments(List<Document> attachments) {
this.attachments = attachments;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand All @@ -205,12 +225,13 @@ public boolean equals(final Object o) {
&& Objects.equals(from, that.from)
&& Objects.equals(to, that.to)
&& Objects.equals(template, that.template)
&& Objects.equals(content, that.content);
&& Objects.equals(content, that.content)
&& Objects.equals(attachments, that.attachments);
}

@Override
public int hashCode() {
return Objects.hash(apiKey, from, to, template, content);
return Objects.hash(apiKey, from, to, template, content, attachments);
}

@Override
Expand All @@ -225,6 +246,8 @@ public String toString() {
+ template
+ ", content="
+ content
+ ", attachments="
+ attachments
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ protected interface ActualValue {
String RECEIVER_NAME = "Jane Doe";
String SENDER_EMAIL = "[email protected]";
String SENDER_NAME = "John Doe";
String ATTACHED_FILE_NAME = "google-my-business-logo-png-transparent.png";

interface Content {
String SUBJECT = "subject_test";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.sendgrid.Method;
import com.sendgrid.Request;
import com.sendgrid.Response;
import com.sendgrid.SendGrid;
import io.camunda.client.api.response.DocumentMetadata;
import io.camunda.connector.api.outbound.OutboundConnectorContext;
import io.camunda.connector.sendgrid.model.SendGridRequest;
import io.camunda.connector.test.outbound.OutboundConnectorContextBuilder;
import io.camunda.document.Document;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import org.junit.jupiter.api.Assertions;
Expand All @@ -42,6 +43,8 @@ public class SendGridFunctionTest extends BaseTest {
private static final String PERSONALIZATION_JSON_NAME = "personalizations";
private static final String NAME_JSON_NAME = "name";
private static final String EMAIL_JSON_NAME = "email";
private static final String ATTACHMENTS_JSON_NAME = "attachments";
private static final String ATTACHMENTS_FILE_NAME_JSON_NAME = "filename";

private OutboundConnectorContext context;
private SendGridFunction function;
Expand Down Expand Up @@ -107,9 +110,15 @@ public void execute_shouldReturnNullIfResponseStatusCodeIs202(int statusCode) th
public void execute_shouldCreateRequestWithMailAndExpectedData(String input) throws Exception {
// Given
context = contextBuilder.variables(input).build();
ArgumentCaptor<Request> requestArgumentCaptor = ArgumentCaptor.forClass(Request.class);

var contextWithMocketDocument = mock(OutboundConnectorContext.class);

var requestWithMockedDocument = prepareRequestWithDocumentMock(context);

when(contextWithMocketDocument.bindVariables(any())).thenReturn(requestWithMockedDocument);

// When
function.execute(context);
function.execute(contextWithMocketDocument);
verify(sendGridMock).api(requestArgumentCaptor.capture());
// Then we have POST request with mail participants,
Request requestValue = requestArgumentCaptor.getValue();
Expand All @@ -122,36 +131,59 @@ public void execute_shouldCreateRequestWithMailAndExpectedData(String input) thr
requestJsonObject.withObject(
JsonPointer.valueOf("/" + PERSONALIZATION_JSON_NAME + "/0/" + TO_JSON_NAME + "/0"));

var array = (ArrayNode) requestJsonObject.get(ATTACHMENTS_JSON_NAME);
String attachmentName = array.get(0).get(ATTACHMENTS_FILE_NAME_JSON_NAME).textValue();

assertThat(from.get(NAME_JSON_NAME).textValue()).isEqualTo(ActualValue.SENDER_NAME);
assertThat(from.get(EMAIL_JSON_NAME).textValue()).isEqualTo(ActualValue.SENDER_EMAIL);
assertThat(to.get(NAME_JSON_NAME).textValue()).isEqualTo(ActualValue.RECEIVER_NAME);
assertThat(to.get(EMAIL_JSON_NAME).textValue()).isEqualTo(ActualValue.RECEIVER_EMAIL);
assertThat(attachmentName).isEqualTo(ActualValue.ATTACHED_FILE_NAME);
}

@ParameterizedTest(name = "Should send mail with template. Test case # {index}")
@MethodSource("successSendMailByTemplateRequestCases")
public void execute_shouldSendMailByTemplateIfTemplateExist(String input) throws Exception {
// Given
context = contextBuilder.variables(input).build();
ArgumentCaptor<Request> requestArgumentCaptor = ArgumentCaptor.forClass(Request.class);

var contextWithMocketDocument = mock(OutboundConnectorContext.class);

var requestWithMockedDocument = prepareRequestWithDocumentMock(context);

when(contextWithMocketDocument.bindVariables(any())).thenReturn(requestWithMockedDocument);

// When
function.execute(context);
function.execute(contextWithMocketDocument);
verify(sendGridMock).api(requestArgumentCaptor.capture());
// Then we have 'template_id' in sendGridRequest with expected ID and 'content' is not exist
Request requestValue = requestArgumentCaptor.getValue();

var requestJsonObject = objectMapper.readTree(requestValue.getBody());

var array = (ArrayNode) requestJsonObject.get(ATTACHMENTS_JSON_NAME);
String attachmentName = array.get(0).get(ATTACHMENTS_FILE_NAME_JSON_NAME).textValue();
assertThat(requestJsonObject.get(TEMPLATE_ID_JSON_NAME).textValue())
.isEqualTo(ActualValue.Template.ID);
assertThat(requestJsonObject.has(CONTENT_JSON_NAME)).isFalse();
assertThat(attachmentName).isEqualTo(ActualValue.ATTACHED_FILE_NAME);
}

@ParameterizedTest(name = "Should send mail with content. Test case # {index}")
@MethodSource("successSendMailWithContentRequestCases")
public void execute_shouldSendMailIfContentExist(String input) throws Exception {
// Given
context = contextBuilder.variables(input).build();

var contextWithMocketDocument = mock(OutboundConnectorContext.class);

var requestWithMockedDocument = prepareRequestWithDocumentMock(context);

when(contextWithMocketDocument.bindVariables(any())).thenReturn(requestWithMockedDocument);

// When
function.execute(context);
function.execute(contextWithMocketDocument);

verify(sendGridMock).api(requestArgumentCaptor.capture());
// Then we have 'content' in sendGridRequest with expected data and template ID is not exist
var requestJsonObject = objectMapper.readTree(requestArgumentCaptor.getValue().getBody());
Expand All @@ -166,4 +198,19 @@ public void execute_shouldSendMailIfContentExist(String input) throws Exception

assertThat(requestJsonObject.has(TEMPLATE_ID_JSON_NAME)).isFalse();
}

private SendGridRequest prepareRequestWithDocumentMock(OutboundConnectorContext context) {
var request = context.bindVariables(SendGridRequest.class);

var document = mock(Document.class);
var documentMetadata = mock(DocumentMetadata.class);
when(document.metadata()).thenReturn(documentMetadata);

String fileName = request.getAttachments().getFirst().metadata().getFileName();
when(documentMetadata.getFileName()).thenReturn(fileName);
when(document.asInputStream()).thenReturn(new ByteArrayInputStream(new byte[0]));

request.setAttachments(List.of(document));
return request;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@
"shipAddress": "{{secrets.TEMPLATE_DATA_SHIP_ADDRESS}}",
"shipZip": "{{secrets.TEMPLATE_DATA_SHIP_ZIP}}"
}
}
},
"attachments" : [ {
"camunda.document.type" : "camunda",
"storeId" : "in-memory",
"documentId" : "2ea3bfcc-7683-4cb6-b0ce-8052ca1d6da0",
"metadata" : {
"contentType" : "image/png",
"fileName" : "google-my-business-logo-png-transparent.png",
"size" : 66497,
"customProperties" : { }
}
}]
},
{
"apiKey": "send_grid_key",
Expand All @@ -35,7 +46,18 @@
"shipAddress": "Krossener Str. 24",
"shipZip": "10245"
}
}
},
"attachments" : [ {
"camunda.document.type" : "camunda",
"storeId" : "in-memory",
"documentId" : "2ea3bfcc-7683-4cb6-b0ce-8052ca1d6da0",
"metadata" : {
"contentType" : "image/png",
"fileName" : "google-my-business-logo-png-transparent.png",
"size" : 66497,
"customProperties" : { }
}
}]
},
{
"to":{
Expand Down Expand Up @@ -78,6 +100,17 @@
]
},
"id":"d-0b51e8f77bf8450fae379e0639ca0d11"
}
},
"attachments" : [ {
"camunda.document.type" : "camunda",
"storeId" : "in-memory",
"documentId" : "2ea3bfcc-7683-4cb6-b0ce-8052ca1d6da0",
"metadata" : {
"contentType" : "image/png",
"fileName" : "google-my-business-logo-png-transparent.png",
"size" : 66497,
"customProperties" : { }
}
}]
}
]
Loading
Loading