Skip to content

Commit

Permalink
Support attachments (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
if0s authored Aug 4, 2023
1 parent 84c959e commit dd3202e
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.1.0 (August 04, 2023)
* Added checkbox `Get Attachment` to `Poll for New Mail` trigger
* Added metadata field `Attachments` to `Send Mail` action

## 2.0.0 (July 19, 2023)
* Breaking change! Reworked authentication mechanism - implemented Secrets feature
* Add new action - Send Mail
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ Per one execution it is possible to poll 1000 mails by defaults, this can be cha
* **Mail Folder** - Dropdown list with available Outlook mail folders
* **Start Time** - Start datetime of polling. Defaults: `1970-01-01T00:00:00.000Z`
* **Poll Only Unread Mail** - CheckBox, if set, only unread mails will be poll
* **Get Attachment** - CheckBox, if checked, email attachments will be downloaded to the platform and the link will be provided as a part of the output metadata with the key `attachments`
* **Emit Behavior** - Options are: default is `Emit Individually` emits each mail in separate message, `Emit All` emits all found mails in one message

#### Expected output metadata
Expand Down Expand Up @@ -182,3 +183,4 @@ In case of a success, output metadata simply repeats the incoming message. I.e.
```

## Known limitations
* When utilizing the `Send Mail` action with attachments, the component operates smoothly with files up to 20MB by default. However, if you intend to work with larger files, it is advisable to create or increase the environment variable `EIO_REQUIRED_RAM_MB`, which serves as the memory usage limit for the component, initially set at 256MB
6 changes: 5 additions & 1 deletion component.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "Outlook",
"description": "elastic.io integration component for Office 365 Outlook REST API",
"version": "2.0.0",
"version": "2.1.0",
"authClientTypes": [
"oauth2"
],
Expand Down Expand Up @@ -55,6 +55,10 @@
"label": "Poll Only Unread Mail",
"viewClass": "CheckBoxView"
},
"getAttachment": {
"label": "Get Attachment",
"viewClass": "CheckBoxView"
},
"emitBehavior": {
"viewClass": "SelectView",
"prompt": "Select Emit Behavior, defaults to Emit Individually",
Expand Down
22 changes: 21 additions & 1 deletion lib/OutlookClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ class OutlookClient extends Client {
}

async sendMail(msg) {
const message = utils.buildMessage(msg);
const message = await utils.buildMessage(msg);
await this.apiRequest({
url: '/me/sendMail',
method: 'POST',
data: message,
maxContentLength: Infinity,
maxBodyLength: Infinity,
});
}

Expand Down Expand Up @@ -47,6 +49,24 @@ class OutlookClient extends Client {
return response.data.value;
}

async getAttachments(folderId, mailId) {
const url = `/me/mailFolders/${folderId}/messages/${mailId}/attachments`;
const response = await this.apiRequest({
url,
method: 'GET',
});
return response.data.value;
}

async downloadAttachment(folderId, mailId, attachmentId) {
const url = `/me/mailFolders/${folderId}/messages/${mailId}/attachments/${attachmentId}/$value`;
return this.apiRequest({
url,
method: 'GET',
responseType: 'stream',
});
}

async getMyLatestContacts(lastModifiedDateTime) {
const response = await this.apiRequest({
url: `/me/contacts?$orderby=lastModifiedDateTime asc&$top=900&$filter=lastModifiedDateTime gt ${lastModifiedDateTime}`,
Expand Down
22 changes: 21 additions & 1 deletion lib/schemas/readMail.out.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@
"required": true
}
}
},
"attachments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"url": {
"type": "string"
},
"contentType": {
"type": "string"
},
"size": {
"type": "number"
}
}
}
}
}
}
}
24 changes: 22 additions & 2 deletions lib/schemas/sendMail.in.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,34 @@
"type": "string",
"required": false,
"name": "The type of the content. Possible values are text and html",
"enum": ["text", "html"]
"enum": [
"text",
"html"
]
}
}
},
"saveToSentItems": {
"type": "boolean",
"required": false,
"name": "Save to Sent items"
},
"attachments": {
"type": "array",
"title": "Attachments",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "File name"
},
"url": {
"type": "string",
"title": "URL to file"
}
}
}
}
}
}
}
40 changes: 38 additions & 2 deletions lib/triggers/readMail.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
/* eslint-disable no-param-reassign */
/* eslint-disable no-param-reassign, no-restricted-syntax, no-loop-func */
const { messages } = require('elasticio-node');
const commons = require('@elastic.io/component-commons-library');
const { OutlookClient } = require('../OutlookClient');
const { getFolders } = require('../utils/selectViewModels');
const {
getUserAgent,
} = require('../utils/utils');

/**
* @type {OutlookClient}
*/
let client;

exports.process = async function process(msg, cfg, snapshot) {
this.logger.info('Poll for New Mail trigger starting...');
const {
folderId, startTime, pollOnlyUnreadMail, emitBehavior = 'emitIndividually',
folderId, startTime, pollOnlyUnreadMail, emitBehavior = 'emitIndividually', getAttachment,
} = cfg;
snapshot.lastModifiedDateTime = snapshot.lastModifiedDateTime || startTime || new Date(0).toISOString();
this.logger.info(`Getting message from specified folder, starting from: ${snapshot.lastModifiedDateTime}, pollOnlyUnreadMail ${pollOnlyUnreadMail || false}`);
Expand All @@ -18,6 +25,35 @@ exports.process = async function process(msg, cfg, snapshot) {
this.logger.debug('Results are received');
if (results.length > 0) {
this.logger.info(`New Mails found, going to emit, Emit Behavior: ${emitBehavior}...`);
if (getAttachment) {
const mailsWithAttachments = results.filter((mail) => mail.hasAttachments);
if (mailsWithAttachments.length > 0) {
this.logger.info(`${mailsWithAttachments.length} mails with attachments found`);
const attachmentsProcessor = new commons.AttachmentProcessor(getUserAgent(), msg.id);
for (const mail of mailsWithAttachments) {
const mailAttachments = await client.getAttachments(folderId, mail.id);
this.logger.info(`${mailAttachments.length} attachments in mail found, going to download`);
for (const mailAttachment of mailAttachments) {
const getAttachmentFunction = async () => (await client.downloadAttachment(folderId, mail.id, mailAttachment.id)).data;
const attachmentId = await attachmentsProcessor.uploadAttachment(getAttachmentFunction);
const attachmentUrl = attachmentsProcessor.getMaesterAttachmentUrlById(attachmentId);
const attachment = {
name: mailAttachment.name,
contentType: mailAttachment.contentType,
size: mailAttachment.size,
url: attachmentUrl,
};
if (mail.attachments) {
mail.attachments.push(attachment);
} else {
mail.attachments = [attachment];
}
}
}
} else {
this.logger.info('Mails with attachments not found');
}
}
switch (emitBehavior) {
case 'emitIndividually':
this.logger.info('Starting to emit individually found mails...');
Expand Down
33 changes: 31 additions & 2 deletions lib/utils/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* eslint-disable no-restricted-syntax */
const { axiosReqWithRetryOnServerError } = require('@elastic.io/component-commons-library/dist/src/externalApi');
const { version, dependencies } = require('@elastic.io/component-commons-library/package.json');
const commons = require('@elastic.io/component-commons-library');
const compJson = require('../../component.json');
const packageJson = require('../../package.json');

const auth = {
username: process.env.ELASTICIO_API_USERNAME,
Expand Down Expand Up @@ -33,14 +36,36 @@ module.exports.refreshSecret = async (credentialId) => {

module.exports.isNumberNaN = (num) => Number(num).toString() === 'NaN';

module.exports.buildMessage = (msg) => {
const getUserAgent = () => {
const { version: compVersion } = compJson;
const libVersion = packageJson.dependencies['@elastic.io/component-commons-library'];
return `${compJson.title}/${compVersion} component-commons-library/${libVersion}`;
};

module.exports.buildMessage = async (msg) => {
const {
ccRecipients,
saveToSentItems,
subject,
toRecipients,
attachments: attachmentsIn,
} = msg.body;
return {
const attachmentsOut = [];
if (attachmentsIn && attachmentsIn.length > 0) {
const attachmentsProcessor = new commons.AttachmentProcessor(getUserAgent(), msg.id);
for (const attachment of attachmentsIn) {
const { name, url } = attachment;
if (!name || !url) throw new Error('Both "File name" and "URL to file" fields must be provided');
const { data } = await attachmentsProcessor.getAttachment(url, 'arraybuffer');
const attachmentBody = {
'@odata.type': '#microsoft.graph.fileAttachment',
name,
contentBytes: Buffer.from(data, 'binary').toString('base64'),
};
attachmentsOut.push(attachmentBody);
}
}
const result = {
message: {
ccRecipients,
body: {
Expand All @@ -52,4 +77,8 @@ module.exports.buildMessage = (msg) => {
},
saveToSentItems: saveToSentItems || true,
};
if (attachmentsOut.length > 0) result.message.attachments = attachmentsOut;
return result;
};

module.exports.getUserAgent = getUserAgent;

0 comments on commit dd3202e

Please sign in to comment.