Skip to content

Commit

Permalink
feat: Add Onedrive Trigger Node (#8742)
Browse files Browse the repository at this point in the history
Co-authored-by: Giulio Andreini <andreini@netseven.it>
Co-authored-by: Marcus <marcus@n8n.io>
  • Loading branch information
3 people committed Mar 19, 2024
1 parent 4f0b52c commit ff8dd4e
Show file tree
Hide file tree
Showing 6 changed files with 571 additions and 3 deletions.
82 changes: 79 additions & 3 deletions packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import type {
JsonObject,
IHttpRequestMethods,
IRequestOptions,
IPollFunctions,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { DateTime } from 'luxon';

export async function microsoftApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
method: IHttpRequestMethods,
resource: string,

Expand Down Expand Up @@ -47,7 +49,7 @@ export async function microsoftApiRequest(
}

export async function microsoftApiRequestAllItems(
this: IExecuteFunctions | ILoadOptionsFunctions,
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
propertyName: string,
method: IHttpRequestMethods,
endpoint: string,
Expand All @@ -74,7 +76,7 @@ export async function microsoftApiRequestAllItems(
}

export async function microsoftApiRequestAllItemsSkip(
this: IExecuteFunctions | ILoadOptionsFunctions,
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
propertyName: string,
method: IHttpRequestMethods,
endpoint: string,
Expand All @@ -96,3 +98,77 @@ export async function microsoftApiRequestAllItemsSkip(

return returnData;
}

export async function microsoftApiRequestAllItemsDelta(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
link: string,
lastDate: DateTime,
eventType: string,
): Promise<any> {
const returnData: IDataObject[] = [];

let responseData;
let deltaLink: string = '';
let uri: string = link;

do {
responseData = (await microsoftApiRequest.call(this, 'GET', '', {}, {}, uri)) as IDataObject;
uri = responseData['@odata.nextLink'] as string;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
for (const value of responseData.value as IDataObject[]) {
if (value.fileSystemInfo as IDataObject) {
const updatedTimeStamp = (value.fileSystemInfo as IDataObject)
?.lastModifiedDateTime as string;
const createdTimeStamp = (value.fileSystemInfo as IDataObject)?.createdDateTime as string;
if (eventType === 'created') {
if (DateTime.fromISO(createdTimeStamp) >= lastDate) {
returnData.push(value);
}
}
if (eventType === 'updated') {
if (
DateTime.fromISO(updatedTimeStamp) >= lastDate &&
DateTime.fromISO(createdTimeStamp) < lastDate
) {
returnData.push(value);
}
}
}
}
//returnData.push.apply(returnData, responseData.value as IDataObject[]);
deltaLink = (responseData['@odata.deltaLink'] as string) || '';
} while (responseData['@odata.nextLink'] !== undefined);

return { deltaLink, returnData };
}

export async function getPath(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
itemId: string,
): Promise<string> {
const responseData = (await microsoftApiRequest.call(
this,
'GET',
'',
{},
{},
`https://graph.microsoft.com/v1.0/me/drive/items/${itemId}`,
)) as IDataObject;
if (responseData.folder) {
return (responseData?.parentReference as IDataObject)?.path + `/${responseData?.name}`;
} else {
const workflow = this.getWorkflow();
const node = this.getNode();
this.logger.error(
`There was a problem in '${node.name}' node in workflow '${workflow.id}': 'Item to watch is not a folder'`,
{
node: node.name,
workflowId: workflow.id,
error: 'Item to watch is not a folder',
},
);
throw new NodeApiError(this.getNode(), {
error: 'Item to watch is not a folder',
} as JsonObject);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"node": "n8n-nodes-base.microsoftOneDriveTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Data & Storage"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/microsoft"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftonedrivetrigger/"
}
],
"generic": [
{
"label": "Hey founders! Your business doesn't need you to operate",
"icon": " 🖥️",
"url": "https://n8n.io/blog/your-business-doesnt-need-you-to-operate/"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import type {
IPollFunctions,
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';

import { DateTime } from 'luxon';
import { triggerDescription } from './TriggerDescription';
import { getPath, microsoftApiRequest, microsoftApiRequestAllItemsDelta } from './GenericFunctions';

export class MicrosoftOneDriveTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Microsoft OneDrive Trigger',
name: 'microsoftOneDriveTrigger',
icon: 'file:oneDrive.svg',
group: ['trigger'],
version: 1,
description: 'Trigger for Microsoft OneDrive API.',
subtitle: '={{($parameter["event"])}}',
defaults: {
name: 'Microsoft OneDrive Trigger',
},
credentials: [
{
name: 'microsoftOneDriveOAuth2Api',
required: true,
},
],
polling: true,
inputs: [],
outputs: ['main'],
properties: [...triggerDescription],
};

methods = {
loadOptions: {},
};

async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const workflowData = this.getWorkflowStaticData('node');
let responseData: IDataObject[];

const lastLink: string =
(workflowData.LastLink as string) ||
'https://graph.microsoft.com/v1.0/me/drive/root/delta?token=latest';

const now = DateTime.now().toUTC();
const start = DateTime.fromISO(workflowData.lastTimeChecked as string) || now;
const end = now;
const event = this.getNodeParameter('event', 'fileCreated') as string;
const watch = this.getNodeParameter('watch', 'anyFile') as string;
const watchFolder = (this.getNodeParameter('watchFolder', false) as boolean) || false;
const folderChild = (this.getNodeParameter('options.folderChild', false) as boolean) || false;

let eventType = 'created';
let eventResource = 'file';
if (event.includes('Updated')) {
eventType = 'updated';
}
if (event.includes('folder')) {
eventResource = 'folder';
}
try {
if (this.getMode() === 'manual') {
responseData = (
await microsoftApiRequest.call(
this,
'GET',
'',
{},
{},
'https://graph.microsoft.com/v1.0/me/drive/root/delta',
)
).value as IDataObject[];
} else {
const response: IDataObject = (await microsoftApiRequestAllItemsDelta.call(
this,
lastLink,
start,
eventType,
)) as IDataObject;
responseData = response.returnData as IDataObject[];
workflowData.LastLink = response.deltaLink;
}

workflowData.lastTimeChecked = end.toISO();
if (watch === 'selectedFile') {
const fileId = (
this.getNodeParameter('fileId', '', {
extractValue: true,
}) as string
).replace('%21', '!');
if (fileId) {
responseData = responseData.filter((item: IDataObject) => item.id === fileId);
}
}

if (
!folderChild &&
(watch === 'oneSelectedFolder' || watch === 'selectedFolder' || watchFolder)
) {
const folderId = (
this.getNodeParameter('folderId', '', {
extractValue: true,
}) as string
).replace('%21', '!');
if (folderId) {
if (watch === 'oneSelectedFolder') {
responseData = responseData.filter((item: IDataObject) => item.id === folderId);
} else {
responseData = responseData.filter(
(item: IDataObject) => (item.parentReference as IDataObject).id === folderId,
);
}
}
}
if (folderChild && (watch === 'selectedFolder' || watchFolder)) {
const folderId = (
this.getNodeParameter('folderId', '', {
extractValue: true,
}) as string
).replace('%21', '!');
const folderPath = await getPath.call(this, folderId);
responseData = responseData.filter((item: IDataObject) =>
((item.parentReference as IDataObject).path as string).startsWith(folderPath),
);
}
responseData = responseData.filter((item: IDataObject) => item[eventResource]);
if (!responseData?.length) {
return null;
}

const simplify = this.getNodeParameter('simple') as boolean;
if (simplify) {
responseData = responseData.map((x) => ({
id: x.id,
createdDateTime: (x.fileSystemInfo as IDataObject)?.createdDateTime,
lastModifiedDateTime: (x.fileSystemInfo as IDataObject)?.lastModifiedDateTime,
name: x.name,
webUrl: x.webUrl,
size: x.size,
path: (x.parentReference as IDataObject)?.path || '',
mimeType: (x.file as IDataObject)?.mimeType || '',
}));
}

if (this.getMode() === 'manual') {
return [this.helpers.returnJsonArray(responseData[0])];
} else {
return [this.helpers.returnJsonArray(responseData)];
}
} catch (error) {
if (this.getMode() === 'manual' || !workflowData.lastTimeChecked) {
throw error;
}
const workflow = this.getWorkflow();
const node = this.getNode();
this.logger.error(
`There was a problem in '${node.name}' node in workflow '${workflow.id}': '${error.description}'`,
{
node: node.name,
workflowId: workflow.id,
error,
},
);
throw error;
}

return null;
}
}

0 comments on commit ff8dd4e

Please sign in to comment.