n8n/packages/nodes-base/nodes/Microsoft/Outlook/v2/helpers/utils.ts
Iván Ovejero e77fd5d286
refactor: Switch plain errors in nodes-base to ApplicationError (no-changelog) (#7914)
Ensure all errors in `nodes-base` are `ApplicationError` or children of
it and contain no variables in the message, to continue normalizing all
the backend errors we report to Sentry. Also, skip reporting to Sentry
errors from user input and from external APIs. In future we should
refine `ApplicationError` to more specific errors.

Follow-up to: [#7877](https://github.com/n8n-io/n8n/pull/7877)

- [x] Test workflows:
https://github.com/n8n-io/n8n/actions/runs/7084627970
- [x] e2e: https://github.com/n8n-io/n8n/actions/runs/7084936861

---------

Co-authored-by: Michael Kret <michael.k@radency.com>
2023-12-05 11:17:08 +01:00

305 lines
7.2 KiB
TypeScript

import type {
IDataObject,
IExecuteFunctions,
IExecuteSingleFunctions,
ILoadOptionsFunctions,
JsonObject,
} from 'n8n-workflow';
import { ApplicationError, jsonParse, NodeApiError } from 'n8n-workflow';
export const messageFields = [
'bccRecipients',
'body',
'bodyPreview',
'categories',
'ccRecipients',
'changeKey',
'conversationId',
'createdDateTime',
'flag',
'from',
'hasAttachments',
'importance',
'inferenceClassification',
'internetMessageId',
'isDeliveryReceiptRequested',
'isDraft',
'isRead',
'isReadReceiptRequested',
'lastModifiedDateTime',
'parentFolderId',
'receivedDateTime',
'replyTo',
'sender',
'sentDateTime',
'subject',
'toRecipients',
'webLink',
].map((field) => ({ name: field, value: field }));
export const eventfields = [
'allowNewTimeProposals',
'attendees',
'body',
'bodyPreview',
'categories',
'changeKey',
'createdDateTime',
'end',
'hasAttachments',
'hideAttendees',
'iCalUId',
'importance',
'isAllDay',
'isCancelled',
'isDraft',
'isOnlineMeeting',
'isOrganizer',
'isReminderOn',
'lastModifiedDateTime',
'location',
'locations',
'onlineMeeting',
'onlineMeetingProvider',
'onlineMeetingUrl',
'organizer',
'originalEndTimeZone',
'originalStartTimeZone',
'recurrence',
'reminderMinutesBeforeStart',
'responseRequested',
'responseStatus',
'sensitivity',
'seriesMasterId',
'showAs',
'start',
'subject',
'transactionId',
'type',
'webLink',
].map((field) => ({ name: field, value: field }));
export const contactFields = [
'createdDateTime',
'lastModifiedDateTime',
'changeKey',
'categories',
'parentFolderId',
'birthday',
'fileAs',
'displayName',
'givenName',
'initials',
'middleName',
'nickName',
'surname',
'title',
'yomiGivenName',
'yomiSurname',
'yomiCompanyName',
'generation',
'imAddresses',
'jobTitle',
'companyName',
'department',
'officeLocation',
'profession',
'businessHomePage',
'assistantName',
'manager',
'homePhones',
'mobilePhone',
'businessPhones',
'spouseName',
'personalNotes',
'children',
'emailAddresses',
'homeAddress',
'businessAddress',
'otherAddress',
].map((field) => ({ name: field, value: field }));
export function makeRecipient(email: string) {
return {
emailAddress: {
address: email,
},
};
}
export function createMessage(fields: IDataObject) {
const message: IDataObject = {};
// Create body object
if (fields.bodyContent || fields.bodyContentType) {
const bodyObject = {
content: fields.bodyContent,
contentType: fields.bodyContentType,
};
message.body = bodyObject;
delete fields.bodyContent;
delete fields.bodyContentType;
}
// Handle custom headers
if (
'internetMessageHeaders' in fields &&
'headers' in (fields.internetMessageHeaders as IDataObject)
) {
fields.internetMessageHeaders = (fields.internetMessageHeaders as IDataObject).headers;
}
for (const [key, value] of Object.entries(fields)) {
if (['bccRecipients', 'ccRecipients', 'replyTo', 'sender', 'toRecipients'].includes(key)) {
if (Array.isArray(value)) {
message[key] = (value as string[]).map((email) => makeRecipient(email));
} else if (typeof value === 'string') {
message[key] = value.split(',').map((recipient: string) => makeRecipient(recipient.trim()));
} else {
throw new ApplicationError(`The "${key}" field must be a string or an array of strings`, {
level: 'warning',
});
}
continue;
}
if (['from', 'sender'].includes(key)) {
if (value) {
message[key] = makeRecipient(value as string);
}
continue;
}
message[key] = value;
}
return message;
}
export function simplifyOutputMessages(data: IDataObject[]) {
return data.map((item: IDataObject) => {
return {
id: item.id,
conversationId: item.conversationId,
subject: item.subject,
bodyPreview: item.bodyPreview,
from: ((item.from as IDataObject)?.emailAddress as IDataObject)?.address,
to: (item.toRecipients as IDataObject[]).map(
(recipient: IDataObject) => (recipient.emailAddress as IDataObject)?.address,
),
categories: item.categories,
hasAttachments: item.hasAttachments,
};
});
}
export function prepareContactFields(fields: IDataObject) {
const returnData: IDataObject = {};
const typeStringCollection = [
'businessPhones',
'categories',
'children',
'homePhones',
'imAddresses',
];
const typeValuesToExtract = ['businessAddress', 'emailAddresses', 'homePhones', 'otherAddress'];
for (const [key, value] of Object.entries(fields)) {
if (value === undefined || value === '') {
continue;
}
if (typeStringCollection.includes(key) && !Array.isArray(value)) {
returnData[key] = (value as string).split(',').map((item) => item.trim());
continue;
}
if (typeValuesToExtract.includes(key)) {
if ((value as IDataObject).values === undefined) continue;
returnData[key] = (value as IDataObject).values;
continue;
}
returnData[key] = value;
}
return returnData;
}
export function prepareFilterString(filters: IDataObject) {
const selectedFilters = filters.filters as IDataObject;
const filterString: string[] = [];
if (selectedFilters.foldersToInclude) {
const folders = (selectedFilters.foldersToInclude as string[])
.filter((folder) => folder !== '')
.map((folder) => `parentFolderId eq '${folder}'`)
.join(' or ');
filterString.push(folders);
}
if (selectedFilters.foldersToExclude) {
for (const folder of selectedFilters.foldersToExclude as string[]) {
filterString.push(`parentFolderId ne '${folder}'`);
}
}
if (selectedFilters.sender) {
const sender = selectedFilters.sender as string;
const byMailAddress = `from/emailAddress/address eq '${sender}'`;
const byName = `from/emailAddress/name eq '${sender}'`;
filterString.push(`(${byMailAddress} or ${byName})`);
}
if (selectedFilters.hasAttachments) {
filterString.push(`hasAttachments eq ${selectedFilters.hasAttachments}`);
}
if (selectedFilters.readStatus && selectedFilters.readStatus !== 'both') {
filterString.push(`isRead eq ${selectedFilters.readStatus === 'read'}`);
}
if (selectedFilters.receivedAfter) {
filterString.push(`receivedDateTime ge ${selectedFilters.receivedAfter}`);
}
if (selectedFilters.receivedBefore) {
filterString.push(`receivedDateTime le ${selectedFilters.receivedBefore}`);
}
if (selectedFilters.custom) {
filterString.push(selectedFilters.custom as string);
}
return filterString.length ? filterString.join(' and ') : undefined;
}
export function prepareApiError(
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
error: IDataObject,
itemIndex = 0,
) {
const [httpCode, err, message] = (error.description as string).split(' - ');
const json = jsonParse(err);
return new NodeApiError(this.getNode(), json as JsonObject, {
itemIndex,
httpCode,
//In UI we are replacing some of the field names to make them more user friendly, updating error message to reflect that
message: message
.replace(/toRecipients/g, 'toRecipients (To)')
.replace(/bodyContent/g, 'bodyContent (Message)')
.replace(/bodyContentType/g, 'bodyContentType (Message Type)'),
});
}
export const encodeOutlookId = (id: string) => {
return id.replace(/-/g, '%2F').replace(/=/g, '%3D').replace(/\+/g, '%2B');
};
export const decodeOutlookId = (id: string) => {
return id.replace(/%2F/g, '-').replace(/%3D/g, '=').replace(/%2B/g, '+');
};