Add BambooHR Node (#2471)

* Feature // Created BambooHR Node, Init Simple Api Calls for Employees

* BambooHR Added EmployeesFiles, CompanyFiles API Operations

* BambooHR / Refactor the code

* Bamboo HR Node // Refactor + Optimization of Employee Api Requests

* Bamboo HR Node // Refactor + Optimization of EmployeeFiles Api Requests

* Bamboo HR Node // Refactor + Optimization of CompanyFiles Api Requests

* Bamboo HR Node // Add Reports - Get Reports by ID

* Bamboo HR Node // Format BambooAPI Credential files

* Bamboo HR Node // Added Account Information API operations

* Bamboo HR Node //Add Https Reposnse Code for All  API Operations, Implement new Http Helper

* Bamboo HR Node // Added Tabular Data Api Operations

* Bamboo HR Node // Added Time Off Api Operations

* Bamboo HR Node //Fixed bugs for AccountInformation Operation, Uploading Employees File, TimeOff EstimationTime

* Bamboo HR Node // Update AccountInformation - UpdateFields Api operation

* Bamboo HR Node //Fixed Add and Update Table rows // Tabular Data API Operations

* Update AccountInformation - Added TimeOff Operations - Get Requests, Create Request, Create History Item, Fixed Get Types

* Bamboo HR Node // Fixed Adjust Time - TimeOff API Operation, Wrote comments, Finished TimeOff Api Operations

* Bamboo HR Node // Add Multi choice Property for Employee Operations, Expand and Add new Optional Fields for Creating and Updating Employee, Write Comments, Fixed Employee API operations

* Bamboo HR Node // Add Multi choice Property + Update Fields and Write Comments for Employee Files & Company Files & Reports Acc Info API Operation

* N8N-2603 Move Company Name Prop to Credentials, Changed Execution Function

* N8N-2603 Obtain Employee ID and bind to the response after create an employee, Refactor GetDirectoy to GetAll + update the output of the response

* N8N-2603 Refactor, Added ID in responses, Added Pagination to some operations

* N8N-2603 Refactor Employee Files to Employee File

* N8N-2603 Refactor CompanyFiles to Company File, Refactor CompanyFile:Create -> CompanyFile:addCategory

* N8N-2603 Refactor employeeFile:create -> employeeFile:addCategory, Get rid off Account Information resources

* N8N-2603 EmployeeFile:Update -> Change ShareWithEmployee Parameter to be boolean

* N8N-2603 CompanyFIle:update -> Change shareWithCompany prop to boolean

* N8N-2603 Added Load Options for getTimeOffTypeId, Rename AdditionalFields to UpdateFields

* N8N-2603 Updated Logo and Border

* N8N-2603 Refactor Employees to Employee

* N8N-2603 Linter Fixes

* N8N-2603 Refactor EmployeeFile:get -> EmployeeFile:download, CompanyFile:get -> CompanyFile:download

* N8N-2603 Linter fix

* N8N-2603 Linter Fixes

* N8N-2603 Hotfix

* N8N-2603 Fixed EmployeeFile:Download

* N8N-2603 Updated Assertion

* N8N-2603 Remove unnecesary description, optimized code, created separate loadOptions file

* N8N-2603 Added Download Function for CompanyFile operation

* N8N-2603 Added DateTime Fields instead of string, Removed Color prop from Node

* N8N-2603 Refactor Del to Delete

* N8N-2603 Added Upload Employ File Operation

* N8N-2603 Updated Possible Types of the Request

* N8N-2603 Fixed Linter Errors

* N8N-2603 Hotfix Upload Employee File Description

* N8N-2603 Added options to download the report

*  Improvements

*  Improvements

*  Simplify node

*  Fix linting issue

*  Improvements

*  Fix returned mimeType

Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Oliver Trajceski 2022-01-22 10:46:13 +01:00 committed by GitHub
parent 49bf786e5b
commit 8cefafa47d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 3452 additions and 118 deletions

236
package-lock.json generated
View file

@ -13561,6 +13561,15 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@ -13596,6 +13605,21 @@
}
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"optional": true
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@ -13658,6 +13682,58 @@
"worker-rpc": "^0.1.0"
}
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"optional": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@ -13692,6 +13768,12 @@
"slash": "^2.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"optional": true
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@ -13720,6 +13802,16 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"optional": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@ -13755,6 +13847,17 @@
}
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@ -13765,6 +13868,15 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"to-regex-range": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
@ -13858,6 +13970,12 @@
"requires": {
"tslib": "^1.8.1"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"optional": true
}
}
},
@ -23351,124 +23469,6 @@
}
}
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"optional": true
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"optional": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"optional": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"optional": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"optional": true
}
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",

View file

@ -0,0 +1,24 @@
import {
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class BambooHRApi implements ICredentialType {
name = 'bambooHRApi';
displayName = 'BambooHR API';
documentationUrl = 'bambooHR';
properties: INodeProperties[] = [
{
displayName: 'Subdomain',
name: 'subdomain',
type: 'string',
default: '',
},
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
default: '',
},
];
}

View file

@ -0,0 +1,34 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
INodeType,
INodeTypeBaseDescription,
INodeTypeDescription,
} from 'n8n-workflow';
import { router } from './v1/actions/router';
import { versionDescription } from './v1/actions/versionDescription';
import { loadOptions } from './v1/methods';
import { credentialTest } from './v1/methods';
export class BambooHR implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
...versionDescription,
};
}
methods = {
loadOptions,
credentialTest,
};
async execute(this: IExecuteFunctions) {
return [await router.call(this)];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,33 @@
import {
AllEntities,
Entity,
PropertiesOf,
} from 'n8n-workflow';
type BambooHRMap = {
employee: 'create' | 'get' | 'getAll' | 'update';
employeeDocument: 'delete' | 'download' | 'get' | 'getAll' | 'update' | 'upload';
file: 'delete' | 'download' | 'getAll' | 'update';
companyReport: 'get';
};
export type BambooHR = AllEntities<BambooHRMap>;
export type BambooHRFile = Entity<BambooHRMap, 'file'>;
export type BambooHREmployee = Entity<BambooHRMap, 'employee'>;
export type BambooHREmployeeDocument = Entity<BambooHRMap, 'employeeDocument'>;
export type BambooHRCompanyReport = Entity<BambooHRMap, 'companyReport'>;
export type FileProperties = PropertiesOf<BambooHRFile>;
export type EmployeeProperties = PropertiesOf<BambooHREmployee>;
export type EmployeeDocumentProperties = PropertiesOf<BambooHREmployeeDocument>;
export type CompanyReportProperties = PropertiesOf<BambooHRCompanyReport>;
export interface IAttachment {
fields: {
item?: object[];
};
actions: {
item?: object[];
};
}

View file

@ -0,0 +1,111 @@
import { INodeProperties } from 'n8n-workflow';
export const companyReportGetDescription: INodeProperties[] = [
{
displayName: 'Report ID',
name: 'reportId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'companyReport',
],
},
},
default: '',
description: 'ID of the report. You can get the report number by hovering over the report name on the reports page and grabbing the ID.',
},
{
displayName: 'Format',
name: 'format',
type: 'options',
options: [
{
name: 'CSV',
value: 'CSV',
},
{
name: 'JSON',
value: 'JSON',
},
{
name: 'PDF',
value: 'PDF',
},
{
name: 'XLS',
value: 'XLS',
},
{
name: 'XML',
value: 'XML',
},
],
required: true,
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'companyReport',
],
},
},
default: 'JSON',
description: 'The output format for the report',
},
{
displayName: 'Put Output In Field',
name: 'output',
type: 'string',
default: 'data',
required: true,
description: 'The name of the output field to put the binary file data in',
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'companyReport',
],
},
hide: {
format: [
'JSON',
],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'companyReport',
],
},
},
options: [
{
displayName: 'Duplicate Field Filtering',
name: 'fd',
type: 'boolean',
default: true,
description: 'Whether to apply the standard duplicate field filtering or not',
},
],
},
];

View file

@ -0,0 +1,65 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function get(this: IExecuteFunctions, index: number) {
const body: IDataObject = {};
const requestMethod = 'GET';
const items = this.getInputData();
//meta data
const reportId = this.getNodeParameter('reportId', index) as string;
const format = this.getNodeParameter('format', 0) as string;
const fd = this.getNodeParameter('options.fd', index, true) as boolean;
//endpoint
const endpoint = `reports/${reportId}/?format=${format}&fd=${fd}`;
if (format === 'JSON') {
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, {}, { resolveWithFullResponse: true });
return this.helpers.returnJsonArray(responseData.body);
}
const output: string = this.getNodeParameter('output', index) as string;
const response = await apiRequest.call(this, requestMethod, endpoint, body, {} as IDataObject,
{ encoding: null, json: false, resolveWithFullResponse: true });
let mimeType = response.headers['content-type'] as string | undefined;
mimeType = mimeType ? mimeType.split(';').find(value => value.includes('/')) : undefined;
const contentDisposition = response.headers['content-disposition'];
const fileNameRegex = /(?<=filename=").*\b/;
const match = fileNameRegex.exec(contentDisposition);
let fileName = '';
// file name was found
if (match !== null) {
fileName = match[0];
}
const newItem: INodeExecutionData = {
json: items[index].json,
binary: {},
};
if (items[index].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, items[index].binary);
}
newItem.binary = {
[output]: await this.helpers.prepareBinaryData(response.body as unknown as Buffer, fileName, mimeType),
};
return this.prepareOutputData(newItem as unknown as INodeExecutionData[]);
}

View file

@ -0,0 +1,7 @@
import { get as execute } from './execute';
import { companyReportGetDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,34 @@
import * as get from './get';
import {
INodeProperties,
} from 'n8n-workflow';
export {
get,
};
export const descriptions: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'companyReport',
],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a company report',
},
],
default: 'get',
description: '',
},
...get.description,
];

View file

@ -0,0 +1,95 @@
import {
EmployeeProperties,
} from '../../Interfaces';
import {
createEmployeeSharedDescription,
} from './shareDescription';
export const employeeCreateDescription: EmployeeProperties = [
{
displayName: 'Synced with Trax Payroll',
name: 'synced',
type: 'boolean',
required: true,
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'employee',
],
},
},
default: false,
description: 'Whether the employee to create was added to a pay schedule synced with Trax Payroll',
},
{
displayName: 'First Name',
name: 'firstName',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'employee',
],
},
},
default: '',
},
{
displayName: 'Last Name',
name: 'lastName',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'employee',
],
},
},
default: '',
},
...createEmployeeSharedDescription(true),
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'employee',
],
},
},
options: [
...createEmployeeSharedDescription(false),
{
displayName: 'Work Email',
name: 'workEmail',
type: 'string',
default: '',
},
{
displayName: 'Work Phone',
name: 'workPhone',
type: 'string',
default: '',
},
],
},
];

View file

@ -0,0 +1,99 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
import * as moment from 'moment';
import {
capitalCase
} from 'change-case';
export async function create(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'POST';
const endpoint = 'employees';
//body parameters
body.firstName = this.getNodeParameter('firstName', index) as string;
body.lastName = this.getNodeParameter('lastName', index) as string;
const additionalFields = this.getNodeParameter('additionalFields', index) as IDataObject;
const synced = this.getNodeParameter('synced', index) as boolean;
if (synced) {
Object.assign(body, { address: this.getNodeParameter('address.value', index, {}) as IDataObject });
Object.assign(body, { payRate: this.getNodeParameter('payRate.value', index, {}) as IDataObject });
body.department = this.getNodeParameter('department', index) as string;
body.dateOfBirth = this.getNodeParameter('dateOfBirth', index);
body.division = this.getNodeParameter('division', index) as string;
body.employeeNumber = this.getNodeParameter('employeeNumber', index) as string;
body.exempt = this.getNodeParameter('exempt', index) as string;
body.gender = this.getNodeParameter('gender', index) as string;
body.hireDate = this.getNodeParameter('hireDate', index) as string;
body.location = this.getNodeParameter('location', index) as string;
body.maritalStatus = this.getNodeParameter('maritalStatus', index) as string;
body.mobilePhone = this.getNodeParameter('mobilePhone', index) as string;
body.paidPer = this.getNodeParameter('paidPer', index) as string;
body.payType = this.getNodeParameter('payType', index) as string;
body.preferredName = this.getNodeParameter('preferredName', index) as string;
body.ssn = this.getNodeParameter('ssn', index) as string;
} else {
Object.assign(body, { address: this.getNodeParameter('additionalFields.address.value', index, {}) as IDataObject });
Object.assign(body, { payRate: this.getNodeParameter('additionalFields.payRate.value', index, {}) as IDataObject });
delete additionalFields.address;
delete additionalFields.payRate;
}
Object.assign(body, additionalFields);
if (body.gender) {
body.gender = capitalCase(body.gender as string);
}
if (body.dateOfBirth) {
body.dateOfBirth = moment(body.dateOfBirth as string).format('YYYY-MM-DD');
}
if (body.exempt) {
body.exempt = capitalCase(body.exempt as string);
}
if (body.hireDate) {
body.hireDate = moment(body.hireDate as string).format('YYYY-MM-DD');
}
if (body.maritalStatus) {
body.maritalStatus = capitalCase(body.maritalStatus as string);
}
if (body.payType) {
body.payType = capitalCase(body.payType as string);
}
if (body.paidPer) {
body.paidPer = capitalCase(body.paidPer as string);
}
if (!Object.keys(body.payRate as IDataObject).length) {
delete body.payRate;
}
//response
const responseData = await apiRequest.call(this, requestMethod, endpoint, body, {}, { resolveWithFullResponse: true });
//obtain employeeID
const rawEmployeeId = responseData.headers.location.lastIndexOf('/');
const employeeId = responseData.headers.location.substring(rawEmployeeId + 1);
//return
return this.helpers.returnJsonArray({ id: employeeId });
}

View file

@ -0,0 +1,7 @@
import { create as execute } from './execute';
import { employeeCreateDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,324 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const createEmployeeSharedDescription = (sync = false): INodeProperties[] => {
let elements = [
{
displayName: 'Address',
name: 'address',
placeholder: 'Address',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
name: 'value',
displayName: 'Address',
values: [
{
displayName: 'Line 1',
name: 'address1',
type: 'string',
default: '',
},
{
displayName: 'Line 2',
name: 'address2',
type: 'string',
default: '',
},
{
displayName: 'City',
name: 'city',
type: 'string',
default: '',
},
{
displayName: 'State',
name: 'state',
type: 'string',
default: '',
placeholder: 'Florida',
description: 'The full name of the state/province',
},
{
displayName: 'Country',
name: 'country',
type: 'string',
default: '',
placeholder: 'United States',
description: 'The name of the country. Must exist in the BambooHR country list',
},
],
},
],
},
{
displayName: 'Date of Birth',
name: 'dateOfBirth',
type: 'dateTime',
default: '',
},
{
displayName: 'Department',
name: 'department',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getDepartments',
},
default: '',
},
{
displayName: 'Division',
name: 'division',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getDivisions',
},
default: '',
},
{
displayName: 'Employee Number',
name: 'employeeNumber',
type: 'string',
default: '',
},
{
displayName: 'FLSA Overtime Status',
name: 'exempt',
type: 'options',
options: [
{
name: 'Exempt',
value: 'exempt',
},
{
name: 'Non-exempt',
value: 'non-exempt',
},
],
default: '',
},
{
displayName: 'Gender',
name: 'gender',
type: 'options',
options: [
{
name: 'Female',
value: 'female',
},
{
name: 'Male',
value: 'male',
},
],
default: '',
},
{
displayName: 'Hire Date',
name: 'hireDate',
type: 'dateTime',
default: '',
},
{
displayName: 'Location',
name: 'location',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getEmployeeLocations',
},
default: '',
},
{
displayName: 'Marital Status',
name: 'maritalStatus',
type: 'options',
options: [
{
name: 'Single',
value: 'single',
},
{
name: 'Married',
value: 'married',
},
{
name: 'Domestic Partnership',
value: 'domesticPartnership',
},
],
default: '',
},
{
displayName: 'Mobile Phone',
name: 'mobilePhone',
type: 'string',
default: '',
},
{
displayName: 'Pay Per',
name: 'paidPer',
type: 'options',
options: [
{
name: 'Hour',
value: 'hour',
},
{
name: 'Day',
value: 'day',
},
{
name: 'Week',
value: 'week',
},
{
name: 'Month',
value: 'month',
},
{
name: 'Quater',
value: 'quater',
},
{
name: 'Year',
value: 'year',
},
],
default: '',
},
{
displayName: 'Pay Rate',
name: 'payRate',
placeholder: 'Add Pay Rate',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
name: 'value',
displayName: 'Pay Rate',
values: [
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
placeholder: '20.00',
},
{
displayName: 'Currency',
name: 'currency',
type: 'string',
default: '',
placeholder: 'USD',
},
],
},
],
},
{
displayName: 'Pay Type',
name: 'payType',
type: 'options',
options: [
{
name: 'Hourly',
value: 'hourly',
},
{
name: 'Salary',
value: 'salary',
},
{
name: 'Commission',
value: 'commission',
},
{
name: 'Exception Hourly',
value: 'exceptionHourly',
},
{
name: 'Monthly',
value: 'monthly',
},
{
name: 'Weekly',
value: 'weekly',
},
{
name: 'Piece Rate',
value: 'pieceRate',
},
{
name: 'Contract',
value: 'contract',
},
{
name: 'Daily',
value: 'daily',
},
{
name: 'Pro Rata',
value: 'proRata',
},
],
default: '',
},
{
displayName: 'Preferred Name',
name: 'preferredName',
type: 'string',
default: '',
},
{
displayName: 'Social Security Number',
name: 'ssn',
type: 'string',
default: '',
placeholder: '123-45-6789',
description: 'A standard United States Social Security number, with dashes',
},
] as INodeProperties[];
if (sync === true) {
elements = elements.map(element => {
return Object.assign(element, {
displayOptions: {
show: {
resource: [
'employee',
],
operation: [
'create',
],
synced: [
true,
],
},
},
required: true,
});
});
return elements;
} else {
elements = elements.map(element => {
return Object.assign(element, {
displayOptions: {
show: {
'/synced': [
false,
],
},
},
});
});
}
return elements;
};

View file

@ -0,0 +1,54 @@
import {
EmployeeProperties,
} from '../../Interfaces';
export const employeeGetDescription: EmployeeProperties = [
{
displayName: 'Employee ID',
name: 'employeeId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'employee',
],
},
},
default: '',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'employee',
],
},
},
options: [
{
displayName: 'Fields',
name: 'fields',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getEmployeeFields',
},
default: [
'all',
],
description: 'Set of fields to get from employee data',
},
],
},
];

View file

@ -0,0 +1,37 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function get(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'GET';
//meta data
const id = this.getNodeParameter('employeeId', index) as string;
//query parameters
let fields = this.getNodeParameter('options.fields', index, ['all']) as string[];
if (fields.includes('all')) {
const { fields: allFields } = await apiRequest.call(this, requestMethod, 'employees/directory', body);
fields = allFields.map((field: IDataObject) => field.id);
}
//endpoint
const endpoint = `employees/${id}?fields=${fields}`;
//response
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
//return
return this.helpers.returnJsonArray(responseData);
}

View file

@ -0,0 +1,7 @@
import { get as execute } from './execute';
import { employeeGetDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,45 @@
import { INodeProperties } from 'n8n-workflow';
export const employeeGetAllDescription: INodeProperties[] = [
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'employee',
],
operation: [
'getAll',
],
},
},
description: 'Whether to return all results',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 5,
typeOptions: {
minValue: 1,
maxValue: 1000,
},
displayOptions: {
show: {
resource: [
'employee',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
description: 'The number of results to return',
},
];

View file

@ -0,0 +1,33 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function getAll(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'GET';
const endpoint = 'employees/directory';
//limit parameters
const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean;
const limit = this.getNodeParameter('limit', 0, 0) as number;
//response
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
//return limited result
if (!returnAll && responseData.employees.length > limit) {
return this.helpers.returnJsonArray(responseData.employees.slice(0, limit));
}
//return all result
return this.helpers.returnJsonArray(responseData.employees);
}

View file

@ -0,0 +1,7 @@
import { getAll as execute } from './execute';
import { employeeGetAllDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,58 @@
import * as create from './create';
import * as get from './get';
import * as getAll from './getAll';
import * as update from './update';
import {
INodeProperties,
} from 'n8n-workflow';
export {
create,
get,
getAll,
update
};
export const descriptions: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'employee',
],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create an employee',
},
{
name: 'Get',
value: 'get',
description: 'Get an employee',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all employees',
},
{
name: 'Update',
value: 'update',
description: 'Update an employee',
},
],
default: 'create',
description: '',
},
...create.description,
...get.description,
...getAll.description,
...update.description,
];

View file

@ -0,0 +1,75 @@
import {
EmployeeProperties,
} from '../../Interfaces';
import { updateEmployeeSharedDescription } from './sharedDescription';
export const employeeUpdateDescription: EmployeeProperties = [
{
displayName: 'Employee ID',
name: 'employeeId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'employee',
],
},
},
default: '',
},
{
displayName: 'Synced with Trax Payroll',
name: 'synced',
type: 'boolean',
required: true,
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'employee',
],
},
},
default: false,
description: 'Whether the employee to create was added to a pay schedule synced with Trax Payroll',
},
...updateEmployeeSharedDescription(true),
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'employee',
],
},
},
options: [
...updateEmployeeSharedDescription(false),
{
displayName: 'Work Email',
name: 'workEmail',
type: 'string',
default: '',
},
{
displayName: 'Work Phone',
name: 'workPhone',
type: 'string',
default: '',
},
],
},
];

View file

@ -0,0 +1,107 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
NodeOperationError,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
import * as moment from 'moment';
import {
capitalCase,
} from 'change-case';
export async function update(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
let body: IDataObject = {};
const requestMethod = 'POST';
//meta data
const id = this.getNodeParameter('employeeId', index) as string;
//endpoint
const endpoint = `employees/${id}`;
//body parameters
body = this.getNodeParameter('updateFields', index) as IDataObject;
const updateFields = this.getNodeParameter('updateFields', index) as IDataObject;
const synced = this.getNodeParameter('synced', index) as boolean;
if (synced) {
Object.assign(body, { address: this.getNodeParameter('address.value', index, {}) as IDataObject });
Object.assign(body, { payRate: this.getNodeParameter('payRate.value', index, {}) as IDataObject });
body.firstName = this.getNodeParameter('firstName', index) as string;
body.lastName = this.getNodeParameter('lastName', index) as string;
body.department = this.getNodeParameter('department', index) as string;
body.dateOfBirth = this.getNodeParameter('dateOfBirth', index) as string;
body.division = this.getNodeParameter('division', index) as string;
body.employeeNumber = this.getNodeParameter('employeeNumber', index) as string;
body.exempt = this.getNodeParameter('exempt', index) as string;
body.gender = this.getNodeParameter('gender', index) as string;
body.hireDate = this.getNodeParameter('hireDate', index) as string;
body.location = this.getNodeParameter('location', index) as string;
body.maritalStatus = this.getNodeParameter('maritalStatus', index) as string;
body.mobilePhone = this.getNodeParameter('mobilePhone', index) as string;
body.paidPer = this.getNodeParameter('paidPer', index) as string;
body.payType = this.getNodeParameter('payType', index) as string;
body.preferredName = this.getNodeParameter('preferredName', index) as string;
body.ssn = this.getNodeParameter('ssn', index) as string;
} else {
if (!Object.keys(updateFields).length) {
throw new NodeOperationError(this.getNode(), 'At least one fields must be updated');
}
Object.assign(body, { address: this.getNodeParameter('updateFields.address.value', index, {}) as IDataObject });
Object.assign(body, { payRate: this.getNodeParameter('updateFields.payRate.value', index, {}) as IDataObject });
delete updateFields.address;
delete updateFields.payRate;
}
Object.assign(body, updateFields);
if (body.gender) {
body.gender = capitalCase(body.gender as string);
}
if (body.dateOfBirth) {
body.dateOfBirth = moment(body.dateOfBirth as string).format('YYYY-MM-DD');
}
if (body.exempt) {
body.exempt = capitalCase(body.exempt as string);
}
if (body.hireDate) {
body.hireDate = moment(body.hireDate as string).format('YYYY-MM-DD');
}
if (body.maritalStatus) {
body.maritalStatus = capitalCase(body.maritalStatus as string);
}
if (body.payType) {
body.payType = capitalCase(body.payType as string);
}
if (body.paidPer) {
body.paidPer = capitalCase(body.paidPer as string);
}
if (!Object.keys(body.payRate as IDataObject).length) {
delete body.payRate;
}
await apiRequest.call(this, requestMethod, endpoint, body);
//return
return this.helpers.returnJsonArray({ success: true });
}

View file

@ -0,0 +1,7 @@
import { update as execute } from './execute';
import { employeeUpdateDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,350 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const updateEmployeeSharedDescription = (sync = false): INodeProperties[] => {
let elements = [
{
displayName: 'Address',
name: 'addasasress',
placeholder: 'Address',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
name: 'value',
displayName: 'Address',
values: [
{
displayName: 'Line 1',
name: 'address1',
type: 'string',
default: '',
},
{
displayName: 'Line 2',
name: 'address2',
type: 'string',
default: '',
},
{
displayName: 'City',
name: 'city',
type: 'string',
default: '',
},
{
displayName: 'State',
name: 'state',
type: 'string',
default: '',
placeholder: 'Florida',
description: 'The full name of the state/province',
},
{
displayName: 'Country',
name: 'country',
type: 'string',
default: '',
placeholder: 'United States',
description: 'The name of the country. Must exist in the BambooHR country list',
},
],
},
],
},
{
displayName: 'Date of Birth',
name: 'dateOfBirth',
type: 'dateTime',
default: '',
},
{
displayName: 'Department',
name: 'department',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getDepartments',
},
default: '',
},
{
displayName: 'Division',
name: 'division',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getDivisions',
},
default: '',
},
{
displayName: 'Employee Number',
name: 'employeeNumber',
type: 'string',
default: '',
},
{
displayName: 'First Name',
name: 'firstName',
type: 'string',
displayOptions: {
show: {
'synced': [
false,
],
},
},
default: '',
},
{
displayName: 'Last Name',
name: 'lastName',
type: 'string',
displayOptions: {
show: {
'synced': [
false,
],
},
},
default: '',
},
{
displayName: 'FLSA Overtime Status',
name: 'exempt',
type: 'options',
options: [
{
name: 'Exempt',
value: 'exempt',
},
{
name: 'Non-exempt',
value: 'non-exempt',
},
],
default: '',
},
{
displayName: 'Gender',
name: 'gender',
type: 'options',
options: [
{
name: 'Female',
value: 'female',
},
{
name: 'Male',
value: 'male',
},
],
default: '',
},
{
displayName: 'Hire Date',
name: 'hireDate',
type: 'dateTime',
default: '',
},
{
displayName: 'Location',
name: 'location',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getEmployeeLocations',
},
default: '',
},
{
displayName: 'Marital Status',
name: 'maritalStatus',
type: 'options',
options: [
{
name: 'Single',
value: 'single',
},
{
name: 'Married',
value: 'married',
},
{
name: 'Domestic Partnership',
value: 'domesticPartnership',
},
],
default: '',
},
{
displayName: 'Mobile Phone',
name: 'mobilePhone',
type: 'string',
default: '',
},
{
displayName: 'Pay Per',
name: 'paidPer',
type: 'options',
options: [
{
name: 'Hour',
value: 'hour',
},
{
name: 'Day',
value: 'day',
},
{
name: 'Week',
value: 'week',
},
{
name: 'Month',
value: 'month',
},
{
name: 'Quater',
value: 'quater',
},
{
name: 'Year',
value: 'year',
},
],
default: '',
},
{
displayName: 'Pay Rate',
name: 'payRate',
placeholder: 'Add Pay Rate',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
name: 'value',
displayName: 'Pay Rate',
values: [
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
placeholder: '20.00',
},
{
displayName: 'Currency',
name: 'currency',
type: 'string',
default: '',
placeholder: 'USD',
},
],
},
],
},
{
displayName: 'Pay Type',
name: 'payType',
type: 'options',
options: [
{
name: 'Hourly',
value: 'hourly',
},
{
name: 'Salary',
value: 'salary',
},
{
name: 'Commission',
value: 'commission',
},
{
name: 'Exception Hourly',
value: 'exceptionHourly',
},
{
name: 'Monthly',
value: 'monthly',
},
{
name: 'Weekly',
value: 'weekly',
},
{
name: 'Piece Rate',
value: 'pieceRate',
},
{
name: 'Contract',
value: 'contract',
},
{
name: 'Daily',
value: 'daily',
},
{
name: 'Pro Rata',
value: 'proRata',
},
],
default: '',
},
{
displayName: 'Preferred Name',
name: 'preferredName',
type: 'string',
default: '',
},
{
displayName: 'Social Security Number',
name: 'ssn',
type: 'string',
default: '',
placeholder: '123-45-6789',
description: 'A standard United States Social Security number, with dashes',
},
] as INodeProperties[];
if (sync === true) {
elements = elements.map(element => {
return Object.assign(element, {
displayOptions: {
show: {
resource: [
'employee',
],
operation: [
'update',
],
synced: [
true,
],
},
},
required: true,
});
});
return elements;
} else {
elements = elements.map(element => {
return Object.assign(element, {
displayOptions: {
show: {
'/synced': [
false,
],
},
},
});
});
}
return elements;
};

View file

@ -0,0 +1,42 @@
import {
EmployeeDocumentProperties,
} from '../../Interfaces';
export const employeeDocumentDelDescription: EmployeeDocumentProperties = [
{
displayName: 'Employee ID',
name: 'employeeId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'delete',
],
resource: [
'employeeDocument',
],
},
},
default: '',
description: 'ID of the employee',
},
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'delete',
],
resource: [
'employeeDocument',
],
},
},
default: '',
description: 'ID of the employee file',
},
];

View file

@ -0,0 +1,30 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function del(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'DELETE';
//meta data
const id: string = this.getNodeParameter('employeeId', index) as string;
const fileId: string = this.getNodeParameter('fileId', index) as string;
//endpoint
const endpoint = `employees/${id}/files/${fileId}`;
//response
await apiRequest.call(this, requestMethod, endpoint, body);
//return
return this.helpers.returnJsonArray({ success: true });
}

View file

@ -0,0 +1,7 @@
import { del as execute } from './execute';
import { employeeDocumentDelDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,60 @@
import {
EmployeeDocumentProperties,
} from '../../Interfaces';
export const employeeDocumentDownloadDescription: EmployeeDocumentProperties = [
{
displayName: 'Employee ID',
name: 'employeeId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'download',
],
resource: [
'employeeDocument',
],
},
},
default: '',
description: 'ID of the employee',
},
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'download',
],
resource: [
'employeeDocument',
],
},
},
default: '',
description: 'ID of the employee file',
},
{
displayName: 'Put Output In Field',
name: 'output',
type: 'string',
default: 'data',
required: true,
description: 'The name of the output field to put the binary file data in',
displayOptions: {
show: {
operation: [
'download',
],
resource: [
'employeeDocument',
],
},
},
},
];

View file

@ -0,0 +1,59 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function download(this: IExecuteFunctions, index: number) {
const body: IDataObject = {};
const requestMethod = 'GET';
const items = this.getInputData();
//meta data
const id: string = this.getNodeParameter('employeeId', index) as string;
const fileId: string = this.getNodeParameter('fileId', index) as string;
const output: string = this.getNodeParameter('output', index) as string;
//endpoint
const endpoint = `employees/${id}/files/${fileId}/`;
//response
const response = await apiRequest.call(this, requestMethod, endpoint, body, {} as IDataObject,
{ encoding: null, json: false, resolveWithFullResponse: true });
let mimeType = response.headers['content-type'] as string | undefined;
mimeType = mimeType ? mimeType.split(';').find(value => value.includes('/')) : undefined;
const contentDisposition = response.headers['content-disposition'];
const fileNameRegex = /(?<=filename=").*\b/;
const match = fileNameRegex.exec(contentDisposition);
let fileName = '';
// file name was found
if (match !== null) {
fileName = match[0];
}
const newItem: INodeExecutionData = {
json: items[index].json,
binary: {},
};
if (items[index].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, items[index].binary);
}
newItem.binary = {
[output]: await this.helpers.prepareBinaryData(response.body as unknown as Buffer, fileName, mimeType),
};
return this.prepareOutputData(newItem as unknown as INodeExecutionData[]);
}

View file

@ -0,0 +1,7 @@
import { download as execute } from './execute';
import { employeeDocumentDownloadDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,81 @@
import {
EmployeeDocumentProperties,
} from '../../Interfaces';
export const employeeDocumentGetAllDescription: EmployeeDocumentProperties = [
{
displayName: 'Employee ID',
name: 'employeeId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'employeeDocument',
],
},
},
default: '',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'employeeDocument',
],
},
},
description: 'Whether to return all results',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 5,
description: 'The number of results to return',
typeOptions: {
minValue: 1,
maxValue: 1000,
},
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'employeeDocument',
],
returnAll: [
false,
],
},
},
},
{
displayName: 'Simplify Output',
name: 'simplifyOutput',
type: 'boolean',
default: true,
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'employeeDocument',
],
},
},
description: 'Whether to simplify the output or not',
},
];

View file

@ -0,0 +1,58 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function getAll(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'GET';
//meta data
const id = this.getNodeParameter('employeeId', index) as string;
//limit parameters
const simplifyOutput: boolean = this.getNodeParameter('simplifyOutput', index) as boolean;
const returnAll: boolean = this.getNodeParameter('returnAll', 0, false) as boolean;
const limit: number = this.getNodeParameter('limit', 0, 0) as number;
//endpoint
const endpoint = `employees/${id}/files/view/`;
//response
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
const onlyFilesArray = [];
//return only files without categories
if (simplifyOutput) {
for (let i = 0; i < responseData.categories.length; i++) {
if (responseData.categories[i].hasOwnProperty('files')) {
for (let j = 0; j < responseData.categories[i].files.length; j++) {
onlyFilesArray.push(responseData.categories[i].files[j]);
}
}
}
if (!returnAll && onlyFilesArray.length > limit) {
return this.helpers.returnJsonArray(onlyFilesArray.slice(0, limit));
} else {
return this.helpers.returnJsonArray(onlyFilesArray);
}
}
//return limited result
if (!returnAll && responseData.categories.length > limit) {
return this.helpers.returnJsonArray(responseData.categories.slice(0, limit));
}
//return
return this.helpers.returnJsonArray(responseData.categories);
}

View file

@ -0,0 +1,7 @@
import { getAll as execute } from './execute';
import { employeeDocumentGetAllDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,65 @@
import * as del from './del';
import * as download from './download';
import * as getAll from './getAll';
import * as update from './update';
import * as upload from './upload';
import {
INodeProperties,
} from 'n8n-workflow';
export {
del,
download,
getAll,
update,
upload,
};
export const descriptions: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'employeeDocument',
],
},
},
options: [
{
name: 'Delete',
value: 'delete',
description: 'Delete an employee document',
},
{
name: 'Download',
value: 'download',
description: 'Download an employee document',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all employee document',
},
{
name: 'Update',
value: 'update',
description: 'Update an employee document',
},
{
name: 'Upload',
value: 'upload',
description: 'Upload an employee document',
},
],
default: 'delete',
},
...del.description,
...download.description,
...getAll.description,
...update.description,
...upload.description,
];

View file

@ -0,0 +1,86 @@
import {
EmployeeDocumentProperties,
} from '../../Interfaces';
export const employeeDocumentUpdateDescription: EmployeeDocumentProperties = [
{
displayName: 'Employee ID',
name: 'employeeId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'employeeDocument',
],
},
},
default: '',
},
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'employeeDocument',
],
},
},
default: '',
},
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'employeeDocument',
],
},
},
options: [
{
displayName: 'Employee Document Category Name/ID',
name: 'categoryId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getEmployeeDocumentCategories',
loadOptionsDependsOn: [
'employeeId',
],
},
default: '',
description: 'ID of the new category of the file',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'New name of the file',
},
{
displayName: 'Share with Employee',
name: 'shareWithEmployee',
type: 'boolean',
default: true,
description: 'Whether this file is shared or not',
},
],
},
];

View file

@ -0,0 +1,34 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function update(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
let body: IDataObject = {};
const requestMethod = 'POST';
//meta data
const id = this.getNodeParameter('employeeId', index) as string;
const fileId = this.getNodeParameter('fileId', index) as string;
//endpoint
const endpoint = `employees/${id}/files/${fileId}`;
//body parameters
body = this.getNodeParameter('updateFields', index) as IDataObject;
body.shareWithEmployee ? body.shareWithEmployee = 'yes' : body.shareWithEmployee = 'no';
//response
await apiRequest.call(this, requestMethod, endpoint, body);
//return
return this.helpers.returnJsonArray({ success: true });
}

View file

@ -0,0 +1,7 @@
import { update as execute } from './execute';
import { employeeDocumentUpdateDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,85 @@
import {
EmployeeDocumentProperties,
} from '../../Interfaces';
export const employeeDocumentUploadDescription: EmployeeDocumentProperties = [
{
displayName: 'Employee ID',
name: 'employeeId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'employeeDocument',
],
},
},
default: '',
description: 'ID of the employee',
},
{
displayName: 'Employee Document Category ID',
name: 'categoryId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'employeeDocument',
],
},
},
default: '',
},
{
displayName: 'Input Data Field Name',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'employeeDocument',
],
},
},
required: true,
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'employeeDocument',
],
},
},
default: {},
options: [
{
displayName: 'Share with Employee',
name: 'share',
type: 'boolean',
default: true,
description: 'Whether this file is shared or not',
},
],
},
];

View file

@ -0,0 +1,66 @@
import {
BINARY_ENCODING,
IExecuteFunctions,
} from 'n8n-core';
import {
IBinaryData,
IBinaryKeyData,
IDataObject, NodeOperationError,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function upload(this: IExecuteFunctions, index: number) {
let body: IDataObject = {};
const requestMethod = 'POST';
const items = this.getInputData();
const category = this.getNodeParameter('categoryId', index) as string;
const options = this.getNodeParameter('options', index) as IDataObject;
if (items[index].binary === undefined) {
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
}
const propertyNameUpload = this.getNodeParameter('binaryPropertyName', index) as string;
if (items[index]!.binary![propertyNameUpload] === undefined) {
throw new NodeOperationError(this.getNode(), `No binary data property "${propertyNameUpload}" does not exists on item!`);
}
const item = items[index].binary as IBinaryKeyData;
const binaryData = item[propertyNameUpload] as IBinaryData;
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(index, propertyNameUpload);
const id: string = this.getNodeParameter('employeeId', index) as string;
body = {
json: false,
formData: {
file: {
value: binaryDataBuffer,
options: {
filename: binaryData.fileName,
contentType: binaryData.mimeType,
},
},
fileName: binaryData.fileName,
category,
},
resolveWithFullResponse: true,
};
if (options.hasOwnProperty('share')) {
Object.assign(body.formData, (options.share) ? { share: 'yes' } : { share: 'no' });
}
//endpoint
const endpoint = `employees/${id}/files`;
const { headers } = await apiRequest.call(this, requestMethod, endpoint, {}, {}, body);
return this.helpers.returnJsonArray({ fileId: headers.location.split('/').pop() });
}

View file

@ -0,0 +1,7 @@
import { upload as execute } from './execute';
import { employeeDocumentUploadDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,24 @@
import {
FileProperties,
} from '../../Interfaces';
export const fileDelDescription: FileProperties = [
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'delete',
],
resource: [
'file',
],
},
},
default: '',
description: 'ID of the file',
},
];

View file

@ -0,0 +1,29 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function del(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'DELETE';
//meta data
const fileId: string = this.getNodeParameter('fileId', index) as string;
//endpoint
const endpoint = `files/${fileId}`;
//response
await apiRequest.call(this, requestMethod, endpoint, body);
//return
return this.helpers.returnJsonArray({ success: true });
}

View file

@ -0,0 +1,7 @@
import { del as execute } from './execute';
import { fileDelDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,42 @@
import {
FileProperties,
} from '../../Interfaces';
export const fileDownloadDescription: FileProperties = [
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'download',
],
resource: [
'file',
],
},
},
default: '',
description: 'ID of the file',
},
{
displayName: 'Put Output In Field',
name: 'output',
type: 'string',
default: 'data',
required: true,
description: 'The name of the output field to put the binary file data in',
displayOptions: {
show: {
operation: [
'download',
],
resource: [
'file',
],
},
},
},
];

View file

@ -0,0 +1,59 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function download(this: IExecuteFunctions, index: number) {
const body: IDataObject = {};
const requestMethod = 'GET';
const items = this.getInputData();
//meta data
const fileId: string = this.getNodeParameter('fileId', index) as string;
const output: string = this.getNodeParameter('output', index) as string;
//endpoint
const endpoint = `files/${fileId}/`;
//response
const response = await apiRequest.call(this, requestMethod, endpoint, body, {} as IDataObject,
{ encoding: null, json: false, resolveWithFullResponse: true });
let mimeType = response.headers['content-type'] as string | undefined;
mimeType = mimeType ? mimeType.split(';').find(value => value.includes('/')) : undefined;
const contentDisposition = response.headers['content-disposition'];
const fileNameRegex = /(?<=filename=").*\b/;
const match = fileNameRegex.exec(contentDisposition);
let fileName = '';
// file name was found
if (match !== null) {
fileName = match[0];
}
const newItem: INodeExecutionData = {
json: items[index].json,
binary: {},
};
if (items[index].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, items[index].binary);
}
newItem.binary = {
[output]: await this.helpers.prepareBinaryData(response.body as unknown as Buffer, fileName, mimeType),
};
return this.prepareOutputData(newItem as unknown as INodeExecutionData[]);
}

View file

@ -0,0 +1,7 @@
import { download as execute } from './execute';
import { fileDownloadDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,64 @@
import {
FileProperties,
} from '../../Interfaces';
export const fileGetAllDescription: FileProperties = [
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Whether to return all results',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'file',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 5,
description: 'The number of results to return',
typeOptions: {
minValue: 1,
maxValue: 1000,
},
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'file',
],
returnAll: [
false,
],
},
},
},
{
displayName: 'Simplify Output',
name: 'simplifyOutput',
type: 'boolean',
default: true,
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'file',
],
},
},
description: 'Whether to simplify the output or not',
},
];

View file

@ -0,0 +1,53 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function getAll(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'GET';
const endpoint = 'files/view';
//limit parameters
const simplifyOutput: boolean = this.getNodeParameter('simplifyOutput', index) as boolean;
const returnAll: boolean = this.getNodeParameter('returnAll', 0, false) as boolean;
const limit: number = this.getNodeParameter('limit', 0, 0) as number;
//response
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
const onlyFilesArray = [];
//return only files without categories
if (simplifyOutput) {
for (let i = 0; i < responseData.categories.length; i++) {
if (responseData.categories[i].hasOwnProperty('files')) {
for (let j = 0; j < responseData.categories[i].files.length; j++) {
onlyFilesArray.push(responseData.categories[i].files[j]);
}
}
}
if (!returnAll && onlyFilesArray.length > limit) {
return this.helpers.returnJsonArray(onlyFilesArray.slice(0, limit));
} else {
return this.helpers.returnJsonArray(onlyFilesArray);
}
}
//return limited result
if (!returnAll && responseData.categories.length > limit) {
return this.helpers.returnJsonArray(responseData.categories.slice(0, limit));
}
//return
return this.helpers.returnJsonArray(responseData.categories);
}

View file

@ -0,0 +1,7 @@
import { getAll as execute } from './execute';
import { fileGetAllDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,66 @@
import * as del from './del';
import * as download from './download';
import * as getAll from './getAll';
import * as update from './update';
import * as upload from './upload';
import {
INodeProperties,
} from 'n8n-workflow';
export {
del,
download,
getAll,
update,
upload,
};
export const descriptions: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'file',
],
},
},
options: [
{
name: 'Delete',
value: 'delete',
description: 'Delete a company file',
},
{
name: 'Download',
value: 'download',
description: 'Download a company file',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all company files',
},
{
name: 'Update',
value: 'update',
description: 'Update a company file',
},
{
name: 'Upload',
value: 'upload',
description: 'Upload a company file',
},
],
default: 'delete',
description: '',
},
...del.description,
...download.description,
...getAll.description,
...update.description,
...upload.description,
];

View file

@ -0,0 +1,67 @@
import {
FileProperties,
} from '../../Interfaces';
export const fileUpdateDescription: FileProperties = [
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
required: true,
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'file',
],
},
},
default: '',
description: 'ID of the file',
},
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: [
'update',
],
resource: [
'file',
],
},
},
options: [
{
displayName: 'Category Name/ID',
name: 'categoryId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCompanyFileCategories',
},
default: '',
description: 'Move the file to a different category',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'New name of the file',
},
{
displayName: 'Share with Employee',
name: 'shareWithEmployee',
type: 'boolean',
default: true,
description: 'Whether this file is shared or not',
},
],
},
];

View file

@ -0,0 +1,34 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function update(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
const body: IDataObject = {};
const requestMethod = 'POST';
//meta data
const fileId: string = this.getNodeParameter('fileId', index) as string;
//endpoint
const endpoint = `files/${fileId}`;
//body parameters
const shareWithEmployee = this.getNodeParameter('updateFields.shareWithEmployee', index, true) as boolean;
body.shareWithEmployee = shareWithEmployee ? 'yes' : 'no';
//response
await apiRequest.call(this, requestMethod, endpoint, body);
//return
return this.helpers.returnJsonArray({ success: true });
}

View file

@ -0,0 +1,7 @@
import { update as execute } from './execute';
import { fileUpdateDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,68 @@
import { INodeProperties } from 'n8n-workflow';
export const fileUploadDescription: INodeProperties[] = [
{
displayName: 'Input Data Field Name',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'file',
],
},
},
required: true,
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG',
},
{
displayName: 'Category Name/ID',
name: 'categoryId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCompanyFileCategories',
},
required: true,
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'file',
],
},
},
default: '',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'file',
],
},
},
default: {},
options: [
{
displayName: 'Share with Employee',
name: 'share',
type: 'boolean',
default: true,
description: 'Whether this file is shared or not',
},
],
},
];

View file

@ -0,0 +1,63 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IBinaryData,
IBinaryKeyData,
IDataObject,
NodeOperationError,
} from 'n8n-workflow';
import {
apiRequest,
} from '../../../transport';
export async function upload(this: IExecuteFunctions, index: number) {
let body: IDataObject = {};
const requestMethod = 'POST';
const items = this.getInputData();
const category = this.getNodeParameter('categoryId', index) as string;
const share = this.getNodeParameter('options.share', index, true) as boolean;
if (items[index].binary === undefined) {
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
}
const propertyNameUpload = this.getNodeParameter('binaryPropertyName', index) as string;
if (items[index]!.binary![propertyNameUpload] === undefined) {
throw new NodeOperationError(this.getNode(), `No binary data property "${propertyNameUpload}" does not exists on item!`);
}
const item = items[index].binary as IBinaryKeyData;
const binaryData = item[propertyNameUpload] as IBinaryData;
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(index, propertyNameUpload);
body = {
json: false,
formData: {
file: {
value: binaryDataBuffer,
options: {
filename: binaryData.fileName,
contentType: binaryData.mimeType,
},
},
fileName: binaryData.fileName,
category,
},
resolveWithFullResponse: true,
};
Object.assign(body.formData, (share) ? { share: 'yes' } : { share: 'no' });
//endpoint
const endpoint = `files`;
const { headers } = await apiRequest.call(this, requestMethod, endpoint, {}, {}, body);
return this.helpers.returnJsonArray({ fileId: headers.location.split('/').pop() });
}

View file

@ -0,0 +1,7 @@
import { upload as execute } from './execute';
import { fileUploadDescription as description } from './description';
export {
description,
execute,
};

View file

@ -0,0 +1,57 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
INodeExecutionData,
} from 'n8n-workflow';
import * as employee from './employee';
import * as employeeDocument from './employeeDocument';
import * as file from './file';
import * as companyReport from './companyReport';
import { BambooHR } from './Interfaces';
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
const items = this.getInputData();
const operationResult: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
const resource = this.getNodeParameter<BambooHR>('resource', i);
const operation = this.getNodeParameter('operation', i);
const bamboohr = {
resource,
operation,
} as BambooHR;
if (bamboohr.operation === 'delete') {
//@ts-ignore
bamboohr.operation = 'del';
}
try {
if (bamboohr.resource === 'employee') {
operationResult.push(...await employee[bamboohr.operation].execute.call(this, i));
} else if (bamboohr.resource === 'employeeDocument') {
//@ts-ignore
operationResult.push(...await employeeDocument[bamboohr.operation].execute.call(this, i));
} else if (bamboohr.resource === 'file') {
//@ts-ignore
operationResult.push(...await file[bamboohr.operation].execute.call(this, i));
} else if (bamboohr.resource === 'companyReport') {
//@ts-ignore
operationResult.push(...await companyReport[bamboohr.operation].execute.call(this, i));
}
} catch (err) {
if (this.continueOnFail()) {
operationResult.push({ json: this.getInputData(i)[0].json, error: err });
} else {
throw err;
}
}
}
return operationResult;
}

View file

@ -0,0 +1,60 @@
import {
INodeTypeDescription,
} from 'n8n-workflow';
import * as file from './file';
import * as employee from './employee';
import * as employeeDocument from './employeeDocument';
import * as companyReport from './companyReport';
export const versionDescription: INodeTypeDescription = {
credentials: [
{
name: 'bambooHRApi',
required: true,
testedBy: 'bambooHrApiCredentialTest',
},
],
defaults: {
name: 'BambooHR',
},
description: 'Consume BambooHR API',
displayName: 'BambooHR',
group: ['transform'],
icon: 'file:bambooHR.png',
inputs: ['main'],
name: 'bambooHR',
outputs: ['main'],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Company Report',
value: 'companyReport',
},
{
name: 'Employee',
value: 'employee',
},
{
name: 'Employee Document',
value: 'employeeDocument',
},
{
name: 'File',
value: 'file',
},
],
default: 'employee',
},
...employee.descriptions,
...employeeDocument.descriptions,
...file.descriptions,
...companyReport.descriptions,
],
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
version: 1,
};

View file

@ -0,0 +1,46 @@
import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IHttpRequestOptions,
NodeCredentialTestResult,
} from 'n8n-workflow';
export async function bambooHrApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
try {
await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject);
} catch (error) {
return {
status: 'Error',
message: 'The API Key included in the request is invalid',
};
}
return {
status: 'OK',
message: 'Connection successful!',
} as NodeCredentialTestResult;
}
async function validateCredentials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject): Promise<any> { // tslint:disable-line:no-any
const credentials = decryptedCredentials;
const {
subdomain,
apiKey,
} = credentials as {
subdomain: string,
apiKey: string,
};
const options: IHttpRequestOptions = {
method: 'GET',
auth: {
username: apiKey as string,
password: 'x',
},
url: `https://api.bamboohr.com/api/gateway.php/${subdomain}/v1/employees/directory`,
};
return await this.helpers.request(options);
}

View file

@ -0,0 +1,2 @@
export * as loadOptions from './loadOptions';
export * as credentialTest from './credentialTest';

View file

@ -0,0 +1,176 @@
import {
IDataObject,
ILoadOptionsFunctions,
INodePropertyOptions,
} from 'n8n-workflow';
import {
apiRequest,
} from '../transport';
// Get all the available channels
export async function getTimeOffTypeID(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const body: IDataObject = {};
const requestMethod = 'GET';
const endPoint = 'meta/time_off/types';
const response = await apiRequest.call(this, requestMethod, endPoint, body);
const timeOffTypeIds = response.body.timeOffTypes;
for (const item of timeOffTypeIds) {
returnData.push({
name: item.name,
value: item.id,
});
}
return returnData;
}
export async function getCompanyFileCategories(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const body: IDataObject = {};
const requestMethod = 'GET';
const endPoint = 'files/view/';
const response = await apiRequest.call(this, requestMethod, endPoint, body);
const categories = response.categories;
for (const category of categories) {
returnData.push({
name: category.name,
value: category.id,
});
}
returnData.sort(sort);
return returnData;
}
export async function getEmployeeDocumentCategories(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const body: IDataObject = {};
const requestMethod = 'GET';
const id = this.getCurrentNodeParameter('employeeId') as string;
const endPoint = `employees/${id}/files/view/`;
const response = await apiRequest.call(this, requestMethod, endPoint, body);
const categories = response.categories;
for (const category of categories) {
returnData.push({
name: category.name,
value: category.id,
});
}
returnData.sort(sort);
return returnData;
}
export async function getEmployeeLocations(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const body: IDataObject = {};
const requestMethod = 'GET';
const endPoint = 'meta/lists/';
//do not request all data?
const fields = await apiRequest.call(this, requestMethod, endPoint, body, {}) as [{ fieldId: number, options: [{ id: number, name: string }] }];
const options = fields.filter(field => field.fieldId === 18)[0].options;
for (const option of options) {
returnData.push({
name: option.name,
value: option.id,
});
}
returnData.sort(sort);
return returnData;
}
export async function getDepartments(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const body: IDataObject = {};
const requestMethod = 'GET';
const endPoint = 'meta/lists/';
//do not request all data?
const fields = await apiRequest.call(this, requestMethod, endPoint, body, {}) as [{ fieldId: number, options: [{ id: number, name: string }] }];
const options = fields.filter(field => field.fieldId === 4)[0].options;
for (const option of options) {
returnData.push({
name: option.name,
value: option.id,
});
}
returnData.sort(sort);
return returnData;
}
export async function getDivisions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const body: IDataObject = {};
const requestMethod = 'GET';
const endPoint = 'meta/lists/';
//do not request all data?
const fields = await apiRequest.call(this, requestMethod, endPoint, body, {}) as [{ fieldId: number, options: [{ id: number, name: string }] }];
const options = fields.filter(field => field.fieldId === 1355)[0].options;
for (const option of options) {
returnData.push({
name: option.name,
value: option.id,
});
}
returnData.sort(sort);
return returnData;
}
export async function getEmployeeFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const body: IDataObject = {};
const requestMethod = 'GET';
const endPoint = 'employees/directory';
const { fields } = await apiRequest.call(this, requestMethod, endPoint, body);
for (const field of fields) {
returnData.push({
name: field.name || field.id,
value: field.id,
});
}
returnData.sort(sort);
returnData.unshift({
name: '[All]',
value: 'all',
});
return returnData;
}
//@ts-ignore
const sort = (a, b) => {
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { return -1; }
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) { return 1; }
return 0;
};

View file

@ -0,0 +1,73 @@
import {
IExecuteFunctions,
IHookFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
import {
IDataObject,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
import {
OptionsWithUrl,
} from 'request';
/**
* Make an API request to Mattermost
*/
export async function apiRequest(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
endpoint: string,
body: string[] | IDataObject = {},
query: IDataObject = {},
option: IDataObject = {},
) {
const credentials = await this.getCredentials('bambooHRApi');
if (!credentials) {
throw new NodeOperationError(this.getNode(), 'No credentials returned!');
}
//set-up credentials
const apiKey = credentials.apiKey;
const subdomain = credentials.subdomain;
//set-up uri
const uri = `https://api.bamboohr.com/api/gateway.php/${subdomain}/v1/${endpoint}`;
const options: OptionsWithUrl = {
method,
body,
qs: query,
url: uri,
auth: {
username: apiKey as string,
password: 'x',
},
json: true,
};
if (Object.keys(option).length) {
Object.assign(options, option);
}
if (!Object.keys(body).length) {
delete options.body;
}
if (!Object.keys(query).length) {
delete options.qs;
}
try {
//@ts-ignore
return await this.helpers.request(options);
} catch (error) {
const description = error?.response?.headers['x-bamboohr-error-messsage'] || '';
const message = error?.message || '';
throw new NodeApiError(this.getNode(), error, { message, description });
}
}

View file

@ -44,6 +44,7 @@
"dist/credentials/AutomizyApi.credentials.js",
"dist/credentials/AutopilotApi.credentials.js",
"dist/credentials/Aws.credentials.js",
"dist/credentials/BambooHRApi.credentials.js",
"dist/credentials/BannerbearApi.credentials.js",
"dist/credentials/BaserowApi.credentials.js",
"dist/credentials/BeeminderApi.credentials.js",
@ -347,6 +348,7 @@
"dist/nodes/Aws/SQS/AwsSqs.node.js",
"dist/nodes/Aws/Textract/AwsTextract.node.js",
"dist/nodes/Aws/Transcribe/AwsTranscribe.node.js",
"dist/nodes/BambooHR/BambooHR.node.js",
"dist/nodes/Bannerbear/Bannerbear.node.js",
"dist/nodes/Baserow/Baserow.node.js",
"dist/nodes/Beeminder/Beeminder.node.js",