diff --git a/package-lock.json b/package-lock.json index 43a1c3e7718c0..f556dd82dfe0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/packages/nodes-base/credentials/BambooHRApi.credentials.ts b/packages/nodes-base/credentials/BambooHRApi.credentials.ts new file mode 100644 index 0000000000000..16b7e77a4617e --- /dev/null +++ b/packages/nodes-base/credentials/BambooHRApi.credentials.ts @@ -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: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/BambooHR/BambooHR.node.ts b/packages/nodes-base/nodes/BambooHR/BambooHR.node.ts new file mode 100644 index 0000000000000..0cdcd5338b79a --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/BambooHR.node.ts @@ -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)]; + } +} diff --git a/packages/nodes-base/nodes/BambooHR/bambooHR.png b/packages/nodes-base/nodes/BambooHR/bambooHR.png new file mode 100644 index 0000000000000..92ab3095bb105 Binary files /dev/null and b/packages/nodes-base/nodes/BambooHR/bambooHR.png differ diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/Interfaces.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/Interfaces.ts new file mode 100644 index 0000000000000..774f566cb45df --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/Interfaces.ts @@ -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; + +export type BambooHRFile = Entity; +export type BambooHREmployee = Entity; +export type BambooHREmployeeDocument = Entity; +export type BambooHRCompanyReport = Entity; + +export type FileProperties = PropertiesOf; +export type EmployeeProperties = PropertiesOf; +export type EmployeeDocumentProperties = PropertiesOf; +export type CompanyReportProperties = PropertiesOf; + +export interface IAttachment { + fields: { + item?: object[]; + }; + actions: { + item?: object[]; + }; +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/description.ts new file mode 100644 index 0000000000000..e49949b79c056 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/description.ts @@ -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', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/execute.ts new file mode 100644 index 0000000000000..0a75b812f281e --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/execute.ts @@ -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[]); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/index.ts new file mode 100644 index 0000000000000..c15f450dd1e96 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/get/index.ts @@ -0,0 +1,7 @@ +import { get as execute } from './execute'; +import { companyReportGetDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/index.ts new file mode 100644 index 0000000000000..e6bc7aa7cfd7b --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/companyReport/index.ts @@ -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, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/description.ts new file mode 100644 index 0000000000000..36eafa765ec12 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/description.ts @@ -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: '', + }, + ], + }, +]; \ No newline at end of file diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/execute.ts new file mode 100644 index 0000000000000..60c74533d5aa1 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/execute.ts @@ -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 { + 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 }); +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/index.ts new file mode 100644 index 0000000000000..3f16a3faae91e --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/index.ts @@ -0,0 +1,7 @@ +import { create as execute } from './execute'; +import { employeeCreateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/shareDescription.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/shareDescription.ts new file mode 100644 index 0000000000000..c5a91f0b3fff6 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/create/shareDescription.ts @@ -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; +}; \ No newline at end of file diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/description.ts new file mode 100644 index 0000000000000..a1a5163523b88 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/description.ts @@ -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', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/execute.ts new file mode 100644 index 0000000000000..7ce3cff6f3402 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/execute.ts @@ -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 { + 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); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/index.ts new file mode 100644 index 0000000000000..f5b38ee652c03 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/get/index.ts @@ -0,0 +1,7 @@ +import { get as execute } from './execute'; +import { employeeGetDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/description.ts new file mode 100644 index 0000000000000..189f8d7ded29e --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/description.ts @@ -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', + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/execute.ts new file mode 100644 index 0000000000000..0158273f538b7 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/execute.ts @@ -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 { + 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); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/index.ts new file mode 100644 index 0000000000000..c500a19ccba67 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/getAll/index.ts @@ -0,0 +1,7 @@ +import { getAll as execute } from './execute'; +import { employeeGetAllDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/index.ts new file mode 100644 index 0000000000000..83c4832e05e71 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/index.ts @@ -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, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/description.ts new file mode 100644 index 0000000000000..a87e62941e6c6 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/description.ts @@ -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: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/execute.ts new file mode 100644 index 0000000000000..2000dc486116e --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/execute.ts @@ -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 { + 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 }); +} + diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/index.ts new file mode 100644 index 0000000000000..af36c6bb1d32c --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/index.ts @@ -0,0 +1,7 @@ +import { update as execute } from './execute'; +import { employeeUpdateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/sharedDescription.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/sharedDescription.ts new file mode 100644 index 0000000000000..a3824cb63f48b --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employee/update/sharedDescription.ts @@ -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; +}; \ No newline at end of file diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/description.ts new file mode 100644 index 0000000000000..d898a312442bc --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/description.ts @@ -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', + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/execute.ts new file mode 100644 index 0000000000000..1d2ebe01b3f25 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/execute.ts @@ -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 { + 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 }); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/index.ts new file mode 100644 index 0000000000000..4b40bf333a062 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/del/index.ts @@ -0,0 +1,7 @@ +import { del as execute } from './execute'; +import { employeeDocumentDelDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/description.ts new file mode 100644 index 0000000000000..6560de5c2af71 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/description.ts @@ -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', + ], + }, + }, + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/execute.ts new file mode 100644 index 0000000000000..4c9870ea4ef43 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/execute.ts @@ -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[]); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/index.ts new file mode 100644 index 0000000000000..956798721cd3e --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/download/index.ts @@ -0,0 +1,7 @@ +import { download as execute } from './execute'; +import { employeeDocumentDownloadDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/description.ts new file mode 100644 index 0000000000000..08e4bb4f89564 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/description.ts @@ -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', + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/execute.ts new file mode 100644 index 0000000000000..2a11cad42c5c7 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/execute.ts @@ -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 { + 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); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/index.ts new file mode 100644 index 0000000000000..60a7d085c521e --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/getAll/index.ts @@ -0,0 +1,7 @@ +import { getAll as execute } from './execute'; +import { employeeDocumentGetAllDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/index.ts new file mode 100644 index 0000000000000..e03a885f430e7 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/index.ts @@ -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, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/description.ts new file mode 100644 index 0000000000000..ed5bbac76d5cf --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/description.ts @@ -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', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/execute.ts new file mode 100644 index 0000000000000..04acce14853f6 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/execute.ts @@ -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 { + 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 }); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/index.ts new file mode 100644 index 0000000000000..46808d0302de2 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/update/index.ts @@ -0,0 +1,7 @@ +import { update as execute } from './execute'; +import { employeeDocumentUpdateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/description.ts new file mode 100644 index 0000000000000..18061ba4498c0 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/description.ts @@ -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', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/execute.ts new file mode 100644 index 0000000000000..cb17234cb8e0f --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/execute.ts @@ -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() }); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/index.ts new file mode 100644 index 0000000000000..9f6571a87fa65 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/employeeDocument/upload/index.ts @@ -0,0 +1,7 @@ +import { upload as execute } from './execute'; +import { employeeDocumentUploadDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/description.ts new file mode 100644 index 0000000000000..a0a66be67d088 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/description.ts @@ -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', + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/execute.ts new file mode 100644 index 0000000000000..62b3775fab693 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/execute.ts @@ -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 { + 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 }); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/index.ts new file mode 100644 index 0000000000000..90105b3d1c8f9 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/del/index.ts @@ -0,0 +1,7 @@ +import { del as execute } from './execute'; +import { fileDelDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/description.ts new file mode 100644 index 0000000000000..55b45d6d79222 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/description.ts @@ -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', + ], + }, + }, + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/execute.ts new file mode 100644 index 0000000000000..358be14eca94d --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/execute.ts @@ -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[]); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/index.ts new file mode 100644 index 0000000000000..0f3b6da933937 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/download/index.ts @@ -0,0 +1,7 @@ +import { download as execute } from './execute'; +import { fileDownloadDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/description.ts new file mode 100644 index 0000000000000..9ac75ea419bc2 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/description.ts @@ -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', + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/execute.ts new file mode 100644 index 0000000000000..b78dc8ee837ff --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/execute.ts @@ -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 { + 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); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/index.ts new file mode 100644 index 0000000000000..3b1b2ee79527c --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/getAll/index.ts @@ -0,0 +1,7 @@ +import { getAll as execute } from './execute'; +import { fileGetAllDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/index.ts new file mode 100644 index 0000000000000..5ea62514e5b68 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/index.ts @@ -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, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/description.ts new file mode 100644 index 0000000000000..e1624800fb5e5 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/description.ts @@ -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', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/execute.ts new file mode 100644 index 0000000000000..aed7b0f3f05f9 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/execute.ts @@ -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 { + 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 }); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/index.ts new file mode 100644 index 0000000000000..eb15a3f702ff0 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/update/index.ts @@ -0,0 +1,7 @@ +import { update as execute } from './execute'; +import { fileUpdateDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/description.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/description.ts new file mode 100644 index 0000000000000..4c70aed5423aa --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/description.ts @@ -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', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/execute.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/execute.ts new file mode 100644 index 0000000000000..be6406848b053 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/execute.ts @@ -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() }); +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/index.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/index.ts new file mode 100644 index 0000000000000..23cd4577f3186 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/file/upload/index.ts @@ -0,0 +1,7 @@ +import { upload as execute } from './execute'; +import { fileUploadDescription as description } from './description'; + +export { + description, + execute, +}; diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/router.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/router.ts new file mode 100644 index 0000000000000..172a0ef92777d --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/router.ts @@ -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 { + const items = this.getInputData(); + const operationResult: INodeExecutionData[] = []; + + for (let i = 0; i < items.length; i++) { + const resource = this.getNodeParameter('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; +} diff --git a/packages/nodes-base/nodes/BambooHR/v1/actions/versionDescription.ts b/packages/nodes-base/nodes/BambooHR/v1/actions/versionDescription.ts new file mode 100644 index 0000000000000..daeb9f46b4ffc --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/actions/versionDescription.ts @@ -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, +}; \ No newline at end of file diff --git a/packages/nodes-base/nodes/BambooHR/v1/methods/credentialTest.ts b/packages/nodes-base/nodes/BambooHR/v1/methods/credentialTest.ts new file mode 100644 index 0000000000000..5e0586c52cc4c --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/methods/credentialTest.ts @@ -0,0 +1,46 @@ +import { + ICredentialDataDecryptedObject, + ICredentialsDecrypted, + ICredentialTestFunctions, + IHttpRequestOptions, + NodeCredentialTestResult, +} from 'n8n-workflow'; + +export async function bambooHrApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + 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 { // 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); +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/BambooHR/v1/methods/index.ts b/packages/nodes-base/nodes/BambooHR/v1/methods/index.ts new file mode 100644 index 0000000000000..f05993825aaeb --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/methods/index.ts @@ -0,0 +1,2 @@ +export * as loadOptions from './loadOptions'; +export * as credentialTest from './credentialTest'; diff --git a/packages/nodes-base/nodes/BambooHR/v1/methods/loadOptions.ts b/packages/nodes-base/nodes/BambooHR/v1/methods/loadOptions.ts new file mode 100644 index 0000000000000..37f57ad54c580 --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/methods/loadOptions.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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; +}; + + + diff --git a/packages/nodes-base/nodes/BambooHR/v1/transport/index.ts b/packages/nodes-base/nodes/BambooHR/v1/transport/index.ts new file mode 100644 index 0000000000000..c491e99a3200d --- /dev/null +++ b/packages/nodes-base/nodes/BambooHR/v1/transport/index.ts @@ -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 }); + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 0b24bf04fca58..4434b0762f330 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -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",